mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
Compare commits
257 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b03c56315 | |||
| eaecc5c80a | |||
| ed8c2f286c | |||
| 7b2356f8f3 | |||
| 6ddd2f2389 | |||
| 4e98b09fa4 | |||
| e786393523 | |||
| 4a2995b533 | |||
| 1495451901 | |||
| d4ef5a7516 | |||
| 044e31dd8a | |||
| bd33dff2f3 | |||
| edf30ac61f | |||
| c67964045d | |||
| 92a6f777ed | |||
| 12ed7aafee | |||
| 3becd8a0a7 | |||
| 818436c946 | |||
| d0813e8595 | |||
| 3c0140f3b8 | |||
| 8d415fa273 | |||
| f8fad4caf4 | |||
| 3b54c03027 | |||
| a5f46a930f | |||
| 6157a9d1fa | |||
| f910fc84e5 | |||
| 9cb09905f9 | |||
| eeb2fbcad6 | |||
| 400faf92c0 | |||
| fb023a039b | |||
| 95b5d848f7 | |||
| ded5e064e6 | |||
| 7ea50ec380 | |||
| 901bef1463 | |||
| 8d44d796b4 | |||
| 5a10ad478e | |||
| fd6680d615 | |||
| e03cdfc42b | |||
| 945d53fcfd | |||
| ac7045b724 | |||
| c907bdc4a5 | |||
| 733ed9ac2f | |||
| 1d8e579a10 | |||
| 567cac9c95 | |||
| 095c09c8c0 | |||
| e9c949822d | |||
| 3482d5416c | |||
| d8cb4a0c94 | |||
| 59acfa7a47 | |||
| 2eb2e690d1 | |||
| f7801261c3 | |||
| 7e7be7f040 | |||
| 0187fd16b2 | |||
| ba0bdb5e99 | |||
| 9d3e4f07bf | |||
| bd81d7584d | |||
| 9577339e14 | |||
| 5c292ef1cb | |||
| 4f3de85068 | |||
| 07a25c9643 | |||
| 8f60426b4c | |||
| 37f87615bd | |||
| 3f96de660b | |||
| e86999770f | |||
| a8bb0c24ec | |||
| 6ba2a08b62 | |||
| d232269416 | |||
| 9d2208e14d | |||
| 8d69d45d1d | |||
| b0c37918b5 | |||
| d5af1c8da3 | |||
| a6105cfc3c | |||
| 0aa5ffd2c2 | |||
| 968f5b986f | |||
| 014a7e0362 | |||
| a92baf09d9 | |||
| 46e2edbe13 | |||
| 5aba86965e | |||
| 5924208aaa | |||
| 526390816b | |||
| d7e28c9704 | |||
| 7c827804f4 | |||
| b0dacbda0d | |||
| d5abde2593 | |||
| bc3290de3b | |||
| 7f438bf776 | |||
| 13cfe24b2f | |||
| 9cf1cd99c2 | |||
| f5cfd77550 | |||
| f27abbd454 | |||
| 6a7a11a811 | |||
| 4a8f5152b3 | |||
| 599ee6b9b8 | |||
| 83ac747cb1 | |||
| 09b98c6c0d | |||
| 13eac21609 | |||
| d0d9e2a9a8 | |||
| b047c93965 | |||
| d5eedd1dd2 | |||
| 9cc6696340 | |||
| 86758383c4 | |||
| 150a0264c5 | |||
| 558a2d8aad | |||
| fa327114f7 | |||
| 6ce3e0bfac | |||
| 4f7dee570a | |||
| e4eadf863e | |||
| 7a70a6ce01 | |||
| ca328e784c | |||
| 9871e252bc | |||
| d40eb6c4e1 | |||
| a7b0e6d0bf | |||
| f4f546e654 | |||
| 1fcbb3ecbc | |||
| 98e3cc973b | |||
| 220ab53ef2 | |||
| 9ba70063d2 | |||
| 076277d0a9 | |||
| bcd2ba1ec9 | |||
| 5827afd09c | |||
| 4a3e49f4e3 | |||
| 95d24aca41 | |||
| 345e37bd81 | |||
| 91b2fabf10 | |||
| bae1803157 | |||
| 911d16de31 | |||
| 1efe924221 | |||
| 0230235427 | |||
| 58e737ee28 | |||
| 78c98f121d | |||
| 2e3b86741b | |||
| fcd9869caa | |||
| f60579b735 | |||
| 6574f36c73 | |||
| 246ce245b9 | |||
| fa0a211db9 | |||
| 1c536df3c8 | |||
| 82fa1b5b1c | |||
| dacce748a1 | |||
| b59088c598 | |||
| 26dbce9dbf | |||
| 9b6d07dc4c | |||
| 00e21c8000 | |||
| 8c5f5326d5 | |||
| 3ebafcbc03 | |||
| 9777859f42 | |||
| 54f76d6576 | |||
| 3bb8293478 | |||
| 16e899cab7 | |||
| b4b9469284 | |||
| e0896864c2 | |||
| c7b16e0ea9 | |||
| f6d6920cfb | |||
| 8b4059a249 | |||
| 94b19e7589 | |||
| a090bb7caa | |||
| 101e97dfec | |||
| 31054a2df7 | |||
| 58231eb19c | |||
| 8f8b5d2684 | |||
| ca5e497dbb | |||
| 13aabcacd2 | |||
| 3afd0bb609 | |||
| e250a8b57f | |||
| 051fa7647d | |||
| 9c4c794d0f | |||
| 9211a28675 | |||
| 8d660ec7c0 | |||
| ed8109fc30 | |||
| 37fc63def8 | |||
| 8c1a8f67d8 | |||
| 94ed7581bb | |||
| 4a84a69fe7 | |||
| 1ebf6c9319 | |||
| d0fc353d0b | |||
| 977919fdb1 | |||
| ae2e37cedb | |||
| 02e5394924 | |||
| cdc9bb73bb | |||
| 3a2c0c744c | |||
| ffeb76f608 | |||
| 3748cb39b2 | |||
| 543ea52bb3 | |||
| 2ad508ec60 | |||
| f9501d6f60 | |||
| 6bbdf9600d | |||
| c554c010c5 | |||
| 075c976d19 | |||
| 6da49e78ee | |||
| 59ce2e0623 | |||
| 09b2a7f1a4 | |||
| bd466ac420 | |||
| 96183eb5df | |||
| 21164859cf | |||
| 9b50fe68c9 | |||
| d8576e4dc6 | |||
| 9fc23ad4be | |||
| 6c997c0b51 | |||
| 9b10421882 | |||
| e5bc98cac0 | |||
| e98f320f41 | |||
| d0ef35fb92 | |||
| 8c512f3163 | |||
| 8cc0fc9987 | |||
| 604200e1db | |||
| 2a1581acdb | |||
| 39e3198821 | |||
| b4c2b3614b | |||
| 5ea0cc7838 | |||
| a9b7fc525a | |||
| b05874fb0a | |||
| 5fdd142a17 | |||
| 243e1da716 | |||
| 772217258c | |||
| 57cc9c9db7 | |||
| e6f6560f3a | |||
| cbce30b4d7 | |||
| 23c8a84b4c | |||
| a521bff1f1 | |||
| 986784b128 | |||
| 238c2b8cd3 | |||
| c6a418e00c | |||
| e59ea1f84d | |||
| a1acde5df0 | |||
| 3ec61165ca | |||
| cc823a18d4 | |||
| b6aa865a67 | |||
| 86f83d995b | |||
| e314e09fdb | |||
| 4aa4238943 | |||
| 5d638b8bf4 | |||
| 5027d817c5 | |||
| 40b92330eb | |||
| b990447226 | |||
| 482019f514 | |||
| 2027af3a3f | |||
| c1a55385d3 | |||
| 8be47c99dd | |||
| 10d11f5b99 | |||
| 3db2c66f12 | |||
| e8614c0072 | |||
| 11b4c43845 | |||
| 36fec4ddbe | |||
| bb64039a0d | |||
| 7b43b0b300 | |||
| fd270c80f5 | |||
| 12b5801b1b | |||
| 23a6b38a7d | |||
| 296118470c | |||
| 6d4dccc6b7 | |||
| e449f86c01 | |||
| a1bdc048a7 | |||
| b5be78416f | |||
| 89d1fe2c49 | |||
| 0a1b08157d | |||
| 61503f4146 | |||
| 34b259562d |
@@ -25,7 +25,7 @@ jobs:
|
||||
echo "Build frontend..."
|
||||
cd ./frontend && pnpm run build
|
||||
- name: upload frontend release
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frontend-package
|
||||
path: frontend/dist
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
- name: Checkout #Checkout代码
|
||||
uses: actions/checkout@v3
|
||||
- name: download frontend release
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: frontend-package
|
||||
path: frontend/dist
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: download frontend release
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: frontend-package
|
||||
path: frontend/dist
|
||||
|
||||
+1
-1
@@ -3,4 +3,4 @@
|
||||
/config.yml
|
||||
/build/
|
||||
/apipark
|
||||
.gitlab-ci.yml
|
||||
.gitlab-ci.yml
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
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
|
||||
APP: apipark
|
||||
APP_PRE: ${APP}_${VERSION}
|
||||
BUILD_DIR: ${APP}-build
|
||||
DEPLOY_DESC: "DEV 环境"
|
||||
VIEW_ADDR: http://172.18.166.219:8288
|
||||
SAVE_DIR: /opt/${APP}
|
||||
NODE_OPTIONS: --max_old_space_size=8192
|
||||
|
||||
stages:
|
||||
- notice
|
||||
- prefix
|
||||
- build
|
||||
- deploy
|
||||
- webhook
|
||||
|
||||
feishu-informer: # 飞书回调
|
||||
stage: notice
|
||||
variables:
|
||||
DIFF_URL: "$CI_MERGE_REQUEST_PROJECT_URL/-/merge_requests/$CI_MERGE_REQUEST_IID/diffs"
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE=="merge_request_event" && $CI_COMMIT_BRANCH =~ "main"
|
||||
script:
|
||||
- echo "merge request"
|
||||
- |
|
||||
curl -X POST -H "Content-Type: application/json" \
|
||||
-d "{\"msg_type\":\"text\",\"content\":{\"text\":\"项目:${CI_PROJECT_NAME}\\n提交人:${GITLAB_USER_NAME}\\n提交信息:${CI_MERGE_REQUEST_TITLE}\\n合并分支信息:${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME} -> ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}\\n差异性地址:${DIFF_URL}\\n请及时review代码\"}}" \
|
||||
https://open.feishu.cn/open-apis/bot/v2/hook/1c334752-2874-41a1-8f1b-3060f2d46b6c
|
||||
|
||||
prebuild:
|
||||
stage: prefix
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == "main"
|
||||
script:
|
||||
- echo "prebuild"
|
||||
- chmod +x ./scripts/prefix.sh
|
||||
- ./scripts/prefix.sh
|
||||
|
||||
builder:
|
||||
stage: build
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == "main"
|
||||
script:
|
||||
- set -e
|
||||
- |
|
||||
if [ ! -d "../artifacts" ]; then
|
||||
mkdir -p ../artifacts
|
||||
fi
|
||||
if [ -d "../artifacts/dist" ]; then
|
||||
cp -r ../artifacts/dist frontend/dist
|
||||
fi
|
||||
- |
|
||||
if [ -n "$(git diff --name-status HEAD~1 HEAD -- frontend)" ]; then
|
||||
./scripts/build.sh $BUILD_DIR ${VERSION} all ""
|
||||
else
|
||||
./scripts/build.sh $BUILD_DIR ${VERSION}
|
||||
fi
|
||||
if [ -d "frontend/dist" ]; then
|
||||
echo "copy frontend/dist to artifacts/dist"
|
||||
rm -fr ../artifacts/dist
|
||||
cp -r frontend/dist ../artifacts/dist
|
||||
fi
|
||||
cp $BUILD_DIR/${APP_PRE}_linux_amd64.tar.gz ${SAVE_DIR}
|
||||
|
||||
deployer:
|
||||
stage: deploy
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == "main"
|
||||
variables:
|
||||
APIPARK_GUEST_MODE: allow
|
||||
APIPARK_GUEST_ID: dklejrfbhjqwdh
|
||||
script:
|
||||
- cd ${SAVE_DIR};mkdir -p ${APP_PRE};tar -zxvf ${APP_PRE}_linux_amd64.tar.gz -C ${APP_PRE};cd ${APP_PRE};./install.sh ${SAVE_DIR};./run.sh restart;cd ${SAVE_DIR} && ./clean.sh ${APP_PRE}
|
||||
when: on_success
|
||||
success:
|
||||
stage: webhook
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == "main"
|
||||
script:
|
||||
- |
|
||||
curl -X POST -H "Content-Type: application/json" \
|
||||
-d "{\"msg_type\":\"text\",\"content\":{\"text\":\"最近一次提交:${CI_COMMIT_TITLE}\\n提交人:${GITLAB_USER_NAME}\\n项目:${CI_PROJECT_NAME}\\n环境:${DEPLOY_DESC}\\n更新部署完成.\\n访问地址:${VIEW_ADDR}\\n工作流地址:${CI_PIPELINE_URL}\"}}" \
|
||||
https://open.feishu.cn/open-apis/bot/v2/hook/c3672932-4dfa-4989-8023-0128bae59338
|
||||
when: on_success
|
||||
failure:
|
||||
stage: webhook
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == "main"
|
||||
script:
|
||||
- |
|
||||
curl -X POST -H "Content-Type: application/json" \
|
||||
-d "{\"msg_type\":\"text\",\"content\":{\"text\":\"最近一次提交:${CI_COMMIT_TITLE}\\n提交人:${GITLAB_USER_NAME}\\n项目:${CI_PROJECT_NAME}\\n环境:${DEPLOY_DESC}\\n更新部署失败,请及时到gitlab上查看\\n工作流地址:${CI_PIPELINE_URL}\"}}" \
|
||||
https://open.feishu.cn/open-apis/bot/v2/hook/c3672932-4dfa-4989-8023-0128bae59338
|
||||
when: on_failure
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"Antd",
|
||||
"apinto",
|
||||
"Apipark",
|
||||
"logsettings",
|
||||
"resourcesettings"
|
||||
]
|
||||
}
|
||||
@@ -210,7 +210,7 @@ APIPark uses the Apache 2.0 License. For more details, please refer to the LICEN
|
||||
For enterprise-level features and professional technical support, contact our pre-sales experts for personalized demos, customized solutions, and pricing.
|
||||
|
||||
- Website: https://apipark.com
|
||||
- Email: dev@apipark.com
|
||||
- Email: contact@apipark.com
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
@@ -40,7 +40,14 @@ func (c *Config) Check(cfg string) error {
|
||||
}
|
||||
|
||||
func (c *Config) GenConfig(target string, origin string) (string, error) {
|
||||
if target == "" {
|
||||
target = "{}"
|
||||
}
|
||||
if origin == "" {
|
||||
origin = "{}"
|
||||
}
|
||||
var targetData map[string]interface{}
|
||||
|
||||
err := json.Unmarshal([]byte(target), &targetData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/config.yml
|
||||
@@ -0,0 +1,77 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/eolinker/go-common/autowire"
|
||||
nsq "github.com/nsqio/go-nsq"
|
||||
|
||||
"github.com/eolinker/go-common/cftool"
|
||||
|
||||
_ "github.com/eolinker/go-common/store/store_mysql"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
var (
|
||||
version string
|
||||
confPath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&confPath, "c", "config.yml", "`config` file path for server ")
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port int `yaml:"port"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 1. 连接 MySQL 数据库
|
||||
cftool.Register[ServerConfig](fmt.Sprintf("root:%s", confPath))
|
||||
cftool.ReadFile(confPath)
|
||||
|
||||
handler := &NSQHandler{}
|
||||
autowire.Autowired(handler)
|
||||
err := autowire.CheckComplete()
|
||||
if err != nil {
|
||||
log.Fatal("check autowired:", err)
|
||||
return
|
||||
}
|
||||
// 2. 创建 NSQ 消费者
|
||||
config := nsq.NewConfig()
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get hostname: %v", err)
|
||||
return
|
||||
}
|
||||
nsqConfig := handler.nsqConfig
|
||||
consumer, err := nsq.NewConsumer(fmt.Sprintf("%s_ai_event", nsqConfig.TopicPrefix), hostname, config)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create NSQ consumer: %v", err)
|
||||
}
|
||||
|
||||
consumer.AddHandler(handler)
|
||||
|
||||
// 4. 连接到 NSQ
|
||||
//nsqAddress := "172.18.166.219:9150" // NSQ 地址
|
||||
err = consumer.ConnectToNSQD(nsqConfig.Addr)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to NSQ: %v", err)
|
||||
}
|
||||
log.Println("Connected to NSQ")
|
||||
|
||||
// 5. 捕获系统信号,优雅关闭
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigChan
|
||||
|
||||
// 优雅停止消费者
|
||||
consumer.Stop()
|
||||
<-consumer.StopChan
|
||||
log.Println("NSQ Consumer stopped")
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eolinker/go-common/cftool"
|
||||
|
||||
ai_dto "github.com/APIParkLab/APIPark/module/ai/dto"
|
||||
|
||||
"github.com/eolinker/go-common/store"
|
||||
|
||||
"github.com/APIParkLab/APIPark/service/ai"
|
||||
|
||||
ai_key "github.com/APIParkLab/APIPark/service/ai-key"
|
||||
|
||||
nsq "github.com/nsqio/go-nsq"
|
||||
|
||||
ai_api "github.com/APIParkLab/APIPark/service/ai-api"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cftool.Register[NSQConfig]("nsq")
|
||||
}
|
||||
|
||||
type NSQConfig struct {
|
||||
Addr string `json:"addr" yaml:"addr"`
|
||||
TopicPrefix string `json:"topic_prefix" yaml:"topic_prefix"`
|
||||
}
|
||||
|
||||
// 定义 NSQ 消息结构
|
||||
type AIProviderStatus struct {
|
||||
Provider string `json:"provider"`
|
||||
Model string `json:"model"`
|
||||
Key string `json:"key"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type AIInfo struct {
|
||||
Model string `json:"ai_model"`
|
||||
Cost interface{} `json:"ai_model_cost"`
|
||||
InputToken interface{} `json:"ai_model_input_token"`
|
||||
OutputToken interface{} `json:"ai_model_output_token"`
|
||||
TotalToken interface{} `json:"ai_model_total_token"`
|
||||
Provider string `json:"ai_provider"`
|
||||
ProviderStats []AIProviderStatus `json:"ai_provider_statuses"`
|
||||
}
|
||||
|
||||
type NSQMessage struct {
|
||||
AI AIInfo `json:"ai"`
|
||||
API string `json:"api"`
|
||||
Provider string `json:"provider"`
|
||||
RequestID string `json:"request_id"`
|
||||
TimeISO8601 string `json:"time_iso8601"`
|
||||
}
|
||||
|
||||
// NSQHandler 处理 NSQ 消息并写入 MySQL
|
||||
type NSQHandler struct {
|
||||
apiUseService ai_api.IAPIUseService `autowired:""`
|
||||
aiKeyService ai_key.IKeyService `autowired:""`
|
||||
aiService ai.IProviderService `autowired:""`
|
||||
transaction store.ITransaction `autowired:""`
|
||||
nsqConfig *NSQConfig `autowired:""`
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func convertInt(value interface{}) int {
|
||||
switch v := value.(type) {
|
||||
case int:
|
||||
return v
|
||||
case float64:
|
||||
return int(v)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func genAIKey(key string, provider string) string {
|
||||
keys := strings.Split(key, "@")
|
||||
return strings.TrimSuffix(keys[0], fmt.Sprintf("-%s", provider))
|
||||
}
|
||||
|
||||
// HandleMessage 处理从 NSQ 读取的消息
|
||||
func (h *NSQHandler) HandleMessage(message *nsq.Message) error {
|
||||
log.Printf("Received message: %s", string(message.Body))
|
||||
|
||||
// 解析消息为结构体
|
||||
var data NSQMessage
|
||||
err := json.Unmarshal(message.Body, &data)
|
||||
if err != nil {
|
||||
log.Printf("Failed to unmarshal message: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 将时间字符串转换为 time.Time
|
||||
timestamp, err := time.Parse(time.RFC3339, data.TimeISO8601)
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse timestamp: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
day := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), 0, 0, 0, 0, timestamp.Location())
|
||||
hour := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), timestamp.Hour(), 0, 0, 0, timestamp.Location())
|
||||
minute := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), timestamp.Hour(), timestamp.Minute(), 0, 0, timestamp.Location())
|
||||
return h.transaction.Transaction(context.Background(), func(ctx context.Context) error {
|
||||
finalStatus := &AIProviderStatus{}
|
||||
for _, s := range data.AI.ProviderStats {
|
||||
status := ToKeyStatus(s.Status).Int()
|
||||
key := genAIKey(s.Key, s.Provider)
|
||||
err = h.aiKeyService.Save(ctx, key, &ai_key.Edit{
|
||||
Status: &status,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Failed to save AI key: %v", err)
|
||||
return nil
|
||||
}
|
||||
if s.Provider != data.AI.Provider {
|
||||
|
||||
pStatus := ai_dto.ProviderAbnormal.Int()
|
||||
err = h.aiService.Save(ctx, s.Provider, &ai.SetProvider{
|
||||
Status: &pStatus,
|
||||
})
|
||||
} else {
|
||||
pStatus := ai_dto.ProviderEnabled.Int()
|
||||
err = h.aiService.Save(ctx, s.Provider, &ai.SetProvider{
|
||||
Status: &pStatus,
|
||||
})
|
||||
}
|
||||
finalStatus = &s
|
||||
}
|
||||
if finalStatus != nil {
|
||||
//keys := strings.Split(finalStatus.Key, "@")
|
||||
key := genAIKey(finalStatus.Key, finalStatus.Provider)
|
||||
err = h.aiKeyService.IncrUseToken(ctx, key, convertInt(data.AI.TotalToken))
|
||||
if err != nil {
|
||||
log.Printf("Failed to increment AI key token: %v", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 调用 AI API 接口
|
||||
err = h.apiUseService.Incr(context.Background(), &ai_api.IncrAPIUse{
|
||||
API: data.API,
|
||||
Service: data.Provider,
|
||||
Provider: data.AI.Provider,
|
||||
Model: data.AI.Model,
|
||||
Day: day.Unix(),
|
||||
Hour: hour.Unix(),
|
||||
Minute: minute.Unix(),
|
||||
InputToken: convertInt(data.AI.InputToken),
|
||||
OutputToken: convertInt(data.AI.OutputToken),
|
||||
TotalToken: convertInt(data.AI.TotalToken),
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Failed to call AI API: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Message processed and saved to MySQL: %+v", data)
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import ai_key_dto "github.com/APIParkLab/APIPark/module/ai-key/dto"
|
||||
|
||||
var (
|
||||
StatusNormal = "normal"
|
||||
StatusInvalidRequest = "invalid request"
|
||||
StatusQuotaExhausted = "quota exhausted"
|
||||
StatusExpired = "expired"
|
||||
StatusExceeded = "exceeded"
|
||||
StatusInvalid = "invalid"
|
||||
StatusTimeout = "timeout"
|
||||
)
|
||||
|
||||
func ToKeyStatus(status string) ai_key_dto.KeyStatus {
|
||||
switch status {
|
||||
case StatusNormal:
|
||||
return ai_key_dto.KeyNormal
|
||||
case StatusInvalidRequest:
|
||||
return ai_key_dto.KeyNormal
|
||||
case StatusQuotaExhausted:
|
||||
return ai_key_dto.KeyExceed
|
||||
case StatusExpired:
|
||||
return ai_key_dto.KeyExpired
|
||||
case StatusExceeded:
|
||||
return ai_key_dto.KeyNormal
|
||||
case StatusInvalid:
|
||||
return ai_key_dto.KeyError
|
||||
case StatusTimeout:
|
||||
return ai_key_dto.KeyError
|
||||
default:
|
||||
return ai_key_dto.KeyNormal
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package ai_api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/APIParkLab/APIPark/model/plugin_model"
|
||||
@@ -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", input.AiModel.Provider),
|
||||
"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: input.AiModel.Provider,
|
||||
Disable: false,
|
||||
//Upstream: input.AiModel.Provider,
|
||||
Disable: false,
|
||||
})
|
||||
|
||||
return 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", input.AiModel.Provider),
|
||||
"provider": input.AiModel.Provider,
|
||||
"config": input.AiModel.Config,
|
||||
},
|
||||
}
|
||||
upstream = &input.AiModel.Provider
|
||||
//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)
|
||||
}
|
||||
|
||||
@@ -318,15 +318,41 @@ func (i *imlServiceController) Get(ctx *gin.Context, id string) (*service_dto.Se
|
||||
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) {
|
||||
|
||||
@@ -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 编辑
|
||||
|
||||
@@ -265,7 +265,8 @@ func (i *imlInitController) OnInit() {
|
||||
return fmt.Errorf("create default team error: %v", err)
|
||||
}
|
||||
// 创建Rest服务
|
||||
_, err = i.serviceModule.Create(ctx, info.Id, &service_dto.CreateService{
|
||||
restPath := "/rest-demo"
|
||||
serviceInfo, err := i.serviceModule.Create(ctx, info.Id, &service_dto.CreateService{
|
||||
Name: "REST Demo Service",
|
||||
Prefix: "/rest-demo",
|
||||
Description: "Auto created By APIPark",
|
||||
@@ -277,6 +278,26 @@ func (i *imlInitController) OnInit() {
|
||||
if err != nil {
|
||||
return fmt.Errorf("create default service error: %v", err)
|
||||
}
|
||||
path := fmt.Sprintf("/%s/", strings.Trim(restPath, "/"))
|
||||
_, err = i.routerModule.Create(ctx, serviceInfo.Id, &router_dto.Create{
|
||||
Id: uuid.NewString(),
|
||||
Name: "",
|
||||
Path: path + "*",
|
||||
Methods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch, http.MethodOptions},
|
||||
Description: "auto create by create service",
|
||||
Protocols: []string{"http", "https"},
|
||||
MatchRules: nil,
|
||||
Upstream: "",
|
||||
Proxy: &router_dto.InputProxy{
|
||||
Path: path,
|
||||
Timeout: 30000,
|
||||
Retry: 0,
|
||||
},
|
||||
Disable: false,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("create default router error: %v", err)
|
||||
}
|
||||
// 创建AI服务
|
||||
err = i.createAIService(ctx, info.Id, &service_dto.CreateService{
|
||||
Name: "AI Demo Service",
|
||||
@@ -416,7 +437,7 @@ func (i *imlInitController) createAIService(ctx context.Context, teamID string,
|
||||
plugins["ai_formatter"] = api.PluginSetting{
|
||||
Config: plugin_model.ConfigType{
|
||||
"model": aiModel.Id,
|
||||
"provider": fmt.Sprintf("%s@ai-provider", info.Provider.Id),
|
||||
"provider": info.Provider.Id,
|
||||
"config": aiModel.Config,
|
||||
},
|
||||
}
|
||||
@@ -436,8 +457,8 @@ func (i *imlInitController) createAIService(ctx context.Context, teamID string,
|
||||
Retry: retry,
|
||||
Plugins: plugins,
|
||||
},
|
||||
Disable: false,
|
||||
Upstream: info.Provider.Id,
|
||||
Disable: false,
|
||||
//Upstream: info.Provider.Id,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
dist
|
||||
build
|
||||
coverage
|
||||
.next
|
||||
*.d.ts
|
||||
*.js
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["react", "@typescript-eslint", "prettier", "unused-imports"],
|
||||
"rules": {
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"prettier/prettier": "error",
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{ "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"trailingComma": "none",
|
||||
"singleQuote": true,
|
||||
"bracketLine": true
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
Create detailed components with these requirements:
|
||||
1. Use 'use client' directive for client-side components
|
||||
2. Style with Tailwind CSS utility classes for responsive design
|
||||
3. Use React Router for navigation
|
||||
4. Use Ant Design for UI components
|
||||
5. Use iconify React for icons (from @iconify/react package). Do NOT use other UI libraries unless requested
|
||||
6. Use local photos from public folder where appropriate, only valid URLs you know exist
|
||||
7. Create root layout.tsx page that wraps necessary navigation items to all pages
|
||||
8. MUST implement the navigation elements items in their rightful place i.e. Left sidebar, Top header
|
||||
9. Accurately implement necessary grid layouts
|
||||
10. Follow proper import practices:
|
||||
- Use @/ path aliases
|
||||
- Keep component imports organized
|
||||
- Update current packages/core/src/pages/Root.tsx with new comprehensive code
|
||||
- Don't forget root route (page.tsx) handling
|
||||
- You MUST complete the entire prompt before stopping
|
||||
11. Table component should use `import PageList from "@common/components/aoplatform/PageList.tsx"`
|
||||
12. PageList component MUST use addNewBtnTitle for add button, NOT toolBarRender. Example:
|
||||
<PageList
|
||||
id="global_team"
|
||||
className="pl-btnbase"
|
||||
ref={pageListRef}
|
||||
columns = {[...columns]}
|
||||
request = {()=>getTeamList()}
|
||||
showPagination={false}
|
||||
addNewBtnTitle={$t('添加团队')}
|
||||
addNewBtnAccess = "system.organization.team.add"
|
||||
searchPlaceholder={$t("输入名称、ID、负责人查找团队")}
|
||||
onAddNewBtnClick={()=>{openModal('add')}}
|
||||
onSearchWordChange={(e)=>{setSearchWord(e.target.value)}}
|
||||
onRowClick={(row:TeamTableListItem)=>(navigate(`../inside/${row.id}/setting`))}
|
||||
/>
|
||||
13. use `const { fetchData } = useFetch()` to fetch http data,such as
|
||||
```tsx
|
||||
fetchData<BasicResponse<{ profile: UserInfoType }>>('account/profile', { method: 'GET' }).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setUserInfo(data.profile)
|
||||
dispatch({ type: 'UPDATE_USERDATA', userData: data.profile })
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
```
|
||||
14. can't not import new package!
|
||||
@@ -6,7 +6,7 @@ const systemLanguage = {
|
||||
en_US: 'en-US',
|
||||
zh_CN: 'zh-CN',
|
||||
ja_JP: 'ja-JP',
|
||||
zh_TW: 'zh-TW'
|
||||
zh_TW: 'zh-TW',
|
||||
};
|
||||
const localesDir = 'packages/common/src/locales/scan';
|
||||
const newJsonDir = 'packages/common/src/locales/scan/newJson';
|
||||
@@ -19,7 +19,7 @@ fs.readdirSync(localesDir).forEach(file => {
|
||||
const lang = path.basename(file, '.json');
|
||||
const filePath = path.join(localesDir, file);
|
||||
try {
|
||||
console.log('Current working directory:', process.cwd(),filePath);
|
||||
console.log('Current working directory:', process.cwd(), filePath);
|
||||
const existJsonData = fs.readFileSync(filePath);
|
||||
existData[lang] = JSON.parse(existJsonData);
|
||||
} catch (error) {
|
||||
@@ -36,20 +36,18 @@ fs.readdirSync(localesDir).forEach(file => {
|
||||
|
||||
const keyList = Object.keys(existData);
|
||||
|
||||
|
||||
// 清空 newJson 目录下的所有语言文件
|
||||
Object.values(systemLanguage).forEach(lng => {
|
||||
const newJsonPath = path.join(newJsonDir, `${lng}.json`);
|
||||
fs.writeFileSync(newJsonPath, JSON.stringify({})); // 清空文件
|
||||
fs.writeFileSync(newJsonPath, JSON.stringify({})); // 清空文件
|
||||
});
|
||||
|
||||
|
||||
module.exports = {
|
||||
input: [
|
||||
'packages/*/src/**/*.{js,jsx,tsx,ts}',
|
||||
// 不需要扫描的文件加!
|
||||
'!packages/*/src/locales/**',
|
||||
'!**/node_modules/**'
|
||||
'!**/node_modules/**',
|
||||
],
|
||||
output: 'packages/common/src/locales/scan', // 输出目录
|
||||
options: {
|
||||
@@ -62,15 +60,15 @@ module.exports = {
|
||||
loadPath: './newJson/{{lng}}.json', // 输入路径 (手动新建目录)
|
||||
savePath: './newJson/{{lng}}.json', // 输出路径 (输出会根据输入路径内容自增, 不会覆盖已有的key)
|
||||
jsonIndent: 2,
|
||||
lineEnding: '\n'
|
||||
lineEnding: '\n',
|
||||
},
|
||||
removeUnusedKeys: true,
|
||||
nsSeparator: false, // namespace separator
|
||||
keySeparator: false, // key separator
|
||||
interpolation: {
|
||||
prefix: '{{',
|
||||
suffix: '}}'
|
||||
}
|
||||
suffix: '}}',
|
||||
},
|
||||
},
|
||||
// 这里我们要实现将中文转换成crc格式, 通过crc格式key作为索引, 最终实现语言包的切换.
|
||||
transform: function (file, enc, done) {
|
||||
@@ -80,11 +78,10 @@ module.exports = {
|
||||
parser.parseFuncFromString(content, { list: ['t'] }, (key, options) => {
|
||||
options.defaultValue = key;
|
||||
const hashKey = `K${crc32(key).toString(16)}`; // crc32转换格式
|
||||
keyHashMap[key] = hashKey;
|
||||
|
||||
keyHashMap[key] = hashKey;
|
||||
|
||||
// 遍历每种语言,逐个语言检查翻译是否存在
|
||||
keyList.forEach((lng) => {
|
||||
keyList.forEach(lng => {
|
||||
const langData = existData[lng] || {};
|
||||
|
||||
// 如果某语言没有翻译该字段,则记录到该语言的 newJson 文件中
|
||||
@@ -116,13 +113,12 @@ module.exports = {
|
||||
});
|
||||
done();
|
||||
},
|
||||
flush: function(done) {
|
||||
flush: function (done) {
|
||||
// 将 keyHashMap 写入文件
|
||||
fs.writeFileSync(keyHashFile, JSON.stringify(keyHashMap, null, 2));
|
||||
|
||||
|
||||
// 遍历每种语言,处理旧字段
|
||||
keyList.forEach((lng) => {
|
||||
keyList.forEach(lng => {
|
||||
const localeFilePath = path.join(localesDir, `${lng}.json`);
|
||||
const oldJsonPath = path.join(oldJsonDir, `${lng}.json`);
|
||||
const langData = existData[lng] || {};
|
||||
@@ -132,7 +128,7 @@ module.exports = {
|
||||
// 将不存在于 keyHashMap 中的键移动到 oldJson 文件中
|
||||
Object.keys(langData).forEach(hashKey => {
|
||||
if (!Object.values(keyHashMap).includes(hashKey)) {
|
||||
oldJsonData[hashKey] = langData[hashKey]; // 将旧的 key 移到 oldJson 中
|
||||
oldJsonData[hashKey] = langData[hashKey]; // 将旧的 key 移到 oldJson 中
|
||||
}
|
||||
});
|
||||
|
||||
@@ -142,5 +138,5 @@ module.exports = {
|
||||
}
|
||||
});
|
||||
done();
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
"serve:remotes": "lerna run serve --scope=remote --parallel",
|
||||
"dev": "lerna run dev --scope=core --stream",
|
||||
"stop": "kill-port --port 5000",
|
||||
"scan": "i18next-scanner --config i18next-scanner.config.js"
|
||||
"scan": "i18next-scanner --config i18next-scanner.config.js",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
|
||||
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix && prettier --write ."
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@@ -65,8 +67,12 @@
|
||||
"antd": "^5.19.4",
|
||||
"babel-jest": "^29.7.0",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.2",
|
||||
"eslint-plugin-react": "7.37.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.4",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next-scanner": "^4.5.0",
|
||||
"jest": "^29.7.0",
|
||||
@@ -78,6 +84,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"postcss-nested": "^6.0.1",
|
||||
"prettier": "^3.1.1",
|
||||
"react-test-renderer": "^18.3.1",
|
||||
"ts-jest": "^29.1.2",
|
||||
"typescript": "^5.2.2",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100" viewBox="0 0 100 100">
|
||||
<image id="icons8-server-100" y="11" width="99" height="78" xlink:href="data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGMAAABOCAYAAAA0Cah9AAAKN0lEQVR4nO2dC3BcVRnH/9/ZPAjt7qYFLFDBF1OlgsNYWqHZx2g3m1A6aBlaQawMxREtQ30NUh2djjKOdooOAxSUEUcQR2rVCqWYF8Ukm0ZgCkpV6FiGxwyPAjbJbmibxz1/5272lc1m9+Zukt1m728mmdxz7z2P77v3nHO/850vQhIO4wmEY/kkMg/AIgDHARwBoIsQnwvA2QDeAGAoRw+W8AD4PoCDAAYBvJQQ4ACAPwDw28jzTAD/BvAagH+ZCnaUUZgwgMMAbgNwQdbV8wGsA9AF4CEAp04h3xsBfDTx98fMY0cZ+bkKwF4AZ1i49loAHQDqLOadnef7HGVMzlIADwComsI9lwK4226BjjImZ1uObmc3gE8BOAXAWQA2Afhf1jXXA/iknQIdZeTmHACXZ50xn/grATwNYAjAWwDuBdAAoC/jOgHwVTuFOsrITXNCqEneAfCdSa49BOCnWWnNdgp1lJGbD2Sl7kt8V0zGY1npiwFUT7VQRxm5qc9KzR4Xsnkn61jlyKMgjjKsMStmCkcZZYSjjDKiSkTG1SYUOuo9Ia7rAFkjwJKEXWau8zaU8YXu1gXPTmI4bUrYoCajNld6V5s79XcB42OccV+XvvDA9Scg2wU4rQIUkMkCUN0C4JpJzp+X+JlRUt2Ur3HgJwL5dQUqIg6JZ0pdh/ib4Q9FvwzBlnFnyL9RpCXr63JOIpTDkXbPvlK3reqScP9CAtuTIweBAZLX9LR7/1riupUTzwJoz1Mf04Z1c7H1raqm3ABJf6AI9fpIe33bySatGaa3q829ZbIiAuHYGdOiDAiuSB4QaHUUUTrMAXxJqnTy8coTQfmgQJyerI1S6q1KF0gpURBJTW+1nh0bjENuHHNIbrIfykIPaS53nUIuPBPKmMr6bk6CwejprOa1VHIxyVNF8CKV2hlpcT9fbN4l5Lmsop8tUJWjCZebcxPHrybS8jGhjKKU4WuMXmUIfiUQr3mctHOJobf4G6M71LD7W52dMlpqydrAdET4EIBVCY+PB/Jl0dXmZiAcWwvgx4mk75lpBYqdUIb4w9HUTaSsj7S7d1mpe0PjwGUC7BER1+RX8Z7uNu9NRQhlzmDFc9PWmLFsGasV5N7ximAPgUdBHMu49GuB5v7lFSPxIrHVTdUtGAxA0uvEhN4caau/y/zb1xxdCo2nJO5tJ0ItXwQmN8IFgzxF18a2gVya6zxFNMi9kXbvndnnfI2xqwW8bjrGPitQ8MxZXs8Pdu0SYybyt9UIUcb5qZeKiEVWencg8d0eafH8x98Y3Q3BhsQFH8+Xl1ET2yjAZmStq6TKiv+ScCAUe7Krw30wmR4M9tVr0Q8CUj3ekWPmECD0Zn/MHMz/OBOF2HuiqEaS7aewuvkpVLeM+RKNIZyP9AUj+fPSfZBCvSVHxGWMW50ZdB8brht2HxPAa6sNdlHSb+XOYJBVuib2SwqXk7I14QCXF3vKEP1c8s0QyCmDRvSOZcu4+cABGfE3RT9LzSvSDzoP5Msq0uF9uCEcrRNiucjEMYyUYVD9pbPD+0pm+oHHFh9bGeoPKVHXinAqDsf2IAxCdUXa3B1W7h+tiV6mRDZK/KHkDivKsDmbovjDMXMcWJZOQh8kvvbx4XQahw0Xl+5vqX9peiRy8uBrGtgglAfHRMMT3a2egg7RNr/AhVSy0Rwv0klYME4RY2m3VqIi7GLbHGJ+YRtQAYIHJ5wk+jR4Q3eb944StOmkpagp4f72+f/AVl4U6B0ManI5BHVK5IUTaqjl6ZbTo5Ut2qlT/Pz8h6K7gCcx9uNQBI7VtoxQ5hw+ozp57EwOU4KZvY41Y6ki5fX0Pcx2hXewiRAfTIs1vkW5sDJE8ELqpviGQs6ObWEus5WKEt/lFIfgP620VlH4+4zji/1NsRsrXZbF4tsf/bpAUtuURfhnK1mq49WDfyLwcjKBmnf7w9EtS9expgzbWdY0N7PWF45uFeD2VD2JQ2rIu9NKveNdUiAc+7Smbs9cnyD5FiCmy+MbAs6IyXiuQEiVCBeDWAWR9P5uclhTVvV0eCJWFpdS40OgKbaeWv8WIs4bMQ2Y9ihQfSlp65vSSp9hxrEQWDIPO+SHRK+IbrC6hJ0k/mYEw/3nGVB/z94OYI4lUtjLwQEYJXBEwBe1lj09He6e+IQ2U5YW3owqcxpm9Ebvz1QEyUe0wq37W72HHEHPHlW+3oGwQAWSJZL8RaTdsylbsw4zjwLVhlQpxKvzXZ5vOIooDUoyV+uEv2tpkaHyqmLloCBMuiSas4AXKl0gpUQBkrE2q5y3ooQ46xllxLR44q1oftdTPVrzCYHUEiP/7elY+NpcEVAx+JsGz6Khl7hGzO8OFFzTKM4L/fL+BRiR7WJgAxRq4lYaVMEfHuiB6M1mxIHZaHQ50hA6ei60PigKHqMmuhPwXF2omra7KXNfhgyrXoG5WzbbniUNpOrxNcVCJ6UkpwGBaoAkQn2IrLGSo21l6FreB0mFAp2A6WkI8mHTJ3amG16WuCSj16Gl5Wxb3ZRpy9KQtamiwC4X1U0jVEeVjN4sIvE906aJxahRGwH8PG9+jbELDOD8WZOpcGRYDe/L5U7kDx87BxhdQU50AHaJ8VxnW/3hmaqWLWUYEH8qogJpGFDrIu3utxNJ3/U1DjSISDIKciCfMnyhaNCAfiL/ppvpp9aofh7gRZnWhpWh984mRg+aztSSwwihKcOBUOziTG/46cRWN6V0Rtgjkf7etCISaTiUPp0/PJwoLp9tRYwVjAuDwbfnZSYp6PPzerWL1FD0sknPF4ktZWglr6bbhNP8jbHPJI/NaS6Yjl5JyMu58kiiqlwPjUW0mb0I+QTeA3FbZ+eiwcz0433zuwDuznJfGruHNMzgNkOuEUvr2Xaw1U2Z/W2tURNNzhYofNQXjt4D4l0xsBGC9yevVZp5XeE7H59vBgJYMVMNnArmloZE7NqSYOvNiA98wq3JYwHmCXCLCLZlzrAI7uvq8OwpVeNONmxPbU0PczLDCyILc+nRNSSfd8zx1inKNhVp95hvQ4DkTnOJluSbJJ8k8BXXsDvQ2el5d5bbc1JTtG2qq9XTbb4olS3G6cGx2pYRjjLKCEVk+EoJK9OONBPo+B7HMWgt6KYSMv0BR1v/yMkhByKZspRXrMjI7KZakgcE1/tWDSzJf4tDIQKh2IUEPpfWRVrGeZUhxP3Jz38RqYXCI5euPupsmrHJyub+j1C4WzBmQjd9bqlHf2Mlt7jx1dcYvV0E304mmrFtxdzVT2lVot8wjKL+MeCcR4m4NORsUVxNyKaxIDZJYfJH3e3erZa90M2QRXULY3tF0Fjpgp1m9pzpda81o/BY9kI3DWRH691rAN43m9bTOQtpht68Sw25r5xKOKQJ+/f84f4VgHwTlNWpNVwHS8Q/E4i9Shs/63piwbgYhFPaLJPNunV0vd53bFEVhxdpV8EYRBWNZpXhUjjSfcm8I2aQglyyKKgMAP8HPtmJtk2SwmQAAAAASUVORK5CYII="/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100" viewBox="0 0 100 100">
|
||||
<image id="icons8-ai-cloud-100" y="16" width="100" height="68" xlink:href="data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABECAYAAAB3TpBiAAAMt0lEQVR4nO1dC3BU1Rn+/nM3CUSpoigqml0CPrBOdUSSDVoVHd+1UxCx9VHrC8iCYn212qpFtOooWjTZhbGtD9AiPtv6qlXBqrAbQXSsDxw0u9EBIg+rYgLJ3vt1zkXCvZtNSJZ7Nxvgm2GYPefs/f9zvj33nPM/TgS9EbxZlS/Zb3/T5CAl1i4WpZ8iDCj+zwSbioDPd1eBVUuOmtDa23rXKwgpq4uWK8qJIEdCUEHKEBGUdPYdEhtFsJTk20qxzoQsbKiIfJY/rXNDwRKy/8LYoIDBi0EZA8ERXjyTxNsinCNW0eP1VZc1evFMr1FwhJTV1Y5UllxDkTMFCPghg0AawAs01dSGkRPe8UNGrigYQoKJmiME6jZATu/yl8jvIPI1yGYIFIk9RGS3bnyfFDwFizelqiZ9lKPqnqLHCTl0fs2uTaXqFgBXAGJ02JBcCch8C1wowJKi4tZPlx85ZXW7dvPmGUOHrtwj3VoyDGQYgioQ+v99OtaCJoGYaTZf98XIq5u96lsu6FFCQotmVUKZcwEJZasn8I0As5Wl5n4WXrEQMtXKSRBvVsHE3scB6kIIzhJg1w5afkTheamKyNKc5HiAHiMklIheQeIuESnOUr0CkDtKWgMPLTvm0m+9lLtpRgYuInmjCPbKrCfZIpDfJcPVd3spt6vIPyHz5hmh4NoZACa1qyObKPLHFkPdu/KoCU1+qnHwm3/utyHQeq2AV0Fkl3aqgDWpisYpOc/KHJFXQoYvnlW0Nm3OhciYzDoSr1sBdcnnR034NJ86hd6qCcIwHoNgZBad5gwIqIvzecBU+RKkZ8aatDm7HRl6pwPekqpcdUK+ydBIHj05lWxedRzA20G4ZoMIzl9jWk9i/s2+bL+zIW8zJBSPxiAyMaO4mcB5qcrqZ/KlR2coi9eeoUQ9AaCvsxnBGanKyJX50CEvhAQT0WqBRJ1lBNYr4ej6isgr+dChqwglZh5L8PksO7HxycrqB/yW7zshemtLMf/j3k1xg7KMkz6rmvCm3/JzQaiu9jhSPeckRdvGIDIqVTlxkZ+yfV1D9l84vS/EfMhFBmFZFs4vVDI0khWTXofIhXp921ymjZlCzhn47iPtdmRewldCDKN0KkQOcZfy9oaqyFN+yvUCqYqJTwNyj+tRgvK+G7+71U+5vhFSvih2IEjXQkjgjWRz4x/8kuk19gyo6wm6ZjKBycFFtcP8kunbGhKKR59ybnFt/4Ti4cmKyDK/ZPoB/cMyhf/NeO0uFGXFACzboAIfeHmI9YWQUDx2OATvugrJ25LhyO/9kOc3gononwQyJZsYbWqBoE6Af5mG8bdtPUv58soi8OuMgrUl6eI7/ZCVD5hmkbZGf5VNlJ45AjkGkGmGaS0PJqJvBBOx0dqgmYtqnhMy9J0Z2mD3c2cZgeleGwnziS9GXrZOiJu6IlKTI8DTobqB7wfroqd1V03PCWltLRrr9ndzA1s502s5+UZ9uLqGxLkk9eHweW2q136UjtWQQ4XyQjARfbb8rejeXVXX8zUkGI++LiLHthUQjyXD1ed5LacQoE35zX2MKkvxp0IZ25ETjGCjkL9Mhie9vDW1PSUkuPTe3WVjn7XandqmjPD0VEXkxcIYQv9gW7ItjiGt6wVyeKYg248vmJKqqI52poSnryzVUnKMkwzt3zDTzQvyMB49Dm2iT1ZMfDxV0XikiHUBgS+dOumADSFqQ4lopztNT2aInhlqQ3EZRa6DyJbXE/lSMhzp9sK2PeCgxbMGtJiWXm9+1q475HXJcOSubN3MiRD73VmqTrMgZwKoEmBotnYkbk2Fq2/c7ke/I5ASSsy8EeAf9P64rRVhUTA2m9uhW4ToCEKhfca4UCD9ttbesji2N9it/IbtfiBqnaTY7gekh9dXXv6JU3yX1hD9SgrGo/eJhY8EMrkrZGhTScDCG4U3PPlHqjISo+Bap2Bt2icCD2tPakZ55wgloqcA+Asgg7bWlsRqAb4FuNLSVt3wpOe96r02e5e0rB8IZTQ3DF/RuE3BB/PmGUNCq/f7NLnXCowb18lZwlsE47EHRHCp66EWrkxWVc/Y/LFjQkgJ1s28QYhbXDsnd5tFEPxdByj0SRd/4PVpfGh8xg/SUnQFKeeI4LA2scB6AC8q8v76cKRbszCYqNkHNBaI4GCQHwuLjs9XnO++i2eVlqTNJW6XBNdZLRja8OOIbZrJ7ry3F6NYDUQimZSR1BEYD4nC9GSlf5bbULz25FaoOQLsJRk6fO/JO5siZ4fi0Uc3BozxXbW4CtUEaDLsD3IIkZ4A4BZfOpEBrWPZ27FLlck3tqwnsocU4SoA9uYn6y8/WBe71yajHbgAAeNHqXBkvJ9m9MHx2FiKej5bIFs7iJxXkjZfsb2TXYGI+5mCLps1vEDDiOq3IJjrfhQn6tmDbIQMjscmtzM1265M3pSsaDwxddSEj/1UuCweO5Tgw92KfBepChilNX7q5SVocZrbPSwDik1rHDIJKVs460gLnO4s00d+S+SCZGVkWj6i+AS4EyKlrkLyGVGoDPRv6UOY+xKMaJN+RpuLtP5+6+cF7Eh7kRdc/SbPgXMN2RRVaD0Cl2dMR+vLJalw9aP5UHRIXe0BpsUznHsNO6QzHLnc0WwVgFioLvoaKDoCpP+mHomIMnXc1/h86LrNEJkN6r5u7idO1JuYthmy1jSrIfihUw4F05Ph6kfypaNJdarr8ESs1ikC2draaxhxh7tUTvVfS2+wUck/7dCizZqLFLVKyUibkOD8B/sQuMEpieB7AwzjBl+06Qhk0F3D1zrL17CA55yfBRikZ3pedc4R9q5QUOfSn9YxNiGqtPl8gQx0VlLJpLxnsYrs7vqIjHUiA8XFLe6EHYH6Wm3YvbPvFBaYGXQ3zCaElIucpQRetrdnPQ0RFuAoegfCdXSgyFBlh+ODVc4KBd7fO3vYu0BRKbfCHKgYMEa5F1KuqW9qfGlHH6x8wBDzG6cYIfoFQBztMo+IzMeoqelCUJjAKcFEbF5H9a2tKOmdV1F8DxMbnSdBihQHIDzEbWOUeM9o1x7fO76yOr+2B5gB1U85j9rkd0og5c6+KVifbHc9L1AYFvu7NZNvFEhXoj0to33u9074AotyoIsO4PMABH3chekeTZx3g++Q8u9O6ktF5PKO6wsdHOZaLgTLAgSaBGhzyRKBrpmx8wJZlApX/7YjSTpsNd1a3IsJcQQUbjIVva8EcG+9lLl1H8RObDP0+U82O8o2Q3GBIsR1h5SFjEY74QsYUL9wzw6uSY1ofE9tChp2IbyTAp+hUxUov3IKEeBJ7W9SQrhsVkIZ1Vsspr0VgxP7jMl8XVmKs/X/KgDjNac7EYI911nsEb8CQXb2ORNNraqdBzNb2bbI8Br6x07AnWdJLG0YEbEtv2p5ePwXFPcssWj1zM6FcF+LZLHT295WVExeR7Jhy9eR0mVeyvAaa0zzikxHoEXettmyvdn8/pCzgUBOKns7dnQ+FdVINQx4WMcD63gvEtNS4S8f7vQLIqRljNZB3fY/4eitmey7LcNDlNdFD7Pj3Bwgubgh3NgW42ufSrTHUEo36GTF/dqaEu8mm1eNKBRDY29H+eJZu1mmpV9LW1KqCUsMVNWPqG7zHNozJDXqog36wjBXnwVHhPruM21HH0gvMPSTGSWWaT7rIgP2GN/nJAPOMKBk08qY9qNnyL8umIieW9jdLWzo1I3Wr4r/AcjxTkX1lbWlu+z5m0zlt1jjR01N0zQudkZC2DG9xINli6Jn7bhDmjv0abyp1FggwMkZD1khpnn2h4eNa8l8uCtQTt9hS/BqZ5nOw1YKj4fisWv06l9QPS5gDE5Ez0ZA6R3ccKeWJL8WyzpNX5yWTfusAxyKR6dD5Kp2FcSLJCKpqurk9j6guUKHwirgbgjapfLpvEOSpzeEI0s6enz2X7yOfq+beX9HF1VCMCvAwD36DNMTnS448GZVlhh4vNIB6sTobOkbJJYFLPMnn46cvLwz9Tt9BQXjsetFOC37Bcf68mF5VSDPiWW+buyZXrb8oCkbsz1ne8OmFGiznJYcCcHxIE4TwQEddpN4rCRdNLEr+TNbXRPK4tETlI5DdZ5Rsks1QXxJwXqBfNN5214Koj/Fzk3ZoyvR+QS/UMBV9ZWRJ7ra4S4t0psymYqnkox0cPHxTripWAfKjNJm854PR01e352x6dauqXzxrDLTNK8FcUG3Lr3fYcB3YMkjJWbRX3NN78tpG6tNLaq0+UwdNwVCH3jKXXnYO8z4cyWBxUJ5lbBe9uIvLHgyiDodq28LhjBgDrT//BCYt4uH8wla2GiJzi+XtQG01C8PT/F2rQTwf2pROIQTkdWSAAAAAElFTkSuQmCC"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
font-size: 40px;
|
||||
fill: #339af0;
|
||||
font-family: Montserrat;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<image id="icons8-api-100" width="100" height="100" xlink:href="data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAKu0lEQVR4nO2dC4xU1RnH/9+ZmV3eO7vQivhqgSbWgsLOavARIU3aGlFCNZpq04cGGGhNaGNta0hraNq09qW2Vplh8ZGm1dTEZ6xaaXRtqhR2EFlK0gYfAUGJwM4sW/Y19/6bO7OD9zXLzO6du3eZ+0s2u/fc87rff8/5zj333nMEFZBI9cySyNBsnZHGSuKHWFGiDVCLfZhJzjhyKtOUFSSROrYQIusAWQHgrNDGY4fE+yJ8DuSDmWRLl1uGDkEuuP/wtMmxxt9B+A1AVPAvcyJCnZBH+gcH1u+97Yxe8wVYBGl76Og5zEeeB7Cw3k3mE10S1ZZ33jrzQKm4k4IMt4x/QnDhaWyAAMK9kT7t0u3rZ/UYdYuWKljsplzFOAHiRQj3AHKiji03BjgFlAUQXAVgijUfuSA/KfpbAKtQaiHDDnyXi894Wg1G1+24bdqHQb/kiUAi1XsmJb9JgBXm6pLUVERf1Llq5p6iAMXRlLJGwjOZg03Xh2J4RyY57YOdB5u+TOBZc6YiEqGu1hl/K5AyPLQ1cyIyFF2LjaJP2KsPKhtFF0bXEugz15CUa43fYtz0QfSPLNUnnswk49fXu+1qSSKdfQrASnMRMcFMBeTPdJQr/PdpcM2BhjQGSVbyos2JUiJTBbSe4cijqbb2owugRWafjA453tc84829N8qgW/xE6liTAIsAFTOONZIxpf1n+5pZ75/ORh8JgfzPflrXo9OqvhNPpLIPUY90UfBy6QfCbZOzue2XtX803Rm/u5Uib1PUq6X4SsnWPCLvLt7cfUsNrnVCU5UgF6ePfg6Ccka8qF+Lfc0eSMidAplpDxdIVBG/qAcjV0NVguhUI872Cug4L8AIaaSxOMoLKVGVIJlD8V0A/+Z2zpjJHNKif7KH6+R9AAdc0wC/hAjdztUr0aque6Poc//Cq9/J9XyJ4Nkmw+b0GF/afcv0rD3Jm2ub/35JuvuzGvUrqYotTHTqULJ75+r49noXwE51ggB44kbRAPy1mjTb1zS/C+Dd0VayngifdwSMUJCAEQoSMEJBAoa7UxcuTKSyN9SxXWqPcKHbOyZlRllyMwQ3T/BLDjju98NhlxUwQkECRihIwCjjQ/hnUJ6uX7P4gHBlwVfbcBeE0pVJxp84na4/aCRS2Xlufj3ssgJGKEjACAUJGKEgASMUJGCEggSMUJCAEQoSMEJBAkYoSMAIBQkYoSABIxQkYISCBIxQkIARChIwQkECRtUvW3sJ6f4lgkh1n4yUy6cclebvlm+1dauWcWshIxmxGgNXK0YpzWjS+UFguyw/DBZEUerehwRNlHERJGhGCFJ9At1CxmIow/maf2pZlpfUTZdVEifo1J0PCboovgsS1OFmUAhMCyn3n1sLAf0sq1p8v1M3G2P+fWxsmpT9FkSuS6S6Z7dtzu7TdT7w5tqW56rJL5HKbhheRXVWKTyRLn0yzyFADgPcT6BDKMbSU29X0nUtae89I0/t9wAXAdLRNzjwXQC9p0w4BsZt6mTpw5x0fDD3AkSWFQKKBpqvlFyVSHf/JLOm+a5K8mlLdV9PwU/LL0FcCP8kIAsFWA7w7tZU9+NRwYbh7+fLUhQDNwzn8ZlJDY3HAPzAi+svx7h1Wb2Due+IYJn7WflxYaGbCqDIkqoKlgI35SG7Eqnc8pGikrjUktR2XAvGz4cIV410WkPk6xXlw9G1cgFmEPozI37cKrTlzdhoyqqGcRHkoge7PwXIvJHiCPj5MRTRBSJT+iHxjmsZIhEIHrk43b1oDGV5yrj4kKjiUkefbwxxTJ6WxGJjJbpMsiVXbf55jSveWtf8njkskTp2LkRuJ/HtghAfM0UD/gDyiiCsTDROXZZy+g6RZ6yHhtEiV3hVYibZsj+zpnm9Aq8rjrxMZUEua93cM6I/8YvxmVwEltqCeiHq146IwjJOf/R0JlueBeRHzqL0r3hd1mjwXZDF7cfOE8GnraF8fdr70/9FoMcSCtqF84RcX9O9IK1L44pcXYuyqsV3QZTuaB3GuowdHRslL+A/bKcWX3LfkRle12HfejFWuLOvjNe88IFss9dlVYv/XRad/kNEdaDoyF+1hEOi2pSYZ37EWigO2IOiMTXbPbJ/+D+5CMfN4Im+puk7Cn8pecWRQPfejwzXw9EalK4cSxT6ja+CGENPp//AG6UFmOc1Ne0CYTFKrfyI2AcW5GDm0NTDtSirGvxtIW5TJURH6U9jPUcKXrOlafXaj7Smc9cAcr61GugIwuYDvgpCKMd/u069w3wspMOP6JNil3tVh0IrhZ62h4vIk16VMRb8bSG0thCS/TMamy1LxSqBw4/oHt2PFFqGyDZALBsQGGsOx473POpFGWPFt6mT4oZjmGsNlW0dt0i/OWTHwfju1jnZYyLScjJWlX4komRtIp3tLhwQDQTnFGeWeb7bNL0C7th2+7l9Lln5jn9zWUPRZbBNFQnY4YhnbHiSzr5m3luDQMJY6P/1VZ84XklRIqZnFlLo9srGJXBPJhl/vOLrqDG+CaKDSx1mEb7TtiU71x7MPPdC5KQghh8Z1BqM+5EXPK0Uce+85qY7dnqa6djwTRAp+AG7JOpRaq6RHUF6sdvyRJDijpv4fiYZfyzjRYYe4osgl6SPnK2d4vnHqZAxO3YOGTMBxmiqobfn0aD4DDu+CDIksWVqjG90GH7E2PzSvlWpG7qur5CIOmScipCaztjhwk3fBNjkzBdBxJj+GOP7aYYfmdTQaNyPvHSquDql663V8fdOFS+I+CMI7E8IeQREmrA8ubOlwRkQfNOWz7JKBJnI1FyQxe0fzYEu862h8komGd8wUrolv9k/eWj69JsKu/B8TE3mtYJE7e/U2eD2soL9uYcDw+mS6LRkBVzstvHY6UTNBVE6r7SHaZr+mntsKwJY57kg0X49dllNKhoQat9ChOdZjonsrg+bXXfbd0nsuJMX8hxrdnDsb9WgxfrtYaNBYJ3Woe24FvjQZVkflRL8Y6XDz1x/kyHIQVPqASXK0roUsNVW3m4PN1S25m3s2Vhjai5I5lD8HpDfI7CV4M96+uN3VJrWePati/oCyccAPm/sR75jTdN/zXE6k/GtIL4K8EUQDwP6tV7V3Xi5msTdJF8msEHY9Cuv8i6HtKZzSwR8w3KeuDOTjIebPtaQRCr7Qwh+bi6BkEvr/ivcoBEKEjCUUHPsWgzhlDq3S80hONVehlL5XqUGI4cchVMq+jYjZPSIyAJ74igjh8TYHLg1ndsvgrNN506A0fmZ5LQPQpt7jzGdJHpsnwCTS5mTOLAzGT9XGQ8aRPisrdQplPwm3MXQx3jNXVRKi20yi4HixGlBg6LByU2k9dmdACtaz8o9lUj1nlmzytUZRstIzMk9DYHlXsmwvUT0TTDv3daazm4R4Fa7iQj0SWHKm12AOAcAIRXAqaRcCMEX7S1jmPbMmvhqmKff+wcH1k+ONbZBcKE55nAGxn5JKyspOsQNcXtNoPQvvzfSp91eOjrpI4xHoxLTrjHmgkKb+gSxW6L6VdvXzzr5XYzFaXfeOvNA39DA5SS22H1KiHcM27bdsLVhc3PGZRtSW/vRBdTVOlKuFcE53lWnfjGGtiJ8TpT+YOeqmXvcDFHRqwdLNmdb8qLN0RlprCB6iA0l2oBx07dtddxYCaI8AP4PK/TIwF3GH5UAAAAASUVORK5CYII="/>
|
||||
<text id="REST" class="cls-1" transform="translate(20.904 48.641) scale(0.553)">REST</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -1,39 +1,37 @@
|
||||
import React from "react"
|
||||
import SwaggerUI from 'swagger-ui-react';
|
||||
import 'swagger-ui-react/swagger-ui.css';
|
||||
import React from 'react'
|
||||
import SwaggerUI from 'swagger-ui-react'
|
||||
import 'swagger-ui-react/swagger-ui.css'
|
||||
|
||||
export default function ApiDocument({spec}:{spec?:string|object}) {
|
||||
|
||||
class OperationsLayout extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
getComponent
|
||||
} = this.props
|
||||
const Operations = getComponent("operations", true)
|
||||
|
||||
return (
|
||||
<div className="swagger-ui">
|
||||
<Operations />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the plugin that provides our layout component
|
||||
const OperationsLayoutPlugin = () => {
|
||||
return {
|
||||
components: {
|
||||
OperationsLayout: OperationsLayout
|
||||
}
|
||||
}
|
||||
export default function ApiDocument({ spec }: { spec?: string | object }) {
|
||||
class OperationsLayout extends React.Component {
|
||||
render() {
|
||||
const { getComponent } = this.props
|
||||
const Operations = getComponent('operations', true)
|
||||
|
||||
return (
|
||||
<div className="swagger-ui">
|
||||
<Operations />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return(
|
||||
<SwaggerUI
|
||||
spec={spec}
|
||||
supportedSubmitMethods={[]}
|
||||
customComponents={{Header:()=>null}}
|
||||
layout="OperationsLayout"
|
||||
plugins={[OperationsLayoutPlugin ]} />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the plugin that provides our layout component
|
||||
const OperationsLayoutPlugin = () => {
|
||||
return {
|
||||
components: {
|
||||
OperationsLayout: OperationsLayout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<SwaggerUI
|
||||
spec={spec}
|
||||
supportedSubmitMethods={[]}
|
||||
customComponents={{ Header: () => null }}
|
||||
layout="OperationsLayout"
|
||||
plugins={[OperationsLayoutPlugin]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,29 +1,22 @@
|
||||
import {
|
||||
MenuProps,
|
||||
App,
|
||||
Button,
|
||||
ConfigProvider,
|
||||
Dropdown
|
||||
} from 'antd';
|
||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
import Logo from '@common/assets/layout-logo.png';
|
||||
import { ProConfigProvider, ProLayout } from '@ant-design/pro-components'
|
||||
import AvatarPic from '@common/assets/default-avatar.png'
|
||||
import { useCallback, useEffect, useMemo, useState} from "react";
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx';
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions.ts';
|
||||
import { BasicResponse, RESPONSE_TIPS, routerKeyMap, STATUS_CODE } from '@common/const/const.tsx';
|
||||
import { UserInfoType } from '@common/const/type.ts';
|
||||
import { useFetch } from '@common/hooks/http.ts';
|
||||
import { ProjectFilled } from '@ant-design/icons';
|
||||
import { getNavItem, transformMenuData } from '@common/utils/navigation';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { $t } from '@common/locales';
|
||||
import { ProConfigProvider, ProLayout } from '@ant-design/pro-components';
|
||||
import LanguageSetting from './LanguageSetting';
|
||||
import { usePluginSlotHub } from '@common/contexts/PluginSlotHubContext';
|
||||
import Logo from '@common/assets/layout-logo.png'
|
||||
import { BasicResponse, RESPONSE_TIPS, routerKeyMap, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions.ts'
|
||||
import { UserInfoType } from '@common/const/type.ts'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { usePluginSlotHub } from '@common/contexts/PluginSlotHubContext'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales'
|
||||
import { transformMenuData } from '@common/utils/navigation'
|
||||
import { Icon } from '@iconify/react'
|
||||
import { App, Button, ConfigProvider, Dropdown, MenuProps } from 'antd'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
|
||||
import LanguageSetting from './LanguageSetting'
|
||||
|
||||
const APP_MODE = import.meta.env.VITE_APP_MODE;
|
||||
export type MenuItem = Required<MenuProps>['items'][number];
|
||||
const APP_MODE = import.meta.env.VITE_APP_MODE
|
||||
export type MenuItem = Required<MenuProps>['items'][number]
|
||||
|
||||
const themeToken = {
|
||||
bgLayout: '#17163E;',
|
||||
@@ -32,92 +25,103 @@ const themeToken = {
|
||||
},
|
||||
pageContainer: {
|
||||
paddingBlockPageContainerContent: 0,
|
||||
paddingInlinePageContainerContent: 0,
|
||||
paddingInlinePageContainerContent: 0
|
||||
}
|
||||
}
|
||||
|
||||
function BasicLayout({ project = 'core' }: { project: string }) {
|
||||
const navigator = useNavigate()
|
||||
const location = useLocation()
|
||||
const currentUrl = location.pathname
|
||||
const { state, accessData, checkPermission, accessInit, dispatch, resetAccess, getGlobalAccessData, menuList } =
|
||||
useGlobalContext()
|
||||
const [pathname, setPathname] = useState(currentUrl)
|
||||
const mainPage = project === 'core' ? '/service/list' : '/serviceHub/list'
|
||||
const [menuItems, setMenuItems] = useState<MenuProps['items']>()
|
||||
const pluginSlotHub = usePluginSlotHub()
|
||||
|
||||
function BasicLayout({project = 'core'}:{project:string}){
|
||||
const navigator = useNavigate()
|
||||
const location = useLocation()
|
||||
const currentUrl = location.pathname
|
||||
const { state,accessData,checkPermission,accessInit,dispatch,resetAccess,getGlobalAccessData, menuList} = useGlobalContext()
|
||||
const [pathname, setPathname] = useState(currentUrl);
|
||||
const mainPage = project === 'core' ?'/service/list':'/serviceHub/list'
|
||||
const [menuItems, setMenuItems] = useState<MenuProps['items']>();
|
||||
const pluginSlotHub = usePluginSlotHub()
|
||||
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
const newMenu = transformMenuData(menuList)
|
||||
setMenuItems(newMenu);
|
||||
},[menuList, state.language,accessInit])
|
||||
setMenuItems(newMenu)
|
||||
}, [menuList, state.language, accessInit])
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUrl === '/') {
|
||||
navigator(mainPage)
|
||||
}
|
||||
|
||||
}, [currentUrl]);
|
||||
}, [currentUrl])
|
||||
|
||||
const headerMenuData = useMemo(() => {
|
||||
// 判断权限
|
||||
const hasAccess = (access: unknown) => checkPermission(access as keyof typeof PERMISSION_DEFINITION[0]);
|
||||
const hasAccess = (access: unknown) => checkPermission(access as keyof (typeof PERMISSION_DEFINITION)[0])
|
||||
|
||||
// 过滤菜单项
|
||||
const filterMenu = (menu: Array<{ [k: string]: unknown }>) => {
|
||||
return [...menu]
|
||||
.filter(x => x) // 过滤掉空数据
|
||||
.filter((x) => x) // 过滤掉空数据
|
||||
.map((item: any) => {
|
||||
if (item.routes && item.routes.length > 0) {
|
||||
// 递归处理子菜单
|
||||
const filteredRoutes: Array<{ [k: string]: unknown }> = filterMenu(item.routes);
|
||||
const filteredRoutes: Array<{ [k: string]: unknown }> = filterMenu(item.routes)
|
||||
|
||||
if (filteredRoutes.length === 0) {
|
||||
return false
|
||||
}
|
||||
return { ...item,routes: filteredRoutes,name:$t(item.name) };
|
||||
return { ...item, routes: filteredRoutes, name: $t(item.name) }
|
||||
}
|
||||
// 处理没有 routes 的菜单项
|
||||
if (item.access) {
|
||||
return (item.access === 'all' || hasAccess(item.access)) ? {...item,name:$t(item.name)} : null;
|
||||
return item.access === 'all' || hasAccess(item.access) ? { ...item, name: $t(item.name) } : null
|
||||
}
|
||||
// 如果没有 access 和 routes,则保留
|
||||
return {...item,name:$t(item.name) };
|
||||
})
|
||||
.filter(x => x); // 过滤掉处理后为 null 的项
|
||||
};
|
||||
|
||||
// 初始过滤操作
|
||||
const res = [...(menuItems || [])]!.filter(x => x).map((x: any) => (x.routes ? { ...x,name:$t(x.name), routes: filterMenu(x.routes) } : {...x,name:$t(x.name)}));
|
||||
// 返回处理后的数据
|
||||
return { path: '/', routes: res.map(x=> ({...x, routes: x.routes?.filter(x=> (x.access || x.routes?.length > 0))})).filter(x=> (x.access || x.routes?.length > 0)) };
|
||||
}, [accessData, state.language,menuItems]);
|
||||
|
||||
const { message } = App.useApp()
|
||||
const [userInfo,setUserInfo] = useState<UserInfoType>()
|
||||
const {fetchData} = useFetch()
|
||||
const navigate = useNavigate();
|
||||
// 如果没有 access 和 routes,则保留
|
||||
return { ...item, name: $t(item.name) }
|
||||
})
|
||||
.filter((x) => x) // 过滤掉处理后为 null 的项
|
||||
}
|
||||
|
||||
// 初始过滤操作
|
||||
const res = [...(menuItems || [])]!
|
||||
.filter((x) => x)
|
||||
.map((x: any) =>
|
||||
x.routes ? { ...x, name: $t(x.name), routes: filterMenu(x.routes) } : { ...x, name: $t(x.name) }
|
||||
)
|
||||
// 返回处理后的数据
|
||||
return {
|
||||
path: '/',
|
||||
routes: res
|
||||
.map((x) => ({ ...x, routes: x.routes?.filter((x) => x.access || x.routes?.length > 0) }))
|
||||
.filter((x) => x.access || x.routes?.length > 0)
|
||||
}
|
||||
}, [accessData, state.language, menuItems])
|
||||
|
||||
const { message } = App.useApp()
|
||||
const [userInfo, setUserInfo] = useState<UserInfoType>()
|
||||
const { fetchData } = useFetch()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const getUserInfo = () => {
|
||||
fetchData<BasicResponse<{ profile: UserInfoType }>>('account/profile', { method: 'GET' })
|
||||
.then(response => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setUserInfo(data.profile)
|
||||
dispatch({ type: 'UPDATE_USERDATA', userData: data.profile })
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
fetchData<BasicResponse<{ profile: UserInfoType }>>('account/profile', { method: 'GET' }).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setUserInfo(data.profile)
|
||||
dispatch({ type: 'UPDATE_USERDATA', userData: data.profile })
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getUserInfo()
|
||||
getGlobalAccessData()
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setPathname(location.pathname)
|
||||
}, [location.pathname])
|
||||
|
||||
const logOut = () => {
|
||||
fetchData<BasicResponse<null>>('account/logout', { method: 'GET' }).then(response => {
|
||||
fetchData<BasicResponse<null>>('account/logout', { method: 'GET' }).then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
dispatch({ type: 'LOGOUT' })
|
||||
@@ -130,133 +134,162 @@ const themeToken = {
|
||||
})
|
||||
}
|
||||
|
||||
const items: MenuProps['items'] = useMemo(() => [
|
||||
userInfo?.type !== 'guest' && {
|
||||
key: '2',
|
||||
label: (
|
||||
<Button key="changePsw" type="text" className="flex items-center p-0 bg-transparent border-none " onClick={() => navigator('/userProfile/changepsw')}>
|
||||
{$t('账号设置')}
|
||||
</Button>)
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: (
|
||||
<Button key="logout" type="text" className="flex items-center p-0 bg-transparent border-none " onClick={logOut}>
|
||||
{$t('退出登录')}
|
||||
</Button>)
|
||||
},
|
||||
].filter(Boolean), [userInfo]);
|
||||
const items: MenuProps['items'] = useMemo(
|
||||
() =>
|
||||
[
|
||||
userInfo?.type !== 'guest' && {
|
||||
key: '2',
|
||||
label: (
|
||||
<Button
|
||||
key="changePsw"
|
||||
type="text"
|
||||
className="flex items-center p-0 bg-transparent border-none"
|
||||
onClick={() => navigator('/userProfile/changepsw')}
|
||||
>
|
||||
{$t('账号设置')}
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: (
|
||||
<Button
|
||||
key="logout"
|
||||
type="text"
|
||||
className="flex items-center p-0 bg-transparent border-none"
|
||||
onClick={logOut}
|
||||
>
|
||||
{$t('退出登录')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
].filter(Boolean),
|
||||
[userInfo]
|
||||
)
|
||||
|
||||
const actionRender = useMemo(() => {
|
||||
return [
|
||||
<LanguageSetting />,
|
||||
<Button
|
||||
className=" text-[#ffffffb3] hover:text-[#fff] border-none"
|
||||
type="default"
|
||||
ghost
|
||||
onClick={() => {
|
||||
window.open('https://docs.apipark.com', '_blank')
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center gap-[8px]">
|
||||
{' '}
|
||||
<Icon icon="ic:baseline-help" width="14" height="14" />
|
||||
{$t('文档')}
|
||||
</span>
|
||||
</Button>,
|
||||
...((pluginSlotHub.getSlot('basicLayoutAfterBtns') as unknown[]) || [])
|
||||
]
|
||||
}, [state.language, pluginSlotHub.getSlot('basicLayoutAfterBtns')])
|
||||
|
||||
const actionRender =useMemo( ()=>{
|
||||
return [
|
||||
<LanguageSetting />,
|
||||
<Button className=" text-[#ffffffb3] hover:text-[#fff] border-none" type="default" ghost onClick={()=>{window.open('https://docs.apipark.com','_blank')}}>
|
||||
<span className='flex items-center gap-[8px]'> <Icon icon="ic:baseline-help" width="14" height="14"/>{$t('文档')}</span>
|
||||
</Button> ,
|
||||
...((pluginSlotHub.getSlot('basicLayoutAfterBtns') as unknown[] )||[] )
|
||||
]
|
||||
},[pluginSlotHub.getSlot('basicLayoutAfterBtns') ])
|
||||
|
||||
|
||||
return(
|
||||
<div
|
||||
id="test-pro-layout"
|
||||
style={{
|
||||
height: '100vh',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
return (
|
||||
<div
|
||||
id="test-pro-layout"
|
||||
style={{
|
||||
height: '100vh',
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
<ProConfigProvider hashed={false}>
|
||||
<ConfigProvider
|
||||
getTargetContainer={() => {
|
||||
return document.getElementById('test-pro-layout') || document.body
|
||||
}}
|
||||
>
|
||||
<ProConfigProvider hashed={false}>
|
||||
<ConfigProvider
|
||||
getTargetContainer={() => {
|
||||
return document.getElementById('test-pro-layout') || document.body;
|
||||
<ProLayout
|
||||
prefixCls="apipark-layout"
|
||||
location={{
|
||||
pathname
|
||||
}}
|
||||
siderWidth={220}
|
||||
breakpoint={'lg'}
|
||||
route={headerMenuData}
|
||||
token={themeToken}
|
||||
siderMenuType="group"
|
||||
menu={{
|
||||
type: 'group',
|
||||
collapsedShowGroupTitle: true
|
||||
}}
|
||||
disableMobile={true}
|
||||
avatarProps={{
|
||||
src: AvatarPic || userInfo?.avatar,
|
||||
size: 'small',
|
||||
title: userInfo?.username || 'unknown',
|
||||
render: (props, dom) => {
|
||||
return (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items
|
||||
}}
|
||||
>
|
||||
<ProLayout
|
||||
prefixCls="apipark-layout"
|
||||
location={{
|
||||
pathname,
|
||||
}}
|
||||
siderWidth={220}
|
||||
breakpoint={'lg'}
|
||||
route={headerMenuData}
|
||||
token={themeToken}
|
||||
siderMenuType="group"
|
||||
menu={{
|
||||
type: 'group',
|
||||
collapsedShowGroupTitle: true,
|
||||
}}
|
||||
disableMobile={true}
|
||||
avatarProps={{
|
||||
src: AvatarPic || userInfo?.avatar,
|
||||
size: 'small',
|
||||
title: userInfo?.username||'unknown',
|
||||
render: (props, dom) => {
|
||||
return (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items
|
||||
}}
|
||||
>
|
||||
<div className='avatar-dom'>{dom}
|
||||
</div>
|
||||
</Dropdown>
|
||||
);
|
||||
},
|
||||
}}
|
||||
actionsRender={(props) => {
|
||||
if (props.isMobile) return [];
|
||||
if (typeof window === 'undefined') return [];
|
||||
return actionRender;
|
||||
}}
|
||||
headerTitleRender={() => (
|
||||
<div className="w-[192px] flex items-center">
|
||||
<img
|
||||
className="h-[20px] cursor-pointer "
|
||||
src={Logo}
|
||||
onClick={()=> navigator(mainPage)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
logo={Logo}
|
||||
pageTitleRender={()=>$t('APIPark')}
|
||||
menuFooterRender={(props) => {
|
||||
if (props?.collapsed) return undefined;
|
||||
}}
|
||||
menuItemRender={(item, dom) => (
|
||||
<div
|
||||
onClick={() => {
|
||||
// 同级目录点击无效
|
||||
if(item.key && routerKeyMap.get(item.key) && routerKeyMap.get(item.key).length > 0 && routerKeyMap.get(item.key)?.indexOf(pathname.split('/')[1]) !== -1){
|
||||
return
|
||||
}
|
||||
if(item.key === pathname.split('/')[1]){
|
||||
return
|
||||
}
|
||||
|
||||
if(item.path){
|
||||
navigator(item.path)
|
||||
}
|
||||
setPathname(item.path || '');
|
||||
}}
|
||||
>
|
||||
{dom}
|
||||
</div>
|
||||
)}
|
||||
fixSiderbar={true}
|
||||
layout='mix'
|
||||
splitMenus={true}
|
||||
collapsed={false}
|
||||
collapsedButtonRender={false}
|
||||
>
|
||||
<div className={`w-full h-calc-100vh-minus-navbar pl-PAGE_INSIDE_X pt-PAGE_INSIDE_T ${currentUrl.startsWith('/role/list') ? 'overflow-auto' : 'overflow-hidden' }`}>
|
||||
<Outlet />
|
||||
</div>
|
||||
</ProLayout>
|
||||
</ConfigProvider>
|
||||
</ProConfigProvider>
|
||||
</div>
|
||||
)
|
||||
>
|
||||
<div className="avatar-dom">{dom}</div>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
}}
|
||||
actionsRender={(props) => {
|
||||
if (props.isMobile) return []
|
||||
if (typeof window === 'undefined') return []
|
||||
return actionRender
|
||||
}}
|
||||
headerTitleRender={() => (
|
||||
<div className="w-[192px] flex items-center">
|
||||
<img className="h-[20px] cursor-pointer " src={Logo} onClick={() => navigator(mainPage)} />
|
||||
</div>
|
||||
)}
|
||||
logo={Logo}
|
||||
pageTitleRender={() => $t('APIPark')}
|
||||
menuFooterRender={(props) => {
|
||||
if (props?.collapsed) return undefined
|
||||
}}
|
||||
menuItemRender={(item, dom) => (
|
||||
<div
|
||||
onClick={() => {
|
||||
// 同级目录点击无效
|
||||
if (
|
||||
item.key &&
|
||||
routerKeyMap.get(item.key) &&
|
||||
routerKeyMap.get(item.key).length > 0 &&
|
||||
routerKeyMap.get(item.key)?.indexOf(pathname.split('/')[1]) !== -1
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (item.key === pathname.split('/')[1]) {
|
||||
return
|
||||
}
|
||||
|
||||
if (item.path) {
|
||||
navigator(item.path)
|
||||
}
|
||||
setPathname(item.path || '')
|
||||
}}
|
||||
>
|
||||
{dom}
|
||||
</div>
|
||||
)}
|
||||
fixSiderbar={true}
|
||||
layout="mix"
|
||||
splitMenus={true}
|
||||
collapsed={false}
|
||||
collapsedButtonRender={false}
|
||||
>
|
||||
<div
|
||||
className={`w-full h-calc-100vh-minus-navbar pl-PAGE_INSIDE_X pt-PAGE_INSIDE_T ${
|
||||
currentUrl.startsWith('/role/list') ? 'overflow-auto' : 'overflow-hidden'
|
||||
}`}
|
||||
>
|
||||
<Outlet />
|
||||
</div>
|
||||
</ProLayout>
|
||||
</ConfigProvider>
|
||||
</ProConfigProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default BasicLayout
|
||||
export default BasicLayout
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { Breadcrumb } from "antd"
|
||||
import { useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import {FC,useEffect} from "react";
|
||||
|
||||
import { Breadcrumb } from 'antd'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { FC, useEffect } from 'react'
|
||||
|
||||
const TopBreadcrumb: FC = () => {
|
||||
const { breadcrumb } = useBreadcrumb()
|
||||
useEffect(() => {
|
||||
}, [breadcrumb]);
|
||||
return (
|
||||
<Breadcrumb items={breadcrumb} />
|
||||
)
|
||||
const { breadcrumb } = useBreadcrumb()
|
||||
useEffect(() => {}, [breadcrumb])
|
||||
return <Breadcrumb items={breadcrumb} />
|
||||
}
|
||||
|
||||
export default TopBreadcrumb
|
||||
export default TopBreadcrumb
|
||||
|
||||
@@ -1,34 +1,32 @@
|
||||
import { FC } from 'react';
|
||||
import { Table } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { $t } from '@common/locales';
|
||||
import { FC } from 'react'
|
||||
import { Table } from 'antd'
|
||||
import type { ColumnsType } from 'antd/es/table'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
interface DataType {
|
||||
httpStatusCode: string;
|
||||
systemStatusCode: string;
|
||||
description: string;
|
||||
|
||||
httpStatusCode: string
|
||||
systemStatusCode: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const columns: ColumnsType<DataType> = [
|
||||
{
|
||||
title:$t('HTTP 状态码'),
|
||||
title: $t('HTTP 状态码'),
|
||||
dataIndex: 'httpStatusCode',
|
||||
key: 'httpStatusCode',
|
||||
key: 'httpStatusCode'
|
||||
},
|
||||
{
|
||||
title:$t('系统状态码'),
|
||||
title: $t('系统状态码'),
|
||||
dataIndex: 'systemStatusCode',
|
||||
key: 'systemStatusCode',
|
||||
key: 'systemStatusCode'
|
||||
},
|
||||
{
|
||||
title: $t('描述'),
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
ellipsis:true
|
||||
},
|
||||
|
||||
];
|
||||
ellipsis: true
|
||||
}
|
||||
]
|
||||
|
||||
const data: DataType[] = [
|
||||
// {
|
||||
@@ -44,12 +42,12 @@ const data: DataType[] = [
|
||||
{
|
||||
httpStatusCode: '413',
|
||||
systemStatusCode: '10003',
|
||||
description: '请求频率过高',
|
||||
description: '请求频率过高'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '403',
|
||||
systemStatusCode: '10004',
|
||||
description: '请求来源非法,不在白名单中',
|
||||
description: '请求来源非法,不在白名单中'
|
||||
},
|
||||
// {
|
||||
// httpStatusCode: '416',
|
||||
@@ -59,7 +57,7 @@ const data: DataType[] = [
|
||||
{
|
||||
httpStatusCode: '504',
|
||||
systemStatusCode: '10006',
|
||||
description: '网关超时',
|
||||
description: '网关超时'
|
||||
},
|
||||
// {
|
||||
// httpStatusCode: '504',
|
||||
@@ -69,7 +67,7 @@ const data: DataType[] = [
|
||||
{
|
||||
httpStatusCode: '404',
|
||||
systemStatusCode: '10007',
|
||||
description: '接口不存在',
|
||||
description: '接口不存在'
|
||||
},
|
||||
// {
|
||||
// httpStatusCode: '416',
|
||||
@@ -84,42 +82,43 @@ const data: DataType[] = [
|
||||
{
|
||||
httpStatusCode: '400',
|
||||
systemStatusCode: '10010',
|
||||
description: '无法识别请求内容,请检查请求体是否正确',
|
||||
description: '无法识别请求内容,请检查请求体是否正确'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '400',
|
||||
systemStatusCode: '10011',
|
||||
description: '请求头部缺少 Content-Type 字段',
|
||||
description: '请求头部缺少 Content-Type 字段'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '400',
|
||||
systemStatusCode: '10011',
|
||||
description: '请求头部 Content-Type 字段错误',
|
||||
description: '请求头部 Content-Type 字段错误'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '400',
|
||||
systemStatusCode: '10014',
|
||||
description: '批量参数超出单次批量数量的最大限制',
|
||||
description: '批量参数超出单次批量数量的最大限制'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '400',
|
||||
systemStatusCode: '10016',
|
||||
description: '参数缺少内容',
|
||||
description: '参数缺少内容'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '500',
|
||||
systemStatusCode: '10017',
|
||||
description: '参数类型错误',
|
||||
},
|
||||
];
|
||||
description: '参数类型错误'
|
||||
}
|
||||
]
|
||||
|
||||
const CodePage: FC = () =>
|
||||
<Table
|
||||
const CodePage: FC = () => (
|
||||
<Table
|
||||
size="small"
|
||||
columns={columns}
|
||||
className='table-border border-b-0 rounded'
|
||||
dataSource={data?.map((item, index) => ({...item, key: index})) || []}
|
||||
columns={columns}
|
||||
className="table-border border-b-0 rounded"
|
||||
dataSource={data?.map((item, index) => ({ ...item, key: index })) || []}
|
||||
pagination={false}
|
||||
/>;
|
||||
/>
|
||||
)
|
||||
|
||||
export default CodePage;
|
||||
export default CodePage
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
|
||||
import { useState,FC } from 'react';
|
||||
import { Tooltip, Button } from 'antd';
|
||||
import useCopyToClipboard from '@common/hooks/copy';
|
||||
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||
import { useState, FC } from 'react'
|
||||
import { Tooltip, Button } from 'antd'
|
||||
import useCopyToClipboard from '@common/hooks/copy'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
|
||||
type AddressItem = {
|
||||
expand?: boolean;
|
||||
[key: string]: unknown;
|
||||
expand?: boolean
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
type CopyAddrListProps = {
|
||||
addrItem: AddressItem;
|
||||
onAddrItemChange?: (addrItem: AddressItem) => void;
|
||||
keyName: string;
|
||||
type CopyAddrListProps = {
|
||||
addrItem: AddressItem
|
||||
onAddrItemChange?: (addrItem: AddressItem) => void
|
||||
keyName: string
|
||||
}
|
||||
|
||||
const CopyAddrList: FC<CopyAddrListProps> = ({ addrItem, onAddrItemChange, keyName }) => {
|
||||
const [localAddrItem, setLocalAddrItem] = useState<AddressItem>(addrItem);
|
||||
const { copyToClipboard } = useCopyToClipboard();
|
||||
const [localAddrItem, setLocalAddrItem] = useState<AddressItem>(addrItem)
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
|
||||
const toggleExpand = () => {
|
||||
const updatedAddrItem = { ...localAddrItem, expand: !localAddrItem.expand };
|
||||
setLocalAddrItem(updatedAddrItem);
|
||||
onAddrItemChange?.(updatedAddrItem);
|
||||
};
|
||||
const updatedAddrItem = { ...localAddrItem, expand: !localAddrItem.expand }
|
||||
setLocalAddrItem(updatedAddrItem)
|
||||
onAddrItemChange?.(updatedAddrItem)
|
||||
}
|
||||
|
||||
const renderTooltipTitle = () => {
|
||||
// 假设keyName对应的值是一个字符串数组
|
||||
const addresses:string[] = localAddrItem[keyName] as string[]
|
||||
const addresses: string[] = localAddrItem[keyName] as string[]
|
||||
return (
|
||||
<div>
|
||||
{addresses?.map((addr, index) => (
|
||||
@@ -36,29 +35,41 @@ const CopyAddrList: FC<CopyAddrListProps> = ({ addrItem, onAddrItemChange, keyNa
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
const renderAddresses = () => {
|
||||
if (!localAddrItem.expand) {
|
||||
return (
|
||||
<span className="overflow-ellipsis w-full inline-block overflow-hidden align-middle">
|
||||
<Tooltip title={renderTooltipTitle}>
|
||||
<span className='flex items-center'>
|
||||
<span className={`overflow-ellipsis inline-block overflow-hidden align-middle ${((localAddrItem[keyName] as string[]).length > 1) ? 'w-5/6' : 'w-full'}`}>
|
||||
<span className="flex items-center">
|
||||
<span
|
||||
className={`overflow-ellipsis inline-block overflow-hidden align-middle ${(localAddrItem[keyName] as string[]).length > 1 ? 'w-5/6' : 'w-full'}`}
|
||||
>
|
||||
{(localAddrItem[keyName] as string[]).join(',')}
|
||||
</span>
|
||||
{(localAddrItem[keyName] as string[]).length === 1 && (
|
||||
<Button type="primary" className="border-none ant-typography-copy text-theme hover:text-A_HOVER " ghost onClick={() => copyToClipboard((localAddrItem[keyName] as string))} icon={<Icon icon="ic:baseline-file-copy" width="14" height="14"/>} size="small" />
|
||||
<Button
|
||||
type="primary"
|
||||
className="border-none ant-typography-copy text-theme hover:text-A_HOVER "
|
||||
ghost
|
||||
onClick={() => copyToClipboard(localAddrItem[keyName] as string)}
|
||||
icon={<Icon icon="ic:baseline-file-copy" width="14" height="14" />}
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
{(localAddrItem[keyName] as string[]).length !== 1 && (
|
||||
<Button className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]" icon={<iconpark-icon name="zhankai" style={{marginTop:'4px'}}></iconpark-icon>} onClick={toggleExpand} />
|
||||
<Button
|
||||
className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]"
|
||||
icon={<iconpark-icon name="zhankai" style={{ marginTop: '4px' }}></iconpark-icon>}
|
||||
onClick={toggleExpand}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
);
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className="flex flex-nowrap items-center justify-between">
|
||||
@@ -66,21 +77,28 @@ const CopyAddrList: FC<CopyAddrListProps> = ({ addrItem, onAddrItemChange, keyNa
|
||||
{(localAddrItem[keyName] as string[])?.map((addr: string, index: number) => (
|
||||
<div key={index} className="block w-full">
|
||||
<span className="leading-6">{addr}</span>
|
||||
<Button type="primary" className="border-none bg-transparent w-[16px] h-[22px] p-[0px] ml-2 text-theme hover:text-A_HOVER" ghost onClick={() => copyToClipboard(addr)} icon={<Icon icon="ic:baseline-file-copy" width="14" height="14"/>} size="small" />
|
||||
<Button
|
||||
type="primary"
|
||||
className="border-none bg-transparent w-[16px] h-[22px] p-[0px] ml-2 text-theme hover:text-A_HOVER"
|
||||
ghost
|
||||
onClick={() => copyToClipboard(addr)}
|
||||
icon={<Icon icon="ic:baseline-file-copy" width="14" height="14" />}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Button className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]" icon={<iconpark-icon name="shouqi-2"></iconpark-icon>} onClick={toggleExpand} />
|
||||
<Button
|
||||
className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]"
|
||||
icon={<iconpark-icon name="shouqi-2"></iconpark-icon>}
|
||||
onClick={toggleExpand}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{renderAddresses()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return <div>{renderAddresses()}</div>
|
||||
}
|
||||
|
||||
export default CopyAddrList;
|
||||
export default CopyAddrList
|
||||
|
||||
@@ -1,59 +1,84 @@
|
||||
|
||||
import { Button, Drawer, DrawerProps, Space } from "antd";
|
||||
import WithPermission from "./WithPermission";
|
||||
import { useEffect, useState } from "react";
|
||||
import { $t } from '@common/locales';
|
||||
import { Button, Drawer, DrawerProps, Space } from 'antd'
|
||||
import WithPermission from './WithPermission'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
export type DrawerWithFooterProps = DrawerProps & {
|
||||
onSubmit?: () => Promise<boolean|string>|undefined
|
||||
submitAccess?: string
|
||||
submitDisabled?:boolean
|
||||
onClose?:()=>void
|
||||
showLastStep?:boolean
|
||||
onLastStep?:()=>void
|
||||
notAutoClose?:boolean
|
||||
showOkBtn?:boolean
|
||||
extraBtn?:React.ReactNode
|
||||
okBtnTitle?:string
|
||||
cancelBtnTitle?:string
|
||||
onSubmit?: () => Promise<boolean | string> | undefined
|
||||
submitAccess?: string
|
||||
submitDisabled?: boolean
|
||||
onClose?: () => void
|
||||
showLastStep?: boolean
|
||||
onLastStep?: () => void
|
||||
notAutoClose?: boolean
|
||||
showOkBtn?: boolean
|
||||
extraBtn?: React.ReactNode
|
||||
okBtnTitle?: string
|
||||
cancelBtnTitle?: string
|
||||
}
|
||||
export function DrawerWithFooter(props:DrawerWithFooterProps){
|
||||
const {children,title,placement='right',onClose,onSubmit,submitDisabled = false,okBtnTitle= $t('提交'),cancelBtnTitle,open,submitAccess,showLastStep,onLastStep,notAutoClose,showOkBtn=true,extraBtn} = props
|
||||
const [submitLoading, setSubmitLoading] = useState<boolean>(false)
|
||||
const handlerSubmit = ()=>{
|
||||
setSubmitLoading(true)
|
||||
onSubmit?.()?.then(()=>{!notAutoClose && onClose?.()}).finally(()=>{setSubmitLoading(false)})
|
||||
}
|
||||
export function DrawerWithFooter(props: DrawerWithFooterProps) {
|
||||
const {
|
||||
children,
|
||||
title,
|
||||
placement = 'right',
|
||||
onClose,
|
||||
onSubmit,
|
||||
submitDisabled = false,
|
||||
okBtnTitle = $t('提交'),
|
||||
cancelBtnTitle,
|
||||
open,
|
||||
submitAccess,
|
||||
showLastStep,
|
||||
onLastStep,
|
||||
notAutoClose,
|
||||
showOkBtn = true,
|
||||
extraBtn
|
||||
} = props
|
||||
const [submitLoading, setSubmitLoading] = useState<boolean>(false)
|
||||
const handlerSubmit = () => {
|
||||
setSubmitLoading(true)
|
||||
onSubmit?.()
|
||||
?.then(() => {
|
||||
!notAutoClose && onClose?.()
|
||||
})
|
||||
.finally(() => {
|
||||
setSubmitLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(()=>{!open && setSubmitLoading(false)},[open])
|
||||
return (<>
|
||||
<Drawer
|
||||
{...props}
|
||||
push={false}
|
||||
title={title}
|
||||
placement={placement}
|
||||
width="60%"
|
||||
destroyOnClose={true}
|
||||
maskClosable={false}
|
||||
classNames={
|
||||
{footer:'text-right'}
|
||||
}
|
||||
footer={
|
||||
<Space className="flex flex-row-reverse" style={{}}>
|
||||
{showOkBtn && <WithPermission access={submitAccess}>
|
||||
<Button onClick={handlerSubmit} type="primary" loading={submitLoading} disabled={submitDisabled}>
|
||||
{ okBtnTitle}
|
||||
</Button>
|
||||
</WithPermission>}
|
||||
{ showLastStep && <Button onClick={onLastStep ?? onClose}> { $t('上一步')}</Button>}
|
||||
{ extraBtn }
|
||||
<Button onClick={onClose}>{cancelBtnTitle ?? (showOkBtn ? $t('取消'):$t('关闭'))}</Button>
|
||||
</Space>
|
||||
}
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
>
|
||||
{children}
|
||||
</Drawer>
|
||||
</>)
|
||||
}
|
||||
useEffect(() => {
|
||||
!open && setSubmitLoading(false)
|
||||
}, [open])
|
||||
return (
|
||||
<>
|
||||
<Drawer
|
||||
{...props}
|
||||
push={false}
|
||||
title={title}
|
||||
placement={placement}
|
||||
width="60%"
|
||||
destroyOnClose={true}
|
||||
maskClosable={false}
|
||||
classNames={{ footer: 'text-right' }}
|
||||
footer={
|
||||
<Space className="flex flex-row-reverse" style={{}}>
|
||||
{showOkBtn && (
|
||||
<WithPermission access={submitAccess}>
|
||||
<Button onClick={handlerSubmit} type="primary" loading={submitLoading} disabled={submitDisabled}>
|
||||
{okBtnTitle}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
)}
|
||||
{showLastStep && <Button onClick={onLastStep ?? onClose}> {$t('上一步')}</Button>}
|
||||
{extraBtn}
|
||||
<Button onClick={onClose}>{cancelBtnTitle ?? (showOkBtn ? $t('取消') : $t('关闭'))}</Button>
|
||||
</Space>
|
||||
}
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
>
|
||||
{children}
|
||||
</Drawer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,91 +1,93 @@
|
||||
|
||||
import {FC } from 'react';
|
||||
import { Input, Space } from 'antd';
|
||||
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||
import { FC } from 'react'
|
||||
import { Input, Space } from 'antd'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
|
||||
type KeyValueInput = {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
key: string
|
||||
value: string
|
||||
}
|
||||
|
||||
type DynamicKeyValueInputProps = {
|
||||
value?: KeyValueInput[];
|
||||
onChange?: (newValue: KeyValueInput[]) => void;
|
||||
};
|
||||
value?: KeyValueInput[]
|
||||
onChange?: (newValue: KeyValueInput[]) => void
|
||||
}
|
||||
|
||||
|
||||
export function transferToList (rawData:unknown):Array<{key:string, value:string}> {
|
||||
const res:Array<{key:string, value:string}> = []
|
||||
if(!rawData)
|
||||
return res
|
||||
const keys:Array<string> = Object.keys(rawData)
|
||||
export function transferToList(rawData: unknown): Array<{ key: string; value: string }> {
|
||||
const res: Array<{ key: string; value: string }> = []
|
||||
if (!rawData) return res
|
||||
const keys: Array<string> = Object.keys(rawData)
|
||||
if (keys?.length > 0) {
|
||||
for (const key of keys) {
|
||||
res.push({ key: key, value: rawData[key] })
|
||||
for (const key of keys) {
|
||||
res.push({ key: key, value: rawData[key] })
|
||||
}
|
||||
return [...res, { key: '', value: '' }]
|
||||
}
|
||||
return [...res, { key: '', value: '' }]
|
||||
}
|
||||
return [{ key: '', value: '' }]
|
||||
return [{ key: '', value: '' }]
|
||||
}
|
||||
|
||||
export function transferToMap (rawData:Array<{key:string, value:string}>):{[key:string]:string} {
|
||||
const res:{[key:string]:string} = {}
|
||||
export function transferToMap(rawData: Array<{ key: string; value: string }>): { [key: string]: string } {
|
||||
const res: { [key: string]: string } = {}
|
||||
for (const kv of rawData) {
|
||||
if (kv.key && kv.value) { res[kv.key] = kv.value }
|
||||
if (kv.key && kv.value) {
|
||||
res[kv.key] = kv.value
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
export const DynamicKeyValueInput: FC<DynamicKeyValueInputProps> = ({value = [{key:'',value:''}],onChange}) => {
|
||||
export const DynamicKeyValueInput: FC<DynamicKeyValueInputProps> = ({ value = [{ key: '', value: '' }], onChange }) => {
|
||||
// const [keyValuePairs, setKeyValuePairs] = useState<KeyValueInput[]>([{ key: '', value: '' }]);
|
||||
|
||||
// Define a handler for when the inputs change
|
||||
// Define a handler for when the inputs change
|
||||
const handleInputChange = (index: number, type: 'key' | 'value', newValue: string) => {
|
||||
// Create a new array with the updated value
|
||||
const newKeyValuePairs = value ? [...value] : [];
|
||||
const newKeyValuePairs = value ? [...value] : []
|
||||
if (newKeyValuePairs[index]) {
|
||||
newKeyValuePairs[index][type] = newValue;
|
||||
newKeyValuePairs[index][type] = newValue
|
||||
// If we're changing the last input and it's not empty, add a new pair
|
||||
if (index === newKeyValuePairs.length - 1 && (newKeyValuePairs[index].key || newKeyValuePairs[index].value)) {
|
||||
newKeyValuePairs.push({ key: '', value: '' });
|
||||
newKeyValuePairs.push({ key: '', value: '' })
|
||||
}
|
||||
// Call the onChange handler if it exists
|
||||
onChange?.(newKeyValuePairs);
|
||||
onChange?.(newKeyValuePairs)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const addNewPair = () => {
|
||||
const newKeyValuePairs = value ? [...value, { key: '', value: '' }] : [{ key: '', value: '' }];
|
||||
onChange?.(newKeyValuePairs);
|
||||
};
|
||||
const newKeyValuePairs = value ? [...value, { key: '', value: '' }] : [{ key: '', value: '' }]
|
||||
onChange?.(newKeyValuePairs)
|
||||
}
|
||||
|
||||
const removePair = (index: number) => {
|
||||
const newKeyValuePairs = value?.filter((_, idx) => idx !== index) || [];
|
||||
onChange?.(newKeyValuePairs);
|
||||
};
|
||||
const newKeyValuePairs = value?.filter((_, idx) => idx !== index) || []
|
||||
onChange?.(newKeyValuePairs)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{value && value?.map((pair, index) => (
|
||||
<Space key={index} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
|
||||
<Input
|
||||
placeholder="Key"
|
||||
value={pair.key}
|
||||
onChange={(e) => handleInputChange(index, 'key', e.target.value)}
|
||||
style={{ width: 162 }} />
|
||||
<Input
|
||||
placeholder="Value"
|
||||
value={pair.value}
|
||||
onChange={(e) => handleInputChange(index, 'value', e.target.value)}
|
||||
style={{ width: 162 }} />
|
||||
{index !== value.length - 1 && (
|
||||
<>
|
||||
<Icon icon="ic:baseline-delete" onClick={() => removePair(index)} width="14" height="14"/>
|
||||
<Icon icon="ic:baseline-add" onClick={addNewPair} width="14" height="14"/>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
<>
|
||||
{value &&
|
||||
value?.map((pair, index) => (
|
||||
<Space key={index} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
|
||||
<Input
|
||||
placeholder="Key"
|
||||
value={pair.key}
|
||||
onChange={(e) => handleInputChange(index, 'key', e.target.value)}
|
||||
style={{ width: 162 }}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Value"
|
||||
value={pair.value}
|
||||
onChange={(e) => handleInputChange(index, 'value', e.target.value)}
|
||||
style={{ width: 162 }}
|
||||
/>
|
||||
{index !== value.length - 1 && (
|
||||
<>
|
||||
<Icon icon="ic:baseline-delete" onClick={() => removePair(index)} width="14" height="14" />
|
||||
<Icon icon="ic:baseline-add" onClick={addNewPair} width="14" height="14" />
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,116 +1,130 @@
|
||||
import { EditableProTable } from "@ant-design/pro-components";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { v4 as uuidv4} from 'uuid';
|
||||
import { PageProColumns } from "./PageList";
|
||||
import TableBtnWithPermission from "./TableBtnWithPermission";
|
||||
import { $t } from "@common/locales";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
|
||||
import { EditableProTable } from '@ant-design/pro-components'
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { PageProColumns } from './PageList'
|
||||
import TableBtnWithPermission from './TableBtnWithPermission'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
|
||||
interface EditableTableProps<T> {
|
||||
configFields: PageProColumns<T>[];
|
||||
value?: T[]; // 外部传入的值
|
||||
className?: string;
|
||||
onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数
|
||||
// tableProps?: TableProps<T>;
|
||||
disabled?:boolean
|
||||
extendsId?:string[] // 自增一行时,需要和上一行数据一致的字段,比如集群id
|
||||
configFields: PageProColumns<T>[]
|
||||
value?: T[] // 外部传入的值
|
||||
className?: string
|
||||
onChange?: (newConfigItems: T[]) => void // 当配置项变化时,外部传入的回调函数
|
||||
// tableProps?: TableProps<T>;
|
||||
disabled?: boolean
|
||||
extendsId?: string[] // 自增一行时,需要和上一行数据一致的字段,比如集群id
|
||||
}
|
||||
|
||||
const EditableTable = <T extends { _id: string }>({
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
// tableProps,
|
||||
disabled,
|
||||
className,
|
||||
extendsId,
|
||||
}: EditableTableProps<T>) => {
|
||||
const [configurations, setConfigurations] = useState<(T | {_id:string})[]>(value ||[{_id:'1234'}]);
|
||||
const {state} = useGlobalContext()
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
// tableProps,
|
||||
disabled,
|
||||
className,
|
||||
extendsId
|
||||
}: EditableTableProps<T>) => {
|
||||
const [configurations, setConfigurations] = useState<(T | { _id: string })[]>(value || [{ _id: '1234' }])
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() =>
|
||||
value?.map((item) => item._id) || ['1234']
|
||||
);
|
||||
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() => value?.map((item) => item._id) || ['1234'])
|
||||
|
||||
useEffect(() => {
|
||||
setConfigurations(value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || [{_id:uuidv4()}]);
|
||||
}, [value]);
|
||||
useEffect(() => {
|
||||
setConfigurations(value?.map((x) => (x._id ? x : { ...x, _id: uuidv4() })) || [{ _id: uuidv4() }])
|
||||
}, [value])
|
||||
|
||||
const getNotEmptyValue = (value:unknown)=>{
|
||||
return value
|
||||
}
|
||||
const getNotEmptyValue = (value: unknown) => {
|
||||
return value
|
||||
}
|
||||
|
||||
const translatedColumns = useMemo(()=>configFields.map((x)=>({...x, title:$t(x.title as string)})),[state.language,configFields])
|
||||
const translatedColumns = useMemo(
|
||||
() => configFields.map((x) => ({ ...x, title: $t(x.title as string) })),
|
||||
[state.language, configFields]
|
||||
)
|
||||
|
||||
return (
|
||||
<EditableProTable<T>
|
||||
className={className}
|
||||
columns={translatedColumns}
|
||||
rowKey="_id"
|
||||
value={configurations as T[]}
|
||||
size="small"
|
||||
bordered={true}
|
||||
recordCreatorProps={false}
|
||||
editable={ {
|
||||
type: 'multiple',
|
||||
editableKeys:disabled ? [] : configurations?.map(x=>x._id),
|
||||
actionRender: (row, config) => {
|
||||
return [
|
||||
<TableBtnWithPermission key="add" btnType="add" onClick={() => {
|
||||
const newId = uuidv4();
|
||||
setConfigurations((prev)=>{
|
||||
const tmpPreData = [...prev];
|
||||
const newId = uuidv4()
|
||||
const lastRecord:{[k:string]:unknown} = tmpPreData[tmpPreData.length - 1];
|
||||
const newRecord :{[k:string]:unknown, _id:string}= { _id: newId };
|
||||
|
||||
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
|
||||
if(extendsId && extendsId.length > 0) {
|
||||
extendsId.forEach(field => {
|
||||
newRecord[field] = lastRecord[field];
|
||||
});
|
||||
}
|
||||
tmpPreData.splice(Number(config.index) + 1, 0,newRecord);
|
||||
onChange?.(getNotEmptyValue(tmpPreData));
|
||||
return tmpPreData});
|
||||
setEditableRowKeys((prev)=>([...prev,newId]))
|
||||
}}
|
||||
btnTitle="增加"/>,
|
||||
return (
|
||||
<EditableProTable<T>
|
||||
className={className}
|
||||
columns={translatedColumns}
|
||||
rowKey="_id"
|
||||
value={configurations as T[]}
|
||||
size="small"
|
||||
bordered={true}
|
||||
recordCreatorProps={false}
|
||||
editable={{
|
||||
type: 'multiple',
|
||||
editableKeys: disabled ? [] : configurations?.map((x) => x._id),
|
||||
actionRender: (row, config) => {
|
||||
return [
|
||||
<TableBtnWithPermission
|
||||
key="add"
|
||||
btnType="add"
|
||||
onClick={() => {
|
||||
const newId = uuidv4()
|
||||
setConfigurations((prev) => {
|
||||
const tmpPreData = [...prev]
|
||||
const newId = uuidv4()
|
||||
const lastRecord: { [k: string]: unknown } = tmpPreData[tmpPreData.length - 1]
|
||||
const newRecord: { [k: string]: unknown; _id: string } = { _id: newId }
|
||||
|
||||
(config.index !== configurations.length - 1 )&& <TableBtnWithPermission key="remove" btnType="remove" btnTitle="删除"
|
||||
onClick={() => {
|
||||
setConfigurations((prev)=>{
|
||||
const tmpPreData = [...prev];
|
||||
tmpPreData.splice(Number(config.index), 1);
|
||||
onChange?.(tmpPreData);
|
||||
return tmpPreData});
|
||||
setEditableRowKeys((prev)=>(prev.filter(x=>x !== config._id)))
|
||||
}}/>,,
|
||||
];
|
||||
},
|
||||
onValuesChange: (record, recordList) => {
|
||||
if(record._id === recordList[recordList.length - 1]._id){
|
||||
const newId = uuidv4()
|
||||
const lastRecord:{[k:string]:unknown} = recordList[recordList.length - 1];
|
||||
const newRecord :{[k:string]:unknown, _id:string}= { _id: newId };
|
||||
|
||||
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
|
||||
if(extendsId && extendsId.length > 0) {
|
||||
extendsId.forEach(field => {
|
||||
newRecord[field] = lastRecord[field];
|
||||
});
|
||||
}
|
||||
|
||||
recordList = ([...recordList, newRecord as T]);
|
||||
setEditableRowKeys((prev)=>[...prev, newId])
|
||||
}
|
||||
setConfigurations(recordList);
|
||||
onChange?.(recordList);
|
||||
},
|
||||
onChange: setEditableRowKeys,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
|
||||
if (extendsId && extendsId.length > 0) {
|
||||
extendsId.forEach((field) => {
|
||||
newRecord[field] = lastRecord[field]
|
||||
})
|
||||
}
|
||||
tmpPreData.splice(Number(config.index) + 1, 0, newRecord)
|
||||
onChange?.(getNotEmptyValue(tmpPreData))
|
||||
return tmpPreData
|
||||
})
|
||||
setEditableRowKeys((prev) => [...prev, newId])
|
||||
}}
|
||||
btnTitle="增加"
|
||||
/>,
|
||||
|
||||
export default EditableTable;
|
||||
config.index !== configurations.length - 1 && (
|
||||
<TableBtnWithPermission
|
||||
key="remove"
|
||||
btnType="remove"
|
||||
btnTitle="删除"
|
||||
onClick={() => {
|
||||
setConfigurations((prev) => {
|
||||
const tmpPreData = [...prev]
|
||||
tmpPreData.splice(Number(config.index), 1)
|
||||
onChange?.(tmpPreData)
|
||||
return tmpPreData
|
||||
})
|
||||
setEditableRowKeys((prev) => prev.filter((x) => x !== config._id))
|
||||
}}
|
||||
/>
|
||||
),
|
||||
,
|
||||
]
|
||||
},
|
||||
onValuesChange: (record, recordList) => {
|
||||
if (record._id === recordList[recordList.length - 1]._id) {
|
||||
const newId = uuidv4()
|
||||
const lastRecord: { [k: string]: unknown } = recordList[recordList.length - 1]
|
||||
const newRecord: { [k: string]: unknown; _id: string } = { _id: newId }
|
||||
|
||||
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
|
||||
if (extendsId && extendsId.length > 0) {
|
||||
extendsId.forEach((field) => {
|
||||
newRecord[field] = lastRecord[field]
|
||||
})
|
||||
}
|
||||
|
||||
recordList = [...recordList, newRecord as T]
|
||||
setEditableRowKeys((prev) => [...prev, newId])
|
||||
}
|
||||
setConfigurations(recordList)
|
||||
onChange?.(recordList)
|
||||
},
|
||||
onChange: setEditableRowKeys
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditableTable
|
||||
|
||||
@@ -1,105 +1,119 @@
|
||||
import { EditableFormInstance, EditableProTable } from "@ant-design/pro-components";
|
||||
import { useState, useEffect, useMemo, useRef, MutableRefObject } from "react";
|
||||
import { v4 as uuidv4} from 'uuid';
|
||||
import { PageProColumns } from "./PageList";
|
||||
import TableBtnWithPermission from "./TableBtnWithPermission";
|
||||
import { $t } from "@common/locales";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
import { Form } from "antd";
|
||||
import { debounce } from "lodash-es";
|
||||
|
||||
import { EditableFormInstance, EditableProTable } from '@ant-design/pro-components'
|
||||
import { useState, useEffect, useMemo, useRef, MutableRefObject } from 'react'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { PageProColumns } from './PageList'
|
||||
import TableBtnWithPermission from './TableBtnWithPermission'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { Form } from 'antd'
|
||||
import { debounce } from 'lodash-es'
|
||||
|
||||
interface EditableTableProps<T> {
|
||||
configFields: PageProColumns<T>[];
|
||||
value?: T[]; // 外部传入的值
|
||||
className?: string;
|
||||
onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数
|
||||
// tableProps?: TableProps<T>;
|
||||
disabled?:boolean
|
||||
getFromRef?:(form:MutableRefObject<EditableFormInstance<T> | undefined>)=>void
|
||||
configFields: PageProColumns<T>[]
|
||||
value?: T[] // 外部传入的值
|
||||
className?: string
|
||||
onChange?: (newConfigItems: T[]) => void // 当配置项变化时,外部传入的回调函数
|
||||
// tableProps?: TableProps<T>;
|
||||
disabled?: boolean
|
||||
getFromRef?: (form: MutableRefObject<EditableFormInstance<T> | undefined>) => void
|
||||
}
|
||||
|
||||
const EditableTableNotAutoGen = <T extends { _id: string }>({
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
// tableProps,
|
||||
disabled,
|
||||
className,
|
||||
getFromRef
|
||||
}: EditableTableProps<T>) => {
|
||||
const [configurations, setConfigurations] = useState<(T | {_id:string})[]>(value ||[{_id:'1234'}]);
|
||||
const {state} = useGlobalContext()
|
||||
const form =useRef<EditableFormInstance<T>>();
|
||||
const [tableForm] = Form.useForm();
|
||||
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() =>
|
||||
value?.map((item) => item._id) || ['1234']
|
||||
);
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
// tableProps,
|
||||
disabled,
|
||||
className,
|
||||
getFromRef
|
||||
}: EditableTableProps<T>) => {
|
||||
const [configurations, setConfigurations] = useState<(T | { _id: string })[]>(value || [{ _id: '1234' }])
|
||||
const { state } = useGlobalContext()
|
||||
const form = useRef<EditableFormInstance<T>>()
|
||||
const [tableForm] = Form.useForm()
|
||||
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() => value?.map((item) => item._id) || ['1234'])
|
||||
|
||||
useEffect(()=>{
|
||||
getFromRef?.(form)
|
||||
},[form])
|
||||
useEffect(() => {
|
||||
getFromRef?.(form)
|
||||
}, [form])
|
||||
|
||||
useEffect(() => {
|
||||
const newValue = value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || [{_id:uuidv4()}]
|
||||
setConfigurations(newValue);
|
||||
setTimeout(()=>validateForm(),1000)
|
||||
}, [value]);
|
||||
useEffect(() => {
|
||||
const newValue = value?.map((x) => (x._id ? x : { ...x, _id: uuidv4() })) || [{ _id: uuidv4() }]
|
||||
setConfigurations(newValue)
|
||||
setTimeout(() => validateForm(), 1000)
|
||||
}, [value])
|
||||
|
||||
const validateForm = async ()=>{
|
||||
await tableForm.validateFields();
|
||||
}
|
||||
const validateForm = async () => {
|
||||
await tableForm.validateFields()
|
||||
}
|
||||
|
||||
const translatedColumns = useMemo(()=>configFields.map((x)=>(
|
||||
{...x,
|
||||
title:$t(x.title as string),
|
||||
formItemProps:{
|
||||
...(x. formItemProps || {}),
|
||||
rules:[...(x.formItemProps?.rules || []).map((r:Record<string, string>)=>{
|
||||
if(r.message){
|
||||
r.message = $t(r.message)
|
||||
}
|
||||
return r
|
||||
})],
|
||||
}})),[state.language,configFields])
|
||||
|
||||
const debouncedOnChange = useMemo(() => debounce((value) => {
|
||||
onChange?.(value);
|
||||
}, 500), [onChange]);
|
||||
const translatedColumns = useMemo(
|
||||
() =>
|
||||
configFields.map((x) => ({
|
||||
...x,
|
||||
title: $t(x.title as string),
|
||||
formItemProps: {
|
||||
...(x.formItemProps || {}),
|
||||
rules: [
|
||||
...(x.formItemProps?.rules || []).map((r: Record<string, string>) => {
|
||||
if (r.message) {
|
||||
r.message = $t(r.message)
|
||||
}
|
||||
return r
|
||||
})
|
||||
]
|
||||
}
|
||||
})),
|
||||
[state.language, configFields]
|
||||
)
|
||||
|
||||
return (
|
||||
<EditableProTable<T>
|
||||
className={className}
|
||||
columns={translatedColumns}
|
||||
onChange={debouncedOnChange}
|
||||
controlled={true}
|
||||
rowKey="_id"
|
||||
value={configurations as T[]}
|
||||
size="small"
|
||||
editableFormRef={form}
|
||||
bordered={true}
|
||||
recordCreatorProps={false}
|
||||
editable={ {
|
||||
type: 'multiple',
|
||||
form: tableForm,
|
||||
// errorType:'default',
|
||||
editableKeys:disabled ? [] : configurations?.map(x=>x._id),
|
||||
actionRender: (row, config) => {
|
||||
return [
|
||||
<TableBtnWithPermission key="delete" btnType="delete" btnTitle="删除"
|
||||
onClick={() => {
|
||||
setConfigurations((prev)=>{
|
||||
const tmpPreData = [...prev];
|
||||
tmpPreData.splice(Number(config.index), 1);
|
||||
onChange?.(tmpPreData);
|
||||
return tmpPreData});
|
||||
setEditableRowKeys((prev)=>(prev.filter(x=>x !== config._id)))
|
||||
}}/>,
|
||||
];
|
||||
},
|
||||
onChange: setEditableRowKeys
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
const debouncedOnChange = useMemo(
|
||||
() =>
|
||||
debounce((value) => {
|
||||
onChange?.(value)
|
||||
}, 500),
|
||||
[onChange]
|
||||
)
|
||||
|
||||
export default EditableTableNotAutoGen;
|
||||
return (
|
||||
<EditableProTable<T>
|
||||
className={className}
|
||||
columns={translatedColumns}
|
||||
onChange={debouncedOnChange}
|
||||
controlled={true}
|
||||
rowKey="_id"
|
||||
value={configurations as T[]}
|
||||
size="small"
|
||||
editableFormRef={form}
|
||||
bordered={true}
|
||||
recordCreatorProps={false}
|
||||
editable={{
|
||||
type: 'multiple',
|
||||
form: tableForm,
|
||||
// errorType:'default',
|
||||
editableKeys: disabled ? [] : configurations?.map((x) => x._id),
|
||||
actionRender: (row, config) => {
|
||||
return [
|
||||
<TableBtnWithPermission
|
||||
key="delete"
|
||||
btnType="delete"
|
||||
btnTitle="删除"
|
||||
onClick={() => {
|
||||
setConfigurations((prev) => {
|
||||
const tmpPreData = [...prev]
|
||||
tmpPreData.splice(Number(config.index), 1)
|
||||
onChange?.(tmpPreData)
|
||||
return tmpPreData
|
||||
})
|
||||
setEditableRowKeys((prev) => prev.filter((x) => x !== config._id))
|
||||
}}
|
||||
/>
|
||||
]
|
||||
},
|
||||
onChange: setEditableRowKeys
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditableTableNotAutoGen
|
||||
|
||||
@@ -1,165 +1,195 @@
|
||||
import {useEffect, useMemo, useState} from 'react';
|
||||
import { Button, Modal, Form, Table, FormInstance, TableProps, Divider } from 'antd';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import WithPermission from './WithPermission';
|
||||
import { $t } from '@common/locales';
|
||||
import { COLUMNS_TITLE, VALIDATE_MESSAGE } from '@common/const/const';
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
|
||||
import TableBtnWithPermission from './TableBtnWithPermission';
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Button, Modal, Form, Table, FormInstance, TableProps, Divider } from 'antd'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import WithPermission from './WithPermission'
|
||||
import { $t } from '@common/locales'
|
||||
import { COLUMNS_TITLE } from '@common/const/const'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import TableBtnWithPermission from './TableBtnWithPermission'
|
||||
|
||||
export interface ConfigField<T> {
|
||||
title: string;
|
||||
key: keyof T;
|
||||
component: React.ReactNode;
|
||||
renderText?: (value: unknown, record: T) => string;
|
||||
required?: boolean;
|
||||
ellipsis?:boolean
|
||||
unRender?:(form:FormInstance)=>boolean
|
||||
title: string
|
||||
key: keyof T
|
||||
component: React.ReactNode
|
||||
renderText?: (value: unknown, record: T) => string
|
||||
required?: boolean
|
||||
ellipsis?: boolean
|
||||
unRender?: (form: FormInstance) => boolean
|
||||
}
|
||||
|
||||
interface EditableTableWithModalProps<T> {
|
||||
configFields: ConfigField<T>[];
|
||||
value?: T[]; // 外部传入的值
|
||||
className?: string;
|
||||
onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数
|
||||
tableProps?: TableProps<T>;
|
||||
disabled?:boolean
|
||||
configFields: ConfigField<T>[]
|
||||
value?: T[] // 外部传入的值
|
||||
className?: string
|
||||
onChange?: (newConfigItems: T[]) => void // 当配置项变化时,外部传入的回调函数
|
||||
tableProps?: TableProps<T>
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const EditableTableWithModal = <T extends { _id?: string }>({
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
tableProps,
|
||||
disabled,
|
||||
className
|
||||
}: EditableTableWithModalProps<T>) => {
|
||||
const [form] = Form.useForm<FormInstance>();
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [configurations, setConfigurations] = useState<T[]>(value ||[]);
|
||||
const [editingConfig, setEditingConfig] = useState<T | null>(null);
|
||||
const {state} = useGlobalContext()
|
||||
const [formsValue, setFormsValue] = useState<FormInstance<unknown>>()
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
tableProps,
|
||||
disabled,
|
||||
className
|
||||
}: EditableTableWithModalProps<T>) => {
|
||||
const [form] = Form.useForm<FormInstance>()
|
||||
const [isModalVisible, setIsModalVisible] = useState(false)
|
||||
const [configurations, setConfigurations] = useState<T[]>(value || [])
|
||||
const [editingConfig, setEditingConfig] = useState<T | null>(null)
|
||||
const { state } = useGlobalContext()
|
||||
const [formsValue, setFormsValue] = useState<FormInstance<unknown>>()
|
||||
|
||||
const showModal = (config?: T) => {
|
||||
if (config) {
|
||||
form.setFieldsValue(config as Record<string, unknown>);
|
||||
setEditingConfig(config);
|
||||
const showModal = (config?: T) => {
|
||||
if (config) {
|
||||
form.setFieldsValue(config as Record<string, unknown>)
|
||||
setEditingConfig(config)
|
||||
} else {
|
||||
form.resetFields()
|
||||
setEditingConfig(null)
|
||||
}
|
||||
setIsModalVisible(true)
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsModalVisible(false)
|
||||
}
|
||||
|
||||
const handleDelete = (_id: string) => {
|
||||
const newConfigurations = configurations.filter((config) => config._id !== _id)
|
||||
setConfigurations(newConfigurations)
|
||||
onChange?.(newConfigurations)
|
||||
}
|
||||
|
||||
const handleOk = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
let newConfigurations = [...configurations]
|
||||
if (editingConfig && editingConfig._id) {
|
||||
newConfigurations = newConfigurations?.map((config) =>
|
||||
config._id === editingConfig._id ? { ...config, ...values } : config
|
||||
)
|
||||
} else {
|
||||
form.resetFields();
|
||||
setEditingConfig(null);
|
||||
const newConfig = { _id: uuidv4(), ...values } as Record<string, unknown>
|
||||
newConfigurations.push(newConfig as T)
|
||||
}
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
setConfigurations(newConfigurations)
|
||||
onChange?.(newConfigurations)
|
||||
setIsModalVisible(false)
|
||||
})
|
||||
.catch((info) => {
|
||||
console.log('Validate Failed:', info)
|
||||
})
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
useEffect(() => {
|
||||
setConfigurations(value?.map((x) => (x._id ? x : { ...x, _id: uuidv4() })) || [])
|
||||
}, [value])
|
||||
|
||||
const handleDelete = (_id: string) => {
|
||||
const newConfigurations = configurations.filter(config => config._id !== _id);
|
||||
setConfigurations(newConfigurations);
|
||||
onChange?.(newConfigurations);
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
form.validateFields()
|
||||
.then(values => {
|
||||
let newConfigurations = [...configurations];
|
||||
if (editingConfig && editingConfig._id) {
|
||||
newConfigurations = newConfigurations?.map(config =>
|
||||
config._id === editingConfig._id ? { ...config, ...values } : config
|
||||
);
|
||||
} else {
|
||||
const newConfig = { _id: uuidv4(), ...values } as Record<string, unknown>;
|
||||
newConfigurations.push(newConfig as T);
|
||||
}
|
||||
setConfigurations(newConfigurations);
|
||||
onChange?.(newConfigurations);
|
||||
setIsModalVisible(false);
|
||||
})
|
||||
.catch(info => {
|
||||
console.log('Validate Failed:', info);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setConfigurations(value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || []);
|
||||
}, [value]);
|
||||
|
||||
const columns = useMemo(()=>[
|
||||
...configFields.map(({ title, key, renderText }) => ({
|
||||
title:$t(title),
|
||||
dataIndex: key as string,
|
||||
key: key as string,
|
||||
render: renderText ? (value, record) => $t(renderText(value, record) || '') : undefined,
|
||||
ellipsis:true
|
||||
})),
|
||||
...(disabled ? []:[{
|
||||
title: COLUMNS_TITLE.operate,
|
||||
key: 'action',
|
||||
btnNums:2,
|
||||
render: (_: unknown, record: T) => (
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
...configFields.map(({ title, key, renderText }) => ({
|
||||
title: $t(title),
|
||||
dataIndex: key as string,
|
||||
key: key as string,
|
||||
render: renderText ? (value, record) => $t(renderText(value, record) || '') : undefined,
|
||||
ellipsis: true
|
||||
})),
|
||||
...(disabled
|
||||
? []
|
||||
: [
|
||||
{
|
||||
title: COLUMNS_TITLE.operate,
|
||||
key: 'action',
|
||||
btnNums: 2,
|
||||
render: (_: unknown, record: T) => (
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
<TableBtnWithPermission key="add" disabled={disabled} btnType="edit" onClick={()=>{showModal(record)}} btnTitle='编辑'/>
|
||||
<div className="flex items-center">
|
||||
<TableBtnWithPermission
|
||||
key="add"
|
||||
disabled={disabled}
|
||||
btnType="edit"
|
||||
onClick={() => {
|
||||
showModal(record)
|
||||
}}
|
||||
btnTitle="编辑"
|
||||
/>
|
||||
<Divider key="div1" type="vertical" />
|
||||
<TableBtnWithPermission key="delete" disabled={disabled} btnType="delete" onClick={()=>{handleDelete(record._id || '')}} btnTitle='删除'/>
|
||||
</div>
|
||||
<TableBtnWithPermission
|
||||
key="delete"
|
||||
disabled={disabled}
|
||||
btnType="delete"
|
||||
onClick={() => {
|
||||
handleDelete(record._id || '')
|
||||
}}
|
||||
btnTitle="删除"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
}] )
|
||||
],[state.language, disabled, configFields])
|
||||
)
|
||||
}
|
||||
])
|
||||
],
|
||||
[state.language, disabled, configFields]
|
||||
)
|
||||
|
||||
|
||||
const formItems = useMemo(()=>{
|
||||
return configFields.map(({ title,key, component, required,unRender }) => {
|
||||
return (
|
||||
unRender && unRender(formsValue) ? null :
|
||||
<Form.Item
|
||||
label={$t(title as string)}
|
||||
name={key as string}
|
||||
rules={[{ required}]}
|
||||
>
|
||||
{component}
|
||||
</Form.Item>
|
||||
)
|
||||
})
|
||||
}
|
||||
,[formsValue])
|
||||
const formItems = useMemo(() => {
|
||||
return configFields.map(({ title, key, component, required, unRender }) => {
|
||||
return unRender && unRender(formsValue) ? null : (
|
||||
<Form.Item label={$t(title as string)} name={key as string} rules={[{ required }]}>
|
||||
{component}
|
||||
</Form.Item>
|
||||
)
|
||||
})
|
||||
}, [formsValue])
|
||||
|
||||
return (
|
||||
<>
|
||||
{!disabled && (
|
||||
<Button className="" disabled={disabled} onClick={() => showModal()}>
|
||||
{$t('添加配置')}
|
||||
</Button>
|
||||
)}
|
||||
{configurations.length > 0 && (
|
||||
<Table
|
||||
className={`mt-btnybase border-solid border-[1px] border-BORDER border-b-0 rounded ${className}`}
|
||||
{...tableProps}
|
||||
dataSource={configurations}
|
||||
size="small"
|
||||
columns={columns}
|
||||
rowKey="_id"
|
||||
pagination={false}
|
||||
/>
|
||||
)}
|
||||
<Modal
|
||||
title={editingConfig ? $t('编辑配置') : $t('添加配置')}
|
||||
open={isModalVisible}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
width={600}
|
||||
maskClosable={false}
|
||||
>
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
form={form}
|
||||
name="editableTableWithModal"
|
||||
layout="vertical"
|
||||
scrollToFirstError
|
||||
onFieldsChange={() => {
|
||||
setFormsValue(form.getFieldsValue())
|
||||
}}
|
||||
// labelCol={{ span: 7 }}
|
||||
// wrapperCol={{ span: 17}}
|
||||
autoComplete="off"
|
||||
>
|
||||
{formItems}
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{!disabled && <Button className="" disabled={disabled} onClick={() => showModal()}>{$t('添加配置')}</Button>}
|
||||
{configurations.length > 0 &&
|
||||
<Table
|
||||
className={`mt-btnybase border-solid border-[1px] border-BORDER border-b-0 rounded ${className}`} {...tableProps} dataSource={configurations} size="small" columns={columns} rowKey="_id" pagination={false}/>}
|
||||
<Modal
|
||||
title={editingConfig ? $t('编辑配置') : $t('添加配置')}
|
||||
open={isModalVisible}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
width={600}
|
||||
maskClosable={false}
|
||||
|
||||
>
|
||||
<WithPermission access=""><Form form={form} name="editableTableWithModal"
|
||||
layout="vertical"
|
||||
scrollToFirstError
|
||||
onFieldsChange={(()=>{
|
||||
setFormsValue(form.getFieldsValue())
|
||||
})}
|
||||
// labelCol={{ span: 7 }}
|
||||
// wrapperCol={{ span: 17}}
|
||||
autoComplete="off">
|
||||
{formItems}
|
||||
</Form></WithPermission>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditableTableWithModal;
|
||||
export default EditableTableWithModal
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from 'react'
|
||||
function ErrorBoundary({ children }) {
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("error", (event) => {
|
||||
setError(event.error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<h1>An error occurred</h1>
|
||||
<pre>{error.message}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('error', (event) => {
|
||||
setError(event.error)
|
||||
})
|
||||
}, [])
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<h1>An error occurred</h1>
|
||||
<pre>{error.message}</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ErrorBoundary
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
export default ErrorBoundary
|
||||
|
||||
@@ -1,66 +1,108 @@
|
||||
|
||||
import { Button, Tag } from "antd"
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission";
|
||||
import { FC, ReactNode } from "react";
|
||||
import { ArrowLeftOutlined, LeftOutlined } from "@ant-design/icons";
|
||||
import { $t } from "@common/locales";
|
||||
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission'
|
||||
import { $t } from '@common/locales'
|
||||
import { Button, Tag } from 'antd'
|
||||
import { FC, ReactNode } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
class InsidePageProps {
|
||||
showBanner?:boolean = true
|
||||
pageTitle:string| React.ReactNode = ''
|
||||
tagList?:Array<{label:string|ReactNode}> = []
|
||||
children:React.ReactNode
|
||||
showBtn?:boolean = false
|
||||
btnTitle?:string = ''
|
||||
description?:string | React.ReactNode= ''
|
||||
onBtnClick?:()=>void
|
||||
backUrl?:string = '/'
|
||||
btnAccess?:string
|
||||
showBorder?:boolean = true
|
||||
className?:string = ''
|
||||
contentClassName?:string=''
|
||||
headerClassName?:string=''
|
||||
/** 整个页面滚动 */
|
||||
scrollPage?:boolean = true
|
||||
customBtn?:ReactNode
|
||||
showBanner?: boolean = true
|
||||
pageTitle: string | React.ReactNode = ''
|
||||
tagList?: Array<{ label: string | ReactNode }> = []
|
||||
children: React.ReactNode
|
||||
showBtn?: boolean = false
|
||||
btnTitle?: string = ''
|
||||
description?: string | React.ReactNode = ''
|
||||
onBtnClick?: () => void
|
||||
backUrl?: string = '/'
|
||||
btnAccess?: string
|
||||
showBorder?: boolean = true
|
||||
className?: string = ''
|
||||
contentClassName?: string = ''
|
||||
headerClassName?: string = ''
|
||||
/** 整个页面滚动 */
|
||||
scrollPage?: boolean = true
|
||||
customBtn?: ReactNode
|
||||
}
|
||||
|
||||
const InsidePage:FC<InsidePageProps> = ({showBanner=true,pageTitle,tagList,showBtn,btnTitle,btnAccess,description,children,onBtnClick,backUrl,showBorder=true,className='',contentClassName='',headerClassName='',scrollPage=true,customBtn})=>{
|
||||
const navigate = useNavigate();
|
||||
const InsidePage: FC<InsidePageProps> = ({
|
||||
showBanner = true,
|
||||
pageTitle,
|
||||
tagList,
|
||||
showBtn,
|
||||
btnTitle,
|
||||
btnAccess,
|
||||
description,
|
||||
children,
|
||||
onBtnClick,
|
||||
backUrl,
|
||||
showBorder = true,
|
||||
className = '',
|
||||
contentClassName = '',
|
||||
headerClassName = '',
|
||||
scrollPage = true,
|
||||
customBtn
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const goBack = () => {
|
||||
navigate(backUrl || '/');
|
||||
};
|
||||
return (
|
||||
// <div className="h-full flex flex-col flex-1 overflow-hidden bg-[#f7f8fa]">
|
||||
<div className={`h-full flex flex-col flex-1 overflow-hidden ${className}`}>
|
||||
{ showBanner && <div className={`border-[0px] mr-PAGE_INSIDE_X ${showBorder ? 'border-b-[1px] border-solid border-BORDER' : ''} ${headerClassName}`}>
|
||||
{!pageTitle && !description && !backUrl &&!customBtn ? <></>: <div className="mb-[30px]">
|
||||
{backUrl &&<div className="text-[18px] leading-[25px] mb-[12px]">
|
||||
<Button type="text" onClick={goBack}><ArrowLeftOutlined className="max-h-[14px]" />{$t('返回')}</Button>
|
||||
</div>}
|
||||
<div className="flex justify-between mb-[20px] items-center ">
|
||||
<div className="flex items-center gap-TAG_LEFT ">
|
||||
<div className="text-theme text-[26px] ">{pageTitle}</div>
|
||||
{tagList && tagList?.length > 0 && tagList?.map((tag)=>{
|
||||
return ( <Tag key={tag.label as string} bordered={false} >{tag.label}</Tag>)
|
||||
})}
|
||||
</div>
|
||||
{showBtn && <WithPermission access={btnAccess}><Button type="primary" onClick={()=> {
|
||||
onBtnClick&&onBtnClick()
|
||||
}}>{btnTitle}</Button></WithPermission>}
|
||||
{customBtn}
|
||||
</div>
|
||||
<div >
|
||||
{description}
|
||||
</div>
|
||||
</div>}
|
||||
</div>}
|
||||
<div className={`h-full ${scrollPage ? 'overflow-hidden' : 'overflow-auto'} ${contentClassName || ''}`}>{children}</div>
|
||||
const goBack = () => {
|
||||
navigate(backUrl || '/')
|
||||
}
|
||||
return (
|
||||
<div className={`flex overflow-hidden flex-col flex-1 h-full ${className}`}>
|
||||
{showBanner && (
|
||||
<div
|
||||
className={`border-[0px] mr-PAGE_INSIDE_X ${showBorder ? 'border-solid border-b-[1px] border-BORDER' : ''} ${headerClassName}`}
|
||||
>
|
||||
{!pageTitle && !description && !backUrl && !customBtn ? (
|
||||
<></>
|
||||
) : (
|
||||
<div className="mb-[30px]">
|
||||
{backUrl && (
|
||||
<div className="text-[18px] leading-[25px] mb-[12px]">
|
||||
<Button type="text" onClick={goBack}>
|
||||
<ArrowLeftOutlined className="max-h-[14px]" />
|
||||
{$t('返回')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between mb-[20px] items-center ">
|
||||
<div className="flex items-center gap-TAG_LEFT">
|
||||
<div className="text-theme text-[26px] ">{pageTitle}</div>
|
||||
{tagList &&
|
||||
tagList?.length > 0 &&
|
||||
tagList?.map((tag) => {
|
||||
return (
|
||||
<Tag key={tag.label as string} bordered={false}>
|
||||
{tag.label}
|
||||
</Tag>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{showBtn && (
|
||||
<WithPermission access={btnAccess}>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
onBtnClick && onBtnClick()
|
||||
}}
|
||||
>
|
||||
{btnTitle}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
)}
|
||||
{customBtn}
|
||||
</div>
|
||||
<div>{description}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
<div className={`h-full ${scrollPage ? 'overflow-hidden' : 'overflow-auto'} ${contentClassName || ''}`}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default InsidePage
|
||||
export default InsidePage
|
||||
|
||||
@@ -1,72 +1,92 @@
|
||||
import { Dropdown, Row, Col, Button } from 'antd';
|
||||
import i18n from '@common/locales';
|
||||
import { memo, useEffect, useMemo } from 'react';
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
|
||||
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import i18n from '@common/locales'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { Button, Dropdown } from 'antd'
|
||||
import { memo, useEffect, useMemo } from 'react'
|
||||
|
||||
const LanguageSetting = ({mode = 'light'}:{mode?:'dark'|'light'}) => {
|
||||
const { dispatch,state} = useGlobalContext()
|
||||
const items = [
|
||||
{
|
||||
key: 'en-US',
|
||||
label:<Button key="en" type="text" className="border-none p-0 flex items-center bg-transparent ">
|
||||
English
|
||||
</Button>,
|
||||
title:'English'
|
||||
},
|
||||
{
|
||||
key: 'ja-JP',
|
||||
label: <Button key="jp" type="text" className="border-none p-0 flex items-center bg-transparent ">
|
||||
日本語
|
||||
</Button>,
|
||||
title: '日本語',
|
||||
},
|
||||
{
|
||||
key: 'zh-TW',
|
||||
label: <Button key="tw" type="text" className="border-none p-0 flex items-center bg-transparent ">
|
||||
繁體中文
|
||||
</Button>,
|
||||
title: '繁體中文',
|
||||
},
|
||||
{
|
||||
key: 'zh-CN',
|
||||
label: <Button key="cn" type="text" className="border-none p-0 flex items-center bg-transparent ">
|
||||
简体中文
|
||||
</Button>,
|
||||
title: '简体中文',
|
||||
},
|
||||
];
|
||||
const LanguageItems = [
|
||||
{
|
||||
key: 'en-US',
|
||||
label: (
|
||||
<Button key="en" type="text" className="flex items-center p-0 bg-transparent border-none">
|
||||
English
|
||||
</Button>
|
||||
),
|
||||
title: 'English'
|
||||
},
|
||||
{
|
||||
key: 'ja-JP',
|
||||
label: (
|
||||
<Button key="jp" type="text" className="flex items-center p-0 bg-transparent border-none">
|
||||
日本語
|
||||
</Button>
|
||||
),
|
||||
title: '日本語'
|
||||
},
|
||||
{
|
||||
key: 'zh-TW',
|
||||
label: (
|
||||
<Button key="tw" type="text" className="flex items-center p-0 bg-transparent border-none">
|
||||
繁體中文
|
||||
</Button>
|
||||
),
|
||||
title: '繁體中文'
|
||||
},
|
||||
{
|
||||
key: 'zh-CN',
|
||||
label: (
|
||||
<Button key="cn" type="text" className="flex items-center p-0 bg-transparent border-none">
|
||||
简体中文
|
||||
</Button>
|
||||
),
|
||||
title: '简体中文'
|
||||
}
|
||||
]
|
||||
const LanguageSetting = ({ mode = 'light' }: { mode?: 'dark' | 'light' }) => {
|
||||
const { dispatch, state } = useGlobalContext()
|
||||
|
||||
const langLabel = useMemo(()=>items.find((item) => item?.key === state.language)?.title,[state.language])
|
||||
const langLabel = useMemo(() => LanguageItems.find((item) => item?.key === state.language)?.title, [state.language])
|
||||
|
||||
useEffect(()=>{
|
||||
const savedLang = sessionStorage.getItem('i18nextLng')
|
||||
const browserLang = navigator.language || navigator.userLanguage
|
||||
if(savedLang){
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: savedLang });
|
||||
}else{
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: browserLang });
|
||||
useEffect(() => {
|
||||
const savedLang = i18n.language || sessionStorage.getItem('i18nextLng')
|
||||
if (savedLang && state.language !== savedLang) {
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: savedLang })
|
||||
} else if (!savedLang) {
|
||||
const browserLang = navigator.language
|
||||
const supportedLang = LanguageItems.find((item) => item.key === browserLang) ? browserLang : 'zh-CN'
|
||||
if (state.language === supportedLang) return
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: supportedLang })
|
||||
i18n.changeLanguage(supportedLang)
|
||||
}
|
||||
},[
|
||||
])
|
||||
}, [])
|
||||
return (
|
||||
<Dropdown
|
||||
trigger={['hover']}
|
||||
menu={{
|
||||
items,
|
||||
style:{minWidth:'80px'},
|
||||
items: LanguageItems,
|
||||
style: { minWidth: '80px' },
|
||||
onClick: (e) => {
|
||||
const { key } = e;
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: key });
|
||||
i18n.changeLanguage(key);
|
||||
const { key } = e
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: key })
|
||||
i18n.changeLanguage(key)
|
||||
sessionStorage.setItem('i18nextLng', key)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button className={`border-none ${mode==='dark' ? "text-[#333] hover:text-[#333333b3]" : "text-[#ffffffb3] hover:text-[#fff] "}`} type="default" ghost >
|
||||
<span className='flex items-center gap-[8px]'> <Icon icon="ic:baseline-language" width="14" height="14"/>{langLabel}</span>
|
||||
</Button>
|
||||
<Button
|
||||
className={`border-none ${
|
||||
mode === 'dark' ? 'text-[#333] hover:text-[#333333b3]' : 'text-[#ffffffb3] hover:text-[#fff] '
|
||||
}`}
|
||||
type="default"
|
||||
ghost
|
||||
>
|
||||
<span className="flex items-center gap-[8px]">
|
||||
{' '}
|
||||
<Icon icon="ic:baseline-language" width="14" height="14" />
|
||||
{langLabel}
|
||||
</span>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
export default memo(LanguageSetting);
|
||||
|
||||
)
|
||||
}
|
||||
export default memo(LanguageSetting)
|
||||
|
||||
@@ -1,171 +1,199 @@
|
||||
|
||||
import { TransferProps, TreeDataNode, Tree, Spin, Input, Empty } from "antd";
|
||||
import { DataNode } from "antd/es/tree";
|
||||
import { Ref, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
||||
import { ApartmentOutlined, LoadingOutlined, UserOutlined } from "@ant-design/icons";
|
||||
import { ColumnsType } from "antd/es/table";
|
||||
import { $t } from "@common/locales";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
import { TransferProps, TreeDataNode, Tree, Spin, Input, Empty } from 'antd'
|
||||
import { DataNode } from 'antd/es/tree'
|
||||
import { Ref, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
|
||||
import { ApartmentOutlined, LoadingOutlined, UserOutlined } from '@ant-design/icons'
|
||||
import { ColumnsType } from 'antd/es/table'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
|
||||
export type TransferTableProps<T> = {
|
||||
request?:(k?:string)=>Promise<{data:T[],success:boolean}>
|
||||
request?: (k?: string) => Promise<{ data: T[]; success: boolean }>
|
||||
columns: ColumnsType<T>
|
||||
primaryKey:string
|
||||
onSelect:(selectedData:string[])=>void
|
||||
tableType?:'member'|'api'
|
||||
disabledData:string[]
|
||||
searchPlaceholder?:string
|
||||
primaryKey: string
|
||||
onSelect: (selectedData: string[]) => void
|
||||
tableType?: 'member' | 'api'
|
||||
disabledData: string[]
|
||||
searchPlaceholder?: string
|
||||
}
|
||||
|
||||
export type TransferTableHandle<T> = {
|
||||
selectedRowKeys: () => React.Key[];
|
||||
selectedRowKeys: () => React.Key[]
|
||||
}
|
||||
|
||||
interface TreeTransferProps {
|
||||
dataSource: TreeDataNode[];
|
||||
targetKeys: TransferProps['targetKeys'];
|
||||
onChange: TransferProps['onChange'];
|
||||
dataSource: TreeDataNode[]
|
||||
targetKeys: TransferProps['targetKeys']
|
||||
onChange: TransferProps['onChange']
|
||||
}
|
||||
|
||||
|
||||
const generateTree = (
|
||||
treeNodes: TreeDataNode[] = [],
|
||||
checkedKeys: TreeTransferProps['targetKeys'] = [],
|
||||
checkedKeys: TreeTransferProps['targetKeys'] = [],
|
||||
filterUnchecked: boolean = false,
|
||||
disabledData:string[],
|
||||
filteredItems?:Set<string>
|
||||
disabledData: string[],
|
||||
filteredItems?: Set<string>
|
||||
): TreeDataNode[] => {
|
||||
const checkedKeysSet = new Set(checkedKeys);
|
||||
const checkedKeysSet = new Set(checkedKeys)
|
||||
return treeNodes
|
||||
.map(({ children, ...props }) => {
|
||||
const childNodes = generateTree(children, checkedKeys, filterUnchecked, disabledData, filteredItems);
|
||||
const isDisabled = (!filterUnchecked && disabledData && disabledData.indexOf(props.id as string) !== -1)
|
||||
? true
|
||||
: (filterUnchecked ? false : checkedKeysSet.has(props.id as string));
|
||||
const hasEnabledChild = childNodes.some(node => !node.disabled);
|
||||
const childNodes = generateTree(children, checkedKeys, filterUnchecked, disabledData, filteredItems)
|
||||
const isDisabled =
|
||||
!filterUnchecked && disabledData && disabledData.indexOf(props.id as string) !== -1
|
||||
? true
|
||||
: filterUnchecked
|
||||
? false
|
||||
: checkedKeysSet.has(props.id as string)
|
||||
const hasEnabledChild = childNodes.some((node) => !node.disabled)
|
||||
|
||||
return {
|
||||
...props,
|
||||
title: <span className="w-full truncate ml-[4px] block">{props.name}</span>,
|
||||
key: props.id,
|
||||
disabled: isDisabled && !hasEnabledChild,
|
||||
children: childNodes,
|
||||
};
|
||||
})
|
||||
.filter(node => {
|
||||
let res:boolean= true
|
||||
if(filterUnchecked){
|
||||
res =(!disabledData || disabledData.indexOf(node.key as string) === -1) && (checkedKeysSet.has(node.key as string) || (node.children && node.children.length > 0) )
|
||||
children: childNodes
|
||||
}
|
||||
|
||||
if(filterUnchecked && filteredItems &&((filteredItems.size && !filteredItems.has(node.key as string))&& !(node.children && node.children.length > 0) )){
|
||||
})
|
||||
.filter((node) => {
|
||||
let res: boolean = true
|
||||
if (filterUnchecked) {
|
||||
res =
|
||||
(!disabledData || disabledData.indexOf(node.key as string) === -1) &&
|
||||
(checkedKeysSet.has(node.key as string) || (node.children && node.children.length > 0))
|
||||
}
|
||||
|
||||
if (
|
||||
filterUnchecked &&
|
||||
filteredItems &&
|
||||
filteredItems.size &&
|
||||
!filteredItems.has(node.key as string) &&
|
||||
!(node.children && node.children.length > 0)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return res
|
||||
}
|
||||
)
|
||||
};
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
const MemberTransfer = forwardRef<
|
||||
TransferTableHandle<{ [k: string]: unknown }>,
|
||||
TransferTableProps<{ [k: string]: unknown }>
|
||||
>(<T extends { [k: string]: unknown }>(props: TransferTableProps<T>, ref: Ref<TransferTableHandle<T>>) => {
|
||||
const { request, columns, primaryKey, onSelect, tableType, disabledData = [], searchPlaceholder } = props
|
||||
const [targetKeys, setTargetKeys] = useState<TreeTransferProps['targetKeys']>([])
|
||||
const [dataSource, setDataSource] = useState<DataNode[]>([])
|
||||
const parentRef = useRef<HTMLDivElement>(null)
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const { state } = useGlobalContext()
|
||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([])
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
useEffect(() => {
|
||||
setTargetKeys(disabledData)
|
||||
}, [disabledData])
|
||||
|
||||
const MemberTransfer= forwardRef<TransferTableHandle<{[k:string]:unknown}>, TransferTableProps<{[k:string]:unknown}>>(
|
||||
<T extends {[k:string]:unknown}>(props: TransferTableProps<T>, ref:Ref<TransferTableHandle<T>>) => {
|
||||
const {request,columns,primaryKey,onSelect,tableType,disabledData = [],searchPlaceholder} = props
|
||||
const [targetKeys, setTargetKeys] = useState<TreeTransferProps['targetKeys']>([]);
|
||||
const [dataSource, setDataSource] = useState<DataNode[] >([])
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const {state} = useGlobalContext()
|
||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
useEffect(()=>{
|
||||
setTargetKeys(disabledData)
|
||||
},[disabledData])
|
||||
useImperativeHandle(ref, () => ({
|
||||
selectedRowKeys: () => targetKeys
|
||||
}))
|
||||
|
||||
useImperativeHandle(ref, () =>({
|
||||
selectedRowKeys: () => targetKeys,}))
|
||||
|
||||
const translatedDataSource = useMemo(()=>{
|
||||
|
||||
const translatedDataSource = useMemo(() => {
|
||||
const loop = (data: DataNode[]): DataNode[] =>
|
||||
data?.map((item) => {
|
||||
const strTitle:string = item.name === '所有成员' ? $t(item.name) as string : item.name as string;
|
||||
const index = strTitle.indexOf(searchWord);
|
||||
const beforeStr = strTitle.substring(0, index);
|
||||
const afterStr = strTitle.slice(index + searchWord.length);
|
||||
const title =
|
||||
index > -1 ? (
|
||||
<span className='w-[calc(100%-16px)] truncate' title={strTitle}>
|
||||
{beforeStr}
|
||||
<span className="text-theme">{searchWord}</span>
|
||||
{afterStr}
|
||||
</span>
|
||||
) : (
|
||||
<span className='w-[calc(100%-16px)] truncate' title={`${strTitle}`}>{strTitle}</span>
|
||||
)
|
||||
if (item.children) {
|
||||
return {
|
||||
...item,
|
||||
title,
|
||||
disableCheckbox:disabledData.indexOf(item.key as string) !== -1,
|
||||
icon:<ApartmentOutlined />,
|
||||
children: loop(item.children as T[]) };
|
||||
}
|
||||
|
||||
const strTitle: string = item.name === '所有成员' ? ($t(item.name) as string) : (item.name as string)
|
||||
const index = strTitle.indexOf(searchWord)
|
||||
const beforeStr = strTitle.substring(0, index)
|
||||
const afterStr = strTitle.slice(index + searchWord.length)
|
||||
const title =
|
||||
index > -1 ? (
|
||||
<span className="w-[calc(100%-16px)] truncate" title={strTitle}>
|
||||
{beforeStr}
|
||||
<span className="text-theme">{searchWord}</span>
|
||||
{afterStr}
|
||||
</span>
|
||||
) : (
|
||||
<span className="w-[calc(100%-16px)] truncate" title={`${strTitle}`}>
|
||||
{strTitle}
|
||||
</span>
|
||||
)
|
||||
if (item.children) {
|
||||
return {
|
||||
...item,
|
||||
title,
|
||||
icon:<UserOutlined />,
|
||||
isLeaf:true,
|
||||
disableCheckbox:disabledData.indexOf(item.key as string) !== -1
|
||||
};
|
||||
});
|
||||
return loop(dataSource);
|
||||
},[dataSource, state.language, searchWord])
|
||||
...item,
|
||||
title,
|
||||
disableCheckbox: disabledData.indexOf(item.key as string) !== -1,
|
||||
icon: <ApartmentOutlined />,
|
||||
children: loop(item.children as T[])
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
title,
|
||||
icon: <UserOutlined />,
|
||||
isLeaf: true,
|
||||
disableCheckbox: disabledData.indexOf(item.key as string) !== -1
|
||||
}
|
||||
})
|
||||
return loop(dataSource)
|
||||
}, [dataSource, state.language, searchWord])
|
||||
|
||||
const getInitExpandKeys = (data:T[], expandKeys:string[] = [])=>{
|
||||
data.forEach((item)=>{
|
||||
if(item.children?.length){
|
||||
const getInitExpandKeys = (data: T[], expandKeys: string[] = []) => {
|
||||
data.forEach((item) => {
|
||||
if (item.children?.length) {
|
||||
expandKeys.push(item.key as string)
|
||||
getInitExpandKeys(item.children,expandKeys)
|
||||
getInitExpandKeys(item.children, expandKeys)
|
||||
}
|
||||
})
|
||||
return expandKeys
|
||||
}
|
||||
|
||||
const getDataSource = ()=>{
|
||||
setLoading(true)
|
||||
request && request().then((res)=>{
|
||||
const {data,success} = res
|
||||
setDataSource(success? data : [])
|
||||
setExpandedKeys(getInitExpandKeys(success? data:[]))
|
||||
}).finally(()=>{setLoading(false)})
|
||||
}
|
||||
|
||||
const getDataSource = () => {
|
||||
setLoading(true)
|
||||
request &&
|
||||
request()
|
||||
.then((res) => {
|
||||
const { data, success } = res
|
||||
setDataSource(success ? data : [])
|
||||
setExpandedKeys(getInitExpandKeys(success ? data : []))
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getDataSource()
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div ref={parentRef}>
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} className=''>
|
||||
<Input className="mb-[10px]" placeholder={searchPlaceholder} onChange={(e)=>setSearchWord(e.target.value)} value={searchWord} />
|
||||
<>{ translatedDataSource && translatedDataSource.length > 0 ? <Tree
|
||||
checkable
|
||||
expandedKeys={expandedKeys}
|
||||
checkedKeys={targetKeys}
|
||||
selectable={false}
|
||||
onCheck={(e)=>{setTargetKeys(e);
|
||||
onSelect(((e as string[])?.filter(x=>disabledData.indexOf(x as string) === -1))||[])}}
|
||||
onExpand={setExpandedKeys}
|
||||
treeData={translatedDataSource}
|
||||
blockNode
|
||||
showIcon
|
||||
/>
|
||||
: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/> }</>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div ref={parentRef}>
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} className="">
|
||||
<Input
|
||||
className="mb-[10px]"
|
||||
placeholder={searchPlaceholder}
|
||||
onChange={(e) => setSearchWord(e.target.value)}
|
||||
value={searchWord}
|
||||
/>
|
||||
<>
|
||||
{translatedDataSource && translatedDataSource.length > 0 ? (
|
||||
<Tree
|
||||
checkable
|
||||
expandedKeys={expandedKeys}
|
||||
checkedKeys={targetKeys}
|
||||
selectable={false}
|
||||
onCheck={(e) => {
|
||||
setTargetKeys(e)
|
||||
onSelect((e as string[])?.filter((x) => disabledData.indexOf(x as string) === -1) || [])
|
||||
}}
|
||||
onExpand={setExpandedKeys}
|
||||
treeData={translatedDataSource}
|
||||
blockNode
|
||||
showIcon
|
||||
/>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
)}
|
||||
</>
|
||||
</Spin>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
export default MemberTransfer;
|
||||
export default MemberTransfer
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Result, Skeleton } from 'antd';
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Result, Skeleton } from 'antd'
|
||||
|
||||
const NotFound: React.FC = () => {
|
||||
const [showPage, setShowPage] = useState<boolean>(false)
|
||||
const [showPage, setShowPage] = useState<boolean>(false)
|
||||
|
||||
useEffect(()=>{
|
||||
setTimeout(()=>setShowPage(true), 1000)
|
||||
},[])
|
||||
useEffect(() => {
|
||||
setTimeout(() => setShowPage(true), 1000)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={`h-full w-full flex flex-1 align-middle ${showPage ? 'items-center' : ''}`}>
|
||||
{ showPage ? <Result
|
||||
className='w-full'
|
||||
status="404"
|
||||
title="404"
|
||||
subTitle="Sorry, the page you visited does not exist."
|
||||
/> : <Skeleton active /> }
|
||||
</div>
|
||||
)}
|
||||
return (
|
||||
<div className={`h-full w-full flex flex-1 align-middle ${showPage ? 'items-center' : ''}`}>
|
||||
{showPage ? (
|
||||
<Result className="w-full" status="404" title="404" subTitle="Sorry, the page you visited does not exist." />
|
||||
) : (
|
||||
<Skeleton active />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotFound;
|
||||
export default NotFound
|
||||
|
||||
@@ -1,243 +1,370 @@
|
||||
|
||||
import {Button, Dropdown, Input, MenuProps, TablePaginationConfig} from 'antd';
|
||||
import {ChangeEvent, RefAttributes, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
|
||||
import type {ActionType, ParamsType, ProColumns, ProTableProps} from '@ant-design/pro-components';
|
||||
import {
|
||||
DragSortTable,
|
||||
ProTable,
|
||||
} from '@ant-design/pro-components';
|
||||
import './PageList.module.css'
|
||||
import {SearchOutlined} from "@ant-design/icons";
|
||||
import { SearchOutlined } from '@ant-design/icons'
|
||||
import type { ActionType, ParamsType, ProColumns, ProTableProps } from '@ant-design/pro-components'
|
||||
import { DragSortTable, ProTable } from '@ant-design/pro-components'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission'
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions'
|
||||
import { withMinimumDelay } from '@common/utils/ux'
|
||||
import { Button, Dropdown, Input, MenuProps, TablePaginationConfig } from 'antd'
|
||||
import { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface'
|
||||
import { debounce } from 'lodash-es'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission';
|
||||
import { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface';
|
||||
import { useGlobalContext } from '../../contexts/GlobalStateContext';
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions';
|
||||
import { withMinimumDelay } from '@common/utils/ux';
|
||||
import {
|
||||
ChangeEvent,
|
||||
RefAttributes,
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react'
|
||||
import { useGlobalContext } from '../../contexts/GlobalStateContext'
|
||||
import './PageList.module.css'
|
||||
|
||||
export type PageProColumns<T = any, ValueType = 'text'> = ProColumns<T , ValueType> & {btnNums? : number}
|
||||
export type PageProColumns<T = any, ValueType = 'text'> = ProColumns<T, ValueType> & { btnNums?: number }
|
||||
|
||||
interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<ActionType> {
|
||||
id?:string
|
||||
columns: PageProColumns<T,'text'>[]
|
||||
request?:(params: (ParamsType & {pageSize?: number | undefined, current?: number | undefined, keyword?: string | undefined}), sorter: unknown, filter: unknown)=>Promise<{data:T[], success:boolean}>
|
||||
dropMenu?:MenuProps
|
||||
searchPlaceholder?:string
|
||||
showPagination?:boolean
|
||||
primaryKey?:string
|
||||
addNewBtnTitle?:string
|
||||
addNewBtnAccess?:string
|
||||
tableClickAccess?:string
|
||||
onAddNewBtnClick?:()=>void
|
||||
beforeSearchNode?:React.ReactNode[]
|
||||
onSearchWordChange?:(e:ChangeEvent<HTMLInputElement>) => void
|
||||
afterNewBtn?:React.ReactNode[]
|
||||
dragSortKey?:string
|
||||
onDragSortEnd?:(beforeIndex: number, afterIndex: number, newDataSource: T[]) => void | Promise<void>
|
||||
tableTitle?:string
|
||||
dataSource?:T[]
|
||||
onRowClick?:(record:T)=>void
|
||||
showColSetting?:boolean
|
||||
minVirtualHeight?:number
|
||||
besidesTableHeight?:number
|
||||
noTop?:boolean
|
||||
tableClass?:string
|
||||
tableTitleClass?:string
|
||||
addNewBtnWrapperClass?:string
|
||||
delayLoading?:boolean
|
||||
noScroll?:boolean
|
||||
interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<ActionType> {
|
||||
id?: string
|
||||
columns: PageProColumns<T, 'text'>[]
|
||||
request?: (
|
||||
params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined },
|
||||
sorter: unknown,
|
||||
filter: unknown
|
||||
) => Promise<{ data: T[]; success: boolean }>
|
||||
dropMenu?: MenuProps
|
||||
searchPlaceholder?: string
|
||||
showPagination?: boolean
|
||||
primaryKey?: string
|
||||
addNewBtnTitle?: string
|
||||
addNewBtnAccess?: string
|
||||
tableClickAccess?: string
|
||||
onAddNewBtnClick?: () => void
|
||||
beforeSearchNode?: React.ReactNode[]
|
||||
onSearchWordChange?: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
afterNewBtn?: React.ReactNode[]
|
||||
dragSortKey?: string
|
||||
onDragSortEnd?: (beforeIndex: number, afterIndex: number, newDataSource: T[]) => void | Promise<void>
|
||||
tableTitle?: string
|
||||
dataSource?: T[]
|
||||
onRowClick?: (record: T) => void
|
||||
showColSetting?: boolean
|
||||
minVirtualHeight?: number
|
||||
besidesTableHeight?: number
|
||||
noTop?: boolean
|
||||
tableClass?: string
|
||||
tableTitleClass?: string
|
||||
addNewBtnWrapperClass?: string
|
||||
delayLoading?: boolean
|
||||
noScroll?: boolean
|
||||
/* 前端分页的表格,需要传入该字段以支持后端搜索 */
|
||||
manualReloadTable?:()=>void
|
||||
manualReloadTable?: () => void
|
||||
}
|
||||
|
||||
|
||||
|
||||
const PageList = <T extends Record<string, unknown>>(props: React.PropsWithChildren<PageListProps<T>>,ref: React.Ref<ActionType>) => {
|
||||
const {id,columns,request,dropMenu,searchPlaceholder,showPagination=true,primaryKey='id',addNewBtnTitle,addNewBtnAccess,tableClickAccess,tableClass,onAddNewBtnClick,beforeSearchNode,onSearchWordChange,manualReloadTable,afterNewBtn,dragSortKey,onDragSortEnd,tableTitle,rowSelection,onChange,dataSource,onRowClick,showColSetting=false,minVirtualHeight,noTop,addNewBtnWrapperClass = '',tableTitleClass,delayLoading = true,besidesTableHeight, noScroll} = props
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
const [tableHeight, setTableHeight] = useState(minVirtualHeight || window.innerHeight);
|
||||
const [tableWidth, setTableWidth] = useState<number|undefined>(undefined);
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [allowTableClick,setAllowTableClick] = useState<boolean>(false)
|
||||
const {accessData,checkPermission,accessInit,state} = useGlobalContext()
|
||||
const PageList = <T extends Record<string, unknown>>(
|
||||
props: React.PropsWithChildren<PageListProps<T>>,
|
||||
ref: React.Ref<ActionType>
|
||||
) => {
|
||||
const {
|
||||
id,
|
||||
columns,
|
||||
request,
|
||||
dropMenu,
|
||||
searchPlaceholder,
|
||||
showPagination = true,
|
||||
primaryKey = 'id',
|
||||
addNewBtnTitle,
|
||||
addNewBtnAccess,
|
||||
tableClickAccess,
|
||||
tableClass,
|
||||
onAddNewBtnClick,
|
||||
beforeSearchNode,
|
||||
onSearchWordChange,
|
||||
manualReloadTable,
|
||||
afterNewBtn,
|
||||
dragSortKey,
|
||||
onDragSortEnd,
|
||||
tableTitle,
|
||||
rowSelection,
|
||||
onChange,
|
||||
dataSource,
|
||||
onRowClick,
|
||||
showColSetting = false,
|
||||
minVirtualHeight,
|
||||
noTop,
|
||||
addNewBtnWrapperClass = '',
|
||||
tableTitleClass,
|
||||
delayLoading = true,
|
||||
besidesTableHeight,
|
||||
noScroll
|
||||
} = props
|
||||
const parentRef = useRef<HTMLDivElement>(null)
|
||||
const [tableHeight, setTableHeight] = useState(minVirtualHeight || window.innerHeight)
|
||||
const [tableWidth, setTableWidth] = useState<number | undefined>(undefined)
|
||||
const actionRef = useRef<ActionType>()
|
||||
const [allowTableClick, setAllowTableClick] = useState<boolean>(false)
|
||||
const { accessData, checkPermission, accessInit, state } = useGlobalContext()
|
||||
const [minTableWidth, setMinTableWidth] = useState<number>(0)
|
||||
|
||||
// 使用useImperativeHandle来自定义暴露给父组件的实例值
|
||||
useImperativeHandle(ref, () => actionRef.current!);
|
||||
useImperativeHandle(ref, () => actionRef.current!)
|
||||
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
actionRef?.current?.reload?.()
|
||||
},[state.language])
|
||||
|
||||
const lastAccess = useMemo(()=>{
|
||||
if(!tableClickAccess) return true
|
||||
return checkPermission(tableClickAccess as keyof typeof PERMISSION_DEFINITION[0])
|
||||
},[allowTableClick, accessData,accessInit])
|
||||
}, [state.language])
|
||||
|
||||
useEffect(()=>{
|
||||
tableClickAccess ? setAllowTableClick(lastAccess) : setAllowTableClick(true)
|
||||
},[accessData])
|
||||
|
||||
const resizeObserverRef = useRef<ResizeObserver |null >(null);
|
||||
const lastAccess = useMemo(() => {
|
||||
if (!tableClickAccess) return true
|
||||
return checkPermission(tableClickAccess as keyof (typeof PERMISSION_DEFINITION)[0])
|
||||
}, [allowTableClick, accessData, accessInit])
|
||||
|
||||
useEffect(() => {
|
||||
tableClickAccess ? setAllowTableClick(lastAccess) : setAllowTableClick(true)
|
||||
}, [accessData])
|
||||
|
||||
const resizeObserverRef = useRef<ResizeObserver | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (parentRef.current && !noScroll) {
|
||||
const res = parentRef.current.getBoundingClientRect();
|
||||
const height = res.height - ((noTop ? 0 : 59) + 54 + (showPagination && !dragSortKey ? 52 : 0) +( besidesTableHeight ?? 0) + 1); // 减去顶部按钮、底部分页、表头高度
|
||||
setTableWidth(minTableWidth - 5> res.width ? minTableWidth : undefined);
|
||||
height && setTableHeight(minVirtualHeight === undefined ? height : (height > minVirtualHeight ? height : minVirtualHeight));
|
||||
const res = parentRef.current.getBoundingClientRect()
|
||||
const height =
|
||||
res.height -
|
||||
((noTop ? 0 : 59) + 54 + (showPagination && !dragSortKey ? 52 : 0) + (besidesTableHeight ?? 0) + 1) // 减去顶部按钮、底部分页、表头高度
|
||||
setTableWidth(minTableWidth - 5 > res.width ? minTableWidth : undefined)
|
||||
height &&
|
||||
setTableHeight(
|
||||
minVirtualHeight === undefined ? height : height > minVirtualHeight ? height : minVirtualHeight
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const debouncedHandleResize = debounce(handleResize, 200);
|
||||
const debouncedHandleResize = debounce(handleResize, 200)
|
||||
|
||||
if (!resizeObserverRef.current && !noScroll) {
|
||||
// 创建一个 ResizeObserver 来监听高度变化,只创建一次
|
||||
resizeObserverRef.current = new ResizeObserver(debouncedHandleResize);
|
||||
resizeObserverRef.current = new ResizeObserver(debouncedHandleResize)
|
||||
// 开始监听
|
||||
if (parentRef.current && !minVirtualHeight) {
|
||||
resizeObserverRef.current.observe(parentRef.current);
|
||||
resizeObserverRef.current.observe(parentRef.current)
|
||||
}
|
||||
}
|
||||
|
||||
// 在 minTableWidth 变化时手动触发 handleResize
|
||||
handleResize();
|
||||
handleResize()
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
if (resizeObserverRef.current) {
|
||||
resizeObserverRef.current.disconnect();
|
||||
resizeObserverRef.current = null;
|
||||
resizeObserverRef.current.disconnect()
|
||||
resizeObserverRef.current = null
|
||||
}
|
||||
};
|
||||
}, [minTableWidth, parentRef, noTop, showPagination, dragSortKey, minVirtualHeight]); // 将相关依赖项作为 useEffect 的依赖项
|
||||
}
|
||||
}, [minTableWidth, parentRef, noTop, showPagination, dragSortKey, minVirtualHeight]) // 将相关依赖项作为 useEffect 的依赖项
|
||||
|
||||
|
||||
|
||||
|
||||
const newColumns = useMemo(()=>{
|
||||
let width:number = 0
|
||||
const res = columns?.map(
|
||||
(x, index)=>{
|
||||
const sorter = localStorage.getItem(`${id}_sorter`)
|
||||
const filters = localStorage.getItem(`${id}_filters`)
|
||||
x.copyable = x.copyable ?? (index === 0 || x.dataIndex === 'id' || x.dataIndex === 'email')
|
||||
if(sorter && x.sorter){
|
||||
const sorterObj = JSON.parse(sorter)
|
||||
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(','):x.dataIndex
|
||||
x.defaultSortOrder = sorterObj?.columnKey === xName ? sorterObj?.order : undefined
|
||||
// x.showSorterTooltip = {target:'sorter-icon'}
|
||||
}
|
||||
if(filters && x.filters){
|
||||
const filtersObj = JSON.parse(filters)
|
||||
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(','):x.dataIndex
|
||||
x.defaultFilteredValue = filtersObj?.[xName as string]
|
||||
}
|
||||
if((index === columns.length -1 || x.key === 'option') && x.btnNums){
|
||||
const optionWidth = 24 + 18 * x.btnNums + (x.btnNums - 1) * 21
|
||||
x.width = Math.max(optionWidth, 54)
|
||||
}
|
||||
width += Number(x.width ?? ((x.filters || x.sorter) ? 120 : 100))
|
||||
return x})
|
||||
setMinTableWidth(width)
|
||||
const newColumns = useMemo(() => {
|
||||
let width: number = 0
|
||||
const res = columns?.map((x, index) => {
|
||||
const sorter = localStorage.getItem(`${id}_sorter`)
|
||||
const filters = localStorage.getItem(`${id}_filters`)
|
||||
x.copyable = x.copyable ?? (index === 0 || x.dataIndex === 'id' || x.dataIndex === 'email')
|
||||
if (sorter && x.sorter) {
|
||||
const sorterObj = JSON.parse(sorter)
|
||||
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(',') : x.dataIndex
|
||||
x.defaultSortOrder = sorterObj?.columnKey === xName ? sorterObj?.order : undefined
|
||||
// x.showSorterTooltip = {target:'sorter-icon'}
|
||||
}
|
||||
if (filters && x.filters) {
|
||||
const filtersObj = JSON.parse(filters)
|
||||
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(',') : x.dataIndex
|
||||
x.defaultFilteredValue = filtersObj?.[xName as string]
|
||||
}
|
||||
if ((index === columns.length - 1 || x.key === 'option') && x.btnNums) {
|
||||
const optionWidth = 24 + 18 * x.btnNums + (x.btnNums - 1) * 21
|
||||
x.width = Math.max(optionWidth, 54)
|
||||
}
|
||||
width += Number(x.width ?? (x.filters || x.sorter ? 120 : 100))
|
||||
return x
|
||||
})
|
||||
setMinTableWidth(width)
|
||||
return res
|
||||
},[columns])
|
||||
}, [columns])
|
||||
|
||||
const headerTitle = ()=>{
|
||||
const headerTitle = () => {
|
||||
return (
|
||||
<>{
|
||||
tableTitle ? <span className={`text-[30px] leading-[42px] my-mbase pl-[20px] ${tableTitleClass}`}>{tableTitle}</span> : (
|
||||
addNewBtnTitle ? <WithPermission access={addNewBtnAccess} ><Button type="primary" className={`mr-btnrbase my-btnbase ${addNewBtnWrapperClass}`} onClick={onAddNewBtnClick}>{addNewBtnTitle}</Button></WithPermission> : undefined
|
||||
)
|
||||
|
||||
}
|
||||
{afterNewBtn ? afterNewBtn as React.ReactNode[] :undefined}
|
||||
</>
|
||||
)
|
||||
<>
|
||||
{tableTitle ? (
|
||||
<span className={`text-[30px] leading-[42px] my-mbase pl-[20px] ${tableTitleClass}`}>{tableTitle}</span>
|
||||
) : addNewBtnTitle ? (
|
||||
<WithPermission access={addNewBtnAccess}>
|
||||
<Button
|
||||
type="primary"
|
||||
className={`mr-btnrbase my-btnbase ${addNewBtnWrapperClass}`}
|
||||
onClick={onAddNewBtnClick}
|
||||
>
|
||||
{addNewBtnTitle}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
) : undefined}
|
||||
{afterNewBtn ? (afterNewBtn as React.ReactNode[]) : undefined}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const requestWithDelay = (params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined;}, sort: unknown, filter: unknown) => {
|
||||
return withMinimumDelay(() => request!(params, sort, filter), delayLoading === false? 0 : undefined);
|
||||
};
|
||||
const getTableActions = () => {
|
||||
return [
|
||||
...(beforeSearchNode ? [beforeSearchNode] : []),
|
||||
...(searchPlaceholder
|
||||
? [
|
||||
<Input
|
||||
key="search-input"
|
||||
className="my-btnbase ml-btnbase"
|
||||
onChange={onSearchWordChange ? (e) => debounce(onSearchWordChange, 100)(e) : undefined}
|
||||
onPressEnter={() => {
|
||||
if (manualReloadTable) {
|
||||
manualReloadTable()
|
||||
return
|
||||
}
|
||||
if (actionRef.current) {
|
||||
actionRef.current.reset?.()
|
||||
actionRef.current.reload?.()
|
||||
}
|
||||
}}
|
||||
allowClear
|
||||
placeholder={searchPlaceholder}
|
||||
prefix={
|
||||
<SearchOutlined
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
if (actionRef.current) {
|
||||
actionRef.current.reset?.()
|
||||
actionRef.current.reload?.()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
]
|
||||
: [])
|
||||
]
|
||||
}
|
||||
|
||||
const requestWithDelay = (
|
||||
params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined },
|
||||
sort: unknown,
|
||||
filter: unknown
|
||||
) => {
|
||||
return withMinimumDelay(() => request!(params, sort, filter), delayLoading === false ? 0 : undefined)
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={parentRef} className={`eo_page_list bg-MAIN_BG ${dragSortKey ? 'eo_page_drag':''} ${tableClass ?? ''}`}style={{ height: '100%' }}>
|
||||
{dragSortKey? <DragSortTable<T>
|
||||
<div
|
||||
ref={parentRef}
|
||||
className={`eo_page_list bg-MAIN_BG ${dragSortKey ? 'eo_page_drag' : ''} ${tableClass ?? ''}`}
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
{dragSortKey ? (
|
||||
<DragSortTable<T>
|
||||
actionRef={actionRef}
|
||||
columns={newColumns}
|
||||
rowKey={primaryKey}
|
||||
search={false}
|
||||
pagination={false}
|
||||
pagination={
|
||||
showPagination
|
||||
? {
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
size: 'default'
|
||||
}
|
||||
: false
|
||||
}
|
||||
request={request}
|
||||
dragSortKey={dragSortKey}
|
||||
onDragSortEnd={onDragSortEnd}
|
||||
scroll={noScroll ? undefined :{ y: tableHeight }}
|
||||
scroll={noScroll ? undefined : { y: tableHeight }}
|
||||
options={{
|
||||
reload: false,
|
||||
density: false,
|
||||
setting: false,
|
||||
setting: false
|
||||
}}
|
||||
headerTitle={
|
||||
headerTitle()
|
||||
}
|
||||
/> : <ProTable<T>
|
||||
toolbar={{
|
||||
actions: getTableActions()
|
||||
}}
|
||||
headerTitle={headerTitle()}
|
||||
/>
|
||||
) : (
|
||||
<ProTable<T>
|
||||
actionRef={actionRef}
|
||||
columns={newColumns}
|
||||
virtual
|
||||
scroll={noScroll ? undefined : {x:tableWidth,y: tableHeight }}
|
||||
scroll={noScroll ? undefined : { x: tableWidth, y: tableHeight }}
|
||||
size="middle"
|
||||
rowSelection={rowSelection}
|
||||
tableAlertRender={false}
|
||||
tableAlertOptionRender={false}
|
||||
request={request ? requestWithDelay : undefined}
|
||||
toolBarRender={() => [
|
||||
dropMenu ? (<Dropdown
|
||||
key="menu"
|
||||
menu={dropMenu}
|
||||
>
|
||||
<Button>
|
||||
筛选
|
||||
</Button>
|
||||
</Dropdown>):null,
|
||||
dropMenu ? (
|
||||
<Dropdown key="menu" menu={dropMenu}>
|
||||
<Button>筛选</Button>
|
||||
</Dropdown>
|
||||
) : null
|
||||
]}
|
||||
toolbar={{
|
||||
actions:[...[beforeSearchNode],...[searchPlaceholder?<Input className="my-btnbase ml-btnbase" onChange={ onSearchWordChange ? (e) => debounce(onSearchWordChange, 100)(e) : undefined } onPressEnter={()=>manualReloadTable ? manualReloadTable():actionRef.current?.reload?.()} allowClear placeholder={searchPlaceholder} prefix={<SearchOutlined className="cursor-pointer" onClick={()=>{actionRef.current?.reload?.()}}/>}/>:null]],
|
||||
actions: getTableActions()
|
||||
}}
|
||||
options={{
|
||||
reload: false,
|
||||
density: false,
|
||||
setting: showColSetting ? {
|
||||
draggable:false,
|
||||
showListItemOption:false
|
||||
} :false,
|
||||
setting: showColSetting
|
||||
? {
|
||||
draggable: false,
|
||||
showListItemOption: false
|
||||
}
|
||||
: false
|
||||
}}
|
||||
showSorterTooltip={false}
|
||||
columnsState={{persistenceType:'localStorage',persistenceKey:id}}
|
||||
pagination={showPagination ? {
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
size:'default'
|
||||
}:false}
|
||||
columnsState={{ persistenceType: 'localStorage', persistenceKey: id }}
|
||||
pagination={
|
||||
showPagination
|
||||
? {
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
size: 'default'
|
||||
}
|
||||
: false
|
||||
}
|
||||
onChange={(
|
||||
pagination: TablePaginationConfig,
|
||||
filters: Record<string, FilterValue | null>,
|
||||
sorter: SorterResult<T> | SorterResult<T>[],
|
||||
extra: TableCurrentDataSource<T>
|
||||
) => {
|
||||
localStorage.setItem(`${id}_filters`, JSON.stringify(filters))
|
||||
!Array.isArray(sorter) &&
|
||||
localStorage.setItem(
|
||||
`${id}_sorter`,
|
||||
JSON.stringify({ columnKey: sorter?.columnKey, order: sorter?.order })
|
||||
)
|
||||
onChange?.(pagination, filters, sorter, extra)
|
||||
}}
|
||||
rowKey={primaryKey}
|
||||
onChange={(pagination: TablePaginationConfig, filters: Record<string, FilterValue | null>, sorter: SorterResult<T> | SorterResult<T>[],extra:TableCurrentDataSource<T>) =>{
|
||||
localStorage.setItem(`${id}_filters`,JSON.stringify(filters))
|
||||
!Array.isArray(sorter) && localStorage.setItem(`${id}_sorter`,JSON.stringify({columnKey:sorter?.columnKey, order: sorter?.order}))
|
||||
onChange?.(pagination,filters,sorter,extra)}}
|
||||
dataSource={dataSource}
|
||||
search={false}
|
||||
headerTitle={
|
||||
headerTitle()
|
||||
headerTitle={headerTitle()}
|
||||
onRow={
|
||||
onRowClick && allowTableClick
|
||||
? (record) => ({
|
||||
onClick: () => {
|
||||
onRowClick(record)
|
||||
}
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
onRow={onRowClick && allowTableClick ? (record) => ({
|
||||
onClick: () => {
|
||||
onRowClick(record);
|
||||
}
|
||||
}):undefined}
|
||||
rowClassName={()=>onRowClick && allowTableClick ?"cursor-pointer":''}
|
||||
/>}
|
||||
rowClassName={() => (onRowClick && allowTableClick ? 'cursor-pointer' : '')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default forwardRef(PageList) as <T extends Record<string,unknown>>(props: React.PropsWithChildren<PageListProps<T>> & { ref?: React.Ref<ActionType> }) => ReturnType<typeof PageList>;
|
||||
export default forwardRef(PageList) as <T extends Record<string, unknown>>(
|
||||
props: React.PropsWithChildren<PageListProps<T>> & { ref?: React.Ref<ActionType> }
|
||||
) => ReturnType<typeof PageList>
|
||||
|
||||
@@ -1,95 +1,100 @@
|
||||
import {App, Form, Input, Row, Table} from "antd";
|
||||
import {forwardRef, useEffect, useImperativeHandle, useMemo} from "react";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {BasicResponse, PLACEHOLDER, PolicyPublishColumns, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import { $t } from "@common/locales";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
import { PolicyPublishModalHandle, PolicyPublishModalProps } from "@common/const/type";
|
||||
import { App, Form, Input, Row, Table } from 'antd'
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { BasicResponse, PLACEHOLDER, PolicyPublishColumns, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { PolicyPublishModalHandle, PolicyPublishModalProps } from '@common/const/type'
|
||||
|
||||
export const PolicyPublishModalContent = forwardRef<PolicyPublishModalHandle, PolicyPublishModalProps>((props, ref) => {
|
||||
const { message } = App.useApp()
|
||||
const { data } = props
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
export const PolicyPublishModalContent = forwardRef<PolicyPublishModalHandle,PolicyPublishModalProps>((props, ref) => {
|
||||
const { message } = App.useApp()
|
||||
const { data} = props
|
||||
const [form] = Form.useForm();
|
||||
const {fetchData} = useFetch()
|
||||
const {state} = useGlobalContext()
|
||||
|
||||
const publish:()=>Promise<boolean | string | Record<string, unknown>> = ()=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
form.validateFields().then((value)=>{
|
||||
const body = {...value, source:data.source}
|
||||
fetchData<BasicResponse<null>>('strategy/global/data-masking/publish',{method: 'POST',eoBody:body,eoTransformKeys:['versionName']}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(response)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, ()=>({
|
||||
publish,
|
||||
const publish: () => Promise<boolean | string | Record<string, unknown>> = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((value) => {
|
||||
const body = { ...value, source: data.source }
|
||||
fetchData<BasicResponse<null>>('strategy/global/data-masking/publish', {
|
||||
method: 'POST',
|
||||
eoBody: body,
|
||||
eoTransformKeys: ['versionName']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(response)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
)
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
form.setFieldsValue(data)
|
||||
},[data])
|
||||
useImperativeHandle(ref, () => ({
|
||||
publish
|
||||
}))
|
||||
|
||||
const translatedPolicyColumns = useMemo(()=>PolicyPublishColumns.map((x)=>({
|
||||
...x,
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title,
|
||||
})),[state.language])
|
||||
useEffect(() => {
|
||||
form.setFieldsValue(data)
|
||||
}, [data])
|
||||
|
||||
const translatedPolicyColumns = useMemo(
|
||||
() =>
|
||||
PolicyPublishColumns.map((x) => ({
|
||||
...x,
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title
|
||||
})),
|
||||
[state.language]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<WithPermission access=""><Form
|
||||
className=" mx-auto"
|
||||
form={form}
|
||||
labelAlign='left'
|
||||
layout='vertical'
|
||||
scrollToFirstError
|
||||
name="publishApprovalModalContent"
|
||||
// labelCol={{span: 3}}
|
||||
// wrapperCol={{span: 21}}
|
||||
autoComplete="off"
|
||||
>
|
||||
return (
|
||||
<>
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
className=" mx-auto"
|
||||
form={form}
|
||||
labelAlign="left"
|
||||
layout="vertical"
|
||||
scrollToFirstError
|
||||
name="publishApprovalModalContent"
|
||||
// labelCol={{span: 3}}
|
||||
// wrapperCol={{span: 21}}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item label={$t('发布名称')} name="versionName" rules={[{ required: true, whitespace: true }]}>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={$t("发布名称")}
|
||||
name='versionName'
|
||||
rules={[{required: true,whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={$t("描述")}
|
||||
name="desc"
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('策略列表')}:</span></Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
columns={translatedPolicyColumns}
|
||||
bordered={true}
|
||||
rowKey="name"
|
||||
size="small"
|
||||
dataSource={data.strategies || []}
|
||||
pagination={false}
|
||||
/>
|
||||
{!data?.isPublish&& data?.unpublishMsg&& <p className="text-status_fail mt-[4px]">{data.unpublishMsg}</p>}
|
||||
</Row>
|
||||
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</>)
|
||||
})
|
||||
<Form.Item label={$t('描述')} name="desc">
|
||||
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold">
|
||||
<span>{$t('策略列表')}:</span>
|
||||
</Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
columns={translatedPolicyColumns}
|
||||
bordered={true}
|
||||
rowKey="name"
|
||||
size="small"
|
||||
dataSource={data.strategies || []}
|
||||
pagination={false}
|
||||
/>
|
||||
{!data?.isPublish && data?.unpublishMsg && <p className="text-status_fail mt-[4px]">{data.unpublishMsg}</p>}
|
||||
</Row>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
+394
-240
@@ -1,257 +1,402 @@
|
||||
import {App, Col, Form, Input, Row, Table, Tooltip} from "antd";
|
||||
import {forwardRef, useEffect, useImperativeHandle, useMemo} from "react";
|
||||
import {PublishApprovalInfoType, PublishApprovalModalHandle, PublishApprovalModalProps, PublishVersionTableListItem} from "@common/const/approval/type.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, STATUS_COLOR} from "@common/const/const.tsx";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import { SYSTEM_PUBLISH_ONLINE_COLUMNS } from "@core/const/system/const.tsx";
|
||||
import { $t } from "@common/locales";
|
||||
import { ApprovalPolicyColumns, ApprovalRouteColumns, ApprovalStatusColorClass, ApprovalUpstreamColumns, ChangeTypeEnum } from "@common/const/approval/const";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { SystemInsidePublishOnlineItems } from "@core/pages/system/publish/SystemInsidePublishOnline";
|
||||
import { App, Col, Form, Input, Row, Table, Tooltip } from 'antd'
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react'
|
||||
import {
|
||||
PublishApprovalInfoType,
|
||||
PublishApprovalModalHandle,
|
||||
PublishApprovalModalProps,
|
||||
PublishVersionTableListItem
|
||||
} from '@common/const/approval/type.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import {
|
||||
BasicResponse,
|
||||
FORM_ERROR_TIPS,
|
||||
PLACEHOLDER,
|
||||
RESPONSE_TIPS,
|
||||
STATUS_CODE,
|
||||
STATUS_COLOR
|
||||
} from '@common/const/const.tsx'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { SYSTEM_PUBLISH_ONLINE_COLUMNS } from '@core/const/system/const.tsx'
|
||||
import { $t } from '@common/locales'
|
||||
import {
|
||||
ApprovalPolicyColumns,
|
||||
ApprovalRouteColumns,
|
||||
ApprovalStatusColorClass,
|
||||
ApprovalUpstreamColumns,
|
||||
ChangeTypeEnum
|
||||
} from '@common/const/approval/const'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { SystemInsidePublishOnlineItems } from '@core/pages/system/publish/SystemInsidePublishOnline'
|
||||
|
||||
|
||||
export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle,PublishApprovalModalProps>((props, ref) => {
|
||||
export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle, PublishApprovalModalProps>(
|
||||
(props, ref) => {
|
||||
const { message } = App.useApp()
|
||||
const { type,data,insidePage = false, serviceType = 'rest', serviceId, teamId} = props
|
||||
const [form] = Form.useForm();
|
||||
const {fetchData} = useFetch()
|
||||
const {state} = useGlobalContext()
|
||||
const { type, data, insidePage = false, serviceType = 'rest', serviceId, teamId } = props
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
const save:(operate:'pass'|'refuse')=>Promise<boolean | string> = (operate)=>{
|
||||
if(type === 'view'){
|
||||
const save: (operate: 'pass' | 'refuse') => Promise<boolean | string> = (operate) => {
|
||||
if (type === 'view') {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
return form
|
||||
.validateFields()
|
||||
.then((value) => {
|
||||
if (operate === 'refuse' && form.getFieldValue('opinion') === '') {
|
||||
form.setFields([
|
||||
{
|
||||
name: 'opinion',
|
||||
errors: [$t(FORM_ERROR_TIPS.refuseOpinion)]
|
||||
}
|
||||
])
|
||||
form.scrollToField('opinion')
|
||||
return Promise.reject($t(RESPONSE_TIPS.refuseOpinion))
|
||||
}
|
||||
return fetchData<BasicResponse<null>>(`service/publish/${operate === 'pass' ? 'accept' : 'refuse'}`, {
|
||||
method: 'PUT',
|
||||
eoBody: { comments: value.opinion },
|
||||
eoParams: { id: data!.id, project: serviceId },
|
||||
eoTransformKeys: ['versionRemark']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
return form.validateFields().then((value)=>{
|
||||
if(operate === 'refuse' && form.getFieldValue('opinion') === '' ){
|
||||
form.setFields([{
|
||||
name:'opinion',errors:[$t(FORM_ERROR_TIPS.refuseOpinion)]
|
||||
}])
|
||||
form.scrollToField('opinion')
|
||||
return Promise.reject($t(RESPONSE_TIPS.refuseOpinion))
|
||||
}
|
||||
return fetchData<BasicResponse<null>>(`service/publish/${operate === 'pass' ? 'accept' : 'refuse'}`,{method: 'PUT',eoBody:({comments:value.opinion}), eoParams:{id:data!.id, project:serviceId},eoTransformKeys:['versionRemark']}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
return Promise.resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> Promise.reject(errorInfo))
|
||||
}).catch((err)=> {form.scrollToField(err.errorFields[0].name[0]); return Promise.reject(err)})
|
||||
}
|
||||
|
||||
const publish:(notSave?:boolean)=>Promise<boolean | string | Record<string, unknown>> = (notSave)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
form.validateFields().then((value)=>{
|
||||
const body = {...value, ...(type === 'publish'&&{release:data.id})}
|
||||
fetchData<BasicResponse<null>>(
|
||||
notSave ? 'service/publish/apply' : 'service/publish/release/do',{method: 'POST',eoBody:body, eoParams:{service:serviceId, team:teamId},eoTransformKeys:['versionRemark']}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(response)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
const online:()=>Promise<boolean | string> = ()=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
form.validateFields().then(()=>{
|
||||
fetchData<BasicResponse<null>>('service/publish/execute',{method: 'PUT', eoParams:{project:serviceId,id:(data as PublishVersionTableListItem).flowId},eoTransformKeys:['versionRemark']}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, ()=>({
|
||||
save,
|
||||
publish,
|
||||
online
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => Promise.reject(errorInfo))
|
||||
})
|
||||
.catch((err) => {
|
||||
form.scrollToField(err.errorFields[0].name[0])
|
||||
return Promise.reject(err)
|
||||
})
|
||||
}
|
||||
|
||||
const publish: (notSave?: boolean) => Promise<boolean | string | Record<string, unknown>> = (notSave) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((value) => {
|
||||
const body = { ...value, ...(type === 'publish' && { release: data.id }) }
|
||||
fetchData<BasicResponse<null>>(notSave ? 'service/publish/apply' : 'service/publish/release/do', {
|
||||
method: 'POST',
|
||||
eoBody: body,
|
||||
eoParams: { service: serviceId, team: teamId },
|
||||
eoTransformKeys: ['versionRemark']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(response)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
const online: () => Promise<boolean | string> = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(() => {
|
||||
fetchData<BasicResponse<null>>('service/publish/execute', {
|
||||
method: 'PUT',
|
||||
eoParams: { project: serviceId, id: (data as PublishVersionTableListItem).flowId },
|
||||
eoTransformKeys: ['versionRemark']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
save,
|
||||
publish,
|
||||
online
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({ opinion: '', ...data })
|
||||
}, [])
|
||||
|
||||
const translatedUpstreamColumns = useMemo(
|
||||
() =>
|
||||
ApprovalUpstreamColumns.map((x) => ({
|
||||
...x,
|
||||
...(x.dataIndex === 'type'
|
||||
? {
|
||||
valueEnum: {
|
||||
static: {
|
||||
text: $t('静态上游')
|
||||
}
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
...(x.dataIndex === 'change'
|
||||
? {
|
||||
render: (_, entity) => (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={
|
||||
entity.change === 'error'
|
||||
? $t('该 API 缺失(0)(1)(2)请先补充', [
|
||||
entity.proxyStatus == 1 && $t('转发信息,'),
|
||||
entity.docStatus == 1 && $t('文档信息,'),
|
||||
entity.upstreamStatus == 1 && $t('上游信息,')
|
||||
])
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}
|
||||
>
|
||||
{$t(ChangeTypeEnum[entity.change as keyof typeof ChangeTypeEnum] || '-')}
|
||||
{entity.change === 'error'
|
||||
? $t('该 API 缺失(0)(1)(2)请先补充', [
|
||||
entity.proxyStatus == 1 && $t('转发信息,'),
|
||||
entity.docStatus == 1 && $t('文档信息,'),
|
||||
entity.upstreamStatus == 1 && $t('上游信息,')
|
||||
])
|
||||
: ''}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
: {}),
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title
|
||||
})),
|
||||
[state.language]
|
||||
)
|
||||
|
||||
useEffect(()=>{
|
||||
form.setFieldsValue({ opinion:'',...data})
|
||||
},[])
|
||||
|
||||
const translatedUpstreamColumns = useMemo(()=>ApprovalUpstreamColumns.map((x)=>({
|
||||
...x,
|
||||
...(x.dataIndex === 'type' ? {valueEnum:{
|
||||
'static':{
|
||||
text:$t('静态上游')
|
||||
}
|
||||
}}:{}),
|
||||
...(x.dataIndex === 'change' ? {
|
||||
render:(_,entity)=>(
|
||||
<Tooltip placement="top" title={entity.change === 'error' ? $t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}>
|
||||
<span className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}>{$t(ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-')}
|
||||
{entity.change === 'error' ?$t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}</span>
|
||||
</Tooltip>)
|
||||
}:{}),
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title,
|
||||
})),[state.language])
|
||||
|
||||
|
||||
const translatedRouteColumns = useMemo(()=>ApprovalRouteColumns.filter(x=> serviceType === 'rest' ? x.dataIndex !== 'name' : x.dataIndex !== 'methods').map((x)=>({
|
||||
...x,
|
||||
...(x.dataIndex === 'change' ? {
|
||||
render:(_,entity)=>(
|
||||
<Tooltip placement="top" title={entity.change === 'error' ?$t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}>
|
||||
<span className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}>
|
||||
{$t(ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-')}
|
||||
{entity.change === 'error' ?$t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}
|
||||
</span>
|
||||
</Tooltip>)
|
||||
}:{}
|
||||
),
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title,
|
||||
})),[state.language, serviceType])
|
||||
|
||||
const translatedPublishColumns = useMemo(()=>SYSTEM_PUBLISH_ONLINE_COLUMNS.map((x)=>{
|
||||
if(x.dataIndex === 'status'){
|
||||
return {...x,title:$t(x.title),
|
||||
render:(_:unknown,entity:SystemInsidePublishOnlineItems)=>{
|
||||
switch(entity.status){
|
||||
case 'done':
|
||||
return <span className={STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]}>{$t('成功')}</span>
|
||||
case 'error':
|
||||
return <Tooltip title={entity.error || $t('上线失败')}><span className={`${STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]} truncate block`}>{$t('失败')} {entity.error}</span></Tooltip>
|
||||
default:
|
||||
return <LoadingOutlined className="text-theme" spin />
|
||||
const translatedRouteColumns = useMemo(
|
||||
() =>
|
||||
ApprovalRouteColumns.filter((x) =>
|
||||
serviceType === 'rest' ? x.dataIndex !== 'name' : x.dataIndex !== 'methods'
|
||||
).map((x) => ({
|
||||
...x,
|
||||
...(x.dataIndex === 'change'
|
||||
? {
|
||||
render: (_, entity) => (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={
|
||||
entity.change === 'error'
|
||||
? $t('该 API 缺失(0)(1)(2)请先补充', [
|
||||
entity.proxyStatus == 1 && $t('转发信息,'),
|
||||
entity.docStatus == 1 && $t('文档信息,'),
|
||||
entity.upstreamStatus == 1 && $t('上游信息,')
|
||||
])
|
||||
: ''
|
||||
}
|
||||
}}
|
||||
}
|
||||
}),[state.language])
|
||||
>
|
||||
<span
|
||||
className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}
|
||||
>
|
||||
{$t(ChangeTypeEnum[entity.change as keyof typeof ChangeTypeEnum] || '-')}
|
||||
{entity.change === 'error'
|
||||
? $t('该 API 缺失(0)(1)(2)请先补充', [
|
||||
entity.proxyStatus == 1 && $t('转发信息,'),
|
||||
entity.docStatus == 1 && $t('文档信息,'),
|
||||
entity.upstreamStatus == 1 && $t('上游信息,')
|
||||
])
|
||||
: ''}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
: {}),
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title
|
||||
})),
|
||||
[state.language, serviceType]
|
||||
)
|
||||
|
||||
|
||||
const translatedPolicyColumns = useMemo(()=>ApprovalPolicyColumns.map((x)=>{
|
||||
return {
|
||||
const translatedPublishColumns = useMemo(
|
||||
() =>
|
||||
SYSTEM_PUBLISH_ONLINE_COLUMNS.map((x) => {
|
||||
if (x.dataIndex === 'status') {
|
||||
return {
|
||||
...x,
|
||||
title: $t(x.title),
|
||||
render: (_: unknown, entity: SystemInsidePublishOnlineItems) => {
|
||||
switch (entity.status) {
|
||||
case 'done':
|
||||
return (
|
||||
<span className={STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]}>{$t('成功')}</span>
|
||||
)
|
||||
case 'error':
|
||||
return (
|
||||
<Tooltip title={entity.error || $t('上线失败')}>
|
||||
<span className={`${STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]} truncate block`}>
|
||||
{$t('失败')} {entity.error}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)
|
||||
default:
|
||||
return <LoadingOutlined className="text-theme" spin />
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
[state.language]
|
||||
)
|
||||
|
||||
const translatedPolicyColumns = useMemo(
|
||||
() =>
|
||||
ApprovalPolicyColumns.map((x) => {
|
||||
return {
|
||||
...x,
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title,
|
||||
...(x.dataIndex === 'status' ? {
|
||||
render:(_,entity)=> (
|
||||
<span className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}>
|
||||
{$t(ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-')}
|
||||
...(x.dataIndex === 'status'
|
||||
? {
|
||||
render: (_, entity) => (
|
||||
<span
|
||||
className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}
|
||||
>
|
||||
{$t(ChangeTypeEnum[entity.change as keyof typeof ChangeTypeEnum] || '-')}
|
||||
</span>
|
||||
)
|
||||
}:{})
|
||||
}
|
||||
}),[state.language])
|
||||
|
||||
)
|
||||
}
|
||||
: {})
|
||||
}
|
||||
}),
|
||||
[state.language]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{!insidePage && <>
|
||||
<>
|
||||
{!insidePage && (
|
||||
<>
|
||||
<Row className="my-mbase">
|
||||
<Col className="text-left" span={4}><span >{$t('申请系统')}:</span></Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).project || '-'}</Col>
|
||||
<Col className="text-left" span={4}>
|
||||
<span>{$t('申请系统')}:</span>
|
||||
</Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).project || '-'}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="my-mbase">
|
||||
<Col className="text-left" span={4}><span >{$t('所属团队')}:</span></Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).team || '-'}</Col>
|
||||
<Col className="text-left" span={4}>
|
||||
<span>{$t('所属团队')}:</span>
|
||||
</Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).team || '-'}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="my-mbase">
|
||||
<Col className="text-left" span={4}><span >{$t('申请人')}:</span></Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).applier || '-'}</Col>
|
||||
<Col className="text-left" span={4}>
|
||||
<span>{$t('申请人')}:</span>
|
||||
</Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).applier || '-'}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="my-mbase">
|
||||
<Col className="text-left" span={4}><span >{$t('申请时间')}:</span></Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).applyTime || '-'}</Col>
|
||||
<Col className="text-left" span={4}>
|
||||
<span>{$t('申请时间')}:</span>
|
||||
</Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).applyTime || '-'}</Col>
|
||||
</Row>
|
||||
</> }
|
||||
<WithPermission access=""><Form
|
||||
className=" mx-auto"
|
||||
form={form}
|
||||
labelAlign='left'
|
||||
layout='vertical'
|
||||
scrollToFirstError
|
||||
name="publishApprovalModalContent"
|
||||
// labelCol={{span: 3}}
|
||||
// wrapperCol={{span: 21}}
|
||||
autoComplete="off"
|
||||
disabled={type === 'view'}
|
||||
>
|
||||
</>
|
||||
)}
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
className=" mx-auto"
|
||||
form={form}
|
||||
labelAlign="left"
|
||||
layout="vertical"
|
||||
scrollToFirstError
|
||||
name="publishApprovalModalContent"
|
||||
// labelCol={{span: 3}}
|
||||
// wrapperCol={{span: 21}}
|
||||
autoComplete="off"
|
||||
disabled={type === 'view'}
|
||||
>
|
||||
{insidePage && (
|
||||
<>
|
||||
<Form.Item label={$t('版本号')} name="version" rules={[{ required: true, whitespace: true }]}>
|
||||
<Input className="w-INPUT_NORMAL" disabled={type !== 'add'} placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
|
||||
{
|
||||
insidePage &&
|
||||
<>
|
||||
<Form.Item
|
||||
label={$t("版本号")}
|
||||
name="version"
|
||||
rules={[{required: true,whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" disabled={type !== 'add'} placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={$t("版本说明")}
|
||||
name="versionRemark"
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" disabled={type !== 'add' && type !== 'publish'} placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
</>
|
||||
}
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('路由列表')}:</span></Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
columns={translatedRouteColumns}
|
||||
bordered={true}
|
||||
rowKey="id"
|
||||
size="small"
|
||||
dataSource={data.diffs?.routers || []}
|
||||
pagination={false}
|
||||
/></Row>
|
||||
{
|
||||
serviceType === 'rest' && <>
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('上游列表')}:</span></Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={translatedUpstreamColumns}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.diffs?.upstreams || []}
|
||||
pagination={false}
|
||||
/></Row>
|
||||
</>
|
||||
}
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('策略列表')}:</span></Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={translatedPolicyColumns}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.diffs?.strategies || []}
|
||||
pagination={false}
|
||||
/></Row>
|
||||
{/* <Form.Item
|
||||
<Form.Item label={$t('版本说明')} name="versionRemark">
|
||||
<Input.TextArea
|
||||
className="w-INPUT_NORMAL"
|
||||
disabled={type !== 'add' && type !== 'publish'}
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold">
|
||||
<span>{$t('路由列表')}:</span>
|
||||
</Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
columns={translatedRouteColumns}
|
||||
bordered={true}
|
||||
rowKey="id"
|
||||
size="small"
|
||||
dataSource={data.diffs?.routers || []}
|
||||
pagination={false}
|
||||
/>
|
||||
</Row>
|
||||
{serviceType === 'rest' && (
|
||||
<>
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold">
|
||||
<span>{$t('上游列表')}:</span>
|
||||
</Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={translatedUpstreamColumns}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.diffs?.upstreams || []}
|
||||
pagination={false}
|
||||
/>
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold">
|
||||
<span>{$t('策略列表')}:</span>
|
||||
</Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={translatedPolicyColumns}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.diffs?.strategies || []}
|
||||
pagination={false}
|
||||
/>
|
||||
</Row>
|
||||
{/* <Form.Item
|
||||
label={$t("备注")}
|
||||
name="remark"
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" disabled={type !== 'add' && type !== 'publish'} placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item> */}
|
||||
{/*
|
||||
{/*
|
||||
{type !== 'add' && type !== 'publish' && <Form.Item
|
||||
label={$t("审核意见"
|
||||
name="opinion"
|
||||
@@ -264,20 +409,29 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
|
||||
},
|
||||
]);}}/>
|
||||
</Form.Item>} */}
|
||||
|
||||
{['error','done'].indexOf(data.status) !== -1 && data.clusterPublishStatus &&data.clusterPublishStatus.length > 0 && <>
|
||||
<Row className="text-left h-[32px] mb-8px]" span={3}><span>{$t('上线情况')}:</span></Row>
|
||||
<Row span={24} className="mb-mbase">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={[...translatedPublishColumns]}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.clusterPublishStatus || []}
|
||||
pagination={false}
|
||||
/>
|
||||
</Row></>}
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</>)
|
||||
})
|
||||
|
||||
{['error', 'done'].indexOf(data.status) !== -1 &&
|
||||
data.clusterPublishStatus &&
|
||||
data.clusterPublishStatus.length > 0 && (
|
||||
<>
|
||||
<Row className="text-left h-[32px] mb-8px]" span={3}>
|
||||
<span>{$t('上线情况')}:</span>
|
||||
</Row>
|
||||
<Row span={24} className="mb-mbase">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={[...translatedPublishColumns]}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.clusterPublishStatus || []}
|
||||
pagination={false}
|
||||
/>
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
|
||||
import {FC, useRef, useEffect, Children, cloneElement, isValidElement } from 'react';
|
||||
import { FC, useRef, useEffect, Children, cloneElement, isValidElement } from 'react'
|
||||
|
||||
interface ScrollableSectionProps {
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const ScrollableSection: FC<ScrollableSectionProps> = ({ children }) => {
|
||||
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
||||
const scrollAreaRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
if (scrollAreaRef.current) {
|
||||
const scrollTop = scrollAreaRef.current.scrollTop;
|
||||
const scrollHeight = scrollAreaRef.current.scrollHeight;
|
||||
const clientHeight = scrollAreaRef.current.clientHeight;
|
||||
const scrollTop = scrollAreaRef.current.scrollTop
|
||||
const scrollHeight = scrollAreaRef.current.scrollHeight
|
||||
const clientHeight = scrollAreaRef.current.clientHeight
|
||||
|
||||
// 如果滚动到顶部,.content-before 应该显示阴影
|
||||
const showTopShadow = scrollTop > 0;
|
||||
// 如果滚动到底部,.content-after 应该显示阴影
|
||||
const showBottomShadow = scrollHeight - scrollTop < clientHeight;
|
||||
// 这里我们不直接更新状态,而是通过ref来设置样式
|
||||
if (showTopShadow && !showBottomShadow) {
|
||||
setElementShadow('.content-before', true);
|
||||
setElementShadow('.content-after', false);
|
||||
} else if (!showTopShadow && showBottomShadow) {
|
||||
setElementShadow('.content-before', false);
|
||||
setElementShadow('.content-after', true);
|
||||
} else {
|
||||
setElementShadow('.content-before', false);
|
||||
setElementShadow('.content-after', false);
|
||||
}
|
||||
// 如果滚动到顶部,.content-before 应该显示阴影
|
||||
const showTopShadow = scrollTop > 0
|
||||
// 如果滚动到底部,.content-after 应该显示阴影
|
||||
const showBottomShadow = scrollHeight - scrollTop < clientHeight
|
||||
// 这里我们不直接更新状态,而是通过ref来设置样式
|
||||
if (showTopShadow && !showBottomShadow) {
|
||||
setElementShadow('.content-before', true)
|
||||
setElementShadow('.content-after', false)
|
||||
} else if (!showTopShadow && showBottomShadow) {
|
||||
setElementShadow('.content-before', false)
|
||||
setElementShadow('.content-after', true)
|
||||
} else {
|
||||
setElementShadow('.content-before', false)
|
||||
setElementShadow('.content-after', false)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
scrollAreaRef.current?.addEventListener('scroll', handleScroll);
|
||||
scrollAreaRef.current?.addEventListener('scroll', handleScroll)
|
||||
|
||||
return () => {
|
||||
scrollAreaRef.current?.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, []);
|
||||
scrollAreaRef.current?.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const setElementShadow = (elementSelector: string, showShadow: boolean) => {
|
||||
const element = document.querySelector(elementSelector);
|
||||
const element = document.querySelector(elementSelector)
|
||||
if (element) {
|
||||
element.style.boxShadow = showShadow ? ( elementSelector === '.content-before' ? '0 2px 2px #0000000d':'0 -2px 2px -2px var(--border-color)') : 'none';
|
||||
element.style.boxShadow = showShadow
|
||||
? elementSelector === '.content-before'
|
||||
? '0 2px 2px #0000000d'
|
||||
: '0 -2px 2px -2px var(--border-color)'
|
||||
: 'none'
|
||||
}
|
||||
}
|
||||
|
||||
const childrenWithRef = Children.toArray(children).map((child) => {
|
||||
if (isValidElement(child) && child.props.className && child.props.className.includes('scroll-area')) {
|
||||
// 将 ref 附加到具有 'scroll-area' 类名的子元素
|
||||
return cloneElement(child, { ref: scrollAreaRef });
|
||||
return cloneElement(child, { ref: scrollAreaRef })
|
||||
}
|
||||
return child;
|
||||
});
|
||||
return child
|
||||
})
|
||||
|
||||
return (
|
||||
<> {childrenWithRef}
|
||||
</>
|
||||
);
|
||||
};
|
||||
return <> {childrenWithRef}</>
|
||||
}
|
||||
|
||||
export default ScrollableSection;
|
||||
export default ScrollableSection
|
||||
|
||||
+110
-96
@@ -1,115 +1,129 @@
|
||||
import {App, Col, Form, Input, Row} from "antd";
|
||||
import { forwardRef, useEffect, useImperativeHandle} from "react";
|
||||
import {SubscribeApprovalInfoType} from "@common/const/approval/type.tsx";
|
||||
import {BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import { SubscribeApprovalList } from "@common/const/approval/const";
|
||||
import { $t } from "@common/locales";
|
||||
import { App, Col, Form, Input, Row } from 'antd'
|
||||
import { forwardRef, useEffect, useImperativeHandle } from 'react'
|
||||
import { SubscribeApprovalInfoType } from '@common/const/approval/type.tsx'
|
||||
import { BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { SubscribeApprovalList } from '@common/const/approval/const'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
type SubscribeApprovalModalProps = {
|
||||
type:'approval'|'view'
|
||||
data?:SubscribeApprovalInfoType
|
||||
inSystem?:boolean
|
||||
serviceId:string
|
||||
teamId:string
|
||||
type: 'approval' | 'view'
|
||||
data?: SubscribeApprovalInfoType
|
||||
inSystem?: boolean
|
||||
serviceId: string
|
||||
teamId: string
|
||||
}
|
||||
|
||||
export type SubscribeApprovalModalHandle = {
|
||||
save:(operate:'pass'|'refuse') =>Promise<boolean|string>
|
||||
save: (operate: 'pass' | 'refuse') => Promise<boolean | string>
|
||||
}
|
||||
|
||||
type FieldType = {
|
||||
reason?:string;
|
||||
opinion?:string;
|
||||
};
|
||||
reason?: string
|
||||
opinion?: string
|
||||
}
|
||||
|
||||
export const SubscribeApprovalModalContent = forwardRef<SubscribeApprovalModalHandle,SubscribeApprovalModalProps>((props, ref) => {
|
||||
export const SubscribeApprovalModalContent = forwardRef<SubscribeApprovalModalHandle, SubscribeApprovalModalProps>(
|
||||
(props, ref) => {
|
||||
const { message } = App.useApp()
|
||||
const {data, type,inSystem=false, teamId, serviceId} = props
|
||||
const [form] = Form.useForm();
|
||||
const {fetchData} = useFetch()
|
||||
const { data, type, inSystem = false, teamId, serviceId } = props
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
|
||||
const save:(operate:'pass'|'refuse')=>Promise<boolean | string> = (operate)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
if(type === 'view'){
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
form.validateFields().then((value)=>{
|
||||
if(operate === 'refuse' && form.getFieldValue('opinion') === ''){
|
||||
form.setFields([{
|
||||
name:'opinion',errors:[$t(FORM_ERROR_TIPS.refuseOpinion)]
|
||||
}])
|
||||
form.scrollToField('opinion')
|
||||
reject($t(RESPONSE_TIPS.refuseOpinion))
|
||||
return
|
||||
const save: (operate: 'pass' | 'refuse') => Promise<boolean | string> = (operate) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (type === 'view') {
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
form
|
||||
.validateFields()
|
||||
.then((value) => {
|
||||
if (operate === 'refuse' && form.getFieldValue('opinion') === '') {
|
||||
form.setFields([
|
||||
{
|
||||
name: 'opinion',
|
||||
errors: [$t(FORM_ERROR_TIPS.refuseOpinion)]
|
||||
}
|
||||
fetchData<BasicResponse<null>>(`${inSystem?'service/':''}approval/subscribe`,{method: 'POST',eoBody:({opinion:value.opinion,operate}), eoParams:(inSystem ? {apply:data!.id, team:teamId} : {id:data!.id,team:teamId})}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
})
|
||||
])
|
||||
form.scrollToField('opinion')
|
||||
reject($t(RESPONSE_TIPS.refuseOpinion))
|
||||
return
|
||||
}
|
||||
fetchData<BasicResponse<null>>(`${inSystem ? 'service/' : ''}approval/subscribe`, {
|
||||
method: 'POST',
|
||||
eoBody: { opinion: value.opinion, operate },
|
||||
eoParams: inSystem ? { apply: data!.id, team: teamId } : { id: data!.id, team: teamId }
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, ()=>({
|
||||
save
|
||||
})
|
||||
)
|
||||
useImperativeHandle(ref, () => ({
|
||||
save
|
||||
}))
|
||||
|
||||
useEffect(()=>{
|
||||
form.setFieldsValue({opinion:'',...data})
|
||||
},[])
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({ opinion: '', ...data })
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="my-btnybase">{
|
||||
SubscribeApprovalList?.map((x)=>(
|
||||
<Row key={x.key} className="leading-[32px] mb-btnbase mx-auto">
|
||||
<Col className="text-left" span={6}>{$t(x.title)}:</Col>
|
||||
<Col >{(data as {[k:string]:unknown})?.[x.key]?.name || (data as {[k:string]:unknown})?.[x.key] || '-'}</Col>
|
||||
</Row>
|
||||
))
|
||||
}
|
||||
<div className="my-btnybase">
|
||||
{SubscribeApprovalList?.map((x) => (
|
||||
<Row key={x.key} className="leading-[32px] mb-btnbase mx-auto">
|
||||
<Col className="text-left" span={6}>
|
||||
{$t(x.title)}:
|
||||
</Col>
|
||||
<Col>
|
||||
{(data as { [k: string]: unknown })?.[x.key]?.name || (data as { [k: string]: unknown })?.[x.key] || '-'}
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
labelAlign='left'
|
||||
layout='vertical'
|
||||
form={form}
|
||||
className="mx-auto "
|
||||
name="subscribeApprovalModalContent"
|
||||
// labelCol={{ span: 6}}
|
||||
// wrapperCol={{ span: 18}}
|
||||
autoComplete="off"
|
||||
disabled={type === 'view'}
|
||||
>
|
||||
|
||||
<Form.Item<FieldType>
|
||||
label={$t("申请原因")}
|
||||
name="reason"
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" disabled={true} placeholder=" " />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType>
|
||||
label={$t("审核意见")}
|
||||
name="opinion"
|
||||
extra={$t(FORM_ERROR_TIPS.refuseOpinion)}
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} onChange={()=>{ form.setFields([
|
||||
{
|
||||
name: 'opinion',
|
||||
errors: [], // 设置为空数组来移除错误信息
|
||||
},
|
||||
])}} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</div>
|
||||
<Form
|
||||
labelAlign="left"
|
||||
layout="vertical"
|
||||
form={form}
|
||||
className="mx-auto "
|
||||
name="subscribeApprovalModalContent"
|
||||
// labelCol={{ span: 6}}
|
||||
// wrapperCol={{ span: 18}}
|
||||
autoComplete="off"
|
||||
disabled={type === 'view'}
|
||||
>
|
||||
<Form.Item<FieldType> label={$t('申请原因')} name="reason">
|
||||
<Input.TextArea className="w-INPUT_NORMAL" disabled={true} placeholder=" " />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType> label={$t('审核意见')} name="opinion" extra={$t(FORM_ERROR_TIPS.refuseOpinion)}>
|
||||
<Input.TextArea
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
onChange={() => {
|
||||
form.setFields([
|
||||
{
|
||||
name: 'opinion',
|
||||
errors: [] // 设置为空数组来移除错误信息
|
||||
}
|
||||
])
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,41 +1,51 @@
|
||||
|
||||
import { Button, Tooltip } from "antd"
|
||||
import { useState, useMemo, useEffect, useCallback } from "react"
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { PERMISSION_DEFINITION } from "@common/const/permissions"
|
||||
import { Icon } from "@iconify/react/dist/iconify.js"
|
||||
import { $t } from "@common/locales"
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { $t } from '@common/locales'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { Button, Tooltip } from 'antd'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
type TableBtnWithPermissionProps = {
|
||||
btnTitle: string
|
||||
access?: keyof typeof PERMISSION_DEFINITION[0],
|
||||
tooltip?: string,
|
||||
disabled?: boolean,
|
||||
navigateTo?: string,
|
||||
access?: keyof (typeof PERMISSION_DEFINITION)[0]
|
||||
tooltip?: string
|
||||
disabled?: boolean
|
||||
navigateTo?: string
|
||||
onClick?: (args?: unknown) => void
|
||||
className?: string
|
||||
btnType: string
|
||||
}
|
||||
|
||||
const TableIconName = {
|
||||
'add': 'ic:baseline-add',
|
||||
'edit': 'ic:baseline-edit',
|
||||
'delete': 'ic:baseline-delete',
|
||||
'remove': 'ic:baseline-minus',
|
||||
'copy': 'ic:baseline-file-copy',
|
||||
'view': 'ic:baseline-remove-red-eye',
|
||||
'publish': 'ic:baseline-publish',
|
||||
'approval': 'ic:baseline-approval',
|
||||
'stop': 'ic:baseline-stop-circle',
|
||||
'online': 'ic:baseline-check-circle',
|
||||
'cancel': 'ic:baseline-cancel-schedule-send',
|
||||
'refresh': 'ic:baseline-refresh',
|
||||
'logs': 'hugeicons:google-doc'
|
||||
add: 'ic:baseline-add',
|
||||
edit: 'ic:baseline-edit',
|
||||
delete: 'ic:baseline-delete',
|
||||
remove: 'ic:baseline-minus',
|
||||
copy: 'ic:baseline-file-copy',
|
||||
view: 'ic:baseline-remove-red-eye',
|
||||
publish: 'ic:baseline-publish',
|
||||
offline: 'ic:baseline-file-download-off',
|
||||
approval: 'ic:baseline-approval',
|
||||
stop: 'ic:baseline-stop-circle',
|
||||
online: 'ic:baseline-check-circle',
|
||||
cancel: 'ic:baseline-cancel-schedule-send',
|
||||
refresh: 'ic:baseline-refresh',
|
||||
logs: 'hugeicons:google-doc',
|
||||
disable: 'ic:baseline-pause-circle',
|
||||
enable: 'ic:baseline-play-circle'
|
||||
}
|
||||
// 表格操作栏按钮,受权限控制
|
||||
const TableBtnWithPermission = ({ btnTitle, access, tooltip, disabled, navigateTo, onClick, className, btnType }: TableBtnWithPermissionProps) => {
|
||||
|
||||
const TableBtnWithPermission = ({
|
||||
btnTitle,
|
||||
access,
|
||||
tooltip,
|
||||
disabled,
|
||||
navigateTo,
|
||||
onClick,
|
||||
className,
|
||||
btnType
|
||||
}: TableBtnWithPermissionProps) => {
|
||||
const [btnAccess, setBtnAccess] = useState<boolean>(false)
|
||||
const [btnStatus, setBtnStatus] = useState<boolean>(false)
|
||||
const [closeToolTip, setCloseToolTip] = useState<boolean>(false)
|
||||
@@ -51,16 +61,18 @@ const TableBtnWithPermission = ({ btnTitle, access, tooltip, disabled, navigateT
|
||||
access ? setBtnAccess(lastAccess) : setBtnAccess(true)
|
||||
}, [access, lastAccess])
|
||||
|
||||
const handleClick = useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
setTimeout(() => {
|
||||
setBtnStatus(false)
|
||||
setCloseToolTip(true)
|
||||
})
|
||||
|
||||
const handleClick = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
setTimeout(() => {
|
||||
setBtnStatus(false)
|
||||
setCloseToolTip(true)
|
||||
})
|
||||
|
||||
navigateTo ? navigate(navigateTo) : onClick?.()
|
||||
}, [navigateTo, navigate, onClick])
|
||||
navigateTo ? navigate(navigateTo) : onClick?.()
|
||||
},
|
||||
[navigateTo, navigate, onClick]
|
||||
)
|
||||
const changeTooltipStatus = (open: boolean) => {
|
||||
setBtnStatus(open)
|
||||
if (closeToolTip) {
|
||||
@@ -68,18 +80,42 @@ const TableBtnWithPermission = ({ btnTitle, access, tooltip, disabled, navigateT
|
||||
setCloseToolTip(false)
|
||||
}
|
||||
}
|
||||
return (<>{
|
||||
!btnAccess || (disabled && tooltip) ?
|
||||
<Tooltip placement="top" title={tooltip ?? $t('暂无(0)权限,请联系管理员分配。', [$t(btnTitle).toLowerCase()])}>
|
||||
<Button type="text" disabled={true} className={`h-[22px] border-none p-0 flex items-center bg-transparent ${className}`} key={btnType} icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />} >{ }</Button>
|
||||
</Tooltip>
|
||||
:
|
||||
<Tooltip placement="top" title={$t(btnTitle)} trigger='hover' open={btnStatus} onOpenChange={changeTooltipStatus}>
|
||||
<Button type="text" disabled={disabled} className={`h-[22px] border-none p-0 flex items-center bg-transparent ${className} `} key={btnType} icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />} onClick={handleClick}>{ }</Button>
|
||||
</Tooltip>
|
||||
|
||||
}</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{!btnAccess || (disabled && tooltip) ? (
|
||||
<Tooltip placement="top" title={tooltip ?? $t('暂无(0)权限,请联系管理员分配。', [$t(btnTitle).toLowerCase()])}>
|
||||
<Button
|
||||
type="text"
|
||||
disabled={true}
|
||||
className={`flex items-center p-0 bg-transparent border-none h-[22px] ${className}`}
|
||||
key={btnType}
|
||||
icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />}
|
||||
>
|
||||
{}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={$t(btnTitle)}
|
||||
trigger="hover"
|
||||
open={btnStatus}
|
||||
onOpenChange={changeTooltipStatus}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
disabled={disabled}
|
||||
className={`flex items-center p-0 bg-transparent border-none h-[22px] ${className}`}
|
||||
key={btnType}
|
||||
icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TableBtnWithPermission
|
||||
export default TableBtnWithPermission
|
||||
|
||||
@@ -1,37 +1,38 @@
|
||||
import { Tag, TagProps } from 'antd'
|
||||
import { useState, useMemo, useEffect } from 'react'
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
|
||||
import { Tag, TagProps } from "antd";
|
||||
import { useState, useMemo, useEffect } from "react";
|
||||
import { PERMISSION_DEFINITION } from "@common/const/permissions";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
|
||||
export interface TagWithPermission extends TagProps{
|
||||
access?:string
|
||||
export interface TagWithPermission extends TagProps {
|
||||
access?: string
|
||||
}
|
||||
export default function TagWithPermission(props:TagWithPermission){
|
||||
const {access,onClose} = props
|
||||
const [editAccess, setEditAccess] = useState<boolean>(access ? false:true)
|
||||
const {accessData,checkPermission,accessInit} = useGlobalContext()
|
||||
const lastAccess = useMemo(()=>{
|
||||
if(!access) return true
|
||||
return checkPermission(access as keyof typeof PERMISSION_DEFINITION[0])
|
||||
},[access, accessData,checkPermission,accessInit])
|
||||
export default function TagWithPermission(props: TagWithPermission) {
|
||||
const { access, onClose } = props
|
||||
const [editAccess, setEditAccess] = useState<boolean>(access ? false : true)
|
||||
const { accessData, checkPermission, accessInit } = useGlobalContext()
|
||||
const lastAccess = useMemo(() => {
|
||||
if (!access) return true
|
||||
return checkPermission(access as keyof (typeof PERMISSION_DEFINITION)[0])
|
||||
}, [access, accessData, checkPermission, accessInit])
|
||||
|
||||
useEffect(()=>{
|
||||
access ? setEditAccess(lastAccess) : setEditAccess(true)
|
||||
},[lastAccess])
|
||||
|
||||
const handleTagClose = (e: React.MouseEvent<HTMLElement>)=>{
|
||||
e.preventDefault();
|
||||
if(!editAccess) return
|
||||
onClose?.(e)
|
||||
}
|
||||
useEffect(() => {
|
||||
access ? setEditAccess(lastAccess) : setEditAccess(true)
|
||||
}, [lastAccess])
|
||||
|
||||
return <Tag
|
||||
closeIcon
|
||||
{...props}
|
||||
className={` rounded-SEARCH_RADIUS h-[32px] text-[14px] leading-[22px] py-[5px] px-btnbase bg-transparent mb-[8px] ${props.className}`}
|
||||
onClose={handleTagClose}>
|
||||
{props.children}
|
||||
</Tag>
|
||||
const handleTagClose = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault()
|
||||
if (!editAccess) return
|
||||
onClose?.(e)
|
||||
}
|
||||
|
||||
}
|
||||
return (
|
||||
<Tag
|
||||
closeIcon
|
||||
{...props}
|
||||
className={` rounded-SEARCH_RADIUS h-[32px] text-[14px] leading-[22px] py-[5px] px-btnbase bg-transparent mb-[8px] ${props.className}`}
|
||||
onClose={handleTagClose}
|
||||
>
|
||||
{props.children}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
const ThemeSwitcher = () => {
|
||||
const [darkMode, setDarkMode] = useState(true);
|
||||
const [darkMode, setDarkMode] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
let isDarkMode = localStorage.getItem('dark-mode');
|
||||
if(isDarkMode !== undefined && isDarkMode !== null){
|
||||
setDarkMode(isDarkMode === 'true')
|
||||
}else{
|
||||
localStorage.setItem('dark-mode', (darkMode).toString());
|
||||
const isDarkMode = localStorage.getItem('dark-mode')
|
||||
if (isDarkMode !== undefined && isDarkMode !== null) {
|
||||
setDarkMode(isDarkMode === 'true')
|
||||
} else {
|
||||
localStorage.setItem('dark-mode', darkMode.toString())
|
||||
}
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
useEffect(()=>{
|
||||
document.documentElement.classList.toggle('dark', darkMode);
|
||||
},[darkMode])
|
||||
useEffect(() => {
|
||||
document.documentElement.classList.toggle('dark', darkMode)
|
||||
}, [darkMode])
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
setDarkMode(!darkMode);
|
||||
localStorage.setItem('dark-mode', (!darkMode).toString());
|
||||
document.documentElement.classList.toggle('dark', !darkMode);
|
||||
};
|
||||
setDarkMode(!darkMode)
|
||||
localStorage.setItem('dark-mode', (!darkMode).toString())
|
||||
document.documentElement.classList.toggle('dark', !darkMode)
|
||||
}
|
||||
|
||||
return (
|
||||
// <button onClick={toggleDarkMode}>
|
||||
// {darkMode ? '切换到白天模式' : '切换到黑夜模式'}
|
||||
// </button>
|
||||
<></>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ThemeSwitcher;
|
||||
export default ThemeSwitcher
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Radio, DatePicker, GetProps, RadioChangeEvent } from 'antd'
|
||||
import dayjs, { Dayjs } from 'dayjs'
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
||||
import '../../index.css'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Radio, DatePicker, GetProps, RadioChangeEvent } from 'antd';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
import "../../index.css"
|
||||
import { $t } from '@common/locales';
|
||||
type RangePickerProps = GetProps<typeof DatePicker.RangePicker>
|
||||
export type RangeValue = [Dayjs | null, Dayjs | null] | null
|
||||
|
||||
type RangePickerProps = GetProps<typeof DatePicker.RangePicker>;
|
||||
export type RangeValue = [Dayjs | null, Dayjs | null] | null;
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
export type TimeRange = {
|
||||
start: number | null
|
||||
end: number | null
|
||||
}
|
||||
|
||||
export type TimeRangeButton = '' | 'hour' | 'day' | 'threeDays' | 'sevenDays';
|
||||
export type TimeRangeButton = '' | 'hour' | 'day' | 'threeDays' | 'sevenDays'
|
||||
|
||||
type TimeRangeSelectorProps = {
|
||||
initialTimeButton?: TimeRangeButton,
|
||||
initialTimeButton?: TimeRangeButton
|
||||
initialDatePickerValue?: RangeValue
|
||||
onTimeRangeChange?: (timeRange: TimeRange) => void
|
||||
hideTitle?: boolean
|
||||
@@ -30,17 +29,27 @@ type TimeRangeSelectorProps = {
|
||||
defaultTimeButton?: TimeRangeButton
|
||||
}
|
||||
const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
|
||||
const { initialTimeButton, initialDatePickerValue, onTimeRangeChange, hideTitle, onTimeButtonChange, labelSize = 'default', bindRef, hideBtns = [], defaultTimeButton = 'hour' } = props
|
||||
const [timeButton, setTimeButton] = useState(initialTimeButton || '');
|
||||
const [datePickerValue, setDatePickerValue] = useState<RangeValue>(initialDatePickerValue || [null, null]);
|
||||
const {
|
||||
initialTimeButton,
|
||||
initialDatePickerValue,
|
||||
onTimeRangeChange,
|
||||
hideTitle,
|
||||
onTimeButtonChange,
|
||||
labelSize = 'default',
|
||||
bindRef,
|
||||
hideBtns = [],
|
||||
defaultTimeButton = 'hour'
|
||||
} = props
|
||||
const [timeButton, setTimeButton] = useState(initialTimeButton || '')
|
||||
const [datePickerValue, setDatePickerValue] = useState<RangeValue>(initialDatePickerValue || [null, null])
|
||||
useEffect(() => {
|
||||
if (bindRef) {
|
||||
bindRef({ reset });
|
||||
bindRef({ reset })
|
||||
}
|
||||
}, [bindRef])
|
||||
// 根据选择的时间范围计算开始和结束时间
|
||||
const calculateTimeRange = (curBtn: TimeRangeButton) => {
|
||||
const currentSecond = Math.floor(Date.now() / 1000); // 当前秒级时间戳
|
||||
const currentSecond = Math.floor(Date.now() / 1000) // 当前秒级时间戳
|
||||
let startMin = currentSecond - 60 * 60
|
||||
switch (curBtn) {
|
||||
case 'hour': {
|
||||
@@ -52,30 +61,26 @@ const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
|
||||
break
|
||||
}
|
||||
case 'threeDays': {
|
||||
startMin =
|
||||
Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) -
|
||||
2 * 24 * 60 * 60
|
||||
startMin = Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) - 2 * 24 * 60 * 60
|
||||
break
|
||||
}
|
||||
case 'sevenDays': {
|
||||
startMin =
|
||||
Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) -
|
||||
6 * 24 * 60 * 60
|
||||
startMin = Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) - 6 * 24 * 60 * 60
|
||||
break
|
||||
}
|
||||
}
|
||||
if (onTimeRangeChange) {
|
||||
onTimeRangeChange({ start: startMin, end: currentSecond });
|
||||
onTimeRangeChange({ start: startMin, end: currentSecond })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 处理单选按钮的变化
|
||||
const handleRadioChange = (e: RadioChangeEvent) => {
|
||||
setTimeButton(e.target.value);
|
||||
setTimeButton(e.target.value)
|
||||
onTimeButtonChange?.(e.target.value)
|
||||
setDatePickerValue(null)
|
||||
calculateTimeRange(e.target.value);
|
||||
};
|
||||
calculateTimeRange(e.target.value)
|
||||
}
|
||||
const reset = () => {
|
||||
setTimeButton(defaultTimeButton)
|
||||
calculateTimeRange(defaultTimeButton)
|
||||
@@ -86,35 +91,43 @@ const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
|
||||
const handleDatePickerChange = (dates: RangeValue) => {
|
||||
setTimeButton(dates ? '' : defaultTimeButton)
|
||||
onTimeButtonChange?.(dates ? '' : defaultTimeButton)
|
||||
setDatePickerValue(dates);
|
||||
setDatePickerValue(dates)
|
||||
if (dates && Array.isArray(dates) && dates.length === 2) {
|
||||
const [startDate, endDate] = dates;
|
||||
const start = startDate!.startOf('day').unix(); // 开始日期的00:00:00
|
||||
const end = endDate!.endOf('day').unix(); // 结束日期的23:59:59
|
||||
const [startDate, endDate] = dates
|
||||
const start = startDate!.startOf('day').unix() // 开始日期的00:00:00
|
||||
const end = endDate!.endOf('day').unix() // 结束日期的23:59:59
|
||||
if (onTimeRangeChange) {
|
||||
onTimeRangeChange({ start, end });
|
||||
onTimeRangeChange({ start, end })
|
||||
}
|
||||
}
|
||||
if (!dates) {
|
||||
calculateTimeRange(defaultTimeButton)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
const disabledDate: RangePickerProps['disabledDate'] = (current) => {
|
||||
// Can not select days before today and today
|
||||
return current && current.valueOf() > dayjs().startOf('day').valueOf();
|
||||
};
|
||||
return current && current.valueOf() > dayjs().startOf('day').valueOf()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-nowrap items-center pt-btnybase mr-btnybase">
|
||||
{!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}:</label>}
|
||||
<Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid">
|
||||
{hideBtns?.length && hideBtns.includes('hour') ? null : <Radio.Button value="hour">{$t('近1小时')}</Radio.Button>}
|
||||
{hideBtns?.length && hideBtns.includes('day') ? null : <Radio.Button value="day">{$t('近24小时')}</Radio.Button>}
|
||||
{hideBtns?.length && hideBtns.includes('threeDays') ? null : <Radio.Button value="threeDays">{$t('近3天')}</Radio.Button>}
|
||||
{hideBtns?.length && hideBtns.includes('sevenDays') ? null : <Radio.Button className="rounded-e-none" value="sevenDays">{$t('近7天')}</Radio.Button>}
|
||||
{hideBtns?.length && hideBtns.includes('hour') ? null : (
|
||||
<Radio.Button value="hour">{$t('近1小时')}</Radio.Button>
|
||||
)}
|
||||
{hideBtns?.length && hideBtns.includes('day') ? null : (
|
||||
<Radio.Button value="day">{$t('近24小时')}</Radio.Button>
|
||||
)}
|
||||
{hideBtns?.length && hideBtns.includes('threeDays') ? null : (
|
||||
<Radio.Button value="threeDays">{$t('近3天')}</Radio.Button>
|
||||
)}
|
||||
{hideBtns?.length && hideBtns.includes('sevenDays') ? null : (
|
||||
<Radio.Button className="rounded-e-none" value="sevenDays">
|
||||
{$t('近7天')}
|
||||
</Radio.Button>
|
||||
)}
|
||||
</Radio.Group>
|
||||
<DatePicker.RangePicker
|
||||
value={datePickerValue}
|
||||
@@ -129,7 +142,7 @@ const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default TimeRangeSelector;
|
||||
export default TimeRangeSelector
|
||||
|
||||
@@ -1,45 +1,94 @@
|
||||
|
||||
import {CheckOutlined, LoadingOutlined, MoreOutlined} from "@ant-design/icons";
|
||||
import {Dropdown, Input, InputRef, MenuProps} from "antd";
|
||||
import { ReactNode, useEffect, useRef, useState} from "react";
|
||||
import { CheckOutlined, LoadingOutlined, MoreOutlined } from '@ant-design/icons'
|
||||
import { Dropdown, Input, InputRef, MenuProps } from 'antd'
|
||||
import { ReactNode, useEffect, useRef, useState } from 'react'
|
||||
|
||||
export type TreeWithMoreProp = {
|
||||
children:ReactNode,
|
||||
dropdownMenu:MenuProps['items']
|
||||
editable?:boolean
|
||||
editingId?:string
|
||||
afterEdit?:(val:string)=>Promise<string|boolean>
|
||||
editKey?:string
|
||||
entity?:{id:string,[k:string]:unknown | string}
|
||||
onBlur?:()=>void
|
||||
stopClick?:boolean
|
||||
children: ReactNode
|
||||
dropdownMenu: MenuProps['items']
|
||||
editable?: boolean
|
||||
editingId?: string
|
||||
afterEdit?: (val: string) => Promise<string | boolean>
|
||||
editKey?: string
|
||||
entity?: { id: string; [k: string]: unknown | string }
|
||||
onBlur?: () => void
|
||||
stopClick?: boolean
|
||||
}
|
||||
|
||||
const TreeWithMore = ({children,dropdownMenu,editable,editingId,entity,editKey='name',afterEdit,onBlur,stopClick=true}:TreeWithMoreProp)=>{
|
||||
const [editValue, setEditValue] = useState<string>(entity?.[editKey] as string)
|
||||
const [submitting, setSubmitting] = useState<boolean>(false)
|
||||
const inputRef = useRef<InputRef>(null)
|
||||
const TreeWithMore = ({
|
||||
children,
|
||||
dropdownMenu,
|
||||
editable,
|
||||
editingId,
|
||||
entity,
|
||||
editKey = 'name',
|
||||
afterEdit,
|
||||
onBlur,
|
||||
stopClick = true
|
||||
}: TreeWithMoreProp) => {
|
||||
const [editValue, setEditValue] = useState<string>(entity?.[editKey] as string)
|
||||
const [submitting, setSubmitting] = useState<boolean>(false)
|
||||
const inputRef = useRef<InputRef>(null)
|
||||
|
||||
const handleSubmit = (val:string)=>{
|
||||
if(submitting) return
|
||||
setSubmitting(true)
|
||||
afterEdit && afterEdit(val).finally(()=>setSubmitting(false))
|
||||
}
|
||||
const handleSubmit = (val: string) => {
|
||||
if (submitting) return
|
||||
setSubmitting(true)
|
||||
afterEdit && afterEdit(val).finally(() => setSubmitting(false))
|
||||
}
|
||||
|
||||
useEffect(()=>{inputRef.current?.focus()},[inputRef])
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus()
|
||||
}, [inputRef])
|
||||
|
||||
return (<>
|
||||
{
|
||||
editable && editingId && entity?.id && editingId === entity.id ? <Input ref={inputRef} value={editValue} onChange={(e)=>{setEditValue(e.target.value)}} onBlur={()=>{onBlur?.()}} onClick={(e)=>stopClick&&e?.stopPropagation()} onPressEnter={()=>{handleSubmit(editValue)}} suffix={submitting ? <LoadingOutlined />:<CheckOutlined onClick={()=>{handleSubmit(editValue)}}/>} />:
|
||||
<Dropdown menu={{items:dropdownMenu}} trigger={['contextMenu']} >
|
||||
<div className='tree-title-hover' >{children}
|
||||
<span onClick={(e)=>{ stopClick && e.stopPropagation();}}>
|
||||
<Dropdown menu={{items:dropdownMenu}} trigger={['click']} >
|
||||
<MoreOutlined className="tree-title-more" onClick={(e)=>{ stopClick && e.stopPropagation(); }} />
|
||||
</Dropdown>
|
||||
</span>
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
{editable && editingId && entity?.id && editingId === entity.id ? (
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={editValue}
|
||||
onChange={(e) => {
|
||||
setEditValue(e.target.value)
|
||||
}}
|
||||
onBlur={() => {
|
||||
onBlur?.()
|
||||
}}
|
||||
onClick={(e) => stopClick && e?.stopPropagation()}
|
||||
onPressEnter={() => {
|
||||
handleSubmit(editValue)
|
||||
}}
|
||||
suffix={
|
||||
submitting ? (
|
||||
<LoadingOutlined />
|
||||
) : (
|
||||
<CheckOutlined
|
||||
onClick={() => {
|
||||
handleSubmit(editValue)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Dropdown menu={{ items: dropdownMenu }} trigger={['contextMenu']}>
|
||||
<div className="tree-title-hover">
|
||||
{children}
|
||||
<span
|
||||
onClick={(e) => {
|
||||
stopClick && e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<Dropdown menu={{ items: dropdownMenu }} trigger={['click']}>
|
||||
<MoreOutlined
|
||||
className="tree-title-more"
|
||||
onClick={(e) => {
|
||||
stopClick && e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
</Dropdown>
|
||||
</span>
|
||||
</div>
|
||||
</Dropdown>
|
||||
}</>)
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default TreeWithMore
|
||||
export default TreeWithMore
|
||||
|
||||
@@ -1,190 +1,190 @@
|
||||
import { $t } from "@common/locales"
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
/* 本组件不在页面渲染,只是为了让i18next-scanner能找到从接口传递的、需要翻译的字段 , 此处的字段除非确认不在页面上渲染了,否则不应删除,容易导致翻译遗漏*/
|
||||
export const TranslateWord = ()=>{
|
||||
return (
|
||||
<>
|
||||
{$t('文件日志')}
|
||||
{$t('HTTP日志')}
|
||||
{$t('Kafka日志')}
|
||||
{$t('NSQ日志')}
|
||||
{$t('Syslog日志')}
|
||||
{$t('未分配')}
|
||||
{$t('超级管理员')}
|
||||
{$t('团队管理员')}
|
||||
{$t('运维管理员')}
|
||||
{$t('普通成员')}
|
||||
{$t('只读成员')}
|
||||
{$t('服务管理员')}
|
||||
{$t('服务开发者')}
|
||||
{$t('消费者开发者')}
|
||||
{$t('消费者管理员')}
|
||||
{$t('驱动名称')}
|
||||
{$t('请求失败数')}
|
||||
{$t('转发失败数')}
|
||||
{$t('作用范围')}
|
||||
{$t('添加条目')}
|
||||
{$t('添加地址')}
|
||||
{$t('文件名称')}
|
||||
{$t('存放目录')}
|
||||
{$t('日志分割周期')}
|
||||
{$t('过期时间')}
|
||||
{$t('单位:天')}
|
||||
{$t('输出格式')}
|
||||
{$t('格式化配置')}
|
||||
{$t('服务器地址')}
|
||||
{$t('Access日志')}
|
||||
{$t('NSQD地址列表')}
|
||||
{$t('鉴权Secret')}
|
||||
{$t('网络协议')}
|
||||
{$t('日志等级')}
|
||||
{$t('单行')}
|
||||
{$t('小时')}
|
||||
{$t('天')}
|
||||
{$t('未发布')}
|
||||
{$t('待发布')}
|
||||
{$t('单位:s,最小值:1')}
|
||||
{$t('上传文件')}
|
||||
{$t('替换文件')}
|
||||
{$t('是否放行')}
|
||||
{$t('监控')}
|
||||
{$t('必填')}
|
||||
{$t('字符非法,仅支持英文')}
|
||||
{$t('上传 OpenAPI 文档 (.json/.yaml)')}
|
||||
{$t('替换 OpenAPI 文档 (.json/.yaml)')}
|
||||
{$t('打开 OpenAPI YAML 编辑器')}
|
||||
{$t('无需审核:允许任何消费者调用该服务')}
|
||||
{$t('人工审核:仅允许通过人工审核的消费者调用该服务')}
|
||||
{$t('永久')}
|
||||
{$t('否')}
|
||||
{$t('是')}
|
||||
{$t('无需审核')}
|
||||
{$t('需要审核')}
|
||||
{$t('创建时间')}
|
||||
{$t('协议')}
|
||||
{$t('方法')}
|
||||
{$t('地址(IP 端口或域名)')}
|
||||
{$t('权重(0-999)')}
|
||||
{$t('带权轮询')}
|
||||
{$t('发布版本')}
|
||||
{$t('发布申请记录')}
|
||||
{$t('创建版本时间')}
|
||||
{$t('版本状态')}
|
||||
{$t('创建人')}
|
||||
{$t('审核时间')}
|
||||
{$t('申请方-消费者')}
|
||||
{$t('审核状态')}
|
||||
{$t('申请人')}
|
||||
{$t('审核人')}
|
||||
{$t('审核时间')}
|
||||
{$t('来源')}
|
||||
{$t('订阅时间')}
|
||||
{$t('请输入')}
|
||||
{$t('请选择')}
|
||||
{$t('创建者')}
|
||||
{$t('服务数量')}
|
||||
{$t('负责人')}
|
||||
{$t('姓名')}
|
||||
{$t('团队角色')}
|
||||
{$t('添加日期')}
|
||||
{$t('请求成功数')}
|
||||
{$t('转发成功数')}
|
||||
{$t('API 名称')}
|
||||
{$t('失败状态码数')}
|
||||
{$t('所属服务')}
|
||||
{$t('平均响应时间(ms)')}
|
||||
{$t('最大响应时间(ms)')}
|
||||
{$t('最小响应时间(ms)')}
|
||||
{$t('平均请求流量(KB)')}
|
||||
{$t('最大请求流量(KB)')}
|
||||
{$t('最小请求流量(KB)')}
|
||||
{$t('所有成员')}
|
||||
{$t('状态')}
|
||||
{$t('角色名称')}
|
||||
{$t('绑定域名')}
|
||||
{$t('过期日期')}
|
||||
{$t('支持字母开头、英文数字中横线下划线组合')}
|
||||
{$t('英文数字下划线任意一种,首字母必须为英文')}
|
||||
{$t('字符非法,仅支持英文')}
|
||||
{$t('无法连接集群,请检查集群地址是否正确或防火墙配置')}
|
||||
{$t('选择拒绝时,审核意见为必填')}
|
||||
{$t('操作成功')}
|
||||
{$t('操作失败')}
|
||||
{$t('正在操作')}
|
||||
{$t('正在加载数据')}
|
||||
{$t('获取数据失败')}
|
||||
{$t('登录成功')}
|
||||
{$t('退出成功,将跳转至登录页')}
|
||||
{$t('未填写审核意见')}
|
||||
{$t('复制成功')}
|
||||
{$t('复制失败,请手动复制')}
|
||||
{$t('服务所属团队')}
|
||||
{$t('在线')}
|
||||
{$t('已拒绝')}
|
||||
{$t('中止')}
|
||||
{$t('发布异常')}
|
||||
{$t('发布中')}
|
||||
{$t('申请方所属团队')}
|
||||
{$t('发布状态')}
|
||||
{$t(' 次')}
|
||||
{$t('每分钟')}
|
||||
{$t('每5分钟')}
|
||||
{$t('每小时')}
|
||||
{$t('每天')}
|
||||
{$t('每周')}
|
||||
{$t('上线结果')}
|
||||
{$t('订阅服务数量')}
|
||||
{$t('鉴权数量')}
|
||||
{$t('列表')}
|
||||
{$t('块')}
|
||||
{$t('HTTP 请求头')}
|
||||
{$t('全等匹配')}
|
||||
{$t('前缀匹配')}
|
||||
{$t('后缀匹配')}
|
||||
{$t('子串匹配')}
|
||||
{$t('非等匹配')}
|
||||
{$t('空值匹配')}
|
||||
{$t('存在匹配')}
|
||||
{$t('不存在匹配')}
|
||||
{$t('区分大小写的正则匹配')}
|
||||
{$t('不区分大小写的正则匹配')}
|
||||
{$t('任意匹配')}
|
||||
{$t('驳回')}
|
||||
{$t('已订阅')}
|
||||
{$t('取消申请')}
|
||||
{$t('透传客户端请求 Host')}
|
||||
{$t('使用上游服务 Host')}
|
||||
{$t('重写 Host')}
|
||||
{$t('动态服务发现')}
|
||||
{$t('地址')}
|
||||
{$t('新增')}
|
||||
{$t('申请方消费者')}
|
||||
{$t('策略名称')}
|
||||
{$t('优先级')}
|
||||
{$t('筛选条件')}
|
||||
{$t('处理数')}
|
||||
{$t('数据格式')}
|
||||
{$t('关键字')}
|
||||
{$t('正则表达式')}
|
||||
{$t('手机号')}
|
||||
{$t('身份证号')}
|
||||
{$t('银行卡号')}
|
||||
{$t('金额')}
|
||||
{$t('日期')}
|
||||
{$t('局部显示')}
|
||||
{$t('局部遮蔽')}
|
||||
{$t('截取')}
|
||||
{$t('替换')}
|
||||
{$t('乱序')}
|
||||
{$t('随机字符串')}
|
||||
{$t('自定义字符串')}
|
||||
{$t('请输入IP地址或CIDR范围,每条以换行分割')}
|
||||
{$t('待更新')}
|
||||
{$t('待删除')}
|
||||
{$t('内容')}
|
||||
{$t('调用地址')}
|
||||
{$t('消费者 IP')}
|
||||
{$t('鉴权名称')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export const TranslateWord = () => {
|
||||
return (
|
||||
<>
|
||||
{$t('文件日志')}
|
||||
{$t('HTTP日志')}
|
||||
{$t('Kafka日志')}
|
||||
{$t('NSQ日志')}
|
||||
{$t('Syslog日志')}
|
||||
{$t('未分配')}
|
||||
{$t('超级管理员')}
|
||||
{$t('团队管理员')}
|
||||
{$t('运维管理员')}
|
||||
{$t('普通成员')}
|
||||
{$t('只读成员')}
|
||||
{$t('服务管理员')}
|
||||
{$t('服务开发者')}
|
||||
{$t('消费者开发者')}
|
||||
{$t('消费者管理员')}
|
||||
{$t('驱动名称')}
|
||||
{$t('请求失败数')}
|
||||
{$t('转发失败数')}
|
||||
{$t('作用范围')}
|
||||
{$t('添加条目')}
|
||||
{$t('添加地址')}
|
||||
{$t('文件名称')}
|
||||
{$t('存放目录')}
|
||||
{$t('日志分割周期')}
|
||||
{$t('过期时间')}
|
||||
{$t('单位:天')}
|
||||
{$t('输出格式')}
|
||||
{$t('格式化配置')}
|
||||
{$t('服务器地址')}
|
||||
{$t('Access日志')}
|
||||
{$t('NSQD地址列表')}
|
||||
{$t('鉴权Secret')}
|
||||
{$t('网络协议')}
|
||||
{$t('日志等级')}
|
||||
{$t('单行')}
|
||||
{$t('小时')}
|
||||
{$t('天')}
|
||||
{$t('未发布')}
|
||||
{$t('待发布')}
|
||||
{$t('单位:s,最小值:1')}
|
||||
{$t('上传文件')}
|
||||
{$t('替换文件')}
|
||||
{$t('是否放行')}
|
||||
{$t('监控')}
|
||||
{$t('必填')}
|
||||
{$t('字符非法,仅支持英文')}
|
||||
{$t('上传 OpenAPI 文档 (.json/.yaml)')}
|
||||
{$t('替换 OpenAPI 文档 (.json/.yaml)')}
|
||||
{$t('打开 OpenAPI YAML 编辑器')}
|
||||
{$t('无需审核:允许任何消费者调用该服务')}
|
||||
{$t('人工审核:仅允许通过人工审核的消费者调用该服务')}
|
||||
{$t('永久')}
|
||||
{$t('否')}
|
||||
{$t('是')}
|
||||
{$t('无需审核')}
|
||||
{$t('需要审核')}
|
||||
{$t('创建时间')}
|
||||
{$t('协议')}
|
||||
{$t('方法')}
|
||||
{$t('地址(IP 端口或域名)')}
|
||||
{$t('权重(0-999)')}
|
||||
{$t('带权轮询')}
|
||||
{$t('发布版本')}
|
||||
{$t('发布申请记录')}
|
||||
{$t('创建版本时间')}
|
||||
{$t('版本状态')}
|
||||
{$t('创建人')}
|
||||
{$t('审核时间')}
|
||||
{$t('申请方-消费者')}
|
||||
{$t('审核状态')}
|
||||
{$t('申请人')}
|
||||
{$t('审核人')}
|
||||
{$t('审核时间')}
|
||||
{$t('来源')}
|
||||
{$t('订阅时间')}
|
||||
{$t('请输入')}
|
||||
{$t('请选择')}
|
||||
{$t('创建者')}
|
||||
{$t('服务数量')}
|
||||
{$t('负责人')}
|
||||
{$t('姓名')}
|
||||
{$t('团队角色')}
|
||||
{$t('添加日期')}
|
||||
{$t('请求成功数')}
|
||||
{$t('转发成功数')}
|
||||
{$t('API 名称')}
|
||||
{$t('失败状态码数')}
|
||||
{$t('所属服务')}
|
||||
{$t('平均响应时间(ms)')}
|
||||
{$t('最大响应时间(ms)')}
|
||||
{$t('最小响应时间(ms)')}
|
||||
{$t('平均请求流量(KB)')}
|
||||
{$t('最大请求流量(KB)')}
|
||||
{$t('最小请求流量(KB)')}
|
||||
{$t('所有成员')}
|
||||
{$t('状态')}
|
||||
{$t('角色名称')}
|
||||
{$t('绑定域名')}
|
||||
{$t('过期日期')}
|
||||
{$t('支持字母开头、英文数字中横线下划线组合')}
|
||||
{$t('英文数字下划线任意一种,首字母必须为英文')}
|
||||
{$t('字符非法,仅支持英文')}
|
||||
{$t('无法连接集群,请检查集群地址是否正确或防火墙配置')}
|
||||
{$t('选择拒绝时,审核意见为必填')}
|
||||
{$t('操作成功')}
|
||||
{$t('操作失败')}
|
||||
{$t('正在操作')}
|
||||
{$t('正在加载数据')}
|
||||
{$t('获取数据失败')}
|
||||
{$t('登录成功')}
|
||||
{$t('退出成功,将跳转至登录页')}
|
||||
{$t('未填写审核意见')}
|
||||
{$t('复制成功')}
|
||||
{$t('复制失败,请手动复制')}
|
||||
{$t('服务所属团队')}
|
||||
{$t('在线')}
|
||||
{$t('已拒绝')}
|
||||
{$t('中止')}
|
||||
{$t('发布异常')}
|
||||
{$t('发布中')}
|
||||
{$t('申请方所属团队')}
|
||||
{$t('发布状态')}
|
||||
{$t(' 次')}
|
||||
{$t('每分钟')}
|
||||
{$t('每5分钟')}
|
||||
{$t('每小时')}
|
||||
{$t('每天')}
|
||||
{$t('每周')}
|
||||
{$t('上线结果')}
|
||||
{$t('订阅服务数量')}
|
||||
{$t('鉴权数量')}
|
||||
{$t('列表')}
|
||||
{$t('块')}
|
||||
{$t('HTTP 请求头')}
|
||||
{$t('全等匹配')}
|
||||
{$t('前缀匹配')}
|
||||
{$t('后缀匹配')}
|
||||
{$t('子串匹配')}
|
||||
{$t('非等匹配')}
|
||||
{$t('空值匹配')}
|
||||
{$t('存在匹配')}
|
||||
{$t('不存在匹配')}
|
||||
{$t('区分大小写的正则匹配')}
|
||||
{$t('不区分大小写的正则匹配')}
|
||||
{$t('任意匹配')}
|
||||
{$t('驳回')}
|
||||
{$t('已订阅')}
|
||||
{$t('取消申请')}
|
||||
{$t('透传客户端请求 Host')}
|
||||
{$t('使用上游服务 Host')}
|
||||
{$t('重写 Host')}
|
||||
{$t('动态服务发现')}
|
||||
{$t('地址')}
|
||||
{$t('新增')}
|
||||
{$t('申请方消费者')}
|
||||
{$t('策略名称')}
|
||||
{$t('优先级')}
|
||||
{$t('筛选条件')}
|
||||
{$t('处理数')}
|
||||
{$t('数据格式')}
|
||||
{$t('关键字')}
|
||||
{$t('正则表达式')}
|
||||
{$t('手机号')}
|
||||
{$t('身份证号')}
|
||||
{$t('银行卡号')}
|
||||
{$t('金额')}
|
||||
{$t('日期')}
|
||||
{$t('局部显示')}
|
||||
{$t('局部遮蔽')}
|
||||
{$t('截取')}
|
||||
{$t('替换')}
|
||||
{$t('乱序')}
|
||||
{$t('随机字符串')}
|
||||
{$t('自定义字符串')}
|
||||
{$t('请输入IP地址或CIDR范围,每条以换行分割')}
|
||||
{$t('待更新')}
|
||||
{$t('待删除')}
|
||||
{$t('内容')}
|
||||
{$t('调用地址')}
|
||||
{$t('消费者 IP')}
|
||||
{$t('鉴权名称')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,49 +1,47 @@
|
||||
|
||||
import { Button, Tooltip, Upload } from "antd";
|
||||
import { ReactElement, cloneElement, useEffect, useMemo, useState } from "react";
|
||||
import { useGlobalContext } from "../../contexts/GlobalStateContext";
|
||||
import { PERMISSION_DEFINITION } from "@common/const/permissions";
|
||||
import { $t } from "@common/locales";
|
||||
import { last } from "lodash-es";
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions'
|
||||
import { $t } from '@common/locales'
|
||||
import { Button, Tooltip, Upload } from 'antd'
|
||||
import { ReactElement, cloneElement, useEffect, useMemo, useState } from 'react'
|
||||
import { useGlobalContext } from '../../contexts/GlobalStateContext'
|
||||
|
||||
type WithPermissionProps = {
|
||||
access?:string | string[]
|
||||
tooltip?:string
|
||||
children:ReactElement
|
||||
disabled?:boolean
|
||||
showDisabled?:boolean
|
||||
access?: string | string[]
|
||||
tooltip?: string
|
||||
children: ReactElement
|
||||
disabled?: boolean
|
||||
showDisabled?: boolean
|
||||
}
|
||||
// 权限控制的高阶组件
|
||||
const WithPermission = ({access, tooltip, children,disabled, showDisabled = true}:WithPermissionProps) => {
|
||||
|
||||
const [editAccess, setEditAccess] = useState<boolean>(access ? false:true)
|
||||
const {accessData,checkPermission,accessInit} = useGlobalContext()
|
||||
const WithPermission = ({ access, tooltip, children, disabled, showDisabled = true }: WithPermissionProps) => {
|
||||
const [editAccess, setEditAccess] = useState<boolean>(access ? false : true)
|
||||
const { accessData, checkPermission, accessInit } = useGlobalContext()
|
||||
|
||||
const lastAccess = useMemo(()=>{
|
||||
if(!access) return true
|
||||
return checkPermission(access as keyof typeof PERMISSION_DEFINITION[0])
|
||||
},[access, accessData,checkPermission,accessInit])
|
||||
const lastAccess = useMemo(() => {
|
||||
if (!access) return true
|
||||
return checkPermission(access as keyof (typeof PERMISSION_DEFINITION)[0])
|
||||
}, [access, accessData, checkPermission, accessInit])
|
||||
|
||||
useEffect(()=>{
|
||||
// 先判断权限,无论权限是否为true,如果disabled为true时则必须为ture
|
||||
access && setEditAccess(lastAccess)
|
||||
},[lastAccess,disabled])
|
||||
useEffect(() => {
|
||||
// 先判断权限,无论权限是否为true,如果disabled为true时则必须为ture
|
||||
access && setEditAccess(lastAccess)
|
||||
}, [lastAccess, disabled])
|
||||
|
||||
return (
|
||||
<>
|
||||
{}
|
||||
{editAccess && !disabled && cloneElement(children)}
|
||||
{editAccess && disabled && <Tooltip title={tooltip}>{cloneElement(children, { disabled: true })}</Tooltip>}
|
||||
{!editAccess && children?.type !== Button && children?.type !== Upload && showDisabled && (
|
||||
<Tooltip title={tooltip ?? $t('暂无操作权限,请联系管理员分配。')}>
|
||||
{cloneElement(children, {
|
||||
disabled: true,
|
||||
onClick: (e) => e.preventDefault(),
|
||||
okButtonProps: { disabled: true }
|
||||
})}
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>{
|
||||
}
|
||||
{editAccess && !disabled && cloneElement(children)}
|
||||
{editAccess && disabled && <Tooltip title={tooltip}>
|
||||
{ cloneElement(children, {disabled:true})}
|
||||
</Tooltip>}
|
||||
{!editAccess && (children?.type !== Button && children?.type !== Upload && showDisabled) && <Tooltip title={tooltip ?? $t("暂无操作权限,请联系管理员分配。")}>
|
||||
{ cloneElement(children, {disabled:true, onClick:(e)=>e.preventDefault(),okButtonProps:{disabled:true}})}
|
||||
</Tooltip>}
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default WithPermission
|
||||
export default WithPermission
|
||||
|
||||
@@ -1,79 +1,85 @@
|
||||
import { set } from 'lodash-es';
|
||||
import { ExoticComponent, JSXElementConstructor, ReactElement, useEffect, useState } from 'react';
|
||||
import { useBlocker, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { JSX } from 'react/jsx-runtime';
|
||||
import { ExoticComponent, JSXElementConstructor, useEffect, useState } from 'react'
|
||||
import { useBlocker, useLocation } from 'react-router-dom'
|
||||
import { JSX } from 'react/jsx-runtime'
|
||||
|
||||
const withRouteGuard = (WrappedComponent: ExoticComponent<any> | JSXElementConstructor<any>, {
|
||||
canActivate,
|
||||
canLoad ,
|
||||
canDeactivate,
|
||||
deactivated,
|
||||
pathPrefix
|
||||
}: { pathPrefix?:string, canActivate?: () => Promise<boolean>; canLoad?: () => Promise<boolean>; canDeactivate?: () => Promise<boolean>; deactivated?: () => Promise<void>; } = {}) => {
|
||||
const withRouteGuard = (
|
||||
WrappedComponent: ExoticComponent<any> | JSXElementConstructor<any>,
|
||||
{
|
||||
canActivate,
|
||||
canLoad,
|
||||
canDeactivate,
|
||||
deactivated,
|
||||
pathPrefix
|
||||
}: {
|
||||
pathPrefix?: string
|
||||
canActivate?: () => Promise<boolean>
|
||||
canLoad?: () => Promise<boolean>
|
||||
canDeactivate?: () => Promise<boolean>
|
||||
deactivated?: () => Promise<void>
|
||||
} = {}
|
||||
) => {
|
||||
return function RouteGuard(props: JSX.IntrinsicAttributes) {
|
||||
const [isActivated, setIsActivated] = useState<boolean>(false);
|
||||
const location = useLocation();
|
||||
const [isActivated, setIsActivated] = useState<boolean>(false)
|
||||
const location = useLocation()
|
||||
// check canActivate
|
||||
const startLifecycle = async ()=>{
|
||||
if(canActivate){
|
||||
const activateRes = await canActivate();
|
||||
setIsActivated(activateRes);
|
||||
}else{
|
||||
setIsActivated(true);
|
||||
const startLifecycle = async () => {
|
||||
if (canActivate) {
|
||||
const activateRes = await canActivate()
|
||||
setIsActivated(activateRes)
|
||||
} else {
|
||||
setIsActivated(true)
|
||||
}
|
||||
}
|
||||
|
||||
// check canDeactivate
|
||||
const handleBeforeUnload = async (event: { preventDefault: () => void; returnValue: string }) => {
|
||||
const deactivateRes = canDeactivate ? await canDeactivate() : true
|
||||
if (!deactivateRes) {
|
||||
event.preventDefault()
|
||||
event.returnValue = ''
|
||||
}
|
||||
}
|
||||
|
||||
// check canDeactivate
|
||||
const handleBeforeUnload =async (event: { preventDefault: () => void; returnValue: string; }) => {
|
||||
const deactivateRes = canDeactivate? await canDeactivate():true;
|
||||
if (!deactivateRes) {
|
||||
event.preventDefault();
|
||||
event.returnValue = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 激活组件时的检查
|
||||
useEffect(() => {
|
||||
startLifecycle();
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
startLifecycle()
|
||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
deactivated?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
deactivated?.()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const blocker = useBlocker((tx) => {
|
||||
const currentPath = location.pathname;
|
||||
const targetPath = tx.nextLocation.pathname;
|
||||
const currentPath = location.pathname
|
||||
const targetPath = tx.nextLocation.pathname
|
||||
|
||||
if (pathPrefix && currentPath.startsWith(pathPrefix) && !targetPath.startsWith(pathPrefix) && canDeactivate) {
|
||||
if (pathPrefix && currentPath.startsWith(pathPrefix) && !targetPath.startsWith(pathPrefix) && canDeactivate) {
|
||||
canDeactivate().then((res) => {
|
||||
if(res){
|
||||
return false;
|
||||
}else{
|
||||
return true;
|
||||
if (res) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
const checkCanLoad = async()=>{
|
||||
const loadRes = await canLoad!();
|
||||
!loadRes && setIsActivated(false);
|
||||
const checkCanLoad = async () => {
|
||||
const loadRes = await canLoad!()
|
||||
!loadRes && setIsActivated(false)
|
||||
}
|
||||
useEffect(() => {
|
||||
if (isActivated && canLoad) {
|
||||
checkCanLoad()
|
||||
}
|
||||
}, [isActivated]);
|
||||
}, [isActivated])
|
||||
|
||||
return isActivated ? <WrappedComponent {...props}/> : null;
|
||||
};
|
||||
return isActivated ? <WrappedComponent {...props} /> : null
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouteGuard;
|
||||
export default withRouteGuard
|
||||
|
||||
+124
-131
@@ -1,150 +1,143 @@
|
||||
|
||||
import {forwardRef, useImperativeHandle, useState} from 'react'
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react'
|
||||
|
||||
import { Input } from 'antd'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
export const ArrayItemBlankComponent = forwardRef(
|
||||
(props: { [k: string]: any }, ref) => {
|
||||
const { onChange, value, dataFormat } = props
|
||||
export const ArrayItemBlankComponent = forwardRef((props: { [k: string]: any }, ref) => {
|
||||
const { onChange, value, dataFormat } = props
|
||||
|
||||
const getDefaultListItem = () => {
|
||||
const defaultData: { [k: string]: unknown } = {}
|
||||
const getDefaultListItem = () => {
|
||||
const defaultData: { [k: string]: unknown } = {}
|
||||
|
||||
for (const data of dataFormat) {
|
||||
defaultData[data.key] = ''
|
||||
}
|
||||
|
||||
return [defaultData]
|
||||
for (const data of dataFormat) {
|
||||
defaultData[data.key] = ''
|
||||
}
|
||||
|
||||
const [resList, setResList] = useState(
|
||||
value && Object.keys(value).length > 0
|
||||
? [
|
||||
...value
|
||||
?.filter((v: string) => {
|
||||
return v
|
||||
})
|
||||
?.map((v: string) => {
|
||||
const vTmp = v
|
||||
const newValue: { [k: string]: unknown } = {}
|
||||
for (let index = 0; index < dataFormat.length; index++) {
|
||||
if (dataFormat[index]?.hideName) {
|
||||
newValue[dataFormat[index].key] = vTmp.split(' ')[index]
|
||||
} else {
|
||||
const vTmp2: string | string[] | undefined =
|
||||
vTmp.indexOf(' ') === -1
|
||||
? vTmp
|
||||
: vTmp.split(' ')[index]
|
||||
return [defaultData]
|
||||
}
|
||||
|
||||
const [resList, setResList] = useState(
|
||||
value && Object.keys(value).length > 0
|
||||
? [
|
||||
...value
|
||||
?.filter((v: string) => {
|
||||
return v
|
||||
})
|
||||
?.map((v: string) => {
|
||||
const vTmp = v
|
||||
const newValue: { [k: string]: unknown } = {}
|
||||
for (let index = 0; index < dataFormat.length; index++) {
|
||||
if (dataFormat[index]?.hideName) {
|
||||
newValue[dataFormat[index].key] = vTmp.split(' ')[index]
|
||||
} else {
|
||||
const vTmp2: string | string[] | undefined =
|
||||
vTmp.indexOf(' ') === -1
|
||||
? vTmp
|
||||
: vTmp.split(' ')[index]
|
||||
? vTmp.split(' ')[index].indexOf('=') === -1
|
||||
? ''
|
||||
: vTmp.split(' ')[index].split('=')
|
||||
: ''
|
||||
|
||||
if (vTmp2 && vTmp2 instanceof Array && vTmp2.length > 0) {
|
||||
vTmp2.shift()
|
||||
}
|
||||
newValue[dataFormat[index].key] =
|
||||
vTmp2 instanceof Array ? vTmp2?.join('=') : vTmp2
|
||||
if (vTmp2 && vTmp2 instanceof Array && vTmp2.length > 0) {
|
||||
vTmp2.shift()
|
||||
}
|
||||
newValue[dataFormat[index].key] = vTmp2 instanceof Array ? vTmp2?.join('=') : vTmp2
|
||||
}
|
||||
return newValue
|
||||
}),
|
||||
...getDefaultListItem()
|
||||
]
|
||||
: [...getDefaultListItem()]
|
||||
)
|
||||
}
|
||||
return newValue
|
||||
}),
|
||||
...getDefaultListItem()
|
||||
]
|
||||
: [...getDefaultListItem()]
|
||||
)
|
||||
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
|
||||
const emitNewArr = () => {
|
||||
const newArr: Array<string> = []
|
||||
for (const r of resList) {
|
||||
if (r[dataFormat[0].key]) {
|
||||
newArr.push(
|
||||
dataFormat?.map((format: { key: string; hideName: boolean }) => {
|
||||
return format?.hideName
|
||||
? r[format.key]
|
||||
: `${format.key}=${r[format.key]}`
|
||||
})
|
||||
.join(' ')
|
||||
)
|
||||
}
|
||||
}
|
||||
onChange(newArr)
|
||||
}
|
||||
|
||||
const changeInputValue = (
|
||||
newValue: string,
|
||||
index: number,
|
||||
keyName: string,
|
||||
dataFormat: unknown
|
||||
) => {
|
||||
const newArr = [...resList]
|
||||
newArr[index][keyName] = newValue
|
||||
newArr[index].status =
|
||||
(dataFormat.required && !newValue) ||
|
||||
(dataFormat.pattern && !dataFormat.pattern.test(newValue))
|
||||
? 'error'
|
||||
: ''
|
||||
setResList(newArr)
|
||||
emitNewArr()
|
||||
if (index === resList.length - 1) {
|
||||
setResList([...newArr, ...getDefaultListItem()])
|
||||
const emitNewArr = () => {
|
||||
const newArr: Array<string> = []
|
||||
for (const r of resList) {
|
||||
if (r[dataFormat[0].key]) {
|
||||
newArr.push(
|
||||
dataFormat
|
||||
?.map((format: { key: string; hideName: boolean }) => {
|
||||
return format?.hideName ? r[format.key] : `${format.key}=${r[format.key]}`
|
||||
})
|
||||
.join(' ')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const addLine = (index: number) => {
|
||||
resList.splice(index + 1, 0, ...getDefaultListItem())
|
||||
const newKvList = [...resList]
|
||||
setResList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
const removeLine = (index: number) => {
|
||||
resList.splice(index, 1)
|
||||
const newKvList = [...resList]
|
||||
setResList([...newKvList])
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{resList?.map((n: unknown, index: unknown) => {
|
||||
return (
|
||||
<div
|
||||
key={n + index}
|
||||
className="flex"
|
||||
style={{ marginTop: index === 0 ? '0px' : '16px' }}
|
||||
>
|
||||
{dataFormat?.map((data: unknown, index2: unknown) => {
|
||||
return (
|
||||
<Input
|
||||
key={data.key + index2}
|
||||
className="mr-[8px]"
|
||||
style={{ width: data.width }}
|
||||
value={n[data.key]}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, data.key, data)
|
||||
}}
|
||||
placeholder={data.placeholder || `请输入${data.key}`}
|
||||
status={n.status}
|
||||
type={data.type || 'text'}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
{index !== resList.length - 1 && (
|
||||
<div style={{ display: 'inline-block' }}>
|
||||
{n[dataFormat[0].key] && (
|
||||
<Icon icon="ic:baseline-add" onClick={() => addLine(index as unknown as number)} width="14" height="14"/>
|
||||
)}
|
||||
<Icon icon="ic:baseline-minus" onClick={() => removeLine(index as unknown as number)} width="14" height="14"/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
onChange(newArr)
|
||||
}
|
||||
)
|
||||
|
||||
const changeInputValue = (newValue: string, index: number, keyName: string, dataFormat: unknown) => {
|
||||
const newArr = [...resList]
|
||||
newArr[index][keyName] = newValue
|
||||
newArr[index].status =
|
||||
(dataFormat.required && !newValue) || (dataFormat.pattern && !dataFormat.pattern.test(newValue)) ? 'error' : ''
|
||||
setResList(newArr)
|
||||
emitNewArr()
|
||||
if (index === resList.length - 1) {
|
||||
setResList([...newArr, ...getDefaultListItem()])
|
||||
}
|
||||
}
|
||||
|
||||
const addLine = (index: number) => {
|
||||
resList.splice(index + 1, 0, ...getDefaultListItem())
|
||||
const newKvList = [...resList]
|
||||
setResList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
const removeLine = (index: number) => {
|
||||
resList.splice(index, 1)
|
||||
const newKvList = [...resList]
|
||||
setResList([...newKvList])
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{resList?.map((n: unknown, index: unknown) => {
|
||||
return (
|
||||
<div key={n + index} className="flex" style={{ marginTop: index === 0 ? '0px' : '16px' }}>
|
||||
{dataFormat?.map((data: unknown, index2: unknown) => {
|
||||
return (
|
||||
<Input
|
||||
key={data.key + index2}
|
||||
className="mr-[8px]"
|
||||
style={{ width: data.width }}
|
||||
value={n[data.key]}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, data.key, data)
|
||||
}}
|
||||
placeholder={data.placeholder || `请输入${data.key}`}
|
||||
status={n.status}
|
||||
type={data.type || 'text'}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
{index !== resList.length - 1 && (
|
||||
<div style={{ display: 'inline-block' }}>
|
||||
{n[dataFormat[0].key] && (
|
||||
<Icon
|
||||
icon="ic:baseline-add"
|
||||
onClick={() => addLine(index as unknown as number)}
|
||||
width="14"
|
||||
height="14"
|
||||
/>
|
||||
)}
|
||||
<Icon
|
||||
icon="ic:baseline-minus"
|
||||
onClick={() => removeLine(index as unknown as number)}
|
||||
width="14"
|
||||
height="14"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
+30
-42
@@ -1,47 +1,35 @@
|
||||
|
||||
import {forwardRef, useImperativeHandle, useState} from 'react'
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react'
|
||||
import { Codebox } from '@common/components/postcat/api/Codebox'
|
||||
|
||||
export const CustomCodeboxComponent = forwardRef(
|
||||
(props: { [k: string]: unknown }, ref) => {
|
||||
const {
|
||||
mode = 'yaml',
|
||||
theme = 'xcode',
|
||||
fontSize,
|
||||
height,
|
||||
width = '100%',
|
||||
onChange,
|
||||
value
|
||||
} = props
|
||||
const [code, setCode] = useState(
|
||||
mode === 'json' ? JSON.stringify(value) : value
|
||||
)
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
const handleChange = (value: string) => {
|
||||
setCode(value)
|
||||
let res = value
|
||||
if (mode === 'json') {
|
||||
try {
|
||||
res = JSON.parse(value)
|
||||
} catch {
|
||||
console.warn(' 输入的json语句格式有误')
|
||||
}
|
||||
export const CustomCodeboxComponent = forwardRef((props: { [k: string]: unknown }, ref) => {
|
||||
const { mode = 'yaml', theme = 'xcode', fontSize, height, width = '100%', onChange, value } = props
|
||||
const [code, setCode] = useState(mode === 'json' ? JSON.stringify(value) : value)
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
const handleChange = (value: string) => {
|
||||
setCode(value)
|
||||
let res = value
|
||||
if (mode === 'json') {
|
||||
try {
|
||||
res = JSON.parse(value)
|
||||
} catch {
|
||||
console.warn(' 输入的json语句格式有误')
|
||||
}
|
||||
onChange(res)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className=" mt-[4px] border-[1px] border-solid border-BORDER">
|
||||
<Codebox
|
||||
value={code}
|
||||
language={mode}
|
||||
enableToolbar={false}
|
||||
theme={theme}
|
||||
fontSize={fontSize}
|
||||
height={height ?? 500}
|
||||
width={width}
|
||||
onChange={handleChange} />
|
||||
</div>
|
||||
)
|
||||
onChange(res)
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<div className=" mt-[4px] border-[1px] border-solid border-BORDER">
|
||||
<Codebox
|
||||
value={code}
|
||||
language={mode}
|
||||
enableToolbar={false}
|
||||
theme={theme}
|
||||
fontSize={fontSize}
|
||||
height={height ?? 500}
|
||||
width={width}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
+48
-53
@@ -1,5 +1,4 @@
|
||||
|
||||
import {forwardRef,useImperativeHandle} from 'react'
|
||||
import { forwardRef, useImperativeHandle } from 'react'
|
||||
import { createSchemaField } from '@formily/react'
|
||||
import {
|
||||
FormItem,
|
||||
@@ -82,57 +81,53 @@ const SchemaField = createSchemaField({
|
||||
}
|
||||
})
|
||||
|
||||
export const CustomDialogComponent = forwardRef(
|
||||
(props: { [k: string]: unknown }, ref) => {
|
||||
const { onChange, title, value, render } = props
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
let editPage: boolean = false
|
||||
try {
|
||||
editPage = Object.keys(JSON.parse(JSON.stringify(value))).length > 0
|
||||
} catch {}
|
||||
export const CustomDialogComponent = forwardRef((props: { [k: string]: unknown }, ref) => {
|
||||
const { onChange, title, value, render } = props
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
let editPage: boolean = false
|
||||
try {
|
||||
editPage = Object.keys(JSON.parse(JSON.stringify(value))).length > 0
|
||||
} catch {}
|
||||
|
||||
return (
|
||||
<FormDialog.Portal>
|
||||
<span
|
||||
className="ant-formily-array-base-config"
|
||||
onClick={() => {
|
||||
const dialog = FormDialog(
|
||||
editPage ? $t('编辑(0)',[title||'']) : $t('添加(0)',[title||'']),
|
||||
() => {
|
||||
return (
|
||||
<FormLayout
|
||||
// labelCol={6}
|
||||
layout={'vertical'}
|
||||
scrollToFirstError
|
||||
name="CustomDialogComponent"
|
||||
// wrapperCol={10}
|
||||
form={value}>
|
||||
<SchemaField schema={JSON.parse(render)} />
|
||||
</FormLayout>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<FormDialog.Portal>
|
||||
<span
|
||||
className="ant-formily-array-base-config"
|
||||
onClick={() => {
|
||||
const dialog = FormDialog(editPage ? $t('编辑(0)', [title || '']) : $t('添加(0)', [title || '']), () => {
|
||||
return (
|
||||
<FormLayout
|
||||
// labelCol={6}
|
||||
layout={'vertical'}
|
||||
scrollToFirstError
|
||||
name="CustomDialogComponent"
|
||||
// wrapperCol={10}
|
||||
form={value}
|
||||
>
|
||||
<SchemaField schema={JSON.parse(render)} />
|
||||
</FormLayout>
|
||||
)
|
||||
dialog
|
||||
.forOpen((payload, next) => {
|
||||
next({
|
||||
initialValues: value
|
||||
})
|
||||
})
|
||||
dialog
|
||||
.forOpen((payload, next) => {
|
||||
next({
|
||||
initialValues: value
|
||||
})
|
||||
.forConfirm((payload, next) => {
|
||||
next(payload)
|
||||
})
|
||||
.forCancel((payload, next) => {
|
||||
next(payload)
|
||||
})
|
||||
.open()
|
||||
.then(onChange)
|
||||
}}
|
||||
>
|
||||
<svg style={{ width: '16px', height: '16px' }}>
|
||||
<use href="#tool"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</FormDialog.Portal>
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
.forConfirm((payload, next) => {
|
||||
next(payload)
|
||||
})
|
||||
.forCancel((payload, next) => {
|
||||
next(payload)
|
||||
})
|
||||
.open()
|
||||
.then(onChange)
|
||||
}}
|
||||
>
|
||||
<svg style={{ width: '16px', height: '16px' }}>
|
||||
<use href="#tool"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</FormDialog.Portal>
|
||||
)
|
||||
})
|
||||
|
||||
+90
-95
@@ -1,104 +1,99 @@
|
||||
import {forwardRef, useImperativeHandle, useState} from 'react'
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react'
|
||||
|
||||
import { Input } from '@formily/antd-v5'
|
||||
import { $t } from '@common/locales'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
export const SimpleMapComponent = forwardRef(
|
||||
(props: { [k: string]: unknown }, ref) => {
|
||||
const {
|
||||
onChange,
|
||||
value,
|
||||
placeholderKey = $t('请输入Key'),
|
||||
placeholderValue = $t('请输入Value')
|
||||
} = props
|
||||
export const SimpleMapComponent = forwardRef((props: { [k: string]: unknown }, ref) => {
|
||||
const { onChange, value, placeholderKey = $t('请输入Key'), placeholderValue = $t('请输入Value') } = props
|
||||
|
||||
const [kvList, setKvList] = useState(
|
||||
value && Object.keys(value).length > 0
|
||||
? [
|
||||
...Object.keys(value)?.map((k: string) => {
|
||||
return { key: k, value: value[k] }
|
||||
}),
|
||||
{ key: '', value: '' }
|
||||
]
|
||||
: [{ key: '', value: '' }]
|
||||
)
|
||||
const [kvList, setKvList] = useState(
|
||||
value && Object.keys(value).length > 0
|
||||
? [
|
||||
...Object.keys(value)?.map((k: string) => {
|
||||
return { key: k, value: value[k] }
|
||||
}),
|
||||
{ key: '', value: '' }
|
||||
]
|
||||
: [{ key: '', value: '' }]
|
||||
)
|
||||
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
|
||||
const emitNewArr = () => {
|
||||
const res: { [k: string]: unknown } = {}
|
||||
for (const kv of kvList) {
|
||||
res[kv.key] = kv.value
|
||||
}
|
||||
onChange(res)
|
||||
const emitNewArr = () => {
|
||||
const res: { [k: string]: unknown } = {}
|
||||
for (const kv of kvList) {
|
||||
res[kv.key] = kv.value
|
||||
}
|
||||
|
||||
const changeInputValue = (
|
||||
newValue: string,
|
||||
index: number,
|
||||
type: 'key' | 'value'
|
||||
) => {
|
||||
const newArr = [...kvList]
|
||||
newArr[index][type] = newValue
|
||||
setKvList(newArr)
|
||||
emitNewArr()
|
||||
if (index === kvList.length - 1) {
|
||||
setKvList([...newArr, { key: '', value: '' }])
|
||||
}
|
||||
}
|
||||
|
||||
const addLine = (index: number) => {
|
||||
kvList.splice(index + 1, 0, { key: '', value: '' })
|
||||
const newKvList = [...kvList]
|
||||
setKvList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
const removeLine = (index: number) => {
|
||||
kvList.splice(index, 1)
|
||||
const newKvList = [...kvList]
|
||||
setKvList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{kvList?.map((n: unknown, index: unknown) => {
|
||||
return (
|
||||
<div
|
||||
key={n + index}
|
||||
className="flex"
|
||||
style={{ marginTop: index === 0 ? '0px' : '16px' }}
|
||||
>
|
||||
<Input
|
||||
className="w-INPUT_NORMAL mr-[8px]"
|
||||
style={{ width: '174px' }}
|
||||
value={n.key}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, 'key')
|
||||
}}
|
||||
placeholder={placeholderKey}
|
||||
/>
|
||||
<Input
|
||||
style={{ width: '164px', marginRight: '10px' }}
|
||||
value={n.value}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, 'value')
|
||||
}}
|
||||
placeholder={placeholderValue}
|
||||
/>
|
||||
{index !== kvList.length - 1 && (
|
||||
<div style={{ display: 'inline-block' }}>
|
||||
{n.key && (
|
||||
<Icon icon="ic:baseline-add" onClick={() => addLine(index as unknown as number)} width="14" height="14"/>
|
||||
)}
|
||||
<Icon icon="ic:baseline-minus" onClick={() => removeLine(index as unknown as number)} width="14" height="14"/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
onChange(res)
|
||||
}
|
||||
)
|
||||
|
||||
const changeInputValue = (newValue: string, index: number, type: 'key' | 'value') => {
|
||||
const newArr = [...kvList]
|
||||
newArr[index][type] = newValue
|
||||
setKvList(newArr)
|
||||
emitNewArr()
|
||||
if (index === kvList.length - 1) {
|
||||
setKvList([...newArr, { key: '', value: '' }])
|
||||
}
|
||||
}
|
||||
|
||||
const addLine = (index: number) => {
|
||||
kvList.splice(index + 1, 0, { key: '', value: '' })
|
||||
const newKvList = [...kvList]
|
||||
setKvList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
const removeLine = (index: number) => {
|
||||
kvList.splice(index, 1)
|
||||
const newKvList = [...kvList]
|
||||
setKvList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{kvList?.map((n: unknown, index: unknown) => {
|
||||
return (
|
||||
<div key={n + index} className="flex" style={{ marginTop: index === 0 ? '0px' : '16px' }}>
|
||||
<Input
|
||||
className="w-INPUT_NORMAL mr-[8px]"
|
||||
style={{ width: '174px' }}
|
||||
value={n.key}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, 'key')
|
||||
}}
|
||||
placeholder={placeholderKey}
|
||||
/>
|
||||
<Input
|
||||
style={{ width: '164px', marginRight: '10px' }}
|
||||
value={n.value}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, 'value')
|
||||
}}
|
||||
placeholder={placeholderValue}
|
||||
/>
|
||||
{index !== kvList.length - 1 && (
|
||||
<div style={{ display: 'inline-block' }}>
|
||||
{n.key && (
|
||||
<Icon
|
||||
icon="ic:baseline-add"
|
||||
onClick={() => addLine(index as unknown as number)}
|
||||
width="14"
|
||||
height="14"
|
||||
/>
|
||||
)}
|
||||
<Icon
|
||||
icon="ic:baseline-minus"
|
||||
onClick={() => removeLine(index as unknown as number)}
|
||||
width="14"
|
||||
height="14"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
+292
-286
@@ -1,327 +1,333 @@
|
||||
import {forwardRef, useEffect, useImperativeHandle, useMemo, useState} from "react";
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react'
|
||||
import { action } from '@formily/reactive'
|
||||
import {
|
||||
FormItem,
|
||||
Space,
|
||||
ArrayItems,
|
||||
DatePicker,
|
||||
Editable,
|
||||
FormButtonGroup,
|
||||
Input,
|
||||
Radio,
|
||||
Select,
|
||||
Submit,
|
||||
Cascader,
|
||||
Form,
|
||||
FormGrid,
|
||||
FormLayout,
|
||||
Upload,
|
||||
FormItem,
|
||||
Space,
|
||||
ArrayItems,
|
||||
DatePicker,
|
||||
Editable,
|
||||
FormButtonGroup,
|
||||
Input,
|
||||
Radio,
|
||||
Select,
|
||||
Submit,
|
||||
Cascader,
|
||||
Form,
|
||||
FormGrid,
|
||||
FormLayout,
|
||||
Upload,
|
||||
ArrayCollapse,
|
||||
ArrayTable,
|
||||
ArrayTabs,
|
||||
Checkbox,
|
||||
FormCollapse,
|
||||
FormDialog,
|
||||
FormDrawer,
|
||||
FormStep,
|
||||
FormTab,
|
||||
NumberPicker,
|
||||
Password,
|
||||
PreviewText,
|
||||
Reset,
|
||||
SelectTable,
|
||||
Switch,
|
||||
TimePicker,
|
||||
Transfer,
|
||||
TreeSelect,
|
||||
ArrayCards
|
||||
} from '@formily/antd-v5'
|
||||
import { createForm } from '@formily/core'
|
||||
import { CustomCodeboxComponent } from '@common/components/aoplatform/formily2-customize/CustomCodeboxComponent.tsx'
|
||||
import { SimpleMapComponent } from '@common/components/aoplatform/formily2-customize/SimpleMapComponent.tsx'
|
||||
import { CustomDialogComponent } from '@common/components/aoplatform/formily2-customize/CustomDialogComponent.tsx'
|
||||
import { ArrayItemBlankComponent } from '@common/components/aoplatform/formily2-customize/ArrayItemBlankComponent.tsx'
|
||||
import { DefaultOptionType } from 'antd/es/cascader'
|
||||
import { createSchemaField, FormProvider, RecursionField, useField, useForm } from '@formily/react'
|
||||
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { App } from 'antd'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { setValidateLanguage } from '@formily/core'
|
||||
|
||||
export const DynamicRender = (props) => {
|
||||
const { schema } = props
|
||||
const field = useField()
|
||||
const form = useForm()
|
||||
const [renderSchema, setRenderSchema] = useState({})
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
useEffect(() => {
|
||||
form.clearFormGraph(`${field.address}.*`)
|
||||
try {
|
||||
const parsedSchema = JSON.parse(schema)
|
||||
setRenderSchema(parsedSchema[form?.values?.driver])
|
||||
} catch (e) {
|
||||
console.error('渲染出错', e?.message)
|
||||
}
|
||||
}, [form.values.driver])
|
||||
|
||||
const translateSchema = (render) => {
|
||||
const res1 = {
|
||||
...render,
|
||||
...(render.title ? { title: $t(render.title) } : {}),
|
||||
...(render.description ? { description: $t(render.description) } : {}),
|
||||
...(render.label ? { label: $t(render.label) } : {}),
|
||||
...(render.properties
|
||||
? {
|
||||
properties: Object.keys(render.properties).reduce((total, cur) => {
|
||||
try {
|
||||
total[cur] = translateSchema(render.properties[cur])
|
||||
} catch (error) {
|
||||
console.error(`Error translating schema for property ${cur}:`, error)
|
||||
}
|
||||
return total
|
||||
}, {})
|
||||
}
|
||||
: {}),
|
||||
...(render.items && Array.isArray(render.items) ? { items: render.items.map((x) => translateSchema(x)) } : {}),
|
||||
...(render.items && !Array.isArray(render.items) ? { items: translateSchema(render.items) } : {}),
|
||||
...(render.additionalProperties ? { additionalProperties: translateSchema(render.additionalProperties) } : {}),
|
||||
...(render.enum ? { enum: render.enum.map((x) => ({ ...x, label: $t(x.label) })) } : {})
|
||||
}
|
||||
return res1
|
||||
}
|
||||
|
||||
const translatedRenderSchema = useMemo(() => {
|
||||
const res = renderSchema && translateSchema(renderSchema)
|
||||
return res
|
||||
}, [state.language, renderSchema])
|
||||
|
||||
return <RecursionField basePath={field.address} schema={translatedRenderSchema} onlyRenderProperties />
|
||||
}
|
||||
|
||||
export type IntelligentPluginConfigProps = {
|
||||
type: 'add' | 'edit'
|
||||
renderSchema: unknown
|
||||
tabData: DefaultOptionType[]
|
||||
moduleId: string
|
||||
driverSelectionOptions: DefaultOptionType[]
|
||||
entityId?: string
|
||||
initFormValue: { [k: string]: unknown }
|
||||
}
|
||||
|
||||
export type IntelligentPluginConfigHandle = {
|
||||
save: () => Promise<boolean | string>
|
||||
}
|
||||
|
||||
const SchemaField = createSchemaField({
|
||||
components: {
|
||||
ArrayCards,
|
||||
ArrayCollapse,
|
||||
ArrayItems,
|
||||
ArrayTable,
|
||||
ArrayTabs,
|
||||
Cascader,
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
Editable,
|
||||
Form,
|
||||
FormButtonGroup,
|
||||
FormCollapse,
|
||||
// @ts-ignore
|
||||
FormDialog,
|
||||
// @ts-ignore
|
||||
FormDrawer,
|
||||
FormGrid,
|
||||
FormItem,
|
||||
FormLayout,
|
||||
FormStep,
|
||||
FormTab,
|
||||
Input,
|
||||
NumberPicker,
|
||||
Password,
|
||||
PreviewText,
|
||||
Radio,
|
||||
Reset,
|
||||
Select,
|
||||
SelectTable,
|
||||
Space,
|
||||
Submit,
|
||||
Switch,
|
||||
TimePicker,
|
||||
Transfer,
|
||||
TreeSelect,
|
||||
ArrayCards
|
||||
} from '@formily/antd-v5'
|
||||
import { createForm } from '@formily/core'
|
||||
import {CustomCodeboxComponent} from "@common/components/aoplatform/formily2-customize/CustomCodeboxComponent.tsx";
|
||||
import {SimpleMapComponent} from "@common/components/aoplatform/formily2-customize/SimpleMapComponent.tsx";
|
||||
import {CustomDialogComponent} from "@common/components/aoplatform/formily2-customize/CustomDialogComponent.tsx";
|
||||
import {ArrayItemBlankComponent} from "@common/components/aoplatform/formily2-customize/ArrayItemBlankComponent.tsx";
|
||||
import {DefaultOptionType} from "antd/es/cascader";
|
||||
import {createSchemaField, FormProvider, RecursionField, useField, useForm} from "@formily/react";
|
||||
import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {App, Descriptions} from "antd";
|
||||
import { $t } from "@common/locales";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
import { setValidateLanguage } from '@formily/core'
|
||||
|
||||
export const DynamicRender = (props) => {
|
||||
const {schema} = props
|
||||
const field = useField()
|
||||
const form = useForm()
|
||||
const [renderSchema, setRenderSchema] = useState({})
|
||||
const {state} = useGlobalContext()
|
||||
|
||||
useEffect(() => {
|
||||
form.clearFormGraph(`${field.address}.*`)
|
||||
try{
|
||||
const parsedSchema = JSON.parse(schema)
|
||||
setRenderSchema(parsedSchema[form?.values?.driver])
|
||||
}catch(e){
|
||||
console.error('渲染出错',e?.message)
|
||||
}
|
||||
}, [form.values.driver])
|
||||
|
||||
|
||||
const translateSchema = (render) =>{
|
||||
const res1 = {
|
||||
...render,
|
||||
...(render.title ? {title:$t(render.title)} : {}),
|
||||
...(render.description) ? {description:$t(render.description)} : {},
|
||||
...(render.label ? {label:$t(render.label)} : {}),
|
||||
...(render.properties ? {properties: Object.keys(render.properties).reduce((total, cur) => {
|
||||
try {
|
||||
total[cur] = translateSchema(render.properties[cur]);
|
||||
} catch (error) {
|
||||
console.error(`Error translating schema for property ${cur}:`, error);
|
||||
}
|
||||
return total;
|
||||
}, {})} : {}),
|
||||
...(render.items && Array.isArray(render.items) ? {items:render.items.map(x=>translateSchema(x))} : {}),
|
||||
...(render.items && !Array.isArray(render.items) ? {items:translateSchema(render.items)} : {}),
|
||||
...(render.additionalProperties ? {additionalProperties: translateSchema(render.additionalProperties)} : {}),
|
||||
...(render.enum ? {enum: render.enum.map(x=>({...x, label:$t(x.label)}))} : {}),
|
||||
|
||||
}
|
||||
return res1
|
||||
}
|
||||
|
||||
const translatedRenderSchema = useMemo(()=>{
|
||||
const res = renderSchema && translateSchema(renderSchema)
|
||||
return res
|
||||
},[state.language,renderSchema])
|
||||
|
||||
return (
|
||||
<RecursionField
|
||||
basePath={field.address}
|
||||
schema={translatedRenderSchema}
|
||||
onlyRenderProperties
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type IntelligentPluginConfigProps = {
|
||||
type:'add'|'edit'
|
||||
renderSchema:unknown
|
||||
tabData:DefaultOptionType[]
|
||||
moduleId:string
|
||||
driverSelectionOptions:DefaultOptionType[]
|
||||
entityId?:string
|
||||
initFormValue:{[k:string]:unknown}
|
||||
}
|
||||
|
||||
export type IntelligentPluginConfigHandle = {
|
||||
save:()=>Promise<boolean | string>
|
||||
}
|
||||
|
||||
const SchemaField = createSchemaField({
|
||||
components: {
|
||||
ArrayCards,
|
||||
ArrayCollapse,
|
||||
ArrayItems,
|
||||
ArrayTable,
|
||||
ArrayTabs,
|
||||
Cascader,
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
Editable,
|
||||
Form,
|
||||
FormButtonGroup,
|
||||
FormCollapse,
|
||||
// @ts-ignore
|
||||
FormDialog,
|
||||
// @ts-ignore
|
||||
FormDrawer,
|
||||
FormGrid,
|
||||
FormItem,
|
||||
FormLayout,
|
||||
FormStep,
|
||||
FormTab,
|
||||
Input,
|
||||
NumberPicker,
|
||||
Password,
|
||||
PreviewText,
|
||||
Radio,
|
||||
Reset,
|
||||
Select,
|
||||
SelectTable,
|
||||
Space,
|
||||
Submit,
|
||||
Switch,
|
||||
TimePicker,
|
||||
Transfer,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
CustomCodeboxComponent,
|
||||
SimpleMapComponent,
|
||||
CustomDialogComponent,
|
||||
ArrayItemBlankComponent,
|
||||
DynamicRender
|
||||
}
|
||||
Upload,
|
||||
CustomCodeboxComponent,
|
||||
SimpleMapComponent,
|
||||
CustomDialogComponent,
|
||||
ArrayItemBlankComponent,
|
||||
DynamicRender
|
||||
}
|
||||
})
|
||||
|
||||
export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle,IntelligentPluginConfigProps>((props,ref)=>{
|
||||
const { type,renderSchema,moduleId,driverSelectionOptions,initFormValue} = props
|
||||
export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle, IntelligentPluginConfigProps>(
|
||||
(props, ref) => {
|
||||
const { type, renderSchema, moduleId, driverSelectionOptions, initFormValue } = props
|
||||
const { message } = App.useApp()
|
||||
const {fetchData} = useFetch()
|
||||
const { fetchData } = useFetch()
|
||||
const form = createForm({ validateFirst: type === 'edit' })
|
||||
form.setInitialValues(initFormValue || {})
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
useEffect(()=>{
|
||||
setValidateLanguage(state.language)
|
||||
},[state.language])
|
||||
useEffect(() => {
|
||||
setValidateLanguage(state.language)
|
||||
}, [state.language])
|
||||
|
||||
const pluginEditSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
layout: {
|
||||
type: 'void',
|
||||
'x-component': 'FormLayout',
|
||||
'x-component-props': {
|
||||
labelCol: 6,
|
||||
wrapperCol: 10,
|
||||
layout: 'vertical',
|
||||
type: 'object',
|
||||
properties: {
|
||||
layout: {
|
||||
type: 'void',
|
||||
'x-component': 'FormLayout',
|
||||
'x-component-props': {
|
||||
labelCol: 6,
|
||||
wrapperCol: 10,
|
||||
layout: 'vertical'
|
||||
},
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
title: $t('ID'),
|
||||
required: true,
|
||||
pattern: /^[a-zA-Z][a-zA-Z0-9-_]*$/,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 4,
|
||||
wrapperCol: 20,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
title: $t('ID'),
|
||||
required: true,
|
||||
pattern: /^[a-zA-Z][a-zA-Z0-9-_]*$/,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol:4,
|
||||
wrapperCol: 20,
|
||||
labelAlign:'left'
|
||||
},
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.specialStartWithAlphabet),
|
||||
},
|
||||
'x-disabled': type === 'edit'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: $t('名称'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol:4,
|
||||
wrapperCol: 20,
|
||||
labelAlign:'left'
|
||||
},
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.input),
|
||||
}
|
||||
},
|
||||
driver: {
|
||||
type: 'string',
|
||||
title: $t('Driver'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol:4,
|
||||
wrapperCol: 20,
|
||||
labelAlign:'left'
|
||||
},
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
disabled: type === 'edit'
|
||||
},
|
||||
'x-display': driverSelectionOptions.length > 1 ? 'visible' : 'hidden',
|
||||
enum: [...driverSelectionOptions]
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
title: $t('描述'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol:4,
|
||||
wrapperCol: 20,
|
||||
labelAlign:'left'
|
||||
},
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.input),
|
||||
}
|
||||
},
|
||||
config: {
|
||||
type: 'object',
|
||||
'x-component': 'DynamicRender',
|
||||
'x-component-props': {
|
||||
schema: JSON.stringify(renderSchema),
|
||||
}
|
||||
}
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.specialStartWithAlphabet)
|
||||
},
|
||||
'x-disabled': type === 'edit'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: $t('名称'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 4,
|
||||
wrapperCol: 20,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.input)
|
||||
}
|
||||
},
|
||||
driver: {
|
||||
type: 'string',
|
||||
title: $t('Driver'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 4,
|
||||
wrapperCol: 20,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
disabled: type === 'edit'
|
||||
},
|
||||
'x-display': driverSelectionOptions.length > 1 ? 'visible' : 'hidden',
|
||||
enum: [...driverSelectionOptions]
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
title: $t('描述'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 4,
|
||||
wrapperCol: 20,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.input)
|
||||
}
|
||||
},
|
||||
config: {
|
||||
type: 'object',
|
||||
'x-component': 'DynamicRender',
|
||||
'x-component-props': {
|
||||
schema: JSON.stringify(renderSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const save :()=>Promise<boolean | string> = ()=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
form.validate().then(()=>{
|
||||
fetchData<BasicResponse<null>>(type === 'add'?`dynamic/${moduleId}`:`dynamic/${moduleId}/config`,{method:type === 'add'? 'POST' : 'PUT',eoBody:form.values, eoParams:{...(type !== 'add' && {id:initFormValue.id})}}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
}).catch((errorInfo:unknown)=> reject(errorInfo))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, ()=>({
|
||||
save
|
||||
})
|
||||
)
|
||||
const save: () => Promise<boolean | string> = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
form
|
||||
.validate()
|
||||
.then(() => {
|
||||
fetchData<BasicResponse<null>>(type === 'add' ? `dynamic/${moduleId}` : `dynamic/${moduleId}/config`, {
|
||||
method: type === 'add' ? 'POST' : 'PUT',
|
||||
eoBody: form.values,
|
||||
eoParams: { ...(type !== 'add' && { id: initFormValue.id }) }
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
.catch((errorInfo: unknown) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
save
|
||||
}))
|
||||
|
||||
const getSkillData = async (skill: string) => {
|
||||
return new Promise((resolve,reject) => {
|
||||
fetchData<BasicResponse<{[k:string]:Array<{name:string,title:string}>}>>(`api/common/provider/${skill}`,{method:'GET'}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
resolve(data[skill]?.map((x:{name:string,title:string})=>{return{label:x.title, value:x.name}}) || [])
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
return new Promise((resolve, reject) => {
|
||||
fetchData<BasicResponse<{ [k: string]: Array<{ name: string; title: string }> }>>(
|
||||
`api/common/provider/${skill}`,
|
||||
{ method: 'GET' }
|
||||
).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
resolve(
|
||||
data[skill]?.map((x: { name: string; title: string }) => {
|
||||
return { label: x.title, value: x.name }
|
||||
}) || []
|
||||
)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const useAsyncDataSource =
|
||||
(service: unknown, skill: string) => (field: unknown) => {
|
||||
field.loading = true
|
||||
service(skill).then(
|
||||
action.bound &&
|
||||
action.bound((data: unknown) => {
|
||||
field.dataSource = data
|
||||
field.loading = false
|
||||
})
|
||||
)
|
||||
}
|
||||
const useAsyncDataSource = (service: unknown, skill: string) => (field: unknown) => {
|
||||
field.loading = true
|
||||
service(skill).then(
|
||||
action.bound &&
|
||||
action.bound((data: unknown) => {
|
||||
field.dataSource = data
|
||||
field.loading = false
|
||||
})
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="pl-[12px]">
|
||||
<FormProvider form={form} >
|
||||
<SchemaField
|
||||
schema={pluginEditSchema}
|
||||
scope={{ useAsyncDataSource, getSkillData, form }}
|
||||
/>
|
||||
</FormProvider>
|
||||
</div>)
|
||||
})
|
||||
<div className="pl-[12px]">
|
||||
<FormProvider form={form}>
|
||||
<SchemaField schema={pluginEditSchema} scope={{ useAsyncDataSource, getSkillData, form }} />
|
||||
</FormProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
+400
-315
@@ -1,349 +1,434 @@
|
||||
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx";
|
||||
import {App, Divider, Spin} from "antd";
|
||||
import {useEffect, useMemo, useRef, useState} from "react";
|
||||
import { useLocation, useOutletContext, useParams} from "react-router-dom";
|
||||
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import {ActionType, ParamsType} from "@ant-design/pro-components";
|
||||
import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx";
|
||||
import {DefaultOptionType} from "antd/es/cascader";
|
||||
import {IntelligentPluginConfig, IntelligentPluginConfigHandle} from "./IntelligentPluginConfig.tsx";
|
||||
import {BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {EntityItem} from "@common/const/type.ts";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission.tsx";
|
||||
import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter.tsx";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { ActionType, ParamsType } from '@ant-design/pro-components'
|
||||
import { DrawerWithFooter } from '@common/components/aoplatform/DrawerWithFooter.tsx'
|
||||
import PageList, { PageProColumns } from '@common/components/aoplatform/PageList.tsx'
|
||||
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { EntityItem } from '@common/const/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
|
||||
import { App, Divider, Spin } from 'antd'
|
||||
import { DefaultOptionType } from 'antd/es/cascader'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useLocation, useOutletContext, useParams } from 'react-router-dom'
|
||||
import { IntelligentPluginConfig, IntelligentPluginConfigHandle } from './IntelligentPluginConfig.tsx'
|
||||
|
||||
type DynamicTableField = {
|
||||
name: string,
|
||||
title: string,
|
||||
attr: string,
|
||||
enum: Array<string>
|
||||
type DynamicTableField = {
|
||||
name: string
|
||||
title: string
|
||||
attr: string
|
||||
enum: Array<string>
|
||||
}
|
||||
|
||||
type DynamicDriverData = {
|
||||
name:string, title:string
|
||||
type DynamicDriverData = {
|
||||
name: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export type DynamicTableConfig = {
|
||||
basic:{
|
||||
id:string,
|
||||
name: string,
|
||||
title: string,
|
||||
drivers: Array<DynamicDriverData>,
|
||||
fields: Array<DynamicTableField>,
|
||||
}
|
||||
list: Array<DynamicTableItem>,
|
||||
total:number
|
||||
basic: {
|
||||
id: string
|
||||
name: string
|
||||
title: string
|
||||
drivers: Array<DynamicDriverData>
|
||||
fields: Array<DynamicTableField>
|
||||
}
|
||||
list: Array<DynamicTableItem>
|
||||
total: number
|
||||
}
|
||||
|
||||
export type DynamicRender = {
|
||||
render:unknown,
|
||||
basic:{
|
||||
id:string,
|
||||
name:string,
|
||||
title:string
|
||||
}
|
||||
render: unknown
|
||||
basic: {
|
||||
id: string
|
||||
name: string
|
||||
title: string
|
||||
}
|
||||
}
|
||||
|
||||
export type DynamicPublishCluster = {
|
||||
name:string,
|
||||
title:string,
|
||||
status:string,
|
||||
updater:EntityItem,
|
||||
update_time:string,
|
||||
checked?:boolean
|
||||
name: string
|
||||
title: string
|
||||
status: string
|
||||
updater: EntityItem
|
||||
update_time: string
|
||||
checked?: boolean
|
||||
}
|
||||
|
||||
export type DynamicPublishData = {
|
||||
id:string,
|
||||
name:string,
|
||||
title:string,
|
||||
description:string
|
||||
clusters:DynamicPublishCluster[]
|
||||
id: string
|
||||
name: string
|
||||
title: string
|
||||
description: string
|
||||
clusters: DynamicPublishCluster[]
|
||||
}
|
||||
|
||||
export type DynamicTableItem = {[k:string]:unknown}
|
||||
export type DynamicTableItem = { [k: string]: unknown }
|
||||
|
||||
export const StatusColorClass = {
|
||||
"已发布":'text-[#03a9f4]',
|
||||
"待发布":'text-[#46BE11]',
|
||||
"未发布":'text-[#03a9f4]'
|
||||
已发布: 'text-[#03a9f4]',
|
||||
待发布: 'text-[#46BE11]',
|
||||
未发布: 'text-[#03a9f4]'
|
||||
}
|
||||
|
||||
|
||||
export type DynamicPublish = {
|
||||
code:number,
|
||||
msg:string,
|
||||
data:{
|
||||
success:Array<string>,
|
||||
fail:Array<string>
|
||||
}
|
||||
code: number
|
||||
msg: string
|
||||
data: {
|
||||
success: Array<string>
|
||||
fail: Array<string>
|
||||
}
|
||||
}
|
||||
|
||||
export default function IntelligentPluginList(){
|
||||
const { modal,message } = App.useApp()
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const { moduleId } = useParams<RouterParams>();
|
||||
const [pluginName,setPluginName] = useState<string>('-')
|
||||
const [partitionOptions] = useState<DefaultOptionType[]>([{label:'default', value:'default'}])
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const [renderSchema ,setRenderSchema] = useState<{[k:string]:unknown}>({})
|
||||
const drawerFormRef = useRef<IntelligentPluginConfigHandle>(null);
|
||||
const [driverOptions, setDriverOptions] = useState<DefaultOptionType[]>([])
|
||||
const [tableListDataSource, setTableListDataSource] = useState<DynamicTableItem[]>([]);
|
||||
export default function IntelligentPluginList() {
|
||||
const { modal, message } = App.useApp()
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const { moduleId } = useParams<RouterParams>()
|
||||
const [pluginName, setPluginName] = useState<string>('-')
|
||||
const [partitionOptions] = useState<DefaultOptionType[]>([{ label: 'default', value: 'default' }])
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const [renderSchema, setRenderSchema] = useState<{ [k: string]: unknown }>({})
|
||||
const drawerFormRef = useRef<IntelligentPluginConfigHandle>(null)
|
||||
const [driverOptions, setDriverOptions] = useState<DefaultOptionType[]>([])
|
||||
const [tableListDataSource, setTableListDataSource] = useState<DynamicTableItem[]>([])
|
||||
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true);
|
||||
const [columns,setColumns] = useState<DynamicTableField[] >([])
|
||||
const {fetchData} = useFetch()
|
||||
const pageListRef = useRef<ActionType>(null);
|
||||
const [publishBtnLoading, setPublishBtnLoading] = useState<boolean>(false)
|
||||
const [curDetail,setCurDetail] = useState<{[k: string]: unknown;}|undefined>()
|
||||
const [drawerType, setDrawerType] = useState<'add'|'edit'>('add')
|
||||
const [drawerOpen, setDrawerOpen] = useState<boolean>(false)
|
||||
const [drawerLoading, setDrawerLoading] = useState<boolean>(false)
|
||||
const location = useLocation().pathname
|
||||
const {accessPrefix} = useOutletContext<{accessPrefix:string}>()
|
||||
const {state} = useGlobalContext()
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true)
|
||||
const [columns, setColumns] = useState<DynamicTableField[]>([])
|
||||
const { fetchData } = useFetch()
|
||||
const pageListRef = useRef<ActionType>(null)
|
||||
const [publishBtnLoading, setPublishBtnLoading] = useState<boolean>(false)
|
||||
const [curDetail, setCurDetail] = useState<{ [k: string]: unknown } | undefined>()
|
||||
const [drawerType, setDrawerType] = useState<'add' | 'edit'>('add')
|
||||
const [drawerOpen, setDrawerOpen] = useState<boolean>(false)
|
||||
const [drawerLoading, setDrawerLoading] = useState<boolean>(false)
|
||||
const location = useLocation().pathname
|
||||
const { accessPrefix } = useOutletContext<{ accessPrefix: string }>()
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
|
||||
const getIntelligentPluginTableList=(params:ParamsType & {
|
||||
pageSize?: number | undefined;
|
||||
current?: number | undefined;
|
||||
keyword?: string | undefined;
|
||||
}): Promise<{ data: DynamicTableItem[], success: boolean }>=> {
|
||||
if(!tableHttpReload){
|
||||
setTableHttpReload(true)
|
||||
return Promise.resolve({
|
||||
data: tableListDataSource,
|
||||
success: true,
|
||||
});
|
||||
const getIntelligentPluginTableList = (
|
||||
params: ParamsType & {
|
||||
pageSize?: number | undefined
|
||||
current?: number | undefined
|
||||
keyword?: string | undefined
|
||||
}
|
||||
): Promise<{ data: DynamicTableItem[]; success: boolean }> => {
|
||||
if (!tableHttpReload) {
|
||||
setTableHttpReload(true)
|
||||
return Promise.resolve({
|
||||
data: tableListDataSource,
|
||||
success: true
|
||||
})
|
||||
}
|
||||
const query = {
|
||||
page: params.current,
|
||||
pageSize: params.pageSize,
|
||||
keyword: searchWord
|
||||
}
|
||||
return fetchData<BasicResponse<DynamicTableConfig>>(`dynamic/${moduleId}/list`, {
|
||||
method: 'GET',
|
||||
eoParams: query,
|
||||
eoTransformKeys: ['pageSize']
|
||||
})
|
||||
.then((res) => {
|
||||
message.destroy()
|
||||
if (res.code === STATUS_CODE.SUCCESS) {
|
||||
getConfig(res.data)
|
||||
setColumns(res.data.basic.fields)
|
||||
setTableListDataSource(res.data.list)
|
||||
return { data: res.data.list, success: true, total: res.data.total }
|
||||
} else {
|
||||
setTableListDataSource([])
|
||||
return { data: [], success: false }
|
||||
}
|
||||
const query = {
|
||||
page:params.current,
|
||||
pageSize:params.pageSize,
|
||||
keyword:searchWord,
|
||||
}
|
||||
return fetchData<BasicResponse<DynamicTableConfig>>(
|
||||
`dynamic/${moduleId}/list`,
|
||||
{method:'GET',eoParams:query,eoTransformKeys:['pageSize']}).then((res)=>{
|
||||
message.destroy();
|
||||
if(res.code === STATUS_CODE.SUCCESS){
|
||||
getConfig(res.data)
|
||||
setColumns(res.data.basic.fields)
|
||||
setTableListDataSource(res.data.list);
|
||||
return ({ data: res.data.list, success: true,total:res.data.total });
|
||||
}else{
|
||||
setTableListDataSource([]);
|
||||
return ({ data: [], success: false });
|
||||
}
|
||||
}).catch((e)=>{console.warn(e);
|
||||
return ({ data: [], success: false });})
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.warn(e)
|
||||
return { data: [], success: false }
|
||||
})
|
||||
}
|
||||
|
||||
const translatedCol = useMemo(()=>columns.map((field:DynamicTableField, index:number)=>({
|
||||
title: typeof field.title === 'string' ? $t(field.title as string): field.title,
|
||||
dataIndex:field.name,
|
||||
fixed:field.name === 'title' ? 'left' : undefined,
|
||||
ellipsis:true,
|
||||
width:field.name === 'title' ? 150 : undefined,
|
||||
...(field.enum?.length > 0 ?{
|
||||
onFilter: (value: string, record: { [x: string]: string | string[]; }) => record[field.name].indexOf(value) === 0,
|
||||
filters:field.enum?.map((x:string)=>{return {text:$t(x), value:x}}),
|
||||
render:(_: unknown, entity: { [x: string]: string; })=> {
|
||||
return <span className={StatusColorClass[entity[field.name] as keyof typeof StatusColorClass]}>{$t(entity[field.name] as string)}</span>
|
||||
},
|
||||
}:{}),
|
||||
})),[state.language,columns])
|
||||
|
||||
|
||||
|
||||
const getConfig = (data:DynamicTableConfig)=>{
|
||||
const {basic,list } = data
|
||||
const {title,drivers} = basic
|
||||
|
||||
setBreadcrumb([
|
||||
{title:location.includes('resourcesettings') ? $t('资源'): $t('日志')},
|
||||
{
|
||||
title
|
||||
}
|
||||
])
|
||||
|
||||
setPluginName(title)
|
||||
setDriverOptions(drivers?.map((driver:DynamicDriverData) => {
|
||||
return { label: driver.title, value: driver.name }
|
||||
}) || [])
|
||||
|
||||
}
|
||||
|
||||
const getRender = ()=>{
|
||||
return fetchData<BasicResponse<DynamicRender>>(`dynamic/${moduleId}/render`,{method:'GET'}).then((resp) => {
|
||||
if (resp.code === STATUS_CODE.SUCCESS) {
|
||||
setRenderSchema(resp.data.render)
|
||||
return Promise.resolve(resp.data.render)
|
||||
}
|
||||
return Promise.reject(resp.msg || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
const operation:PageProColumns<DynamicTableItem>[] =[
|
||||
{
|
||||
title: COLUMNS_TITLE.operate,
|
||||
key: 'option',
|
||||
fixed:'right',
|
||||
valueType: 'option',
|
||||
btnNums:3,
|
||||
render: (_: React.ReactNode, entity: DynamicTableItem) => [
|
||||
<TableBtnWithPermission access={`${accessPrefix}.publish`} key="publish" btnType="publish" onClick={()=>{openModal('publish',entity)}} btnTitle={entity.status === $t('已发布') ? $t('下线') : $t('上线')}/>,
|
||||
<Divider type="vertical" className="mx-0" key="div1"/>,
|
||||
<TableBtnWithPermission access={`${accessPrefix}.view`} key="edit" btnType="edit" onClick={()=>{openDrawer('edit',entity)}} btnTitle={$t("查看")}/>,
|
||||
<Divider type="vertical" className="mx-0" key="div2"/>,
|
||||
<TableBtnWithPermission access={`${accessPrefix}.delete`} key="delete" btnType="delete" onClick={()=>{openModal('delete',entity)}} btnTitle={$t("删除")}/>,
|
||||
],
|
||||
}
|
||||
]
|
||||
const handleClusterChange = (e:string[])=>{
|
||||
setTableHttpReload(true)
|
||||
pageListRef.current?.reload()
|
||||
}
|
||||
|
||||
const manualReloadTable = () => {
|
||||
setTableHttpReload(true); // 表格数据需要从后端接口获取
|
||||
pageListRef.current?.reload()
|
||||
};
|
||||
|
||||
const deleteInstance = (entity:DynamicTableItem)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
fetchData<BasicResponse<null>>(`dynamic/${moduleId}/batch`,{method:'DELETE',eoParams:{ids:JSON.stringify([entity!.id])}}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const openDrawer = async (type:'add'|'edit', entity?:DynamicTableItem)=>{
|
||||
switch (type){
|
||||
case 'add':
|
||||
setCurDetail({driver:driverOptions[0].value || '',config:{}})
|
||||
break;
|
||||
case 'edit':{
|
||||
setDrawerLoading(true)
|
||||
fetchData<BasicResponse<{info:DynamicTableItem}>>(
|
||||
`dynamic/${moduleId}/info`,
|
||||
{method:'GET',eoParams:{id:entity!.id}}).then((res)=>{
|
||||
const {code, data, msg } = res
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
if(data.info.config){
|
||||
}
|
||||
setCurDetail(data.info)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).finally(()=>setDrawerLoading(false))
|
||||
break;
|
||||
}
|
||||
}
|
||||
setDrawerType(type)
|
||||
setDrawerOpen(true)
|
||||
}
|
||||
|
||||
const openModal = async (type:'publish'|'delete', entity?:DynamicTableItem)=>{
|
||||
let title:string = ''
|
||||
let content:string|React.ReactNode = ''
|
||||
switch (type){
|
||||
case 'publish':{
|
||||
message.loading($t(RESPONSE_TIPS.operating))
|
||||
await fetchData<BasicResponse<DynamicPublish>>(`dynamic/${moduleId}/${entity!.status === $t('已发布') ? 'offline':'online'}`, {
|
||||
method: 'PUT',
|
||||
eoParams:{id:entity!.id},
|
||||
}).then(response => {
|
||||
const {code, msg} = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
return Promise.resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> Promise.reject(errorInfo))
|
||||
message.destroy()
|
||||
return;}
|
||||
case 'delete':
|
||||
title='删除'
|
||||
content=<span>{$t(DELETE_TIPS.default)}</span>
|
||||
break;
|
||||
}
|
||||
|
||||
modal.confirm({
|
||||
title,
|
||||
content,
|
||||
onOk:()=>{
|
||||
switch (type){ // case 'publish':
|
||||
// return editRef.current?.save().then((res)=>{if(res === true) manualReloadTable()})
|
||||
case 'delete':
|
||||
return deleteInstance(entity!).then((res)=>{if(res === true) manualReloadTable()})
|
||||
}
|
||||
},
|
||||
width: type === 'delete'? 600 : 900,
|
||||
okText:$t('确认'),
|
||||
okButtonProps:{
|
||||
disabled:false
|
||||
},
|
||||
cancelText:$t('取消'),
|
||||
closable:true,
|
||||
icon:<></>,
|
||||
footer:(_, { OkBtn, CancelBtn }) =>{
|
||||
const translatedCol = useMemo(
|
||||
() =>
|
||||
columns.map((field: DynamicTableField, index: number) => ({
|
||||
title: typeof field.title === 'string' ? $t(field.title as string) : field.title,
|
||||
dataIndex: field.name,
|
||||
fixed: field.name === 'title' ? 'left' : undefined,
|
||||
ellipsis: true,
|
||||
width: field.name === 'title' ? 150 : undefined,
|
||||
...(field.enum?.length > 0
|
||||
? {
|
||||
onFilter: (value: string, record: { [x: string]: string | string[] }) =>
|
||||
record[field.name].indexOf(value) === 0,
|
||||
filters: field.enum?.map((x: string) => {
|
||||
return { text: $t(x), value: x }
|
||||
}),
|
||||
render: (_: unknown, entity: { [x: string]: string }) => {
|
||||
return (
|
||||
<>
|
||||
<WithPermission access=""><CancelBtn/></WithPermission>
|
||||
<WithPermission access=""><OkBtn/></WithPermission>
|
||||
</>
|
||||
);
|
||||
},
|
||||
<span className={StatusColorClass[entity[field.name] as keyof typeof StatusColorClass]}>
|
||||
{$t(entity[field.name] as string)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
: {})
|
||||
})),
|
||||
[state.language, columns]
|
||||
)
|
||||
|
||||
const getConfig = (data: DynamicTableConfig) => {
|
||||
const { basic, list } = data
|
||||
const { title, drivers } = basic
|
||||
|
||||
setBreadcrumb([
|
||||
{ title: location.includes('resourcesettings') ? $t('资源') : $t('日志') },
|
||||
{
|
||||
title
|
||||
}
|
||||
])
|
||||
|
||||
setPluginName(title)
|
||||
setDriverOptions(
|
||||
drivers?.map((driver: DynamicDriverData) => {
|
||||
return { label: driver.title, value: driver.name }
|
||||
}) || []
|
||||
)
|
||||
}
|
||||
|
||||
const getRender = () => {
|
||||
return fetchData<BasicResponse<DynamicRender>>(`dynamic/${moduleId}/render`, { method: 'GET' }).then((resp) => {
|
||||
if (resp.code === STATUS_CODE.SUCCESS) {
|
||||
setRenderSchema(resp.data.render)
|
||||
return Promise.resolve(resp.data.render)
|
||||
}
|
||||
return Promise.reject(resp.msg || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
const operation: PageProColumns<DynamicTableItem>[] = [
|
||||
{
|
||||
title: COLUMNS_TITLE.operate,
|
||||
key: 'option',
|
||||
fixed: 'right',
|
||||
valueType: 'option',
|
||||
btnNums: 3,
|
||||
render: (_: React.ReactNode, entity: DynamicTableItem) => [
|
||||
<TableBtnWithPermission
|
||||
access={`${accessPrefix}.publish`}
|
||||
key={entity.status === $t('已发布') ? 'offline' : 'publish'}
|
||||
btnType={entity.status === $t('已发布') ? 'offline' : 'publish'}
|
||||
onClick={() => {
|
||||
openModal('publish', entity)
|
||||
}}
|
||||
btnTitle={entity.status === $t('已发布') ? $t('下线') : $t('上线')}
|
||||
/>,
|
||||
<Divider type="vertical" className="mx-0" key="div1" />,
|
||||
<TableBtnWithPermission
|
||||
access={`${accessPrefix}.view`}
|
||||
key="edit"
|
||||
btnType="edit"
|
||||
onClick={() => {
|
||||
openDrawer('edit', entity)
|
||||
}}
|
||||
btnTitle={$t('查看 ')}
|
||||
/>,
|
||||
<Divider type="vertical" className="mx-0" key="div2" />,
|
||||
<TableBtnWithPermission
|
||||
access={`${accessPrefix}.delete`}
|
||||
key="delete"
|
||||
btnType="delete"
|
||||
onClick={() => {
|
||||
openModal('delete', entity)
|
||||
}}
|
||||
btnTitle={$t('删除')}
|
||||
/>
|
||||
]
|
||||
}
|
||||
]
|
||||
const handleClusterChange = (e: string[]) => {
|
||||
setTableHttpReload(true)
|
||||
pageListRef.current?.reload()
|
||||
}
|
||||
|
||||
const manualReloadTable = () => {
|
||||
setTableHttpReload(true) // 表格数据需要从后端接口获取
|
||||
pageListRef.current?.reload()
|
||||
}
|
||||
|
||||
const deleteInstance = (entity: DynamicTableItem) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetchData<BasicResponse<null>>(`dynamic/${moduleId}/batch`, {
|
||||
method: 'DELETE',
|
||||
eoParams: { ids: JSON.stringify([entity!.id]) }
|
||||
}).then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const openDrawer = async (type: 'add' | 'edit', entity?: DynamicTableItem) => {
|
||||
switch (type) {
|
||||
case 'add':
|
||||
setCurDetail({ driver: driverOptions[0].value || '', config: {} })
|
||||
break
|
||||
case 'edit': {
|
||||
setDrawerLoading(true)
|
||||
fetchData<BasicResponse<{ info: DynamicTableItem }>>(`dynamic/${moduleId}/info`, {
|
||||
method: 'GET',
|
||||
eoParams: { id: entity!.id }
|
||||
})
|
||||
.then((res) => {
|
||||
const { code, data, msg } = res
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
if (data.info.config) {
|
||||
}
|
||||
setCurDetail(data.info)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.finally(() => setDrawerLoading(false))
|
||||
break
|
||||
}
|
||||
}
|
||||
setDrawerType(type)
|
||||
setDrawerOpen(true)
|
||||
}
|
||||
|
||||
const openModal = async (type: 'publish' | 'delete', entity?: DynamicTableItem) => {
|
||||
let title: string = ''
|
||||
let content: string | React.ReactNode = ''
|
||||
switch (type) {
|
||||
case 'publish': {
|
||||
message.loading($t(RESPONSE_TIPS.operating))
|
||||
await fetchData<BasicResponse<DynamicPublish>>(
|
||||
`dynamic/${moduleId}/${entity!.status === $t('已发布') ? 'offline' : 'online'}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
eoParams: { id: entity!.id }
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
manualReloadTable()
|
||||
return Promise.resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => Promise.reject(errorInfo))
|
||||
return
|
||||
}
|
||||
case 'delete':
|
||||
title = '删除'
|
||||
content = <span>{$t(DELETE_TIPS.default)}</span>
|
||||
break
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getRender()
|
||||
pageListRef.current?.reload()
|
||||
}, [moduleId]);
|
||||
modal.confirm({
|
||||
title,
|
||||
content,
|
||||
onOk: () => {
|
||||
switch (
|
||||
type // case 'publish':
|
||||
) {
|
||||
// return editRef.current?.save().then((res)=>{if(res === true) manualReloadTable()})
|
||||
case 'delete':
|
||||
return deleteInstance(entity!).then((res) => {
|
||||
if (res === true) manualReloadTable()
|
||||
})
|
||||
}
|
||||
},
|
||||
width: type === 'delete' ? 600 : 900,
|
||||
okText: $t('确认'),
|
||||
okButtonProps: {
|
||||
disabled: false
|
||||
},
|
||||
cancelText: $t('取消'),
|
||||
closable: true,
|
||||
icon: <></>,
|
||||
footer: (_, { OkBtn, CancelBtn }) => {
|
||||
return (
|
||||
<>
|
||||
<WithPermission access="">
|
||||
<CancelBtn />
|
||||
</WithPermission>
|
||||
<WithPermission access="">
|
||||
<OkBtn />
|
||||
</WithPermission>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getRender()
|
||||
pageListRef.current?.reload()
|
||||
}, [moduleId])
|
||||
|
||||
return (<>
|
||||
<PageList
|
||||
ref={pageListRef}
|
||||
columns = {[...translatedCol,...operation]}
|
||||
request={(params)=>getIntelligentPluginTableList(params)}
|
||||
addNewBtnTitle={$t('添加(0)',[$t(pluginName)])}
|
||||
searchPlaceholder={$t('搜索(0)名称',[$t(pluginName)])}
|
||||
onChange={() => {
|
||||
setTableHttpReload(false)
|
||||
}}
|
||||
addNewBtnAccess={`${accessPrefix}.add`}
|
||||
onAddNewBtnClick={()=>{openDrawer('add')}}
|
||||
onSearchWordChange={(e)=>{setSearchWord(e.target.value);setTableHttpReload(true);setTableHttpReload(true)}}
|
||||
/>
|
||||
|
||||
<DrawerWithFooter title={`${drawerType === 'add' ? $t('添加(0)',[$t(pluginName)]) : $t('编辑(0)',[$t(pluginName)])}`} open={drawerOpen} onClose={()=>{setCurDetail(undefined);setDrawerOpen(false)}} onSubmit={()=>drawerFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res})} submitAccess=''>
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin/>} spinning={drawerLoading}>
|
||||
<IntelligentPluginConfig
|
||||
ref={drawerFormRef!}
|
||||
type={drawerType}
|
||||
renderSchema={renderSchema}
|
||||
tabData={partitionOptions}
|
||||
moduleId={moduleId!}
|
||||
driverSelectionOptions={driverOptions}
|
||||
initFormValue={curDetail as { [k: string]: unknown; }} />
|
||||
</Spin>
|
||||
</DrawerWithFooter>
|
||||
</>)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<PageList
|
||||
ref={pageListRef}
|
||||
columns={[...translatedCol, ...operation]}
|
||||
request={(params) => getIntelligentPluginTableList(params)}
|
||||
addNewBtnTitle={$t('添加(0)', [$t(pluginName)])}
|
||||
searchPlaceholder={$t('搜索(0)名称', [$t(pluginName)])}
|
||||
onChange={() => {
|
||||
setTableHttpReload(false)
|
||||
}}
|
||||
addNewBtnAccess={`${accessPrefix}.add`}
|
||||
onAddNewBtnClick={() => {
|
||||
openDrawer('add')
|
||||
}}
|
||||
onSearchWordChange={(e) => {
|
||||
setSearchWord(e.target.value)
|
||||
setTableHttpReload(true)
|
||||
setTableHttpReload(true)
|
||||
}}
|
||||
/>
|
||||
|
||||
<DrawerWithFooter
|
||||
title={`${drawerType === 'add' ? $t('添加(0)', [$t(pluginName)]) : $t('编辑(0)', [$t(pluginName)])}`}
|
||||
open={drawerOpen}
|
||||
onClose={() => {
|
||||
setCurDetail(undefined)
|
||||
setDrawerOpen(false)
|
||||
}}
|
||||
onSubmit={() =>
|
||||
drawerFormRef.current?.save()?.then((res) => {
|
||||
res && manualReloadTable()
|
||||
return res
|
||||
})
|
||||
}
|
||||
submitAccess=""
|
||||
>
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={drawerLoading}>
|
||||
<IntelligentPluginConfig
|
||||
ref={drawerFormRef!}
|
||||
type={drawerType}
|
||||
renderSchema={renderSchema}
|
||||
tabData={partitionOptions}
|
||||
moduleId={moduleId!}
|
||||
driverSelectionOptions={driverOptions}
|
||||
initFormValue={curDetail as { [k: string]: unknown }}
|
||||
/>
|
||||
</Spin>
|
||||
</DrawerWithFooter>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import type {
|
||||
EditorState,
|
||||
} from 'lexical'
|
||||
import {
|
||||
$getRoot,
|
||||
TextNode,
|
||||
} from 'lexical'
|
||||
import { useEffect } from 'react'
|
||||
import type { EditorState } from 'lexical'
|
||||
import { $getRoot, TextNode } from 'lexical'
|
||||
import { CodeNode } from '@lexical/code'
|
||||
import { LexicalComposer } from '@lexical/react/LexicalComposer'
|
||||
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
|
||||
@@ -19,25 +14,13 @@ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'
|
||||
// import TreeView from './plugins/tree-view'
|
||||
import Placeholder from './plugins/placeholder'
|
||||
import ComponentPickerBlock from './plugins/component-picker-block/index'
|
||||
import {
|
||||
ContextBlock,
|
||||
ContextBlockNode,
|
||||
ContextBlockReplacementBlock,
|
||||
} from './plugins/context-block/index'
|
||||
import {
|
||||
QueryBlock,
|
||||
QueryBlockNode,
|
||||
QueryBlockReplacementBlock,
|
||||
} from './plugins/query-block/index'
|
||||
import {
|
||||
HistoryBlock,
|
||||
HistoryBlockNode,
|
||||
HistoryBlockReplacementBlock,
|
||||
} from './plugins/history-block/index'
|
||||
import { ContextBlock, ContextBlockNode, ContextBlockReplacementBlock } from './plugins/context-block/index'
|
||||
import { QueryBlock, QueryBlockNode, QueryBlockReplacementBlock } from './plugins/query-block/index'
|
||||
import { HistoryBlock, HistoryBlockNode, HistoryBlockReplacementBlock } from './plugins/history-block/index'
|
||||
import {
|
||||
WorkflowVariableBlock,
|
||||
WorkflowVariableBlockNode,
|
||||
WorkflowVariableBlockReplacementBlock,
|
||||
WorkflowVariableBlockReplacementBlock
|
||||
} from './plugins/workflow-variable-block/index'
|
||||
import VariableBlock from './plugins/variable-block/index'
|
||||
import VariableValueBlock from './plugins/variable-value-block/index'
|
||||
@@ -52,12 +35,9 @@ import type {
|
||||
HistoryBlockType,
|
||||
QueryBlockType,
|
||||
VariableBlockType,
|
||||
WorkflowVariableBlockType,
|
||||
WorkflowVariableBlockType
|
||||
} from './types'
|
||||
import {
|
||||
UPDATE_DATASETS_EVENT_EMITTER,
|
||||
UPDATE_HISTORY_EVENT_EMITTER,
|
||||
} from './constants'
|
||||
import { UPDATE_DATASETS_EVENT_EMITTER, UPDATE_HISTORY_EVENT_EMITTER } from './constants'
|
||||
import { useEventEmitterContextContext } from '@common/contexts/EventEmitterContext'
|
||||
|
||||
export type PromptEditorProps = {
|
||||
@@ -97,7 +77,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
historyBlock,
|
||||
variableBlock,
|
||||
externalToolBlock,
|
||||
workflowVariableBlock,
|
||||
workflowVariableBlock
|
||||
}) => {
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const initialConfig = {
|
||||
@@ -107,51 +87,58 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
CustomTextNode,
|
||||
{
|
||||
replace: TextNode,
|
||||
with: (node: TextNode) => new CustomTextNode(node.__text),
|
||||
with: (node: TextNode) => new CustomTextNode(node.__text)
|
||||
},
|
||||
ContextBlockNode,
|
||||
HistoryBlockNode,
|
||||
QueryBlockNode,
|
||||
WorkflowVariableBlockNode,
|
||||
VariableValueBlockNode,
|
||||
VariableValueBlockNode
|
||||
],
|
||||
editorState: textToEditorState(value || ''),
|
||||
onError: (error: Error) => {
|
||||
throw error
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const handleEditorChange = (editorState: EditorState) => {
|
||||
const text = editorState.read(() => {
|
||||
return $getRoot().getChildren().map(p => p.getTextContent()).join('\n')
|
||||
return $getRoot()
|
||||
.getChildren()
|
||||
.map((p) => p.getTextContent())
|
||||
.join('\n')
|
||||
})
|
||||
if (onChange)
|
||||
onChange(text)
|
||||
if (onChange) onChange(text)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
eventEmitter?.emit({
|
||||
type: UPDATE_DATASETS_EVENT_EMITTER,
|
||||
payload: contextBlock?.datasets,
|
||||
payload: contextBlock?.datasets
|
||||
} as any)
|
||||
}, [eventEmitter, contextBlock?.datasets])
|
||||
useEffect(() => {
|
||||
eventEmitter?.emit({
|
||||
type: UPDATE_HISTORY_EVENT_EMITTER,
|
||||
payload: historyBlock?.history,
|
||||
payload: historyBlock?.history
|
||||
} as any)
|
||||
}, [eventEmitter, historyBlock?.history])
|
||||
|
||||
return (
|
||||
<LexicalComposer initialConfig={{ ...initialConfig, editable }}>
|
||||
<div className='relative min-h-5'>
|
||||
<div className="relative min-h-5">
|
||||
<RichTextPlugin
|
||||
contentEditable={<ContentEditable className={`${className} outline-none ${compact ? 'leading-5 text-[13px]' : 'leading-6 text-sm'} text-gray-700`} style={style || {}} />}
|
||||
contentEditable={
|
||||
<ContentEditable
|
||||
className={`${className} outline-none ${compact ? 'leading-5 text-[13px]' : 'leading-6 text-sm'} text-gray-700`}
|
||||
style={style || {}}
|
||||
/>
|
||||
}
|
||||
placeholder={<Placeholder value={placeholder} className={placeholderClassName} compact={compact} />}
|
||||
ErrorBoundary={LexicalErrorBoundary}
|
||||
/>
|
||||
<ComponentPickerBlock
|
||||
triggerString='/'
|
||||
triggerString="/"
|
||||
contextBlock={contextBlock}
|
||||
historyBlock={historyBlock}
|
||||
queryBlock={queryBlock}
|
||||
@@ -160,7 +147,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
workflowVariableBlock={workflowVariableBlock}
|
||||
/>
|
||||
<ComponentPickerBlock
|
||||
triggerString='{'
|
||||
triggerString="{"
|
||||
contextBlock={contextBlock}
|
||||
historyBlock={historyBlock}
|
||||
queryBlock={queryBlock}
|
||||
@@ -168,46 +155,36 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
externalToolBlock={externalToolBlock}
|
||||
workflowVariableBlock={workflowVariableBlock}
|
||||
/>
|
||||
{
|
||||
contextBlock?.show && (
|
||||
<>
|
||||
<ContextBlock {...contextBlock} />
|
||||
<ContextBlockReplacementBlock {...contextBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
queryBlock?.show && (
|
||||
<>
|
||||
<QueryBlock {...queryBlock} />
|
||||
<QueryBlockReplacementBlock />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
historyBlock?.show && (
|
||||
<>
|
||||
<HistoryBlock {...historyBlock} />
|
||||
<HistoryBlockReplacementBlock {...historyBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
(variableBlock?.show || externalToolBlock?.show) && (
|
||||
<>
|
||||
<VariableBlock />
|
||||
<VariableValueBlock />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
workflowVariableBlock?.show && (
|
||||
<>
|
||||
<WorkflowVariableBlock {...workflowVariableBlock} />
|
||||
<WorkflowVariableBlockReplacementBlock {...workflowVariableBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{contextBlock?.show && (
|
||||
<>
|
||||
<ContextBlock {...contextBlock} />
|
||||
<ContextBlockReplacementBlock {...contextBlock} />
|
||||
</>
|
||||
)}
|
||||
{queryBlock?.show && (
|
||||
<>
|
||||
<QueryBlock {...queryBlock} />
|
||||
<QueryBlockReplacementBlock />
|
||||
</>
|
||||
)}
|
||||
{historyBlock?.show && (
|
||||
<>
|
||||
<HistoryBlock {...historyBlock} />
|
||||
<HistoryBlockReplacementBlock {...historyBlock} />
|
||||
</>
|
||||
)}
|
||||
{(variableBlock?.show || externalToolBlock?.show) && (
|
||||
<>
|
||||
<VariableBlock />
|
||||
<VariableValueBlock />
|
||||
</>
|
||||
)}
|
||||
{workflowVariableBlock?.show && (
|
||||
<>
|
||||
<WorkflowVariableBlock {...workflowVariableBlock} />
|
||||
<WorkflowVariableBlockReplacementBlock {...workflowVariableBlock} />
|
||||
</>
|
||||
)}
|
||||
<OnChangePlugin onChange={handleEditorChange} />
|
||||
<OnBlurBlock onBlur={onBlur} onFocus={onFocus} />
|
||||
<UpdateBlock instanceId={instanceId} />
|
||||
|
||||
+88
-76
@@ -1,82 +1,94 @@
|
||||
import PromptEditor from '@common/components/aoplatform/prompt-editor/PromptEditor.tsx'
|
||||
import PromptEditorHeightResizeWrap from '@common/components/aoplatform/prompt-editor/prompt-editor-height-resize-wrap.tsx'
|
||||
import { useState } from 'react'
|
||||
import { getVars } from './utils'
|
||||
import { VariableItems } from '@core/const/ai-service/type'
|
||||
|
||||
import PromptEditor from '@common/components/aoplatform/prompt-editor/PromptEditor.tsx';
|
||||
import PromptEditorHeightResizeWrap from '@common/components/aoplatform/prompt-editor/prompt-editor-height-resize-wrap.tsx';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getVars } from './utils';
|
||||
import { VariableItems } from '@core/const/ai-service/type';
|
||||
const PromptEditorResizable = (props: {
|
||||
value?: string
|
||||
onChange?: (value: string) => void
|
||||
variablesChange?: (keys: string[]) => void
|
||||
promptVariables: VariableItems[]
|
||||
disabled?: boolean
|
||||
}) => {
|
||||
const { value, onChange, variablesChange, promptVariables, disabled } = props
|
||||
const minHeight = 68
|
||||
const [editorHeight, setEditorHeight] = useState(minHeight)
|
||||
const [previousKeys, setPreviousKeys] = useState<string[]>([])
|
||||
const handleChange = (newTemplates: string, keys: string[]) => {
|
||||
onChange?.(newTemplates)
|
||||
}
|
||||
|
||||
const PromptEditorResizable = (props:{value?:string, onChange?:(value:string)=>void, variablesChange?:(keys:string[])=>void,promptVariables:VariableItems[]}) =>{
|
||||
const {value , onChange,variablesChange,promptVariables} = props
|
||||
const minHeight = 68
|
||||
const [editorHeight, setEditorHeight] = useState(minHeight)
|
||||
const [previousKeys, setPreviousKeys] = useState<string[]>([])
|
||||
const handleChange = (newTemplates: string, keys: string[]) => {
|
||||
onChange?.(newTemplates)
|
||||
return (
|
||||
<PromptEditorHeightResizeWrap
|
||||
className="px-4 pt-2 min-h-[94px] bg-white rounded-t-xl text-sm text-gray-700"
|
||||
height={editorHeight}
|
||||
minHeight={minHeight}
|
||||
onHeightChange={setEditorHeight}
|
||||
hideResize={false}
|
||||
footer={
|
||||
<div className="pl-4 pb-2 flex bg-white rounded-b-xl">
|
||||
<div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">
|
||||
{value?.length || 0}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
return ( <PromptEditorHeightResizeWrap
|
||||
className='px-4 pt-2 min-h-[94px] bg-white rounded-t-xl text-sm text-gray-700'
|
||||
height={editorHeight}
|
||||
minHeight={minHeight}
|
||||
onHeightChange={setEditorHeight}
|
||||
hideResize={false}
|
||||
footer={(
|
||||
<div className='pl-4 pb-2 flex bg-white rounded-b-xl'>
|
||||
<div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{value?.length || 0}</div>
|
||||
</div>
|
||||
>
|
||||
<>
|
||||
{value !== undefined && (
|
||||
<PromptEditor
|
||||
className="min-h-[68px]"
|
||||
compact
|
||||
value={value}
|
||||
contextBlock={{
|
||||
show: false,
|
||||
selectable: true
|
||||
// datasets: dataSets.map(item => ({
|
||||
// id: item.id,
|
||||
// name: item.name,
|
||||
// type: item.data_source_type,
|
||||
// })),
|
||||
// onAddContext: ()=>{console.log('?onAddContext')},
|
||||
}}
|
||||
variableBlock={{
|
||||
show: true,
|
||||
variables: promptVariables?.map((x) => ({ name: x.key, value: x.key })) || []
|
||||
}}
|
||||
externalToolBlock={{
|
||||
show: false,
|
||||
externalTools: []
|
||||
// onAddExternalTool: handleOpenExternalDataToolModal,
|
||||
}}
|
||||
historyBlock={{
|
||||
show: false,
|
||||
selectable: false,
|
||||
history: {
|
||||
user: '',
|
||||
assistant: ''
|
||||
},
|
||||
onEditRole: () => {}
|
||||
}}
|
||||
queryBlock={{
|
||||
show: false,
|
||||
selectable: true
|
||||
}}
|
||||
onChange={(value) => {
|
||||
handleChange?.(value, [])
|
||||
}}
|
||||
onBlur={() => {
|
||||
const keys = getVars(value)
|
||||
handleChange(value, keys)
|
||||
if (keys.filter((key) => !previousKeys.includes(key)).length > 0) {
|
||||
variablesChange?.(keys)
|
||||
setPreviousKeys(keys)
|
||||
}
|
||||
}}
|
||||
editable={disabled ? false : true}
|
||||
/>
|
||||
)}
|
||||
><>
|
||||
{value !== undefined && <PromptEditor
|
||||
className='min-h-[68px]'
|
||||
compact
|
||||
value={value}
|
||||
contextBlock={{
|
||||
show: false,
|
||||
selectable: true,
|
||||
// datasets: dataSets.map(item => ({
|
||||
// id: item.id,
|
||||
// name: item.name,
|
||||
// type: item.data_source_type,
|
||||
// })),
|
||||
// onAddContext: ()=>{console.log('?onAddContext')},
|
||||
}}
|
||||
variableBlock={{
|
||||
show: true,
|
||||
variables:promptVariables?.map(x=>({name:x.key, value:x.key})) || [],
|
||||
}}
|
||||
externalToolBlock={{
|
||||
show: false,
|
||||
externalTools: [],
|
||||
// onAddExternalTool: handleOpenExternalDataToolModal,
|
||||
}}
|
||||
historyBlock={{
|
||||
show: false,
|
||||
selectable: false,
|
||||
history: {
|
||||
user: '',
|
||||
assistant: '',
|
||||
},
|
||||
onEditRole: () => { },
|
||||
}}
|
||||
queryBlock={{
|
||||
show: false,
|
||||
selectable: true,
|
||||
}}
|
||||
onChange={(value) => {
|
||||
handleChange?.(value, [])
|
||||
}}
|
||||
onBlur={() => {
|
||||
const keys = getVars(value)
|
||||
handleChange(value, keys)
|
||||
if(keys.filter(key => !previousKeys.includes(key)).length > 0){
|
||||
variablesChange?.(keys)
|
||||
setPreviousKeys(keys)
|
||||
}
|
||||
}}
|
||||
editable={true}
|
||||
/>
|
||||
}</>
|
||||
</PromptEditorHeightResizeWrap>)
|
||||
</>
|
||||
</PromptEditorHeightResizeWrap>
|
||||
)
|
||||
}
|
||||
|
||||
export default PromptEditorResizable
|
||||
export default PromptEditorResizable
|
||||
|
||||
@@ -9,40 +9,35 @@ export const UPDATE_HISTORY_EVENT_EMITTER = 'prompt-editor-history-block-update-
|
||||
export const MAX_VAR_KEY_LENGTH = 30
|
||||
|
||||
export const checkHasContextBlock = (text: string) => {
|
||||
if (!text)
|
||||
return false
|
||||
if (!text) return false
|
||||
return text.includes(CONTEXT_PLACEHOLDER_TEXT)
|
||||
}
|
||||
|
||||
export const checkHasHistoryBlock = (text: string) => {
|
||||
if (!text)
|
||||
return false
|
||||
if (!text) return false
|
||||
return text.includes(HISTORY_PLACEHOLDER_TEXT)
|
||||
}
|
||||
|
||||
export const checkHasQueryBlock = (text: string) => {
|
||||
if (!text)
|
||||
return false
|
||||
if (!text) return false
|
||||
return text.includes(QUERY_PLACEHOLDER_TEXT)
|
||||
}
|
||||
|
||||
/*
|
||||
* {{#1711617514996.name#}} => [1711617514996, name]
|
||||
* {{#1711617514996.sys.query#}} => [sys, query]
|
||||
*/
|
||||
* {{#1711617514996.name#}} => [1711617514996, name]
|
||||
* {{#1711617514996.sys.query#}} => [sys, query]
|
||||
*/
|
||||
export const getInputVars = (text: string) => {
|
||||
if (!text)
|
||||
return []
|
||||
if (!text) return []
|
||||
|
||||
const allVars = text.match(/{{#([^#]*)#}}/g)
|
||||
if (allVars && allVars?.length > 0) {
|
||||
// {{#context#}}, {{#query#}} is not input vars
|
||||
const inputVars = allVars
|
||||
.filter(item => item.includes('.'))
|
||||
.filter((item) => item.includes('.'))
|
||||
.map((item) => {
|
||||
const valueSelector = item.replace('{{#', '').replace('#}}', '').split('.')
|
||||
if (valueSelector[1] === 'sys' && /^\d+$/.test(valueSelector[0]))
|
||||
return valueSelector.slice(1)
|
||||
if (valueSelector[1] === 'sys' && /^\d+$/.test(valueSelector[0])) return valueSelector.slice(1)
|
||||
|
||||
return valueSelector
|
||||
})
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import type { Dispatch, RefObject, SetStateAction } from 'react'
|
||||
import type {
|
||||
Klass,
|
||||
LexicalCommand,
|
||||
LexicalEditor,
|
||||
TextNode,
|
||||
} from 'lexical'
|
||||
import type { Klass, LexicalCommand, LexicalEditor, TextNode } from 'lexical'
|
||||
import {
|
||||
$getNodeByKey,
|
||||
$getSelection,
|
||||
@@ -18,12 +8,10 @@ import {
|
||||
$isNodeSelection,
|
||||
COMMAND_PRIORITY_LOW,
|
||||
KEY_BACKSPACE_COMMAND,
|
||||
KEY_DELETE_COMMAND,
|
||||
KEY_DELETE_COMMAND
|
||||
} from 'lexical'
|
||||
import type { EntityMatch } from '@lexical/text'
|
||||
import {
|
||||
mergeRegister,
|
||||
} from '@lexical/utils'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { $isContextBlockNode } from './plugins/context-block/node'
|
||||
@@ -35,7 +23,10 @@ import { DELETE_QUERY_BLOCK_COMMAND } from './plugins/query-block'
|
||||
import type { CustomTextNode } from './plugins/custom-text/node'
|
||||
import { registerLexicalTextEntity } from './utils'
|
||||
|
||||
export type UseSelectOrDeleteHandler = (nodeKey: string, command?: LexicalCommand<undefined>) => [RefObject<HTMLDivElement>, boolean]
|
||||
export type UseSelectOrDeleteHandler = (
|
||||
nodeKey: string,
|
||||
command?: LexicalCommand<undefined>
|
||||
) => [RefObject<HTMLDivElement>, boolean]
|
||||
export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, command?: LexicalCommand<undefined>) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const [editor] = useLexicalComposerContext()
|
||||
@@ -46,13 +37,11 @@ export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, com
|
||||
const selection = $getSelection()
|
||||
const nodes = selection?.getNodes()
|
||||
if (
|
||||
!isSelected
|
||||
&& nodes?.length === 1
|
||||
&& (
|
||||
($isContextBlockNode(nodes[0]) && command === DELETE_CONTEXT_BLOCK_COMMAND)
|
||||
|| ($isHistoryBlockNode(nodes[0]) && command === DELETE_HISTORY_BLOCK_COMMAND)
|
||||
|| ($isQueryBlockNode(nodes[0]) && command === DELETE_QUERY_BLOCK_COMMAND)
|
||||
)
|
||||
!isSelected &&
|
||||
nodes?.length === 1 &&
|
||||
(($isContextBlockNode(nodes[0]) && command === DELETE_CONTEXT_BLOCK_COMMAND) ||
|
||||
($isHistoryBlockNode(nodes[0]) && command === DELETE_HISTORY_BLOCK_COMMAND) ||
|
||||
($isQueryBlockNode(nodes[0]) && command === DELETE_QUERY_BLOCK_COMMAND))
|
||||
)
|
||||
editor.dispatchCommand(command, undefined)
|
||||
|
||||
@@ -60,8 +49,7 @@ export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, com
|
||||
event.preventDefault()
|
||||
const node = $getNodeByKey(nodeKey)
|
||||
if ($isDecoratorNode(node)) {
|
||||
if (command)
|
||||
editor.dispatchCommand(command, undefined)
|
||||
if (command) editor.dispatchCommand(command, undefined)
|
||||
|
||||
node.remove()
|
||||
return true
|
||||
@@ -70,38 +58,31 @@ export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, com
|
||||
|
||||
return false
|
||||
},
|
||||
[isSelected, nodeKey, command, editor],
|
||||
[isSelected, nodeKey, command, editor]
|
||||
)
|
||||
|
||||
const handleSelect = useCallback((e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
clearSelection()
|
||||
setSelected(true)
|
||||
}, [setSelected, clearSelection])
|
||||
const handleSelect = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
clearSelection()
|
||||
setSelected(true)
|
||||
},
|
||||
[setSelected, clearSelection]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const ele = ref.current
|
||||
|
||||
if (ele)
|
||||
ele.addEventListener('click', handleSelect)
|
||||
if (ele) ele.addEventListener('click', handleSelect)
|
||||
|
||||
return () => {
|
||||
if (ele)
|
||||
ele.removeEventListener('click', handleSelect)
|
||||
if (ele) ele.removeEventListener('click', handleSelect)
|
||||
}
|
||||
}, [handleSelect])
|
||||
useEffect(() => {
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
KEY_DELETE_COMMAND,
|
||||
handleDelete,
|
||||
COMMAND_PRIORITY_LOW,
|
||||
),
|
||||
editor.registerCommand(
|
||||
KEY_BACKSPACE_COMMAND,
|
||||
handleDelete,
|
||||
COMMAND_PRIORITY_LOW,
|
||||
),
|
||||
editor.registerCommand(KEY_DELETE_COMMAND, handleDelete, COMMAND_PRIORITY_LOW),
|
||||
editor.registerCommand(KEY_BACKSPACE_COMMAND, handleDelete, COMMAND_PRIORITY_LOW)
|
||||
)
|
||||
}, [editor, clearSelection, handleDelete])
|
||||
|
||||
@@ -114,17 +95,15 @@ export const useTrigger: UseTriggerHandler = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const handleOpen = useCallback((e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
setOpen(v => !v)
|
||||
setOpen((v) => !v)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const trigger = triggerRef.current
|
||||
if (trigger)
|
||||
trigger.addEventListener('click', handleOpen)
|
||||
if (trigger) trigger.addEventListener('click', handleOpen)
|
||||
|
||||
return () => {
|
||||
if (trigger)
|
||||
trigger.removeEventListener('click', handleOpen)
|
||||
if (trigger) trigger.removeEventListener('click', handleOpen)
|
||||
}
|
||||
}, [handleOpen])
|
||||
|
||||
@@ -134,7 +113,7 @@ export const useTrigger: UseTriggerHandler = () => {
|
||||
export function useLexicalTextEntity<T extends TextNode>(
|
||||
getMatch: (text: string) => null | EntityMatch,
|
||||
targetNode: Klass<T>,
|
||||
createNode: (textNode: CustomTextNode) => T,
|
||||
createNode: (textNode: CustomTextNode) => T
|
||||
) {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
@@ -148,24 +127,16 @@ export type MenuTextMatch = {
|
||||
matchingString: string
|
||||
replaceableString: string
|
||||
}
|
||||
export type TriggerFn = (
|
||||
text: string,
|
||||
editor: LexicalEditor,
|
||||
) => MenuTextMatch | null
|
||||
export type TriggerFn = (text: string, editor: LexicalEditor) => MenuTextMatch | null
|
||||
export const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;'
|
||||
export function useBasicTypeaheadTriggerMatch(
|
||||
trigger: string,
|
||||
{ minLength = 1, maxLength = 75 }: { minLength?: number; maxLength?: number },
|
||||
{ minLength = 1, maxLength = 75 }: { minLength?: number; maxLength?: number }
|
||||
): TriggerFn {
|
||||
return useCallback(
|
||||
(text: string) => {
|
||||
const validChars = `[${PUNCTUATION}\\s]`
|
||||
const TypeaheadTriggerRegex = new RegExp(
|
||||
'(.*)('
|
||||
+ `[${trigger}]`
|
||||
+ `((?:${validChars}){0,${maxLength}})`
|
||||
+ ')$',
|
||||
)
|
||||
const TypeaheadTriggerRegex = new RegExp('(.*)(' + `[${trigger}]` + `((?:${validChars}){0,${maxLength}})` + ')$')
|
||||
const match = TypeaheadTriggerRegex.exec(text)
|
||||
if (match !== null) {
|
||||
const maybeLeadingWhitespace = match[1]
|
||||
@@ -174,12 +145,12 @@ export function useBasicTypeaheadTriggerMatch(
|
||||
return {
|
||||
leadOffset: match.index + maybeLeadingWhitespace.length,
|
||||
matchingString,
|
||||
replaceableString: match[2],
|
||||
replaceableString: match[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
[maxLength, minLength, trigger],
|
||||
[maxLength, minLength, trigger]
|
||||
)
|
||||
}
|
||||
|
||||
+50
-58
@@ -8,7 +8,7 @@ import type {
|
||||
HistoryBlockType,
|
||||
QueryBlockType,
|
||||
VariableBlockType,
|
||||
WorkflowVariableBlockType,
|
||||
WorkflowVariableBlockType
|
||||
} from '../../types'
|
||||
import { INSERT_CONTEXT_BLOCK_COMMAND } from '../context-block'
|
||||
import { INSERT_HISTORY_BLOCK_COMMAND } from '../history-block'
|
||||
@@ -32,32 +32,35 @@ import { $t } from '@common/locales'
|
||||
export const usePromptOptions = (
|
||||
contextBlock?: ContextBlockType,
|
||||
queryBlock?: QueryBlockType,
|
||||
historyBlock?: HistoryBlockType,
|
||||
historyBlock?: HistoryBlockType
|
||||
) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const promptOptions: PickerBlockMenuOption[] = []
|
||||
if (contextBlock?.show) {
|
||||
promptOptions.push(new PickerBlockMenuOption({
|
||||
key: $t('上下文'),
|
||||
group: 'prompt context',
|
||||
render: ({ isSelected, onSelect, onSetHighlight }) => {
|
||||
return <PromptMenuItem
|
||||
title={$t('上下文')}
|
||||
icon={<></>}
|
||||
// icon={<File05 className='w-4 h-4 text-[#6938EF]' />}
|
||||
disabled={!contextBlock.selectable}
|
||||
isSelected={isSelected}
|
||||
onClick={onSelect}
|
||||
onMouseEnter={onSetHighlight}
|
||||
/>
|
||||
},
|
||||
onSelect: () => {
|
||||
if (!contextBlock?.selectable)
|
||||
return
|
||||
editor.dispatchCommand(INSERT_CONTEXT_BLOCK_COMMAND, undefined)
|
||||
},
|
||||
}))
|
||||
promptOptions.push(
|
||||
new PickerBlockMenuOption({
|
||||
key: $t('上下文'),
|
||||
group: 'prompt context',
|
||||
render: ({ isSelected, onSelect, onSetHighlight }) => {
|
||||
return (
|
||||
<PromptMenuItem
|
||||
title={$t('上下文')}
|
||||
icon={<></>}
|
||||
// icon={<File05 className='w-4 h-4 text-[#6938EF]' />}
|
||||
disabled={!contextBlock.selectable}
|
||||
isSelected={isSelected}
|
||||
onClick={onSelect}
|
||||
onMouseEnter={onSetHighlight}
|
||||
/>
|
||||
)
|
||||
},
|
||||
onSelect: () => {
|
||||
if (!contextBlock?.selectable) return
|
||||
editor.dispatchCommand(INSERT_CONTEXT_BLOCK_COMMAND, undefined)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (queryBlock?.show) {
|
||||
@@ -79,11 +82,10 @@ export const usePromptOptions = (
|
||||
)
|
||||
},
|
||||
onSelect: () => {
|
||||
if (!queryBlock?.selectable)
|
||||
return
|
||||
if (!queryBlock?.selectable) return
|
||||
editor.dispatchCommand(INSERT_QUERY_BLOCK_COMMAND, undefined)
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -98,8 +100,7 @@ export const usePromptOptions = (
|
||||
title={$t('会话历史')}
|
||||
icon={<></>}
|
||||
// icon={<MessageClockCircle className='w-4 h-4 text-[#DD2590]' />}
|
||||
disabled={!historyBlock.selectable
|
||||
}
|
||||
disabled={!historyBlock.selectable}
|
||||
isSelected={isSelected}
|
||||
onClick={onSelect}
|
||||
onMouseEnter={onSetHighlight}
|
||||
@@ -107,11 +108,10 @@ export const usePromptOptions = (
|
||||
)
|
||||
},
|
||||
onSelect: () => {
|
||||
if (!historyBlock?.selectable)
|
||||
return
|
||||
if (!historyBlock?.selectable) return
|
||||
editor.dispatchCommand(INSERT_HISTORY_BLOCK_COMMAND, undefined)
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
return promptOptions
|
||||
@@ -119,16 +119,15 @@ export const usePromptOptions = (
|
||||
|
||||
export const useVariableOptions = (
|
||||
variableBlock?: VariableBlockType,
|
||||
queryString?: string,
|
||||
queryString?: string
|
||||
): PickerBlockMenuOption[] => {
|
||||
const { t } = useTranslation()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const options = useMemo(() => {
|
||||
if (!variableBlock?.variables)
|
||||
return []
|
||||
if (!variableBlock?.variables) return []
|
||||
|
||||
const baseOptions = (variableBlock.variables).map((item) => {
|
||||
const baseOptions = variableBlock.variables.map((item) => {
|
||||
return new PickerBlockMenuOption({
|
||||
key: item.value,
|
||||
group: 'prompt variable',
|
||||
@@ -147,15 +146,14 @@ export const useVariableOptions = (
|
||||
},
|
||||
onSelect: () => {
|
||||
editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${item.value}}}`)
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
if (!queryString)
|
||||
return baseOptions
|
||||
if (!queryString) return baseOptions
|
||||
|
||||
const regex = new RegExp(queryString, 'i')
|
||||
|
||||
return baseOptions.filter(option => regex.test(option.key))
|
||||
return baseOptions.filter((option) => regex.test(option.key))
|
||||
}, [editor, queryString, variableBlock])
|
||||
|
||||
const addOption = useMemo(() => {
|
||||
@@ -182,7 +180,7 @@ export const useVariableOptions = (
|
||||
$insertNodes([prefixNode, suffixNode])
|
||||
prefixNode.select()
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
||||
}, [editor, t])
|
||||
|
||||
@@ -191,17 +189,13 @@ export const useVariableOptions = (
|
||||
}, [options, addOption, variableBlock?.show])
|
||||
}
|
||||
|
||||
export const useExternalToolOptions = (
|
||||
externalToolBlockType?: ExternalToolBlockType,
|
||||
queryString?: string,
|
||||
) => {
|
||||
export const useExternalToolOptions = (externalToolBlockType?: ExternalToolBlockType, queryString?: string) => {
|
||||
const { t } = useTranslation()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const options = useMemo(() => {
|
||||
if (!externalToolBlockType?.externalTools)
|
||||
return []
|
||||
const baseToolOptions = (externalToolBlockType.externalTools).map((item) => {
|
||||
if (!externalToolBlockType?.externalTools) return []
|
||||
const baseToolOptions = externalToolBlockType.externalTools.map((item) => {
|
||||
return new PickerBlockMenuOption({
|
||||
key: item.name,
|
||||
group: 'external tool',
|
||||
@@ -217,7 +211,7 @@ export const useExternalToolOptions = (
|
||||
// background={item.icon_background}
|
||||
// />
|
||||
// }
|
||||
extraElement={<div className='text-xs text-gray-400'>{item.variableName}</div>}
|
||||
extraElement={<div className="text-xs text-gray-400">{item.variableName}</div>}
|
||||
queryString={queryString}
|
||||
isSelected={isSelected}
|
||||
onClick={onSelect}
|
||||
@@ -227,15 +221,14 @@ export const useExternalToolOptions = (
|
||||
},
|
||||
onSelect: () => {
|
||||
editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${item.variableName}}}`)
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
if (!queryString)
|
||||
return baseToolOptions
|
||||
if (!queryString) return baseToolOptions
|
||||
|
||||
const regex = new RegExp(queryString, 'i')
|
||||
|
||||
return baseToolOptions.filter(option => regex.test(option.key))
|
||||
return baseToolOptions.filter((option) => regex.test(option.key))
|
||||
}, [editor, queryString, externalToolBlockType])
|
||||
|
||||
const addOption = useMemo(() => {
|
||||
@@ -257,7 +250,7 @@ export const useExternalToolOptions = (
|
||||
},
|
||||
onSelect: () => {
|
||||
externalToolBlockType?.onAddExternalTool?.()
|
||||
},
|
||||
}
|
||||
})
|
||||
}, [externalToolBlockType, t])
|
||||
|
||||
@@ -273,14 +266,13 @@ export const useOptions = (
|
||||
variableBlock?: VariableBlockType,
|
||||
externalToolBlockType?: ExternalToolBlockType,
|
||||
workflowVariableBlockType?: WorkflowVariableBlockType,
|
||||
queryString?: string,
|
||||
queryString?: string
|
||||
) => {
|
||||
const promptOptions = usePromptOptions(contextBlock, queryBlock, historyBlock)
|
||||
const variableOptions = useVariableOptions(variableBlock, queryString)
|
||||
const externalToolOptions = useExternalToolOptions(externalToolBlockType, queryString)
|
||||
const workflowVariableOptions = useMemo(() => {
|
||||
if (!workflowVariableBlockType?.show)
|
||||
return []
|
||||
if (!workflowVariableBlockType?.show) return []
|
||||
|
||||
return workflowVariableBlockType.variables || []
|
||||
}, [workflowVariableBlockType])
|
||||
@@ -288,7 +280,7 @@ export const useOptions = (
|
||||
return useMemo(() => {
|
||||
return {
|
||||
workflowVariableOptions,
|
||||
allFlattenOptions: [...promptOptions, ...variableOptions, ...externalToolOptions],
|
||||
allFlattenOptions: [...promptOptions, ...variableOptions, ...externalToolOptions]
|
||||
}
|
||||
}, [promptOptions, variableOptions, externalToolOptions, workflowVariableOptions])
|
||||
}
|
||||
|
||||
+79
-98
@@ -1,16 +1,6 @@
|
||||
import {
|
||||
Fragment,
|
||||
memo,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { Fragment, memo, useCallback, useState } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import {
|
||||
flip,
|
||||
offset,
|
||||
shift,
|
||||
useFloating,
|
||||
} from '@floating-ui/react'
|
||||
import { flip, offset, shift, useFloating } from '@floating-ui/react'
|
||||
import type { TextNode } from 'lexical'
|
||||
import type { MenuRenderFn } from '@lexical/react/LexicalTypeaheadMenuPlugin'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
@@ -21,7 +11,7 @@ import type {
|
||||
HistoryBlockType,
|
||||
QueryBlockType,
|
||||
VariableBlockType,
|
||||
WorkflowVariableBlockType,
|
||||
WorkflowVariableBlockType
|
||||
} from '../../types'
|
||||
import { useBasicTypeaheadTriggerMatch } from '../../hooks'
|
||||
import { INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND } from '../workflow-variable-block'
|
||||
@@ -48,7 +38,7 @@ const ComponentPicker = ({
|
||||
historyBlock,
|
||||
variableBlock,
|
||||
externalToolBlock,
|
||||
workflowVariableBlock,
|
||||
workflowVariableBlock
|
||||
}: ComponentPickerProps) => {
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const { refs, floatingStyles, isPositioned } = useFloating({
|
||||
@@ -56,15 +46,15 @@ const ComponentPicker = ({
|
||||
middleware: [
|
||||
offset(0), // fix hide cursor
|
||||
shift({
|
||||
padding: 8,
|
||||
padding: 8
|
||||
}),
|
||||
flip(),
|
||||
],
|
||||
flip()
|
||||
]
|
||||
})
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const checkForTriggerMatch = useBasicTypeaheadTriggerMatch(triggerString, {
|
||||
minLength: 0,
|
||||
maxLength: 0,
|
||||
maxLength: 0
|
||||
})
|
||||
|
||||
const [queryString, setQueryString] = useState<string | null>(null)
|
||||
@@ -74,123 +64,114 @@ const ComponentPicker = ({
|
||||
editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${v.payload}}}`)
|
||||
})
|
||||
|
||||
const {
|
||||
allFlattenOptions,
|
||||
workflowVariableOptions,
|
||||
} = useOptions(
|
||||
const { allFlattenOptions, workflowVariableOptions } = useOptions(
|
||||
contextBlock,
|
||||
queryBlock,
|
||||
historyBlock,
|
||||
variableBlock,
|
||||
externalToolBlock,
|
||||
workflowVariableBlock,
|
||||
workflowVariableBlock
|
||||
)
|
||||
|
||||
const onSelectOption = useCallback(
|
||||
(
|
||||
selectedOption: PickerBlockMenuOption,
|
||||
nodeToRemove: TextNode | null,
|
||||
closeMenu: () => void,
|
||||
) => {
|
||||
(selectedOption: PickerBlockMenuOption, nodeToRemove: TextNode | null, closeMenu: () => void) => {
|
||||
editor.update(() => {
|
||||
if (nodeToRemove && selectedOption?.key)
|
||||
nodeToRemove.remove()
|
||||
if (nodeToRemove && selectedOption?.key) nodeToRemove.remove()
|
||||
|
||||
selectedOption.onSelectMenuOption()
|
||||
closeMenu()
|
||||
})
|
||||
},
|
||||
[editor],
|
||||
[editor]
|
||||
)
|
||||
|
||||
const handleSelectWorkflowVariable = useCallback((variables: string[]) => {
|
||||
editor.update(() => {
|
||||
const needRemove = $splitNodeContainingQuery(checkForTriggerMatch(triggerString, editor)!)
|
||||
if (needRemove)
|
||||
needRemove.remove()
|
||||
})
|
||||
const handleSelectWorkflowVariable = useCallback(
|
||||
(variables: string[]) => {
|
||||
editor.update(() => {
|
||||
const needRemove = $splitNodeContainingQuery(checkForTriggerMatch(triggerString, editor)!)
|
||||
if (needRemove) needRemove.remove()
|
||||
})
|
||||
|
||||
if (variables[1] === 'sys.query' || variables[1] === 'sys.files')
|
||||
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, [variables[1]])
|
||||
else
|
||||
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables)
|
||||
}, [editor, checkForTriggerMatch, triggerString])
|
||||
if (variables[1] === 'sys.query' || variables[1] === 'sys.files')
|
||||
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, [variables[1]])
|
||||
else editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables)
|
||||
},
|
||||
[editor, checkForTriggerMatch, triggerString]
|
||||
)
|
||||
|
||||
const renderMenu = useCallback<MenuRenderFn<PickerBlockMenuOption>>((
|
||||
anchorElementRef,
|
||||
{ options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex },
|
||||
) => {
|
||||
if (!(anchorElementRef.current && (allFlattenOptions.length || workflowVariableBlock?.show)))
|
||||
return null
|
||||
refs.setReference(anchorElementRef.current)
|
||||
const renderMenu = useCallback<MenuRenderFn<PickerBlockMenuOption>>(
|
||||
(anchorElementRef, { options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }) => {
|
||||
if (!(anchorElementRef.current && (allFlattenOptions.length || workflowVariableBlock?.show))) return null
|
||||
refs.setReference(anchorElementRef.current)
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
ReactDOM.createPortal(
|
||||
return (
|
||||
<>
|
||||
{ReactDOM.createPortal(
|
||||
// The `LexicalMenu` will try to calculate the position of the floating menu based on the first child.
|
||||
// Since we use floating ui, we need to wrap it with a div to prevent the position calculation being affected.
|
||||
// See https://github.com/facebook/lexical/blob/ac97dfa9e14a73ea2d6934ff566282d7f758e8bb/packages/lexical-react/src/shared/LexicalMenu.ts#L493
|
||||
<div className='w-0 h-0'>
|
||||
<div className="w-0 h-0">
|
||||
<div
|
||||
className='p-1 w-[260px] bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg overflow-y-auto overflow-x-hidden'
|
||||
className="p-1 w-[260px] bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg overflow-y-auto overflow-x-hidden"
|
||||
style={{
|
||||
...floatingStyles,
|
||||
visibility: isPositioned ? 'visible' : 'hidden',
|
||||
maxHeight: 'calc(1 / 3 * 100vh)',
|
||||
maxHeight: 'calc(1 / 3 * 100vh)'
|
||||
}}
|
||||
ref={refs.setFloating}
|
||||
>
|
||||
{
|
||||
options.map((option, index) => (
|
||||
<Fragment key={option.key}>
|
||||
{
|
||||
// Divider
|
||||
index !== 0 && options.at(index - 1)?.group !== option.group && (
|
||||
<div className='h-px bg-gray-100 my-1 w-screen -translate-x-1'></div>
|
||||
)
|
||||
{options.map((option, index) => (
|
||||
<Fragment key={option.key}>
|
||||
{
|
||||
// Divider
|
||||
index !== 0 && options.at(index - 1)?.group !== option.group && (
|
||||
<div className="h-px bg-gray-100 my-1 w-screen -translate-x-1"></div>
|
||||
)
|
||||
}
|
||||
{option.renderMenuOption({
|
||||
queryString,
|
||||
isSelected: selectedIndex === index,
|
||||
onSelect: () => {
|
||||
selectOptionAndCleanUp(option)
|
||||
},
|
||||
onSetHighlight: () => {
|
||||
setHighlightedIndex(index)
|
||||
}
|
||||
{option.renderMenuOption({
|
||||
queryString,
|
||||
isSelected: selectedIndex === index,
|
||||
onSelect: () => {
|
||||
selectOptionAndCleanUp(option)
|
||||
},
|
||||
onSetHighlight: () => {
|
||||
setHighlightedIndex(index)
|
||||
},
|
||||
})}
|
||||
</Fragment>
|
||||
))
|
||||
}
|
||||
{
|
||||
workflowVariableBlock?.show && (
|
||||
<>
|
||||
{
|
||||
(!!options.length) && (
|
||||
<div className='h-px bg-gray-100 my-1 w-screen -translate-x-1'></div>
|
||||
)
|
||||
}
|
||||
<div className='p-1'>
|
||||
{/* <VarReferenceVars
|
||||
})}
|
||||
</Fragment>
|
||||
))}
|
||||
{workflowVariableBlock?.show && (
|
||||
<>
|
||||
{!!options.length && <div className="h-px bg-gray-100 my-1 w-screen -translate-x-1"></div>}
|
||||
<div className="p-1">
|
||||
{/* <VarReferenceVars
|
||||
hideSearch
|
||||
vars={workflowVariableOptions}
|
||||
onChange={(variables: string[]) => {
|
||||
handleSelectWorkflowVariable(variables)
|
||||
}}
|
||||
/> */}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>,
|
||||
anchorElementRef.current,
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}, [allFlattenOptions.length, workflowVariableBlock?.show, refs, isPositioned, floatingStyles, queryString, workflowVariableOptions, handleSelectWorkflowVariable])
|
||||
anchorElementRef.current
|
||||
)}
|
||||
</>
|
||||
)
|
||||
},
|
||||
[
|
||||
allFlattenOptions.length,
|
||||
workflowVariableBlock?.show,
|
||||
refs,
|
||||
isPositioned,
|
||||
floatingStyles,
|
||||
queryString,
|
||||
workflowVariableOptions,
|
||||
handleSelectWorkflowVariable
|
||||
]
|
||||
)
|
||||
|
||||
return (
|
||||
<LexicalTypeaheadMenuPlugin
|
||||
@@ -202,7 +183,7 @@ const ComponentPicker = ({
|
||||
//
|
||||
// We no need the position function of the `LexicalTypeaheadMenuPlugin`,
|
||||
// so the reference anchor should be positioned based on the range of the trigger string, and the menu will be positioned by the floating ui.
|
||||
anchorClassName='z-[999999] translate-y-[calc(-100%-3px)]'
|
||||
anchorClassName="z-[999999] translate-y-[calc(-100%-3px)]"
|
||||
menuRenderFn={renderMenu}
|
||||
triggerFn={checkForTriggerMatch}
|
||||
/>
|
||||
|
||||
+4
-2
@@ -20,12 +20,14 @@ export class PickerBlockMenuOption extends MenuOption {
|
||||
group?: string
|
||||
onSelect?: () => void
|
||||
render: (menuRenderProps: MenuOptionRenderProps) => JSX.Element
|
||||
},
|
||||
}
|
||||
) {
|
||||
super(data.key)
|
||||
this.group = data.group
|
||||
}
|
||||
|
||||
public onSelectMenuOption = () => this.data.onSelect?.()
|
||||
public renderMenuOption = (menuRenderProps: MenuOptionRenderProps) => <Fragment key={this.data.key}>{this.data.render(menuRenderProps)}</Fragment>
|
||||
public renderMenuOption = (menuRenderProps: MenuOptionRenderProps) => (
|
||||
<Fragment key={this.data.key}>{this.data.render(menuRenderProps)}</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
+22
-29
@@ -9,37 +9,30 @@ type PromptMenuItemMenuItemProps = {
|
||||
onMouseEnter: () => void
|
||||
setRefElement?: (element: HTMLDivElement) => void
|
||||
}
|
||||
export const PromptMenuItem = memo(({
|
||||
icon,
|
||||
title,
|
||||
disabled,
|
||||
isSelected,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
setRefElement,
|
||||
}: PromptMenuItemMenuItemProps) => {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
export const PromptMenuItem = memo(
|
||||
({ icon, title, disabled, isSelected, onClick, onMouseEnter, setRefElement }: PromptMenuItemMenuItemProps) => {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex items-center px-3 h-6 cursor-pointer hover:bg-gray-50 rounded-md
|
||||
${isSelected && !disabled && '!bg-gray-50'}
|
||||
${disabled ? 'cursor-not-allowed opacity-30' : 'hover:bg-gray-50 cursor-pointer'}
|
||||
`}
|
||||
tabIndex={-1}
|
||||
ref={setRefElement}
|
||||
onMouseEnter={() => {
|
||||
if (disabled)
|
||||
return
|
||||
onMouseEnter()
|
||||
}}
|
||||
onClick={() => {
|
||||
if (disabled)
|
||||
return
|
||||
onClick()
|
||||
}}>
|
||||
{icon}
|
||||
<div className='ml-1 text-[13px] text-gray-900'>{title}</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
tabIndex={-1}
|
||||
ref={setRefElement}
|
||||
onMouseEnter={() => {
|
||||
if (disabled) return
|
||||
onMouseEnter()
|
||||
}}
|
||||
onClick={() => {
|
||||
if (disabled) return
|
||||
onClick()
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
<div className="ml-1 text-[13px] text-gray-900">{title}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
PromptMenuItem.displayName = 'PromptMenuItem'
|
||||
|
||||
+40
-39
@@ -10,51 +10,52 @@ type VariableMenuItemProps = {
|
||||
onMouseEnter: () => void
|
||||
setRefElement?: (element: HTMLDivElement) => void
|
||||
}
|
||||
export const VariableMenuItem = memo(({
|
||||
title,
|
||||
icon,
|
||||
extraElement,
|
||||
isSelected,
|
||||
queryString,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
setRefElement,
|
||||
}: VariableMenuItemProps) => {
|
||||
let before = title
|
||||
let middle = ''
|
||||
let after = ''
|
||||
export const VariableMenuItem = memo(
|
||||
({
|
||||
title,
|
||||
icon,
|
||||
extraElement,
|
||||
isSelected,
|
||||
queryString,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
setRefElement
|
||||
}: VariableMenuItemProps) => {
|
||||
let before = title
|
||||
let middle = ''
|
||||
let after = ''
|
||||
|
||||
if (queryString) {
|
||||
const regex = new RegExp(queryString, 'i')
|
||||
const match = regex.exec(title)
|
||||
if (queryString) {
|
||||
const regex = new RegExp(queryString, 'i')
|
||||
const match = regex.exec(title)
|
||||
|
||||
if (match) {
|
||||
before = title.substring(0, match.index)
|
||||
middle = match[0]
|
||||
after = title.substring(match.index + match[0].length)
|
||||
if (match) {
|
||||
before = title.substring(0, match.index)
|
||||
middle = match[0]
|
||||
after = title.substring(match.index + match[0].length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex items-center px-3 h-6 rounded-md hover:bg-[#EBEEF2] cursor-pointer
|
||||
${isSelected && 'bg-[#EBEEF2]'}
|
||||
`}
|
||||
tabIndex={-1}
|
||||
ref={setRefElement}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onClick={onClick}>
|
||||
<div className='mr-2'>
|
||||
{icon}
|
||||
tabIndex={-1}
|
||||
ref={setRefElement}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="mr-2">{icon}</div>
|
||||
<div className="grow text-[13px] text-gray-900 truncate" title={title}>
|
||||
{before}
|
||||
<span className="text-[#2970FF]">{middle}</span>
|
||||
{after}
|
||||
</div>
|
||||
{extraElement}
|
||||
</div>
|
||||
<div className='grow text-[13px] text-gray-900 truncate' title={title}>
|
||||
{before}
|
||||
<span className='text-[#2970FF]'>{middle}</span>
|
||||
{after}
|
||||
</div>
|
||||
{extraElement}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
)
|
||||
VariableMenuItem.displayName = 'VariableMenuItem'
|
||||
|
||||
+8
-8
@@ -1,6 +1,5 @@
|
||||
import type { FC } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
// import {
|
||||
// RiAddLine,
|
||||
// } from '@remixicon/react'
|
||||
@@ -28,7 +27,7 @@ const ContextBlockComponent: FC<ContextBlockComponentProps> = ({
|
||||
nodeKey,
|
||||
datasets = [],
|
||||
onAddContext,
|
||||
canNotAddContext,
|
||||
canNotAddContext
|
||||
}) => {
|
||||
const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_CONTEXT_BLOCK_COMMAND)
|
||||
const [triggerRef, open, setOpen] = useTrigger()
|
||||
@@ -36,19 +35,20 @@ const ContextBlockComponent: FC<ContextBlockComponentProps> = ({
|
||||
const [localDatasets, setLocalDatasets] = useState<Dataset[]>(datasets)
|
||||
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v?.type === UPDATE_DATASETS_EVENT_EMITTER)
|
||||
setLocalDatasets(v.payload)
|
||||
if (v?.type === UPDATE_DATASETS_EVENT_EMITTER) setLocalDatasets(v.payload)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={`
|
||||
<div
|
||||
className={`
|
||||
group inline-flex items-center pl-1 pr-0.5 h-6 border border-transparent bg-[#F4F3FF] text-[#6938EF] rounded-[5px] hover:bg-[#EBE9FE]
|
||||
${open ? 'bg-[#EBE9FE]' : 'bg-[#F4F3FF]'}
|
||||
${isSelected && '!border-[#9B8AFB]'}
|
||||
`} ref={ref}>
|
||||
`}
|
||||
ref={ref}
|
||||
>
|
||||
{/* <File05 className='mr-1 w-[14px] h-[14px]' /> */}
|
||||
<div className='mr-1 text-xs font-medium'>{$t('上下文')}</div>
|
||||
|
||||
<div className="mr-1 text-xs font-medium">{$t('上下文')}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+9
-16
@@ -1,18 +1,11 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import { memo, useCallback, useEffect } from 'react'
|
||||
import { $applyNodeReplacement } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { decoratorTransform } from '../../utils'
|
||||
import { CONTEXT_PLACEHOLDER_TEXT } from '../../constants'
|
||||
import type { ContextBlockType } from '../../types'
|
||||
import {
|
||||
$createContextBlockNode,
|
||||
ContextBlockNode,
|
||||
} from './node'
|
||||
import { $createContextBlockNode, ContextBlockNode } from './node'
|
||||
import { CustomTextNode } from '../custom-text/node'
|
||||
|
||||
const REGEX = new RegExp(CONTEXT_PLACEHOLDER_TEXT)
|
||||
@@ -21,7 +14,7 @@ const ContextBlockReplacementBlock = ({
|
||||
datasets = [],
|
||||
onAddContext = () => {},
|
||||
onInsert,
|
||||
canNotAddContext,
|
||||
canNotAddContext
|
||||
}: ContextBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
@@ -31,29 +24,29 @@ const ContextBlockReplacementBlock = ({
|
||||
}, [editor])
|
||||
|
||||
const createContextBlockNode = useCallback((): ContextBlockNode => {
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
if (onInsert) onInsert()
|
||||
return $applyNodeReplacement($createContextBlockNode(datasets, onAddContext, canNotAddContext))
|
||||
}, [datasets, onAddContext, onInsert, canNotAddContext])
|
||||
|
||||
const getMatch = useCallback((text: string) => {
|
||||
const matchArr = REGEX.exec(text)
|
||||
|
||||
if (matchArr === null)
|
||||
return null
|
||||
if (matchArr === null) return null
|
||||
|
||||
const startOffset = matchArr.index
|
||||
const endOffset = startOffset + CONTEXT_PLACEHOLDER_TEXT.length
|
||||
return {
|
||||
end: endOffset,
|
||||
start: startOffset,
|
||||
start: startOffset
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
REGEX.lastIndex = 0
|
||||
return mergeRegister(
|
||||
editor.registerNodeTransform(CustomTextNode, textNode => decoratorTransform(textNode, getMatch, createContextBlockNode)),
|
||||
editor.registerNodeTransform(CustomTextNode, (textNode) =>
|
||||
decoratorTransform(textNode, getMatch, createContextBlockNode)
|
||||
)
|
||||
)
|
||||
}, [])
|
||||
|
||||
|
||||
+33
-49
@@ -1,19 +1,9 @@
|
||||
import {
|
||||
memo,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import {
|
||||
$insertNodes,
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
createCommand,
|
||||
} from 'lexical'
|
||||
import { memo, useEffect } from 'react'
|
||||
import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import type { ContextBlockType } from '../../types'
|
||||
import {
|
||||
$createContextBlockNode,
|
||||
ContextBlockNode,
|
||||
} from './node'
|
||||
import { $createContextBlockNode, ContextBlockNode } from './node'
|
||||
|
||||
export const INSERT_CONTEXT_BLOCK_COMMAND = createCommand('INSERT_CONTEXT_BLOCK_COMMAND')
|
||||
export const DELETE_CONTEXT_BLOCK_COMMAND = createCommand('DELETE_CONTEXT_BLOCK_COMMAND')
|
||||
@@ -24,49 +14,43 @@ export type Dataset = {
|
||||
type: string
|
||||
}
|
||||
|
||||
const ContextBlock = memo(({
|
||||
datasets = [],
|
||||
onAddContext = () => {},
|
||||
onInsert,
|
||||
onDelete,
|
||||
canNotAddContext,
|
||||
}: ContextBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const ContextBlock = memo(
|
||||
({ datasets = [], onAddContext = () => {}, onInsert, onDelete, canNotAddContext }: ContextBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([ContextBlockNode]))
|
||||
throw new Error('ContextBlockPlugin: ContextBlock not registered on editor')
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([ContextBlockNode]))
|
||||
throw new Error('ContextBlockPlugin: ContextBlock not registered on editor')
|
||||
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
INSERT_CONTEXT_BLOCK_COMMAND,
|
||||
() => {
|
||||
const contextBlockNode = $createContextBlockNode(datasets, onAddContext, canNotAddContext)
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
INSERT_CONTEXT_BLOCK_COMMAND,
|
||||
() => {
|
||||
const contextBlockNode = $createContextBlockNode(datasets, onAddContext, canNotAddContext)
|
||||
|
||||
$insertNodes([contextBlockNode])
|
||||
$insertNodes([contextBlockNode])
|
||||
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
if (onInsert) onInsert()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
editor.registerCommand(
|
||||
DELETE_CONTEXT_BLOCK_COMMAND,
|
||||
() => {
|
||||
if (onDelete)
|
||||
onDelete()
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
),
|
||||
editor.registerCommand(
|
||||
DELETE_CONTEXT_BLOCK_COMMAND,
|
||||
() => {
|
||||
if (onDelete) onDelete()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
)
|
||||
}, [editor, datasets, onAddContext, onInsert, onDelete, canNotAddContext])
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
)
|
||||
)
|
||||
}, [editor, datasets, onAddContext, onInsert, onDelete, canNotAddContext])
|
||||
|
||||
return null
|
||||
})
|
||||
return null
|
||||
}
|
||||
)
|
||||
ContextBlock.displayName = 'ContextBlock'
|
||||
|
||||
export { ContextBlock }
|
||||
|
||||
+17
-7
@@ -3,7 +3,11 @@ import { DecoratorNode } from 'lexical'
|
||||
import ContextBlockComponent from './component'
|
||||
import type { Dataset } from './index'
|
||||
|
||||
export type SerializedNode = SerializedLexicalNode & { datasets: Dataset[]; onAddContext: () => void; canNotAddContext: boolean }
|
||||
export type SerializedNode = SerializedLexicalNode & {
|
||||
datasets: Dataset[]
|
||||
onAddContext: () => void
|
||||
canNotAddContext: boolean
|
||||
}
|
||||
|
||||
export class ContextBlockNode extends DecoratorNode<JSX.Element> {
|
||||
__datasets: Dataset[]
|
||||
@@ -70,7 +74,11 @@ export class ContextBlockNode extends DecoratorNode<JSX.Element> {
|
||||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedNode): ContextBlockNode {
|
||||
const node = $createContextBlockNode(serializedNode.datasets, serializedNode.onAddContext, serializedNode.canNotAddContext)
|
||||
const node = $createContextBlockNode(
|
||||
serializedNode.datasets,
|
||||
serializedNode.onAddContext,
|
||||
serializedNode.canNotAddContext
|
||||
)
|
||||
|
||||
return node
|
||||
}
|
||||
@@ -81,7 +89,7 @@ export class ContextBlockNode extends DecoratorNode<JSX.Element> {
|
||||
version: 1,
|
||||
datasets: this.getDatasets(),
|
||||
onAddContext: this.getOnAddContext(),
|
||||
canNotAddContext: this.getCanNotAddContext(),
|
||||
canNotAddContext: this.getCanNotAddContext()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,12 +97,14 @@ export class ContextBlockNode extends DecoratorNode<JSX.Element> {
|
||||
return '{{#context#}}'
|
||||
}
|
||||
}
|
||||
export function $createContextBlockNode(datasets: Dataset[], onAddContext: () => void, canNotAddContext?: boolean): ContextBlockNode {
|
||||
export function $createContextBlockNode(
|
||||
datasets: Dataset[],
|
||||
onAddContext: () => void,
|
||||
canNotAddContext?: boolean
|
||||
): ContextBlockNode {
|
||||
return new ContextBlockNode(datasets, onAddContext, undefined, canNotAddContext)
|
||||
}
|
||||
|
||||
export function $isContextBlockNode(
|
||||
node: ContextBlockNode | LexicalNode | null | undefined,
|
||||
): boolean {
|
||||
export function $isContextBlockNode(node: ContextBlockNode | LexicalNode | null | undefined): boolean {
|
||||
return node instanceof ContextBlockNode
|
||||
}
|
||||
|
||||
+2
-3
@@ -37,13 +37,12 @@ export class CustomTextNode extends TextNode {
|
||||
style: this.getStyle(),
|
||||
text: this.getTextContent(),
|
||||
type: 'custom-text',
|
||||
version: 1,
|
||||
version: 1
|
||||
}
|
||||
}
|
||||
|
||||
isSimpleText() {
|
||||
return (
|
||||
(this.__type === 'text' || this.__type === 'custom-text') && this.__mode === 0)
|
||||
return (this.__type === 'text' || this.__type === 'custom-text') && this.__mode === 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-7
@@ -1,6 +1,5 @@
|
||||
import type { FC } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
// import {
|
||||
// RiMoreFill,
|
||||
// } from '@remixicon/react'
|
||||
@@ -26,7 +25,7 @@ type HistoryBlockComponentProps = {
|
||||
const HistoryBlockComponent: FC<HistoryBlockComponentProps> = ({
|
||||
nodeKey,
|
||||
roleName = { user: '', assistant: '' },
|
||||
onEditRole,
|
||||
onEditRole
|
||||
}) => {
|
||||
const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_HISTORY_BLOCK_COMMAND)
|
||||
const [triggerRef, open, setOpen] = useTrigger()
|
||||
@@ -34,18 +33,20 @@ const HistoryBlockComponent: FC<HistoryBlockComponentProps> = ({
|
||||
const [localRoleName, setLocalRoleName] = useState<RoleName>(roleName)
|
||||
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v?.type === UPDATE_HISTORY_EVENT_EMITTER)
|
||||
setLocalRoleName(v.payload)
|
||||
if (v?.type === UPDATE_HISTORY_EVENT_EMITTER) setLocalRoleName(v.payload)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={`
|
||||
<div
|
||||
className={`
|
||||
group inline-flex items-center pl-1 pr-0.5 h-6 border border-transparent text-[#DD2590] rounded-[5px] hover:bg-[#FCE7F6]
|
||||
${open ? 'bg-[#FCE7F6]' : 'bg-[#FDF2FA]'}
|
||||
${isSelected && '!border-[#F670C7]'}
|
||||
`} ref={ref}>
|
||||
`}
|
||||
ref={ref}
|
||||
>
|
||||
{/* <MessageClockCircle className='mr-1 w-[14px] h-[14px]' /> */}
|
||||
<div className='mr-1 text-xs font-medium'>{$t('会话历史')}</div>
|
||||
<div className="mr-1 text-xs font-medium">{$t('会话历史')}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+9
-15
@@ -1,17 +1,11 @@
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { $applyNodeReplacement } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { decoratorTransform } from '../../utils'
|
||||
import { HISTORY_PLACEHOLDER_TEXT } from '../../constants'
|
||||
import type { HistoryBlockType } from '../../types'
|
||||
import {
|
||||
$createHistoryBlockNode,
|
||||
HistoryBlockNode,
|
||||
} from './node'
|
||||
import { $createHistoryBlockNode, HistoryBlockNode } from './node'
|
||||
import { CustomTextNode } from '../custom-text/node'
|
||||
|
||||
const REGEX = new RegExp(HISTORY_PLACEHOLDER_TEXT)
|
||||
@@ -19,7 +13,7 @@ const REGEX = new RegExp(HISTORY_PLACEHOLDER_TEXT)
|
||||
const HistoryBlockReplacementBlock = ({
|
||||
history = { user: '', assistant: '' },
|
||||
onEditRole = () => {},
|
||||
onInsert,
|
||||
onInsert
|
||||
}: HistoryBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
@@ -29,29 +23,29 @@ const HistoryBlockReplacementBlock = ({
|
||||
}, [editor])
|
||||
|
||||
const createHistoryBlockNode = useCallback((): HistoryBlockNode => {
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
if (onInsert) onInsert()
|
||||
return $applyNodeReplacement($createHistoryBlockNode(history, onEditRole))
|
||||
}, [history, onEditRole, onInsert])
|
||||
|
||||
const getMatch = useCallback((text: string) => {
|
||||
const matchArr = REGEX.exec(text)
|
||||
|
||||
if (matchArr === null)
|
||||
return null
|
||||
if (matchArr === null) return null
|
||||
|
||||
const startOffset = matchArr.index
|
||||
const endOffset = startOffset + HISTORY_PLACEHOLDER_TEXT.length
|
||||
return {
|
||||
end: endOffset,
|
||||
start: startOffset,
|
||||
start: startOffset
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
REGEX.lastIndex = 0
|
||||
return mergeRegister(
|
||||
editor.registerNodeTransform(CustomTextNode, textNode => decoratorTransform(textNode, getMatch, createHistoryBlockNode)),
|
||||
editor.registerNodeTransform(CustomTextNode, (textNode) =>
|
||||
decoratorTransform(textNode, getMatch, createHistoryBlockNode)
|
||||
)
|
||||
)
|
||||
}, [])
|
||||
|
||||
|
||||
+33
-48
@@ -1,19 +1,9 @@
|
||||
import {
|
||||
memo,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import {
|
||||
$insertNodes,
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
createCommand,
|
||||
} from 'lexical'
|
||||
import { memo, useEffect } from 'react'
|
||||
import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import type { HistoryBlockType } from '../../types'
|
||||
import {
|
||||
$createHistoryBlockNode,
|
||||
HistoryBlockNode,
|
||||
} from './node'
|
||||
import { $createHistoryBlockNode, HistoryBlockNode } from './node'
|
||||
|
||||
export const INSERT_HISTORY_BLOCK_COMMAND = createCommand('INSERT_HISTORY_BLOCK_COMMAND')
|
||||
export const DELETE_HISTORY_BLOCK_COMMAND = createCommand('DELETE_HISTORY_BLOCK_COMMAND')
|
||||
@@ -30,48 +20,43 @@ export type HistoryBlockProps = {
|
||||
onDelete?: () => void
|
||||
}
|
||||
|
||||
const HistoryBlock = memo(({
|
||||
history = { user: '', assistant: '' },
|
||||
onEditRole = () => {},
|
||||
onInsert,
|
||||
onDelete,
|
||||
}: HistoryBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const HistoryBlock = memo(
|
||||
({ history = { user: '', assistant: '' }, onEditRole = () => {}, onInsert, onDelete }: HistoryBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([HistoryBlockNode]))
|
||||
throw new Error('HistoryBlockPlugin: HistoryBlock not registered on editor')
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([HistoryBlockNode]))
|
||||
throw new Error('HistoryBlockPlugin: HistoryBlock not registered on editor')
|
||||
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
INSERT_HISTORY_BLOCK_COMMAND,
|
||||
() => {
|
||||
const historyBlockNode = $createHistoryBlockNode(history, onEditRole)
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
INSERT_HISTORY_BLOCK_COMMAND,
|
||||
() => {
|
||||
const historyBlockNode = $createHistoryBlockNode(history, onEditRole)
|
||||
|
||||
$insertNodes([historyBlockNode])
|
||||
$insertNodes([historyBlockNode])
|
||||
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
if (onInsert) onInsert()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
editor.registerCommand(
|
||||
DELETE_HISTORY_BLOCK_COMMAND,
|
||||
() => {
|
||||
if (onDelete)
|
||||
onDelete()
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
),
|
||||
editor.registerCommand(
|
||||
DELETE_HISTORY_BLOCK_COMMAND,
|
||||
() => {
|
||||
if (onDelete) onDelete()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
)
|
||||
}, [editor, history, onEditRole, onInsert, onDelete])
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
)
|
||||
)
|
||||
}, [editor, history, onEditRole, onInsert, onDelete])
|
||||
|
||||
return null
|
||||
})
|
||||
return null
|
||||
}
|
||||
)
|
||||
HistoryBlock.displayName = 'HistoryBlock'
|
||||
|
||||
export { HistoryBlock }
|
||||
|
||||
+3
-9
@@ -40,11 +40,7 @@ export class HistoryBlockNode extends DecoratorNode<JSX.Element> {
|
||||
|
||||
decorate(): JSX.Element {
|
||||
return (
|
||||
<HistoryBlockComponent
|
||||
nodeKey={this.getKey()}
|
||||
roleName={this.getRoleName()}
|
||||
onEditRole={this.getOnEditRole()}
|
||||
/>
|
||||
<HistoryBlockComponent nodeKey={this.getKey()} roleName={this.getRoleName()} onEditRole={this.getOnEditRole()} />
|
||||
)
|
||||
}
|
||||
|
||||
@@ -71,7 +67,7 @@ export class HistoryBlockNode extends DecoratorNode<JSX.Element> {
|
||||
type: 'history-block',
|
||||
version: 1,
|
||||
roleName: this.getRoleName(),
|
||||
onEditRole: this.getOnEditRole,
|
||||
onEditRole: this.getOnEditRole
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +79,6 @@ export function $createHistoryBlockNode(roleName: RoleName, onEditRole: () => vo
|
||||
return new HistoryBlockNode(roleName, onEditRole)
|
||||
}
|
||||
|
||||
export function $isHistoryBlockNode(
|
||||
node: HistoryBlockNode | LexicalNode | null | undefined,
|
||||
): node is HistoryBlockNode {
|
||||
export function $isHistoryBlockNode(node: HistoryBlockNode | LexicalNode | null | undefined): node is HistoryBlockNode {
|
||||
return node instanceof HistoryBlockNode
|
||||
}
|
||||
|
||||
+8
-18
@@ -1,11 +1,6 @@
|
||||
import type { FC } from 'react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import {
|
||||
BLUR_COMMAND,
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
FOCUS_COMMAND,
|
||||
KEY_ESCAPE_COMMAND,
|
||||
} from 'lexical'
|
||||
import { BLUR_COMMAND, COMMAND_PRIORITY_EDITOR, FOCUS_COMMAND, KEY_ESCAPE_COMMAND } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { CLEAR_HIDE_MENU_TIMEOUT } from './workflow-variable-block'
|
||||
@@ -14,10 +9,7 @@ type OnBlurBlockProps = {
|
||||
onBlur?: () => void
|
||||
onFocus?: () => void
|
||||
}
|
||||
const OnBlurBlock: FC<OnBlurBlockProps> = ({
|
||||
onBlur,
|
||||
onFocus,
|
||||
}) => {
|
||||
const OnBlurBlock: FC<OnBlurBlockProps> = ({ onBlur, onFocus }) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const ref = useRef<any>(null)
|
||||
@@ -33,7 +25,7 @@ const OnBlurBlock: FC<OnBlurBlockProps> = ({
|
||||
}
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
),
|
||||
editor.registerCommand(
|
||||
BLUR_COMMAND,
|
||||
@@ -42,22 +34,20 @@ const OnBlurBlock: FC<OnBlurBlockProps> = ({
|
||||
editor.dispatchCommand(KEY_ESCAPE_COMMAND, new KeyboardEvent('keydown', { key: 'Escape' }))
|
||||
}, 200)
|
||||
|
||||
if (onBlur)
|
||||
onBlur()
|
||||
if (onBlur) onBlur()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
),
|
||||
editor.registerCommand(
|
||||
FOCUS_COMMAND,
|
||||
() => {
|
||||
if (onFocus)
|
||||
onFocus()
|
||||
if (onFocus) onFocus()
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
)
|
||||
)
|
||||
}, [editor, onBlur, onFocus])
|
||||
|
||||
|
||||
+3
-11
@@ -1,18 +1,10 @@
|
||||
import { $t } from '@common/locales'
|
||||
import { memo } from 'react'
|
||||
|
||||
const Placeholder = ({
|
||||
compact,
|
||||
value
|
||||
}: {
|
||||
compact?: boolean
|
||||
value?: string
|
||||
className?: string
|
||||
}) => {
|
||||
|
||||
const Placeholder = ({ compact, value }: { compact?: boolean; value?: string; className?: string }) => {
|
||||
return (
|
||||
<div
|
||||
className={`absolute top-0 left-0 h-full w-full text-sm text-[#BBB] select-none pointer-events-none ${compact ? 'leading-5 text-[13px]' : 'leading-6 text-sm'}`}
|
||||
<div
|
||||
className={`absolute top-0 left-0 h-full w-full text-sm text-[#BBB] select-none pointer-events-none ${compact ? 'leading-5 text-[13px]' : 'leading-6 text-sm'}`}
|
||||
>
|
||||
{value || $t('AI 模型调用默认仅使用 Query 变量,可输入 “{” 增加新变量。')}
|
||||
</div>
|
||||
|
||||
+4
-7
@@ -1,5 +1,4 @@
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelectOrDelete } from '../../hooks'
|
||||
import { DELETE_QUERY_BLOCK_COMMAND } from './index'
|
||||
import { $t } from '@common/locales'
|
||||
@@ -9,9 +8,7 @@ type QueryBlockComponentProps = {
|
||||
nodeKey: string
|
||||
}
|
||||
|
||||
const QueryBlockComponent: FC<QueryBlockComponentProps> = ({
|
||||
nodeKey,
|
||||
}) => {
|
||||
const QueryBlockComponent: FC<QueryBlockComponentProps> = ({ nodeKey }) => {
|
||||
const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_QUERY_BLOCK_COMMAND)
|
||||
|
||||
return (
|
||||
@@ -23,9 +20,9 @@ const QueryBlockComponent: FC<QueryBlockComponentProps> = ({
|
||||
ref={ref}
|
||||
>
|
||||
{/* <UserEdit02 className='mr-1 w-[14px] h-[14px] text-[#FD853A]' /> */}
|
||||
<div className='text-xs font-medium text-[#EC4A0A] opacity-60'>{'{{'}</div>
|
||||
<div className='text-xs font-medium text-[#EC4A0A]'>{$t('查询内容')}</div>
|
||||
<div className='text-xs font-medium text-[#EC4A0A] opacity-60'>{'}}'}</div>
|
||||
<div className="text-xs font-medium text-[#EC4A0A] opacity-60">{'{{'}</div>
|
||||
<div className="text-xs font-medium text-[#EC4A0A]">{$t('查询内容')}</div>
|
||||
<div className="text-xs font-medium text-[#EC4A0A] opacity-60">{'}}'}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+10
-26
@@ -1,19 +1,9 @@
|
||||
import {
|
||||
memo,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import {
|
||||
$insertNodes,
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
createCommand,
|
||||
} from 'lexical'
|
||||
import { memo, useEffect } from 'react'
|
||||
import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import type { QueryBlockType } from '../../types'
|
||||
import {
|
||||
$createQueryBlockNode,
|
||||
QueryBlockNode,
|
||||
} from './node'
|
||||
import { $createQueryBlockNode, QueryBlockNode } from './node'
|
||||
|
||||
export const INSERT_QUERY_BLOCK_COMMAND = createCommand('INSERT_QUERY_BLOCK_COMMAND')
|
||||
export const DELETE_QUERY_BLOCK_COMMAND = createCommand('DELETE_QUERY_BLOCK_COMMAND')
|
||||
@@ -22,15 +12,11 @@ export type QueryBlockProps = {
|
||||
onInsert?: () => void
|
||||
onDelete?: () => void
|
||||
}
|
||||
const QueryBlock = memo(({
|
||||
onInsert,
|
||||
onDelete,
|
||||
}: QueryBlockType) => {
|
||||
const QueryBlock = memo(({ onInsert, onDelete }: QueryBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([QueryBlockNode]))
|
||||
throw new Error('QueryBlockPlugin: QueryBlock not registered on editor')
|
||||
if (!editor.hasNodes([QueryBlockNode])) throw new Error('QueryBlockPlugin: QueryBlock not registered on editor')
|
||||
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
@@ -39,23 +25,21 @@ const QueryBlock = memo(({
|
||||
const contextBlockNode = $createQueryBlockNode()
|
||||
|
||||
$insertNodes([contextBlockNode])
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
if (onInsert) onInsert()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
),
|
||||
editor.registerCommand(
|
||||
DELETE_QUERY_BLOCK_COMMAND,
|
||||
() => {
|
||||
if (onDelete)
|
||||
onDelete()
|
||||
if (onDelete) onDelete()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
)
|
||||
)
|
||||
}, [editor, onInsert, onDelete])
|
||||
|
||||
|
||||
+2
-4
@@ -40,7 +40,7 @@ export class QueryBlockNode extends DecoratorNode<JSX.Element> {
|
||||
exportJSON(): SerializedNode {
|
||||
return {
|
||||
type: 'query-block',
|
||||
version: 1,
|
||||
version: 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,6 @@ export function $createQueryBlockNode(): QueryBlockNode {
|
||||
return new QueryBlockNode()
|
||||
}
|
||||
|
||||
export function $isQueryBlockNode(
|
||||
node: QueryBlockNode | LexicalNode | null | undefined,
|
||||
): node is QueryBlockNode {
|
||||
export function $isQueryBlockNode(node: QueryBlockNode | LexicalNode | null | undefined): node is QueryBlockNode {
|
||||
return node instanceof QueryBlockNode
|
||||
}
|
||||
|
||||
+9
-18
@@ -1,25 +1,16 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import { memo, useCallback, useEffect } from 'react'
|
||||
import { $applyNodeReplacement } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { decoratorTransform } from '../../utils'
|
||||
import { QUERY_PLACEHOLDER_TEXT } from '../../constants'
|
||||
import type { QueryBlockType } from '../../types'
|
||||
import {
|
||||
$createQueryBlockNode,
|
||||
QueryBlockNode,
|
||||
} from './node'
|
||||
import { $createQueryBlockNode, QueryBlockNode } from './node'
|
||||
import { CustomTextNode } from '../custom-text/node'
|
||||
|
||||
const REGEX = new RegExp(QUERY_PLACEHOLDER_TEXT)
|
||||
|
||||
const QueryBlockReplacementBlock = ({
|
||||
onInsert,
|
||||
}: QueryBlockType) => {
|
||||
const QueryBlockReplacementBlock = ({ onInsert }: QueryBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -28,29 +19,29 @@ const QueryBlockReplacementBlock = ({
|
||||
}, [editor])
|
||||
|
||||
const createQueryBlockNode = useCallback((): QueryBlockNode => {
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
if (onInsert) onInsert()
|
||||
return $applyNodeReplacement($createQueryBlockNode())
|
||||
}, [onInsert])
|
||||
|
||||
const getMatch = useCallback((text: string) => {
|
||||
const matchArr = REGEX.exec(text)
|
||||
|
||||
if (matchArr === null)
|
||||
return null
|
||||
if (matchArr === null) return null
|
||||
|
||||
const startOffset = matchArr.index
|
||||
const endOffset = startOffset + QUERY_PLACEHOLDER_TEXT.length
|
||||
return {
|
||||
end: endOffset,
|
||||
start: startOffset,
|
||||
start: startOffset
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
REGEX.lastIndex = 0
|
||||
return mergeRegister(
|
||||
editor.registerNodeTransform(CustomTextNode, textNode => decoratorTransform(textNode, getMatch, createQueryBlockNode)),
|
||||
editor.registerNodeTransform(CustomTextNode, (textNode) =>
|
||||
decoratorTransform(textNode, getMatch, createQueryBlockNode)
|
||||
)
|
||||
)
|
||||
}, [])
|
||||
|
||||
|
||||
+1
-3
@@ -11,9 +11,7 @@ export const PROMPT_EDITOR_INSERT_QUICKLY = 'PROMPT_EDITOR_INSERT_QUICKLY'
|
||||
type UpdateBlockProps = {
|
||||
instanceId?: string
|
||||
}
|
||||
const UpdateBlock = ({
|
||||
instanceId,
|
||||
}: UpdateBlockProps) => {
|
||||
const UpdateBlock = ({ instanceId }: UpdateBlockProps) => {
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
|
||||
+4
-8
@@ -1,9 +1,5 @@
|
||||
import { useEffect } from 'react'
|
||||
import {
|
||||
$insertNodes,
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
createCommand,
|
||||
} from 'lexical'
|
||||
import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { CustomTextNode } from '../custom-text/node'
|
||||
@@ -24,7 +20,7 @@ const VariableBlock = () => {
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
),
|
||||
editor.registerCommand(
|
||||
INSERT_VARIABLE_VALUE_BLOCK_COMMAND,
|
||||
@@ -34,8 +30,8 @@ const VariableBlock = () => {
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
)
|
||||
)
|
||||
}, [editor])
|
||||
|
||||
|
||||
+5
-12
@@ -1,14 +1,8 @@
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import type { TextNode } from 'lexical'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { useLexicalTextEntity } from '../../hooks'
|
||||
import {
|
||||
$createVariableValueBlockNode,
|
||||
VariableValueBlockNode,
|
||||
} from './node'
|
||||
import { $createVariableValueBlockNode, VariableValueBlockNode } from './node'
|
||||
import { getHashtagRegexString } from './utils'
|
||||
|
||||
const REGEX = new RegExp(getHashtagRegexString(), 'i')
|
||||
@@ -28,22 +22,21 @@ const VariableValueBlock = () => {
|
||||
const getVariableValueMatch = useCallback((text: string) => {
|
||||
const matchArr = REGEX.exec(text)
|
||||
|
||||
if (matchArr === null)
|
||||
return null
|
||||
if (matchArr === null) return null
|
||||
|
||||
const hashtagLength = matchArr[0].length
|
||||
const startOffset = matchArr.index
|
||||
const endOffset = startOffset + hashtagLength
|
||||
return {
|
||||
end: endOffset,
|
||||
start: startOffset,
|
||||
start: startOffset
|
||||
}
|
||||
}, [])
|
||||
|
||||
useLexicalTextEntity<VariableValueBlockNode>(
|
||||
getVariableValueMatch,
|
||||
VariableValueBlockNode,
|
||||
createVariableValueBlockNode,
|
||||
createVariableValueBlockNode
|
||||
)
|
||||
|
||||
return null
|
||||
|
||||
+13
-15
@@ -1,13 +1,5 @@
|
||||
import type {
|
||||
EditorConfig,
|
||||
LexicalNode,
|
||||
NodeKey,
|
||||
SerializedTextNode,
|
||||
} from 'lexical'
|
||||
import {
|
||||
$applyNodeReplacement,
|
||||
TextNode,
|
||||
} from 'lexical'
|
||||
import type { EditorConfig, LexicalNode, NodeKey, SerializedTextNode } from 'lexical'
|
||||
import { $applyNodeReplacement, TextNode } from 'lexical'
|
||||
|
||||
export class VariableValueBlockNode extends TextNode {
|
||||
static getType(): string {
|
||||
@@ -24,7 +16,15 @@ export class VariableValueBlockNode extends TextNode {
|
||||
|
||||
createDOM(config: EditorConfig): HTMLElement {
|
||||
const element = super.createDOM(config)
|
||||
element.classList.add('inline-flex', 'items-center', 'px-0.5', 'h-[22px]', 'text-[#155EEF]', 'rounded-[5px]', 'align-middle')
|
||||
element.classList.add(
|
||||
'inline-flex',
|
||||
'items-center',
|
||||
'px-0.5',
|
||||
'h-[22px]',
|
||||
'text-[#155EEF]',
|
||||
'rounded-[5px]',
|
||||
'align-middle'
|
||||
)
|
||||
return element
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export class VariableValueBlockNode extends TextNode {
|
||||
style: this.getStyle(),
|
||||
text: this.getTextContent(),
|
||||
type: 'variable-value-block',
|
||||
version: 1,
|
||||
version: 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,6 @@ export function $createVariableValueBlockNode(text = ''): VariableValueBlockNode
|
||||
return $applyNodeReplacement(new VariableValueBlockNode(text))
|
||||
}
|
||||
|
||||
export function $isVariableValueNodeBlock(
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is VariableValueBlockNode {
|
||||
export function $isVariableValueNodeBlock(node: LexicalNode | null | undefined): node is VariableValueBlockNode {
|
||||
return node instanceof VariableValueBlockNode
|
||||
}
|
||||
|
||||
+8
-18
@@ -1,12 +1,6 @@
|
||||
import {
|
||||
memo,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
} from 'lexical'
|
||||
import { COMMAND_PRIORITY_EDITOR } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
// import {
|
||||
@@ -15,10 +9,7 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
|
||||
import { useSelectOrDelete } from '../../hooks'
|
||||
import type { WorkflowNodesMap } from './node'
|
||||
import { WorkflowVariableBlockNode } from './node'
|
||||
import {
|
||||
DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND,
|
||||
UPDATE_WORKFLOW_NODES_MAP,
|
||||
} from './index'
|
||||
import { DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND, UPDATE_WORKFLOW_NODES_MAP } from './index'
|
||||
// import cn from '@/utils/classnames'
|
||||
// import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
// import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
@@ -36,7 +27,7 @@ type WorkflowVariableBlockComponentProps = {
|
||||
const WorkflowVariableBlockComponent = ({
|
||||
nodeKey,
|
||||
variables,
|
||||
workflowNodesMap = {},
|
||||
workflowNodesMap = {}
|
||||
}: WorkflowVariableBlockComponentProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
@@ -66,14 +57,14 @@ const WorkflowVariableBlockComponent = ({
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
)
|
||||
)
|
||||
}, [editor])
|
||||
|
||||
const Item = (
|
||||
<div
|
||||
className={`mx-0.5 relative group/wrap flex items-center h-[18px] pl-0.5 pr-[3px] rounded-[5px] border select-none ${isSelected ? 'border-[#84ADFF] bg-[#F5F8FF]' : 'border-black/5 bg-white'}` }
|
||||
className={`mx-0.5 relative group/wrap flex items-center h-[18px] pl-0.5 pr-[3px] rounded-[5px] border select-none ${isSelected ? 'border-[#84ADFF] bg-[#F5F8FF]' : 'border-black/5 bg-white'}`}
|
||||
ref={ref}
|
||||
>
|
||||
{/* {!isEnv && !isChatVar && (
|
||||
@@ -93,7 +84,7 @@ const WorkflowVariableBlockComponent = ({
|
||||
<Line3 className='mr-0.5 text-gray-300'></Line3>
|
||||
</div>
|
||||
)} */}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
<div className="flex items-center text-primary-600">
|
||||
{/* {!isEnv && !isChatVar && <Variable02 className='shrink-0 w-3.5 h-3.5' />}
|
||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||
@@ -107,7 +98,6 @@ const WorkflowVariableBlockComponent = ({
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
return Item
|
||||
}
|
||||
|
||||
|
||||
+9
-25
@@ -1,19 +1,9 @@
|
||||
import {
|
||||
memo,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import {
|
||||
$insertNodes,
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
createCommand,
|
||||
} from 'lexical'
|
||||
import { memo, useEffect } from 'react'
|
||||
import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import type { WorkflowVariableBlockType } from '../../types'
|
||||
import {
|
||||
$createWorkflowVariableBlockNode,
|
||||
WorkflowVariableBlockNode,
|
||||
} from './node'
|
||||
import { $createWorkflowVariableBlockNode, WorkflowVariableBlockNode } from './node'
|
||||
// import type { Node } from '@/app/components/workflow/types'
|
||||
|
||||
export const INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND = createCommand('INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND')
|
||||
@@ -26,11 +16,7 @@ export type WorkflowVariableBlockProps = {
|
||||
onInsert?: () => void
|
||||
onDelete?: () => void
|
||||
}
|
||||
const WorkflowVariableBlock = memo(({
|
||||
workflowNodesMap,
|
||||
onInsert,
|
||||
onDelete,
|
||||
}: WorkflowVariableBlockType) => {
|
||||
const WorkflowVariableBlock = memo(({ workflowNodesMap, onInsert, onDelete }: WorkflowVariableBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -51,23 +37,21 @@ const WorkflowVariableBlock = memo(({
|
||||
const workflowVariableBlockNode = $createWorkflowVariableBlockNode(variables, workflowNodesMap)
|
||||
|
||||
$insertNodes([workflowVariableBlockNode])
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
if (onInsert) onInsert()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
),
|
||||
editor.registerCommand(
|
||||
DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND,
|
||||
() => {
|
||||
if (onDelete)
|
||||
onDelete()
|
||||
if (onDelete) onDelete()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
)
|
||||
)
|
||||
}, [editor, onInsert, onDelete, workflowNodesMap])
|
||||
|
||||
|
||||
+6
-3
@@ -63,7 +63,7 @@ export class WorkflowVariableBlockNode extends DecoratorNode<JSX.Element> {
|
||||
type: 'workflow-variable-block',
|
||||
version: 1,
|
||||
variables: this.getVariables(),
|
||||
workflowNodesMap: this.getWorkflowNodesMap(),
|
||||
workflowNodesMap: this.getWorkflowNodesMap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,12 +81,15 @@ export class WorkflowVariableBlockNode extends DecoratorNode<JSX.Element> {
|
||||
return `{{#${this.getVariables().join('.')}#}}`
|
||||
}
|
||||
}
|
||||
export function $createWorkflowVariableBlockNode(variables: string[], workflowNodesMap: WorkflowNodesMap): WorkflowVariableBlockNode {
|
||||
export function $createWorkflowVariableBlockNode(
|
||||
variables: string[],
|
||||
workflowNodesMap: WorkflowNodesMap
|
||||
): WorkflowVariableBlockNode {
|
||||
return new WorkflowVariableBlockNode(variables, workflowNodesMap)
|
||||
}
|
||||
|
||||
export function $isWorkflowVariableBlockNode(
|
||||
node: WorkflowVariableBlockNode | LexicalNode | null | undefined,
|
||||
node: WorkflowVariableBlockNode | LexicalNode | null | undefined
|
||||
): node is WorkflowVariableBlockNode {
|
||||
return node instanceof WorkflowVariableBlockNode
|
||||
}
|
||||
|
||||
+19
-24
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import { memo, useCallback, useEffect } from 'react'
|
||||
import type { TextNode } from 'lexical'
|
||||
import { $applyNodeReplacement } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
@@ -16,10 +12,7 @@ import { WorkflowVariableBlockNode } from './index'
|
||||
|
||||
export const REGEX = /\{\{(#[a-zA-Z0-9_-]{1,50}(\.[a-zA-Z_][a-zA-Z0-9_]{0,29}){1,10}#)\}\}/gi
|
||||
|
||||
const WorkflowVariableBlockReplacementBlock = ({
|
||||
workflowNodesMap,
|
||||
onInsert,
|
||||
}: WorkflowVariableBlockType) => {
|
||||
const WorkflowVariableBlockReplacementBlock = ({ workflowNodesMap, onInsert }: WorkflowVariableBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -27,37 +20,39 @@ const WorkflowVariableBlockReplacementBlock = ({
|
||||
throw new Error('WorkflowVariableBlockNodePlugin: WorkflowVariableBlockNode not registered on editor')
|
||||
}, [editor])
|
||||
|
||||
const createWorkflowVariableBlockNode = useCallback((textNode: TextNode): WorkflowVariableBlockNode => {
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
const createWorkflowVariableBlockNode = useCallback(
|
||||
(textNode: TextNode): WorkflowVariableBlockNode => {
|
||||
if (onInsert) onInsert()
|
||||
|
||||
const nodePathString = textNode.getTextContent().slice(3, -3)
|
||||
return $applyNodeReplacement($createWorkflowVariableBlockNode(nodePathString.split('.'), workflowNodesMap))
|
||||
}, [onInsert, workflowNodesMap])
|
||||
const nodePathString = textNode.getTextContent().slice(3, -3)
|
||||
return $applyNodeReplacement($createWorkflowVariableBlockNode(nodePathString.split('.'), workflowNodesMap))
|
||||
},
|
||||
[onInsert, workflowNodesMap]
|
||||
)
|
||||
|
||||
const getMatch = useCallback((text: string) => {
|
||||
const matchArr = REGEX.exec(text)
|
||||
|
||||
if (matchArr === null)
|
||||
return null
|
||||
if (matchArr === null) return null
|
||||
|
||||
const startOffset = matchArr.index
|
||||
const endOffset = startOffset + matchArr[0].length
|
||||
return {
|
||||
end: endOffset,
|
||||
start: startOffset,
|
||||
start: startOffset
|
||||
}
|
||||
}, [])
|
||||
|
||||
const transformListener = useCallback((textNode: any) => {
|
||||
return decoratorTransform(textNode, getMatch, createWorkflowVariableBlockNode)
|
||||
}, [createWorkflowVariableBlockNode, getMatch])
|
||||
const transformListener = useCallback(
|
||||
(textNode: any) => {
|
||||
return decoratorTransform(textNode, getMatch, createWorkflowVariableBlockNode)
|
||||
},
|
||||
[createWorkflowVariableBlockNode, getMatch]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
REGEX.lastIndex = 0
|
||||
return mergeRegister(
|
||||
editor.registerNodeTransform(CustomTextNode, transformListener),
|
||||
)
|
||||
return mergeRegister(editor.registerNodeTransform(CustomTextNode, transformListener))
|
||||
}, [])
|
||||
|
||||
return null
|
||||
|
||||
+22
-21
@@ -20,7 +20,7 @@ const PromptEditorHeightResizeWrap: FC<Props> = ({
|
||||
onHeightChange,
|
||||
children,
|
||||
footer,
|
||||
hideResize,
|
||||
hideResize
|
||||
}) => {
|
||||
const [clientY, setClientY] = useState(0)
|
||||
const [isResizing, setIsResizing] = useState(false)
|
||||
@@ -38,19 +38,20 @@ const PromptEditorHeightResizeWrap: FC<Props> = ({
|
||||
document.body.style.userSelect = prevUserSelectStyle
|
||||
}, [prevUserSelectStyle])
|
||||
|
||||
const { run: didHandleResize } = useDebounceFn((e) => {
|
||||
if (!isResizing)
|
||||
return
|
||||
const { run: didHandleResize } = useDebounceFn(
|
||||
(e) => {
|
||||
if (!isResizing) return
|
||||
|
||||
const offset = e.clientY - clientY
|
||||
let newHeight = height + offset
|
||||
setClientY(e.clientY)
|
||||
if (newHeight < minHeight)
|
||||
newHeight = minHeight
|
||||
onHeightChange(newHeight)
|
||||
}, {
|
||||
wait: 0,
|
||||
})
|
||||
const offset = e.clientY - clientY
|
||||
let newHeight = height + offset
|
||||
setClientY(e.clientY)
|
||||
if (newHeight < minHeight) newHeight = minHeight
|
||||
onHeightChange(newHeight)
|
||||
},
|
||||
{
|
||||
wait: 0
|
||||
}
|
||||
)
|
||||
|
||||
const handleResize = useCallback(didHandleResize, [isResizing, height, minHeight, clientY])
|
||||
|
||||
@@ -69,12 +70,11 @@ const PromptEditorHeightResizeWrap: FC<Props> = ({
|
||||
}, [handleStopResize])
|
||||
|
||||
return (
|
||||
<div
|
||||
className='relative rounded ant-input-outlined'
|
||||
>
|
||||
<div className={`${className} overflow-y-auto`}
|
||||
<div className="relative rounded ant-input-outlined">
|
||||
<div
|
||||
className={`${className} overflow-y-auto`}
|
||||
style={{
|
||||
height,
|
||||
height
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@@ -83,9 +83,10 @@ const PromptEditorHeightResizeWrap: FC<Props> = ({
|
||||
{footer}
|
||||
{!hideResize && (
|
||||
<div
|
||||
className='absolute bottom-0 left-0 w-full flex justify-center h-2 cursor-row-resize'
|
||||
onMouseDown={handleStartResize}>
|
||||
<div className='w-5 h-[3px] rounded-sm bg-gray-300'></div>
|
||||
className="absolute bottom-0 left-0 w-full flex justify-center h-2 cursor-row-resize"
|
||||
onMouseDown={handleStartResize}
|
||||
>
|
||||
<div className="w-5 h-[3px] rounded-sm bg-gray-300"></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { RoleName } from './plugins/history-block/index'
|
||||
// Node,
|
||||
// } from '@/app/components/workflow/types'
|
||||
|
||||
|
||||
export type NodeOutPutVar = {
|
||||
nodeId: string
|
||||
title: string
|
||||
@@ -12,7 +11,6 @@ export type NodeOutPutVar = {
|
||||
isStartNode?: boolean
|
||||
}
|
||||
|
||||
|
||||
export type Option = {
|
||||
value: string
|
||||
name: string
|
||||
|
||||
@@ -1,45 +1,34 @@
|
||||
import { $isAtNodeEnd } from '@lexical/selection'
|
||||
import type {
|
||||
ElementNode,
|
||||
Klass,
|
||||
LexicalEditor,
|
||||
LexicalNode,
|
||||
RangeSelection,
|
||||
TextNode,
|
||||
} from 'lexical'
|
||||
import {
|
||||
$createTextNode,
|
||||
$getSelection,
|
||||
$isRangeSelection,
|
||||
$isTextNode,
|
||||
} from 'lexical'
|
||||
import type { ElementNode, Klass, LexicalEditor, LexicalNode, RangeSelection, TextNode } from 'lexical'
|
||||
import { $createTextNode, $getSelection, $isRangeSelection, $isTextNode } from 'lexical'
|
||||
import type { EntityMatch } from '@lexical/text'
|
||||
import { CustomTextNode } from './plugins/custom-text/node'
|
||||
import type { MenuTextMatch } from './types'
|
||||
import { CONTEXT_PLACEHOLDER_TEXT, HISTORY_PLACEHOLDER_TEXT, QUERY_PLACEHOLDER_TEXT, PRE_PROMPT_PLACEHOLDER_TEXT, MAX_VAR_KEY_LENGTH } from './constants'
|
||||
import {
|
||||
CONTEXT_PLACEHOLDER_TEXT,
|
||||
HISTORY_PLACEHOLDER_TEXT,
|
||||
QUERY_PLACEHOLDER_TEXT,
|
||||
PRE_PROMPT_PLACEHOLDER_TEXT,
|
||||
MAX_VAR_KEY_LENGTH
|
||||
} from './constants'
|
||||
|
||||
export function getSelectedNode(
|
||||
selection: RangeSelection,
|
||||
): TextNode | ElementNode {
|
||||
export function getSelectedNode(selection: RangeSelection): TextNode | ElementNode {
|
||||
const anchor = selection.anchor
|
||||
const focus = selection.focus
|
||||
const anchorNode = selection.anchor.getNode()
|
||||
const focusNode = selection.focus.getNode()
|
||||
if (anchorNode === focusNode)
|
||||
return anchorNode
|
||||
if (anchorNode === focusNode) return anchorNode
|
||||
|
||||
const isBackward = selection.isBackward()
|
||||
if (isBackward)
|
||||
return $isAtNodeEnd(focus) ? anchorNode : focusNode
|
||||
else
|
||||
return $isAtNodeEnd(anchor) ? anchorNode : focusNode
|
||||
if (isBackward) return $isAtNodeEnd(focus) ? anchorNode : focusNode
|
||||
else return $isAtNodeEnd(anchor) ? anchorNode : focusNode
|
||||
}
|
||||
|
||||
export function registerLexicalTextEntity<T extends TextNode>(
|
||||
editor: LexicalEditor,
|
||||
getMatch: (text: string) => null | EntityMatch,
|
||||
targetNode: Klass<T>,
|
||||
createNode: (textNode: TextNode) => T,
|
||||
createNode: (textNode: TextNode) => T
|
||||
) {
|
||||
const isTargetNode = (node: LexicalNode | null | undefined): node is T => {
|
||||
return node instanceof targetNode
|
||||
@@ -56,8 +45,7 @@ export function registerLexicalTextEntity<T extends TextNode>(
|
||||
}
|
||||
|
||||
const textNodeTransform = (node: TextNode) => {
|
||||
if (!node.isSimpleText())
|
||||
return
|
||||
if (!node.isSimpleText()) return
|
||||
|
||||
const prevSibling = node.getPreviousSibling()
|
||||
let text = node.getTextContent()
|
||||
@@ -73,8 +61,7 @@ export function registerLexicalTextEntity<T extends TextNode>(
|
||||
if (prevMatch === null || getMode(prevSibling) !== 0) {
|
||||
replaceWithSimpleText(prevSibling)
|
||||
return
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const diff = prevMatch.end - previousText.length
|
||||
|
||||
if (diff > 0) {
|
||||
@@ -85,8 +72,7 @@ export function registerLexicalTextEntity<T extends TextNode>(
|
||||
|
||||
if (diff === text.length) {
|
||||
node.remove()
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const remainingText = text.slice(diff)
|
||||
node.setTextContent(remainingText)
|
||||
}
|
||||
@@ -94,8 +80,7 @@ export function registerLexicalTextEntity<T extends TextNode>(
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (prevMatch === null || prevMatch.start < previousText.length) {
|
||||
} else if (prevMatch === null || prevMatch.start < previousText.length) {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -113,44 +98,34 @@ export function registerLexicalTextEntity<T extends TextNode>(
|
||||
const nextMatch = getMatch(nextText)
|
||||
|
||||
if (nextMatch === null) {
|
||||
if (isTargetNode(nextSibling))
|
||||
replaceWithSimpleText(nextSibling)
|
||||
else
|
||||
nextSibling.markDirty()
|
||||
if (isTargetNode(nextSibling)) replaceWithSimpleText(nextSibling)
|
||||
else nextSibling.markDirty()
|
||||
|
||||
return
|
||||
}
|
||||
else if (nextMatch.start !== 0) {
|
||||
} else if (nextMatch.start !== 0) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const nextMatch = getMatch(nextText)
|
||||
|
||||
if (nextMatch !== null && nextMatch.start === 0)
|
||||
return
|
||||
if (nextMatch !== null && nextMatch.start === 0) return
|
||||
}
|
||||
|
||||
if (match === null)
|
||||
return
|
||||
if (match === null) return
|
||||
|
||||
if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity())
|
||||
continue
|
||||
if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity()) continue
|
||||
|
||||
let nodeToReplace
|
||||
|
||||
if (match.start === 0)
|
||||
[nodeToReplace, currentNode] = currentNode.splitText(match.end)
|
||||
else
|
||||
[, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end)
|
||||
if (match.start === 0) [nodeToReplace, currentNode] = currentNode.splitText(match.end)
|
||||
else [, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end)
|
||||
|
||||
const replacementNode = createNode(nodeToReplace)
|
||||
replacementNode.setFormat(nodeToReplace.getFormat())
|
||||
nodeToReplace.replace(replacementNode)
|
||||
|
||||
if (currentNode == null)
|
||||
return
|
||||
if (currentNode == null) return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,8 +156,7 @@ export function registerLexicalTextEntity<T extends TextNode>(
|
||||
if ($isTextNode(nextSibling) && nextSibling.isTextEntity()) {
|
||||
replaceWithSimpleText(nextSibling) // This may have already been converted in the previous block
|
||||
|
||||
if (isTargetNode(node))
|
||||
replaceWithSimpleText(node)
|
||||
if (isTargetNode(node)) replaceWithSimpleText(node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,10 +168,9 @@ export function registerLexicalTextEntity<T extends TextNode>(
|
||||
export const decoratorTransform = (
|
||||
node: CustomTextNode,
|
||||
getMatch: (text: string) => null | EntityMatch,
|
||||
createNode: (textNode: TextNode) => LexicalNode,
|
||||
createNode: (textNode: TextNode) => LexicalNode
|
||||
) => {
|
||||
if (!node.isSimpleText())
|
||||
return
|
||||
if (!node.isSimpleText()) return
|
||||
|
||||
const prevSibling = node.getPreviousSibling()
|
||||
let text = node.getTextContent()
|
||||
@@ -219,79 +192,56 @@ export const decoratorTransform = (
|
||||
if (nextMatch === null) {
|
||||
nextSibling.markDirty()
|
||||
return
|
||||
}
|
||||
else if (nextMatch.start !== 0) {
|
||||
} else if (nextMatch.start !== 0) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const nextMatch = getMatch(nextText)
|
||||
|
||||
if (nextMatch !== null && nextMatch.start === 0)
|
||||
return
|
||||
if (nextMatch !== null && nextMatch.start === 0) return
|
||||
}
|
||||
|
||||
if (match === null)
|
||||
return
|
||||
if (match === null) return
|
||||
|
||||
if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity())
|
||||
continue
|
||||
if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity()) continue
|
||||
|
||||
let nodeToReplace
|
||||
|
||||
if (match.start === 0)
|
||||
[nodeToReplace, currentNode] = currentNode.splitText(match.end)
|
||||
else
|
||||
[, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end)
|
||||
if (match.start === 0) [nodeToReplace, currentNode] = currentNode.splitText(match.end)
|
||||
else [, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end)
|
||||
|
||||
const replacementNode = createNode(nodeToReplace)
|
||||
nodeToReplace.replace(replacementNode)
|
||||
|
||||
if (currentNode == null)
|
||||
return
|
||||
if (currentNode == null) return
|
||||
}
|
||||
}
|
||||
|
||||
function getFullMatchOffset(
|
||||
documentText: string,
|
||||
entryText: string,
|
||||
offset: number,
|
||||
): number {
|
||||
function getFullMatchOffset(documentText: string, entryText: string, offset: number): number {
|
||||
let triggerOffset = offset
|
||||
for (let i = triggerOffset; i <= entryText.length; i++) {
|
||||
if (documentText.substr(-i) === entryText.substr(0, i))
|
||||
triggerOffset = i
|
||||
if (documentText.substr(-i) === entryText.substr(0, i)) triggerOffset = i
|
||||
}
|
||||
return triggerOffset
|
||||
}
|
||||
|
||||
export function $splitNodeContainingQuery(match: MenuTextMatch): TextNode | null {
|
||||
const selection = $getSelection()
|
||||
if (!$isRangeSelection(selection) || !selection.isCollapsed())
|
||||
return null
|
||||
if (!$isRangeSelection(selection) || !selection.isCollapsed()) return null
|
||||
const anchor = selection.anchor
|
||||
if (anchor.type !== 'text')
|
||||
return null
|
||||
if (anchor.type !== 'text') return null
|
||||
const anchorNode = anchor.getNode()
|
||||
if (!anchorNode.isSimpleText())
|
||||
return null
|
||||
if (!anchorNode.isSimpleText()) return null
|
||||
const selectionOffset = anchor.offset
|
||||
const textContent = anchorNode.getTextContent().slice(0, selectionOffset)
|
||||
const characterOffset = match.replaceableString.length
|
||||
const queryOffset = getFullMatchOffset(
|
||||
textContent,
|
||||
match.matchingString,
|
||||
characterOffset,
|
||||
)
|
||||
const queryOffset = getFullMatchOffset(textContent, match.matchingString, characterOffset)
|
||||
const startOffset = selectionOffset - queryOffset
|
||||
if (startOffset < 0)
|
||||
return null
|
||||
if (startOffset < 0) return null
|
||||
let newNode
|
||||
if (startOffset === 0)
|
||||
[newNode] = anchorNode.splitText(selectionOffset)
|
||||
else
|
||||
[, newNode] = anchorNode.splitText(startOffset, selectionOffset)
|
||||
if (startOffset === 0) [newNode] = anchorNode.splitText(selectionOffset)
|
||||
else [, newNode] = anchorNode.splitText(startOffset, selectionOffset)
|
||||
|
||||
return newNode
|
||||
}
|
||||
@@ -303,49 +253,57 @@ export function textToEditorState(text: string) {
|
||||
root: {
|
||||
children: paragraph.map((p) => {
|
||||
return {
|
||||
children: [{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: p,
|
||||
type: 'custom-text',
|
||||
version: 1,
|
||||
}],
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: p,
|
||||
type: 'custom-text',
|
||||
version: 1
|
||||
}
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
version: 1
|
||||
}
|
||||
}),
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'root',
|
||||
version: 1,
|
||||
},
|
||||
version: 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
const varRegex = /\{\{(.+?)\}\}/g
|
||||
export const getVars = (value: string) => {
|
||||
if (!value)
|
||||
return []
|
||||
if (!value) return []
|
||||
|
||||
const keys = value.match(varRegex)?.filter((item) => {
|
||||
return ![CONTEXT_PLACEHOLDER_TEXT, HISTORY_PLACEHOLDER_TEXT, QUERY_PLACEHOLDER_TEXT, PRE_PROMPT_PLACEHOLDER_TEXT].includes(item)
|
||||
}).map((item) => {
|
||||
return item.replace('{{', '').replace('}}', '')
|
||||
}).filter(key => key.length <= MAX_VAR_KEY_LENGTH) || []
|
||||
const keys =
|
||||
value
|
||||
.match(varRegex)
|
||||
?.filter((item) => {
|
||||
return ![
|
||||
CONTEXT_PLACEHOLDER_TEXT,
|
||||
HISTORY_PLACEHOLDER_TEXT,
|
||||
QUERY_PLACEHOLDER_TEXT,
|
||||
PRE_PROMPT_PLACEHOLDER_TEXT
|
||||
].includes(item)
|
||||
})
|
||||
.map((item) => {
|
||||
return item.replace('{{', '').replace('}}', '')
|
||||
})
|
||||
.filter((key) => key.length <= MAX_VAR_KEY_LENGTH) || []
|
||||
const keyObj: Record<string, boolean> = {}
|
||||
// remove duplicate keys
|
||||
const res: string[] = []
|
||||
keys.forEach((key) => {
|
||||
if (keyObj[key])
|
||||
return
|
||||
if (keyObj[key]) return
|
||||
|
||||
keyObj[key] = true
|
||||
res.push(key)
|
||||
|
||||
+3
-11
@@ -1,14 +1,6 @@
|
||||
import { $t } from "@common/locales"
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
export type PARAM_TYPE =
|
||||
| 'string'
|
||||
| 'float'
|
||||
| 'integer'
|
||||
| 'boolean'
|
||||
| 'date'
|
||||
| 'time'
|
||||
| 'datatime'
|
||||
| string
|
||||
export type PARAM_TYPE = 'string' | 'float' | 'integer' | 'boolean' | 'date' | 'time' | 'datatime' | string
|
||||
export type PARAM_KEY_REF_TYPE = {
|
||||
key: string
|
||||
type: string
|
||||
@@ -17,7 +9,7 @@ export type PARAM_KEY_REF_TYPE = {
|
||||
attribute?: string
|
||||
description?: string
|
||||
filter?: string
|
||||
arrayItemKey?:string
|
||||
arrayItemKey?: string
|
||||
}
|
||||
export type PARAM_TYPE_REF_TYPE = {
|
||||
[key: string | number]: PARAM_TYPE
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { parseFormData, parseFileValue, parseFileType, parseHeaders, parseRequestBodyToString, parseUri, payloadStr, goCodeParseFormData } from './transform'
|
||||
import {
|
||||
parseFormData,
|
||||
parseFileValue,
|
||||
parseFileType,
|
||||
parseHeaders,
|
||||
parseRequestBodyToString,
|
||||
parseUri,
|
||||
payloadStr,
|
||||
goCodeParseFormData
|
||||
} from './transform'
|
||||
// import { getJson } from '../.@common/utils/';
|
||||
import { ApiBodyType } from '@common/const/api-detail';
|
||||
import { $t } from '@common/locales';
|
||||
import { ApiBodyType } from '@common/const/api-detail'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
function sameNameToParams(params: unknown) {
|
||||
params = cloneDeep(params)
|
||||
@@ -54,9 +63,9 @@ function enrichParams(params: unknown) {
|
||||
export function generateCode(
|
||||
type: string,
|
||||
multipart: boolean,
|
||||
{ protocol, URL, headers, params, method, requestType, apiRequestParamJsonType, raw }: unknown,
|
||||
{ protocol, URL, headers, params, method, requestType, apiRequestParamJsonType, raw }: unknown
|
||||
) {
|
||||
requestType=ApiBodyType[requestType]
|
||||
requestType = ApiBodyType[requestType]
|
||||
let code: string = ''
|
||||
const indent = ' '
|
||||
let urlObj: unknown = {}
|
||||
@@ -243,9 +252,7 @@ export function generateCode(
|
||||
`${indent}"headers": {\r\n` +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
`${indent}},\r\n` +
|
||||
`${
|
||||
multipart ? `${indent}"processData": false,\r\n${indent}"contentType": false,\r\n` : ''
|
||||
}` +
|
||||
`${multipart ? `${indent}"processData": false,\r\n${indent}"contentType": false,\r\n` : ''}` +
|
||||
`${indent}"data": data,\r\n` +
|
||||
`${indent}"crossDomain": true\r\n` +
|
||||
'})\r\n' +
|
||||
@@ -334,14 +341,10 @@ export function generateCode(
|
||||
'var requestInfo={\r\n' +
|
||||
`${indent}"method": "${method}",\r\n` +
|
||||
`${urlObj.hostname ? `${indent}"hostname": "${urlObj.hostname}",\r\n` : ''}` +
|
||||
`${
|
||||
urlObj.port ? `${indent}"port": "${urlObj.port}",\r\n` : ''
|
||||
}` +
|
||||
`${urlObj.port ? `${indent}"port": "${urlObj.port}",\r\n` : ''}` +
|
||||
`${
|
||||
urlObj.pathname || urlObj.search
|
||||
? `${indent}"path": "${urlObj.pathname || ''}${
|
||||
urlObj.search || ''
|
||||
}",\r\n`
|
||||
? `${indent}"path": "${urlObj.pathname || ''}${urlObj.search || ''}",\r\n`
|
||||
: ''
|
||||
}` +
|
||||
`${indent}"headers": {\r\n` +
|
||||
@@ -366,19 +369,11 @@ export function generateCode(
|
||||
`var http = require("${urlObj.protocol.replace(':', '')}");\r\n` +
|
||||
'var requestInfo={\r\n' +
|
||||
`${indent}"method": "${method}",\r\n` +
|
||||
`${
|
||||
urlObj.hostname
|
||||
? `${indent}"hostname": "${urlObj.hostname}",\r\n`
|
||||
: ''
|
||||
}` +
|
||||
`${
|
||||
urlObj.port ? `${indent}"port": "${urlObj.port}",\r\n` : ''
|
||||
}` +
|
||||
`${urlObj.hostname ? `${indent}"hostname": "${urlObj.hostname}",\r\n` : ''}` +
|
||||
`${urlObj.port ? `${indent}"port": "${urlObj.port}",\r\n` : ''}` +
|
||||
`${
|
||||
urlObj.pathname || urlObj.search
|
||||
? `${indent}"path": "${urlObj.pathname || ''}${
|
||||
urlObj.search || ''
|
||||
}",\r\n`
|
||||
? `${indent}"path": "${urlObj.pathname || ''}${urlObj.search || ''}",\r\n`
|
||||
: ''
|
||||
}` +
|
||||
`${indent}"headers": {\r\n` +
|
||||
@@ -439,11 +434,7 @@ export function generateCode(
|
||||
}
|
||||
}
|
||||
code =
|
||||
`${
|
||||
(requestType || 'FORMDATA') === 'FORMDATA' && multipart
|
||||
? 'var fs = require("fs");\r\n'
|
||||
: ''
|
||||
}` +
|
||||
`${(requestType || 'FORMDATA') === 'FORMDATA' && multipart ? 'var fs = require("fs");\r\n' : ''}` +
|
||||
'var request = require("request");\r\n' +
|
||||
'var requestInfo={\r\n' +
|
||||
` method: "${method}",\r\n` +
|
||||
@@ -472,7 +463,7 @@ export function generateCode(
|
||||
params = sameNameToParams(params)
|
||||
let tmpOutput: unknown = ''
|
||||
params.map((val: unknown, key: number) => {
|
||||
if(val.data_type === 'file') {
|
||||
if (val.data_type === 'file') {
|
||||
tmpOutput +=
|
||||
` "${val.name}" =>array(\r\n` +
|
||||
` "type" => "${parseFileType(val.value)}",\r\n` +
|
||||
@@ -481,11 +472,9 @@ export function generateCode(
|
||||
` )${key === params.length - 1 ? '' : ','}\r\n`
|
||||
} else {
|
||||
tmpOutput += ` ${JSON.stringify(val.name)} => ${JSON.stringify(val.value)}\r`
|
||||
|
||||
}
|
||||
})
|
||||
langTmp.paramsStr =
|
||||
'addForm(' + `${tmpOutput.length ? `array(\r\n${tmpOutput})` : ''}` + '\r\n);'
|
||||
langTmp.paramsStr = 'addForm(' + `${tmpOutput.length ? `array(\r\n${tmpOutput})` : ''}` + '\r\n);'
|
||||
langTmp.fileValue = parseFileValue(params)
|
||||
} else {
|
||||
langTmp.paramsStr = `append(new http\\QueryString(array({\r\n${parseFormData(params, {
|
||||
@@ -516,9 +505,7 @@ export function generateCode(
|
||||
const tmpOutput: unknown = []
|
||||
urlObj.searchParams.forEach((val: unknown, key: unknown) => {
|
||||
tmpOutput.push(` ${JSON.stringify(key)} => ${JSON.stringify(val)}`)
|
||||
langTmp.queryStr = `$request->setQuery(new http\\QueryString(array(\r\n${tmpOutput.join(
|
||||
',\r\n'
|
||||
)}\r\n)));`
|
||||
langTmp.queryStr = `$request->setQuery(new http\\QueryString(array(\r\n${tmpOutput.join(',\r\n')}\r\n)));`
|
||||
})
|
||||
if (multipart) {
|
||||
code =
|
||||
@@ -534,7 +521,7 @@ export function generateCode(
|
||||
'$request->setBody($body);\r\n\r\n' +
|
||||
`$request->getBody()->${langTmp.paramsStr}\r\n\r\n` +
|
||||
'$request->setHeaders(array(\r\n' +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
' "Content-Type":"multipart/form-data"\r\n' +
|
||||
'));\r\n\r\n' +
|
||||
'$client->enqueue($request)->send();\r\n' +
|
||||
@@ -552,7 +539,7 @@ export function generateCode(
|
||||
'$request->setBody($body);\r\n\r\n' +
|
||||
`${langTmp.queryStr ? `${langTmp.queryStr}\r\n\r\n` : ''}` +
|
||||
'$request->setHeaders(array(\r\n' +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
'));\r\n\r\n' +
|
||||
'$client->enqueue($request)->send();\r\n' +
|
||||
'$response = $client->getResponse();\r\n\r\n' +
|
||||
@@ -572,9 +559,7 @@ export function generateCode(
|
||||
let tmpOutput = ''
|
||||
params.map((val: unknown, key: number) => {
|
||||
if (val.data_type === 'file') {
|
||||
tmpOutput += ` "${val.name}" => new CURLFile($file_path)${
|
||||
key === params.length - 1 ? '' : ','
|
||||
}\r\n`
|
||||
tmpOutput += ` "${val.name}" => new CURLFile($file_path)${key === params.length - 1 ? '' : ','}\r\n`
|
||||
} else {
|
||||
tmpOutput += ` ${JSON.stringify(val.name)} => ${JSON.stringify(val.value)}\r`
|
||||
}
|
||||
@@ -614,7 +599,7 @@ export function generateCode(
|
||||
` CURLOPT_CUSTOMREQUEST => "${method}",\r\n` +
|
||||
` CURLOPT_POSTFIELDS => ${langTmp.paramsStr},\r\n` +
|
||||
' CURLOPT_HTTPHEADER => array(\r\n' +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr},\r\n` : ''}` +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr},\r\n` : ''}` +
|
||||
' "Content-Type:multipart/form-data"' +
|
||||
'\r\n ),\r\n' +
|
||||
'));\r\n\r\n' +
|
||||
@@ -769,36 +754,33 @@ export function generateCode(
|
||||
tmpOutput.push(`${JSON.stringify(key)} : ${JSON.stringify(val)}`)
|
||||
// langTmp.querystring = `querystring={${tmpOutput.join(',')}};`
|
||||
langTmp.querystring = `{${tmpOutput.join(',')}}`
|
||||
|
||||
})
|
||||
if(multipart) {
|
||||
code =
|
||||
'import requests \r\n\r\n' +
|
||||
'headers = {\r\n' +
|
||||
`${langTmp.headerStr}\r\n` +
|
||||
'}\r\n' +
|
||||
`url = "${method === 'GET' ? urlObj.origin + urlObj.pathname : urlObj.href}"\r\n` +
|
||||
'//获取文件,需填路径 \r\n' +
|
||||
"file_path = '' \r\n" +
|
||||
`filename = "${langTmp.fileValue}" \r\n` +
|
||||
`filetype = "${langTmp.fileType}" \r\n` +
|
||||
'data = { \r\n' +
|
||||
`${langTmp.paramsStr}\r\n` +
|
||||
'} \r\n' +
|
||||
'files = {"file": (filename, open(file_path, "rb"), filetype)} \r\n' +
|
||||
`response=requests.${method.toLowerCase()}(url, files=files, headers=headers, data=data)\r\n` +
|
||||
'print(response.text)\r\n'
|
||||
if (multipart) {
|
||||
code =
|
||||
'import requests \r\n\r\n' +
|
||||
'headers = {\r\n' +
|
||||
`${langTmp.headerStr}\r\n` +
|
||||
'}\r\n' +
|
||||
`url = "${method === 'GET' ? urlObj.origin + urlObj.pathname : urlObj.href}"\r\n` +
|
||||
'//获取文件,需填路径 \r\n' +
|
||||
"file_path = '' \r\n" +
|
||||
`filename = "${langTmp.fileValue}" \r\n` +
|
||||
`filetype = "${langTmp.fileType}" \r\n` +
|
||||
'data = { \r\n' +
|
||||
`${langTmp.paramsStr}\r\n` +
|
||||
'} \r\n' +
|
||||
'files = {"file": (filename, open(file_path, "rb"), filetype)} \r\n' +
|
||||
`response=requests.${method.toLowerCase()}(url, files=files, headers=headers, data=data)\r\n` +
|
||||
'print(response.text)\r\n'
|
||||
} else {
|
||||
code =
|
||||
'import requests\r\n\r\n' +
|
||||
`url = "${method === 'GET' ? urlObj.origin + urlObj.pathname : urlObj.href}"\r\n\r\n` +
|
||||
`payload = ${
|
||||
langTmp.querystring ? langTmp.querystring : langTmp.paramsStr
|
||||
}\r\n\r\n` +
|
||||
`headers = {\r\n${langTmp.headerStr}` +
|
||||
'\r\n}\r\n\r\n' +
|
||||
`response=requests.request("${method}", url, ${payloadStr(method, headers)}, headers=headers)\r\n\r\n` +
|
||||
'print(response.text)\r\n'
|
||||
code =
|
||||
'import requests\r\n\r\n' +
|
||||
`url = "${method === 'GET' ? urlObj.origin + urlObj.pathname : urlObj.href}"\r\n\r\n` +
|
||||
`payload = ${langTmp.querystring ? langTmp.querystring : langTmp.paramsStr}\r\n\r\n` +
|
||||
`headers = {\r\n${langTmp.headerStr}` +
|
||||
'\r\n}\r\n\r\n' +
|
||||
`response=requests.request("${method}", url, ${payloadStr(method, headers)}, headers=headers)\r\n\r\n` +
|
||||
'print(response.text)\r\n'
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -815,9 +797,7 @@ export function generateCode(
|
||||
let tmpOutput = ''
|
||||
params.map((val: unknown, key: number) => {
|
||||
if (val.data_type === 'file') {
|
||||
tmpOutput += ` "${val.name}" => new CURLFile($file_path)${
|
||||
key === params.length - 1 ? '' : ','
|
||||
}\r\n`
|
||||
tmpOutput += ` "${val.name}" => new CURLFile($file_path)${key === params.length - 1 ? '' : ','}\r\n`
|
||||
} else {
|
||||
tmpOutput += ` ${JSON.stringify(val.name)} => ${JSON.stringify(val.value)}\r`
|
||||
}
|
||||
@@ -847,7 +827,7 @@ export function generateCode(
|
||||
`request = Net::HTTP::${method.toLowerCase().replace(/^\S/, (s: string) => {
|
||||
return s?.toUpperCase()
|
||||
})}.new(url)\r\n` +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
`request.body = ${langTmp.paramsStr}\r\n\r\n` +
|
||||
'response = http.request(request)\r\n' +
|
||||
'puts response.read_body'
|
||||
@@ -883,25 +863,19 @@ export function generateCode(
|
||||
}
|
||||
}
|
||||
code =
|
||||
`${
|
||||
requestType === 'JSON' ? `printf '${langTmp.paramsStr}'|` : ''
|
||||
} http ${
|
||||
(requestType || 'FORMDATA').toString() === 'FORMDATA' &&
|
||||
!multipart
|
||||
`${requestType === 'JSON' ? `printf '${langTmp.paramsStr}'|` : ''} http ${
|
||||
(requestType || 'FORMDATA').toString() === 'FORMDATA' && !multipart
|
||||
? '--form'
|
||||
: (requestType || 'JSON').toString() === 'JSON' &&
|
||||
!multipart
|
||||
? '--follow'
|
||||
: ''
|
||||
: (requestType || 'JSON').toString() === 'JSON' && !multipart
|
||||
? '--follow'
|
||||
: ''
|
||||
} ${multipart ? '--ignore-stdin --form --follow' : ''} ${method} '${
|
||||
urlObj.href
|
||||
}' ${multipart ? '\\\r' : '\\'}` +
|
||||
`${multipart ? langTmp.paramsStr : ''}\r` +
|
||||
`${langTmp.headerStr}` +
|
||||
`${
|
||||
(requestType || 'FORMDATA').toString() === 'FORMDATA' &&
|
||||
!multipart &&
|
||||
langTmp.paramsStr
|
||||
(requestType || 'FORMDATA').toString() === 'FORMDATA' && !multipart && langTmp.paramsStr
|
||||
? ` \\\r\n${langTmp.paramsStr}`
|
||||
: ''
|
||||
}`
|
||||
@@ -923,7 +897,6 @@ export function generateCode(
|
||||
}
|
||||
})
|
||||
langTmp.paramsStr = tmpOutput
|
||||
|
||||
} else {
|
||||
const tmpOutput = parseFormData(params, {
|
||||
format: '${name}=${value}',
|
||||
@@ -1024,7 +997,7 @@ export function generateCode(
|
||||
' if err != nil {\r\n' +
|
||||
' return nil, err\r\n' +
|
||||
' }\r\n' +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
' req.Header.Add("Content-Type", writer.FormDataContentType())\r\n\r\n' +
|
||||
' client := &http.Client{}\r\n' +
|
||||
' resp, err := client.Do(req)\r\n' +
|
||||
@@ -1071,7 +1044,7 @@ export function generateCode(
|
||||
}
|
||||
default: {
|
||||
langTmp.paramsStr = requestParam ? JSON.stringify(requestParam) : ''
|
||||
code =
|
||||
code =
|
||||
'package main\r\n\r\n' +
|
||||
'import (\r\n' +
|
||||
' "bytes"\r\n' +
|
||||
@@ -1091,7 +1064,7 @@ export function generateCode(
|
||||
'func request() ([]byte, error) {\r\n' +
|
||||
` uri := "${urlObj.href}"\r\n\r\n` +
|
||||
` ${
|
||||
langTmp.paramsStr
|
||||
langTmp.paramsStr
|
||||
? ` payload := map[string]interface{}${langTmp.paramsStr}`
|
||||
: ' payload := strings.NewReader("")'
|
||||
}\r\n\r\n` +
|
||||
@@ -1116,7 +1089,7 @@ export function generateCode(
|
||||
map: stringifyHeaders
|
||||
})
|
||||
let mediaType = 'application/octet-stream'
|
||||
switch ( (requestType || 'FORMDATA').toUpperCase()) {
|
||||
switch ((requestType || 'FORMDATA').toUpperCase()) {
|
||||
case 'FORMDATA': {
|
||||
if (multipart) {
|
||||
mediaType = 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW'
|
||||
@@ -1154,8 +1127,8 @@ export function generateCode(
|
||||
break
|
||||
}
|
||||
}
|
||||
if(multipart) {
|
||||
code =
|
||||
if (multipart) {
|
||||
code =
|
||||
'OkHttpClient client = new OkHttpClient();\r\n\r\n' +
|
||||
'//获取文件,需填路径 \r\n' +
|
||||
'File file = new File(""); \r\n\r\n' +
|
||||
@@ -1175,11 +1148,7 @@ export function generateCode(
|
||||
code =
|
||||
'OkHttpClient client = new OkHttpClient().newBuilder().build();\r\n' +
|
||||
`MediaType mediaType = MediaType.parse("${mediaType}");\r\n` +
|
||||
`${
|
||||
method === 'GET'
|
||||
? ''
|
||||
: `RequestBody body = RequestBody.create(mediaType, ${langTmp.paramsStr});\r\n`
|
||||
}` +
|
||||
`${method === 'GET' ? '' : `RequestBody body = RequestBody.create(mediaType, ${langTmp.paramsStr});\r\n`}` +
|
||||
'Request request = new Request.Builder()\r\n' +
|
||||
` .url("${urlObj.href}")\r\n` +
|
||||
` .method("${method}",${method === 'GET' ? 'null' : 'body'})\r\n` +
|
||||
@@ -1188,7 +1157,7 @@ export function generateCode(
|
||||
'Response response = client.newCall(request).execute();\r\n' +
|
||||
'System.out.println(response.body().string());\r\n'
|
||||
}
|
||||
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1233,9 +1202,7 @@ export function generateCode(
|
||||
`${indent}"header": {\r\n` +
|
||||
`${langTmp.headerStr}\r\n` +
|
||||
`${indent}},\r\n` +
|
||||
`${
|
||||
multipart ? `${indent}"processData": false,\r\n${indent}"contentType": false,\r\n` : ''
|
||||
}` +
|
||||
`${multipart ? `${indent}"processData": false,\r\n${indent}"contentType": false,\r\n` : ''}` +
|
||||
`${langTmp.paramsStr ? `${indent}"data": data,\r\n` : ''}` +
|
||||
`${indent}"success": (response)=> {\r\n` +
|
||||
`${indent + indent}console.log(response.data)\r\n` +
|
||||
|
||||
@@ -1,218 +1,228 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { Cascader } from 'antd';
|
||||
import CODE_LANG from '@common/const/code/const';
|
||||
import type { DefaultOptionType } from 'antd/es/cascader';
|
||||
import { useState } from 'react';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { paramsJsonType } from './code-snippets.type';
|
||||
import { DOMAIN_SUFIX } from './code-example.type';
|
||||
import { transfromUrlParam } from './transform';
|
||||
import { generateCode } from './generate-code';
|
||||
import {ApiDetail} from "@common/const/api-detail";
|
||||
import {Codebox} from "@common/components/postcat//api/Codebox";
|
||||
import {Collapse} from "@common/components/postcat/api/Collapse";
|
||||
import {Box} from "@mui/material";
|
||||
import { $t } from '@common/locales';
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { Cascader } from 'antd'
|
||||
import CODE_LANG from '@common/const/code/const'
|
||||
import type { DefaultOptionType } from 'antd/es/cascader'
|
||||
import { useState } from 'react'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { paramsJsonType } from './code-snippets.type'
|
||||
import { DOMAIN_SUFIX } from './code-example.type'
|
||||
import { transfromUrlParam } from './transform'
|
||||
import { generateCode } from './generate-code'
|
||||
import { ApiDetail } from '@common/const/api-detail'
|
||||
import { Codebox } from '@common/components/postcat//api/Codebox'
|
||||
import { Collapse } from '@common/components/postcat/api/Collapse'
|
||||
import { Box } from '@mui/material'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
|
||||
type CodeSnippetCompoType = {
|
||||
title:string
|
||||
api:ApiDetail,
|
||||
extraTitle:unknown,
|
||||
extraContent:unknown,
|
||||
minLines:number
|
||||
title: string
|
||||
api: ApiDetail
|
||||
extraTitle: unknown
|
||||
extraContent: unknown
|
||||
minLines: number
|
||||
}
|
||||
|
||||
const file: unknown[] = []
|
||||
const env: unknown = {}
|
||||
const loading: boolean = false
|
||||
const codeMode: string = 'rust'
|
||||
const codeMens: string[] = ['reset', 'copy', 'download', 'newTab', 'search']
|
||||
const DOMAIN_REGEX: RegExp = new RegExp(
|
||||
`^(((http|ftp|https):\/\/)|)(([\\\w\\\-_]+([\\\w\\\-\\\.]*)?(\\\.(${DOMAIN_SUFIX.join(
|
||||
'|'
|
||||
)})))|((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(localhost))((\\\/)|(\\\?)|(:)|($))`
|
||||
)
|
||||
const file: unknown[] = []
|
||||
const env: unknown = {}
|
||||
const loading: boolean = false
|
||||
const codeMode: string = 'rust'
|
||||
const codeMens: string[] = ['reset', 'copy', 'download', 'newTab', 'search']
|
||||
const DOMAIN_REGEX: RegExp = new RegExp(
|
||||
`^(((http|ftp|https):\/\/)|)(([\\\w\\\-_]+([\\\w\\\-\\\.]*)?(\\\.(${DOMAIN_SUFIX.join(
|
||||
'|'
|
||||
)})))|((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(localhost))((\\\/)|(\\\?)|(:)|($))`
|
||||
)
|
||||
|
||||
let isMultipart: boolean = false
|
||||
let isMultipart: boolean = false
|
||||
|
||||
export default function CodeSnippetCompo({title,api, extraTitle, extraContent, minLines=15}: CodeSnippetCompoType) {
|
||||
const {state } = useGlobalContext()
|
||||
// const [tokenState ] = useTokenBasicInfo()
|
||||
const pretreatmentRequestInfo = (apiDoc: ApiDetail) =>{
|
||||
isMultipart = false
|
||||
const result: ApiDetail = cloneDeep(apiDoc)
|
||||
const files: unknown = file || []
|
||||
let isMuti: boolean = false
|
||||
const headers: string[] = []
|
||||
let alreadyHadContentType: boolean = false
|
||||
result.headers = []
|
||||
const originHeader = apiDoc.requestParams?.headerParams
|
||||
//处理请求头部
|
||||
originHeader?.forEach((header: unknown) => {
|
||||
// if (
|
||||
// tokenState?.selected_X_apibee_token &&
|
||||
// originHeader.length &&
|
||||
// originHeader[0].name == 'X-APISpace-Token'
|
||||
// ) {
|
||||
// originHeader[0].value = tokenState?.selected_X_apibee_token
|
||||
// }
|
||||
const { checkbox, name } = header
|
||||
if ((checkbox || !header.hasOwnProperty('checkbox')) && name) {
|
||||
headers.push(name?.toLowerCase())
|
||||
result.headers.push(header)
|
||||
if (/content-type/i.test(name)) {
|
||||
alreadyHadContentType = true
|
||||
}
|
||||
export default function CodeSnippetCompo({
|
||||
title,
|
||||
api,
|
||||
extraTitle,
|
||||
extraContent,
|
||||
minLines = 15
|
||||
}: CodeSnippetCompoType) {
|
||||
const { state } = useGlobalContext()
|
||||
// const [tokenState ] = useTokenBasicInfo()
|
||||
const pretreatmentRequestInfo = (apiDoc: ApiDetail) => {
|
||||
isMultipart = false
|
||||
const result: ApiDetail = cloneDeep(apiDoc)
|
||||
const files: unknown = file || []
|
||||
let isMuti: boolean = false
|
||||
const headers: string[] = []
|
||||
let alreadyHadContentType: boolean = false
|
||||
result.headers = []
|
||||
const originHeader = apiDoc.requestParams?.headerParams
|
||||
//处理请求头部
|
||||
originHeader?.forEach((header: unknown) => {
|
||||
// if (
|
||||
// tokenState?.selected_X_apibee_token &&
|
||||
// originHeader.length &&
|
||||
// originHeader[0].name == 'X-APISpace-Token'
|
||||
// ) {
|
||||
// originHeader[0].value = tokenState?.selected_X_apibee_token
|
||||
// }
|
||||
const { checkbox, name } = header
|
||||
if ((checkbox || !header.hasOwnProperty('checkbox')) && name) {
|
||||
headers.push(name?.toLowerCase())
|
||||
result.headers.push(header)
|
||||
if (/content-type/i.test(name)) {
|
||||
alreadyHadContentType = true
|
||||
}
|
||||
})
|
||||
const query: unknown = {}
|
||||
}
|
||||
})
|
||||
const query: unknown = {}
|
||||
|
||||
apiDoc.requestParams?.queryParams?.forEach((query: unknown) => {
|
||||
const { checkbox, name } = query
|
||||
if ((checkbox || !query.hasOwnProperty('checkbox')) && name) {
|
||||
query[name] = query?.paramAttr.example || ''
|
||||
apiDoc.requestParams?.queryParams?.forEach((query: unknown) => {
|
||||
const { checkbox, name } = query
|
||||
if ((checkbox || !query.hasOwnProperty('checkbox')) && name) {
|
||||
query[name] = query?.paramAttr.example || ''
|
||||
}
|
||||
})
|
||||
result.URL = transfromUrlParam(result.uri, query)
|
||||
|
||||
//处理 restful 参数
|
||||
apiDoc.requestParams?.restParams?.forEach((rest: unknown) => {
|
||||
if ((rest.checkbox || !rest.hasOwnProperty('checkbox')) && rest.name && rest.paramAttr.example) {
|
||||
if (eval(`/:${rest.name}/`).test(result.URL.trim())) {
|
||||
result.URL = result.URL.replaceAll(`:${rest.name}`, rest.paramAttr.example)
|
||||
} else if (
|
||||
result.URL.trim().indexOf(`{{${rest.name}}}`) == -1 &&
|
||||
result.URL.trim().indexOf(`{${rest.name}}`) > -1
|
||||
) {
|
||||
result.URL = result.URL.replaceAll(`{${rest.name}}`, rest.paramAttr.example)
|
||||
}
|
||||
})
|
||||
result.URL = transfromUrlParam(result.uri, query)
|
||||
}
|
||||
})
|
||||
|
||||
//处理 restful 参数
|
||||
apiDoc.requestParams?.restParams?.forEach((rest: unknown) => {
|
||||
if ((rest.checkbox || !rest.hasOwnProperty('checkbox')) && rest.name && rest.paramAttr.example) {
|
||||
if (eval(`/:${rest.name}/`).test(result.URL.trim())) {
|
||||
result.URL = result.URL.replaceAll(`:${rest.name}`, rest.paramAttr.example )
|
||||
} else if (
|
||||
result.URL.trim().indexOf(`{{${rest.name}}}`) == -1 &&
|
||||
result.URL.trim().indexOf(`{${rest.name}}`) > -1
|
||||
) {
|
||||
result.URL = result.URL.replaceAll(`{${rest.name}}`, rest.paramAttr.example)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
result.params = []
|
||||
//为请求参数 中的header、reset、body、query 添加 value 和 valueQuery 的值
|
||||
switch (result.requestParams?.bodyParams?.[0]?.contentType) {
|
||||
case 0: {
|
||||
result.requestParams?.bodyParams?.forEach((body: unknown, key: unknown) => {
|
||||
if ((body.checkbox || !body.hasOwnProperty('checkbox')) && body.name) {
|
||||
if (paramsJsonType[body.dataType] == 'string' && body.paramAttr.example) {
|
||||
isMuti = true
|
||||
body.files = files[key] || []
|
||||
}
|
||||
result.params.push(body)
|
||||
result.params = []
|
||||
//为请求参数 中的header、reset、body、query 添加 value 和 valueQuery 的值
|
||||
switch (result.requestParams?.bodyParams?.[0]?.contentType) {
|
||||
case 0: {
|
||||
result.requestParams?.bodyParams?.forEach((body: unknown, key: unknown) => {
|
||||
if ((body.checkbox || !body.hasOwnProperty('checkbox')) && body.name) {
|
||||
if (paramsJsonType[body.dataType] == 'string' && body.paramAttr.example) {
|
||||
isMuti = true
|
||||
body.files = files[key] || []
|
||||
}
|
||||
result.params.push(body)
|
||||
}
|
||||
})
|
||||
if (!alreadyHadContentType) {
|
||||
if (isMuti) {
|
||||
isMultipart = true
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW',
|
||||
checkbox: true
|
||||
})
|
||||
} else {
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'application/x-www-form-urlencoded',
|
||||
checkbox: true
|
||||
})
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 1: {
|
||||
result.params = apiDoc.requestParams?.bodyParams
|
||||
break
|
||||
}
|
||||
case 2: {
|
||||
result.params = apiDoc.requestParams?.bodyParams
|
||||
if (!alreadyHadContentType) {
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'application/json',
|
||||
checkbox: true
|
||||
})
|
||||
if (!alreadyHadContentType) {
|
||||
if (isMuti) {
|
||||
isMultipart = true
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW',
|
||||
checkbox: true
|
||||
})
|
||||
} else {
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'application/x-www-form-urlencoded',
|
||||
checkbox: true
|
||||
})
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 1: {
|
||||
result.params = apiDoc.requestParams?.bodyParams
|
||||
break
|
||||
}
|
||||
case 2: {
|
||||
result.params = apiDoc.requestParams?.bodyParams
|
||||
if (!alreadyHadContentType) {
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'application/json',
|
||||
checkbox: true
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case 3: {
|
||||
result.params = apiDoc.requestParams?.bodyParams
|
||||
if (!alreadyHadContentType) {
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'application/xml',
|
||||
checkbox: true
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
case 3: {
|
||||
result.params = apiDoc.requestParams?.bodyParams
|
||||
if (!alreadyHadContentType) {
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'application/xml',
|
||||
checkbox: true
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
result.requestType = result.requestParams?.bodyParams?.[0]?.contentType || 0
|
||||
return result
|
||||
}
|
||||
|
||||
const [code, setCode] = useState<string>('')
|
||||
const [lang, setLang] = useState<number[]>([20])
|
||||
result.requestType = result.requestParams?.bodyParams?.[0]?.contentType || 0
|
||||
return result
|
||||
}
|
||||
|
||||
let tempCode = ''
|
||||
const getCode = (language: number | string) => {
|
||||
if (!['HTTPS', 'HTTP'].includes(api.protocol?.toUpperCase())) {
|
||||
tempCode = $t('暂不支持生成非 HTTPS 或非 HTTP 协议的代码示例')
|
||||
setCode(tempCode)
|
||||
return
|
||||
}
|
||||
tempCode = generateCode(
|
||||
language.toString(),
|
||||
isMultipart,
|
||||
pretreatmentRequestInfo(cloneDeep(api))
|
||||
)
|
||||
const [code, setCode] = useState<string>('')
|
||||
const [lang, setLang] = useState<number[]>([20])
|
||||
|
||||
let tempCode = ''
|
||||
const getCode = (language: number | string) => {
|
||||
if (!['HTTPS', 'HTTP'].includes(api.protocol?.toUpperCase())) {
|
||||
tempCode = $t('暂不支持生成非 HTTPS 或非 HTTP 协议的代码示例')
|
||||
setCode(tempCode)
|
||||
return
|
||||
}
|
||||
tempCode = generateCode(language.toString(), isMultipart, pretreatmentRequestInfo(cloneDeep(api)))
|
||||
setCode(tempCode)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if(!Object.keys(api).length) return
|
||||
getCode(lang[lang.length -1 ])
|
||||
}, [api])
|
||||
useEffect(() => {
|
||||
if (!Object.keys(api).length) return
|
||||
getCode(lang[lang.length - 1])
|
||||
}, [api])
|
||||
|
||||
const onChange = (value: number[], record: DefaultOptionType[]) => {
|
||||
const num = value[value.length - 1]
|
||||
setLang(value)
|
||||
if (!Object.keys(api).length) return
|
||||
getCode(num)
|
||||
}
|
||||
|
||||
const onChange = (value: number[],record:DefaultOptionType[]) => {
|
||||
const num = value[value.length - 1]
|
||||
setLang(value)
|
||||
if(!Object.keys(api).length) return
|
||||
getCode(num)
|
||||
};
|
||||
const filter = (inputValue: string, path: DefaultOptionType[]) =>
|
||||
path.some((option) => (option.label as string).toLowerCase().indexOf(inputValue.toLowerCase()) > -1)
|
||||
|
||||
const filter = (inputValue: string, path: DefaultOptionType[]) =>
|
||||
path.some(
|
||||
(option) => (option.label as string).toLowerCase().indexOf(inputValue.toLowerCase()) > -1,
|
||||
);
|
||||
const [placeholderTxt, setPlaceholderTxt] = useState($t('搜索编程语言...'))
|
||||
const [selectItemTxt, setSelectItemTxt] = useState('')
|
||||
|
||||
const [placeholderTxt, setPlaceholderTxt] = useState($t('搜索编程语言...'))
|
||||
const [selectItemTxt, setSelectItemTxt ] = useState('')
|
||||
|
||||
const codeLangOptions = useMemo(()=>CODE_LANG.map(x=>({...x, label:$t(x.label as string)})),[state.language])
|
||||
return (
|
||||
|
||||
<Collapse title={title}>
|
||||
<Box width="100%">
|
||||
<>
|
||||
<Codebox extraContent={<><span className="ml-[12px]">{$t('编程语言')}:</span><Cascader
|
||||
const codeLangOptions = useMemo(
|
||||
() => CODE_LANG.map((x) => ({ ...x, label: $t(x.label as string) })),
|
||||
[state.language]
|
||||
)
|
||||
return (
|
||||
<Collapse title={title}>
|
||||
<Box width="100%">
|
||||
<>
|
||||
<Codebox
|
||||
extraContent={
|
||||
<>
|
||||
<span className="ml-[12px]">{$t('编程语言')}:</span>
|
||||
<Cascader
|
||||
options={codeLangOptions}
|
||||
onChange={(value,record) => onChange(value as unknown as number[],record)}
|
||||
onChange={(value, record) => onChange(value as unknown as number[], record)}
|
||||
placeholder={placeholderTxt}
|
||||
value={lang} // 当前的值
|
||||
showSearch={{ filter }}
|
||||
size="small"
|
||||
allowClear={false}
|
||||
// onDropdownVisibleChange={value => openChange(value)}
|
||||
/></>}
|
||||
language={'javascript'} value={code} readOnly={true} height={'250px'} width={'100%'}/>
|
||||
</>
|
||||
</Box>
|
||||
</Collapse>
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
language={'javascript'}
|
||||
value={code}
|
||||
readOnly={true}
|
||||
height={'250px'}
|
||||
width={'100%'}
|
||||
/>
|
||||
</>
|
||||
</Box>
|
||||
</Collapse>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { PARAM_KEY_REF_TYPE, PARAM_TYPE_REF_TYPE } from './code-snippets.type';
|
||||
import { tranformJson, tranformXml } from './util';
|
||||
import { PARAM_KEY_REF_TYPE, PARAM_TYPE_REF_TYPE } from './code-snippets.type'
|
||||
import { tranformJson, tranformXml } from './util'
|
||||
type LANG_TYPE = 'Java' | 'HTTP' | 'shellHttpie' | 'go' | 'NodeJSNative'
|
||||
type PARAM_HEADER_TYPE = { name: string; value: string }
|
||||
type PARSE_OPTS_TYPE = {
|
||||
@@ -30,201 +30,199 @@ export const paramsJsonType: unknown = {
|
||||
NULL: 'null'
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description 拼接地址栏query参数,返回url
|
||||
* @param {string} url 地址栏url
|
||||
* @param {Object} query 地址栏query对象
|
||||
*/
|
||||
export function transfromUrlParam(url: string, query: { [key: string]: string }) {
|
||||
const querys: string[] = []
|
||||
Object.entries(query).forEach((item: string[]) => {
|
||||
querys.push(`${item[0]}=${item[1]}`)
|
||||
})
|
||||
if (!querys.length) return url
|
||||
return `${url}${url.includes('?') ? '&' : '?'}${querys.join('&')}`
|
||||
}
|
||||
export function parseUri(protocol: string, url: string) {
|
||||
if (!/((http:\/\/)|(https:\/\/))/.test(url)) {
|
||||
url = (protocol == 'HTTPS' ? 'https://' : 'http://') + url
|
||||
}
|
||||
return url
|
||||
/**
|
||||
* @description 拼接地址栏query参数,返回url
|
||||
* @param {string} url 地址栏url
|
||||
* @param {Object} query 地址栏query对象
|
||||
*/
|
||||
export function transfromUrlParam(url: string, query: { [key: string]: string }) {
|
||||
const querys: string[] = []
|
||||
Object.entries(query).forEach((item: string[]) => {
|
||||
querys.push(`${item[0]}=${item[1]}`)
|
||||
})
|
||||
if (!querys.length) return url
|
||||
return `${url}${url.includes('?') ? '&' : '?'}${querys.join('&')}`
|
||||
}
|
||||
export function parseUri(protocol: string, url: string) {
|
||||
if (!/((http:\/\/)|(https:\/\/))/.test(url)) {
|
||||
url = (protocol == 'HTTPS' ? 'https://' : 'http://') + url
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 处理FormData格式请求参数
|
||||
* @param {Object} options {format:生成Formdata格式[option],separator:组合字符串的分割符[option]}
|
||||
* @param {Array} params 待拼接数组
|
||||
*/
|
||||
export function parseFormData(params: unknown, { map, init, format, separator, langType, hasFileParams }: PARSE_OPTS_TYPE = {}) {
|
||||
if (map) params = cloneDeep(params)
|
||||
if (init) params = init(params)
|
||||
if (format) {
|
||||
//x-www
|
||||
const result: unknown = []
|
||||
params.map((val: unknown) => {
|
||||
if (map) val = map(val)
|
||||
result.push(format.replace('${name}', val.name).replace('${value}', val.value))
|
||||
})
|
||||
return result.join(separator || '&')
|
||||
}
|
||||
//multipart
|
||||
let result: string = ''
|
||||
const boundary: string = 'WebKitFormBoundary7MA4YWxkTrZu0gW'
|
||||
params.forEach((val: unknown) => {
|
||||
/**
|
||||
* @description 处理FormData格式请求参数
|
||||
* @param {Object} options {format:生成Formdata格式[option],separator:组合字符串的分割符[option]}
|
||||
* @param {Array} params 待拼接数组
|
||||
*/
|
||||
export function parseFormData(
|
||||
params: unknown,
|
||||
{ map, init, format, separator, langType, hasFileParams }: PARSE_OPTS_TYPE = {}
|
||||
) {
|
||||
if (map) params = cloneDeep(params)
|
||||
if (init) params = init(params)
|
||||
if (format) {
|
||||
//x-www
|
||||
const result: unknown = []
|
||||
params.map((val: unknown) => {
|
||||
if (map) val = map(val)
|
||||
if (val.files) {
|
||||
if (val.files.length) {
|
||||
result += `------${boundary}\r\n`
|
||||
val.files.map((childVal: unknown) => {
|
||||
result += `content-disposition: form-data; name="${val.name}"; filename="${val.value}"\r\n`
|
||||
if (typeof childVal === 'string') {
|
||||
result += `Content-Type: ${((childVal.match(/data:(.*);/) || [])[0] || '')
|
||||
.replace(/^data:/, '')
|
||||
.replace(/;$/, '')}\r\n`
|
||||
} else {
|
||||
result += `Content-Type: ${(childVal.dataUrl.match(/data:(.*);/)[0] || '')
|
||||
.replace(/^data:/, '')
|
||||
.replace(/;$/, '')}\r\n`
|
||||
}
|
||||
result += '\r\n'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
result += `------${boundary}\r\n`
|
||||
result += `content-disposition: form-data; name="${val.name}"\r\n`
|
||||
result += '\r\n'
|
||||
result += `${val.value}\r\n`
|
||||
}
|
||||
result.push(format.replace('${name}', val.name).replace('${value}', val.value))
|
||||
})
|
||||
result += `------${boundary}--`
|
||||
return result
|
||||
return result.join(separator || '&')
|
||||
}
|
||||
export function parseFileValue (params: unknown[]) {
|
||||
const tmp = {
|
||||
output: ''
|
||||
}
|
||||
if (params && params.length) {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
if (params[i].data_type === 'file') {
|
||||
tmp.output = params[i].value || ''
|
||||
break
|
||||
}
|
||||
//multipart
|
||||
let result: string = ''
|
||||
const boundary: string = 'WebKitFormBoundary7MA4YWxkTrZu0gW'
|
||||
params.forEach((val: unknown) => {
|
||||
if (map) val = map(val)
|
||||
if (val.files) {
|
||||
if (val.files.length) {
|
||||
result += `------${boundary}\r\n`
|
||||
val.files.map((childVal: unknown) => {
|
||||
result += `content-disposition: form-data; name="${val.name}"; filename="${val.value}"\r\n`
|
||||
if (typeof childVal === 'string') {
|
||||
result += `Content-Type: ${((childVal.match(/data:(.*);/) || [])[0] || '')
|
||||
.replace(/^data:/, '')
|
||||
.replace(/;$/, '')}\r\n`
|
||||
} else {
|
||||
result += `Content-Type: ${(childVal.dataUrl.match(/data:(.*);/)[0] || '')
|
||||
.replace(/^data:/, '')
|
||||
.replace(/;$/, '')}\r\n`
|
||||
}
|
||||
result += '\r\n'
|
||||
})
|
||||
}
|
||||
return tmp.output
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function payloadStr (method: string, headers: unknown[]) {
|
||||
let tmpStr = ''
|
||||
if (method === 'GET') {
|
||||
tmpStr = 'params=payload'
|
||||
} else {
|
||||
headers.forEach((item) => {
|
||||
if (item.name === 'Content-Type') {
|
||||
switch (item.description || item.value) {
|
||||
case 'application/x-www-form-urlencoded':
|
||||
tmpStr = 'data=payload'
|
||||
break
|
||||
case 'application/json':
|
||||
tmpStr = 'data=json.dumps(payload)'
|
||||
break
|
||||
default: {
|
||||
tmpStr = 'params=payload'
|
||||
}
|
||||
result += `------${boundary}\r\n`
|
||||
result += `content-disposition: form-data; name="${val.name}"\r\n`
|
||||
result += '\r\n'
|
||||
result += `${val.value}\r\n`
|
||||
}
|
||||
})
|
||||
result += `------${boundary}--`
|
||||
return result
|
||||
}
|
||||
export function parseFileValue(params: unknown[]) {
|
||||
const tmp = {
|
||||
output: ''
|
||||
}
|
||||
if (params && params.length) {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
if (params[i].data_type === 'file') {
|
||||
tmp.output = params[i].value || ''
|
||||
break
|
||||
}
|
||||
}
|
||||
return tmp.output
|
||||
}
|
||||
}
|
||||
|
||||
export function payloadStr(method: string, headers: unknown[]) {
|
||||
let tmpStr = ''
|
||||
if (method === 'GET') {
|
||||
tmpStr = 'params=payload'
|
||||
} else {
|
||||
headers.forEach((item) => {
|
||||
if (item.name === 'Content-Type') {
|
||||
switch (item.description || item.value) {
|
||||
case 'application/x-www-form-urlencoded':
|
||||
tmpStr = 'data=payload'
|
||||
break
|
||||
case 'application/json':
|
||||
tmpStr = 'data=json.dumps(payload)'
|
||||
break
|
||||
default: {
|
||||
tmpStr = 'params=payload'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return tmpStr
|
||||
}
|
||||
|
||||
export function goCodeParseFormData(params: unknown[]) {
|
||||
if (!params.length) return
|
||||
let output = ''
|
||||
params.forEach((item) => {
|
||||
output += `payload.Set("${item.name}", "${item.value}")\r\n `
|
||||
})
|
||||
return output
|
||||
}
|
||||
export function parseFileType (fileValue: string) {
|
||||
const isPng = fileValue.endsWith('.png')
|
||||
const isJpeg = fileValue.endsWith('.jpeg')
|
||||
const isJpg = fileValue.endsWith('.jpg')
|
||||
let result = 'image/jpeg'
|
||||
if (isPng) {
|
||||
result = 'image/png'
|
||||
} else if (isJpg) {
|
||||
result = 'image/jpeg'
|
||||
} else if (isJpeg) {
|
||||
result = 'image/jpeg'
|
||||
}
|
||||
return result
|
||||
}
|
||||
/**
|
||||
* @description 处理请求头格式
|
||||
* @param {Object} options {format:生成请求头格式[option],separator:组合字符串的分割符[option]}
|
||||
* @param {Array} headers 待拼接数组
|
||||
*/
|
||||
export function parseHeaders(
|
||||
headers: PARAM_HEADER_TYPE[],
|
||||
{ map, filter, format, separator }: PARSE_OPTS_TYPE = {}
|
||||
) {
|
||||
const result = []
|
||||
if (map) {
|
||||
headers = cloneDeep(headers)
|
||||
}
|
||||
for (const key in headers) {
|
||||
let val = headers[key]
|
||||
if (map) {
|
||||
val = map(val)
|
||||
}
|
||||
if (filter) {
|
||||
if (filter(val)) {
|
||||
result.push(format?.replace('${name}', val.name)?.replace('${value}', val.value))
|
||||
}
|
||||
} else {
|
||||
})
|
||||
}
|
||||
return tmpStr
|
||||
}
|
||||
|
||||
export function goCodeParseFormData(params: unknown[]) {
|
||||
if (!params.length) return
|
||||
let output = ''
|
||||
params.forEach((item) => {
|
||||
output += `payload.Set("${item.name}", "${item.value}")\r\n `
|
||||
})
|
||||
return output
|
||||
}
|
||||
export function parseFileType(fileValue: string) {
|
||||
const isPng = fileValue.endsWith('.png')
|
||||
const isJpeg = fileValue.endsWith('.jpeg')
|
||||
const isJpg = fileValue.endsWith('.jpg')
|
||||
let result = 'image/jpeg'
|
||||
if (isPng) {
|
||||
result = 'image/png'
|
||||
} else if (isJpg) {
|
||||
result = 'image/jpeg'
|
||||
} else if (isJpeg) {
|
||||
result = 'image/jpeg'
|
||||
}
|
||||
return result
|
||||
}
|
||||
/**
|
||||
* @description 处理请求头格式
|
||||
* @param {Object} options {format:生成请求头格式[option],separator:组合字符串的分割符[option]}
|
||||
* @param {Array} headers 待拼接数组
|
||||
*/
|
||||
export function parseHeaders(headers: PARAM_HEADER_TYPE[], { map, filter, format, separator }: PARSE_OPTS_TYPE = {}) {
|
||||
const result = []
|
||||
if (map) {
|
||||
headers = cloneDeep(headers)
|
||||
}
|
||||
for (const key in headers) {
|
||||
let val = headers[key]
|
||||
if (map) {
|
||||
val = map(val)
|
||||
}
|
||||
if (filter) {
|
||||
if (filter(val)) {
|
||||
result.push(format?.replace('${name}', val.name)?.replace('${value}', val.value))
|
||||
}
|
||||
} else {
|
||||
result.push(format?.replace('${name}', val.name)?.replace('${value}', val.value))
|
||||
}
|
||||
return result.join(separator || '')
|
||||
}
|
||||
const keyRefs: PARAM_KEY_REF_TYPE = {
|
||||
key: 'name',
|
||||
type: 'data_type',
|
||||
value: 'value',
|
||||
childKey: 'child_list',
|
||||
arrayItemKey: 'isArrItem'
|
||||
}
|
||||
return result.join(separator || '')
|
||||
}
|
||||
const keyRefs: PARAM_KEY_REF_TYPE = {
|
||||
key: 'name',
|
||||
type: 'data_type',
|
||||
value: 'value',
|
||||
childKey: 'child_list',
|
||||
arrayItemKey: 'isArrItem'
|
||||
}
|
||||
|
||||
const typeRefs: PARAM_TYPE_REF_TYPE = paramsJsonType
|
||||
const typeRefs: PARAM_TYPE_REF_TYPE = paramsJsonType
|
||||
|
||||
export const parseRequestBodyToString = ({ requestType, params, apiRequestParamJsonType, raw }: unknown) => {
|
||||
let result: string = ''
|
||||
switch ((requestType || 'FORAMDATA').toString()) {
|
||||
case 'RAW': {
|
||||
//raw
|
||||
// todo
|
||||
result = raw
|
||||
break
|
||||
}
|
||||
case 'JSON': {
|
||||
//json
|
||||
if (!params[0]?.hasOwnProperty('value')) keyRefs.value = 'name'
|
||||
result = tranformJson(params, keyRefs, typeRefs)
|
||||
if (apiRequestParamJsonType === 'ARRAY') {
|
||||
//array
|
||||
result = `[${result}]`
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'XML': {
|
||||
//xml
|
||||
if (!params[0]?.hasOwnProperty('value')) keyRefs.value = 'name'
|
||||
result = tranformXml(params, keyRefs, typeRefs)
|
||||
break
|
||||
}
|
||||
export const parseRequestBodyToString = ({ requestType, params, apiRequestParamJsonType, raw }: unknown) => {
|
||||
let result: string = ''
|
||||
switch ((requestType || 'FORAMDATA').toString()) {
|
||||
case 'RAW': {
|
||||
//raw
|
||||
// todo
|
||||
result = raw
|
||||
break
|
||||
}
|
||||
return result
|
||||
}
|
||||
case 'JSON': {
|
||||
//json
|
||||
if (!params[0]?.hasOwnProperty('value')) keyRefs.value = 'name'
|
||||
result = tranformJson(params, keyRefs, typeRefs)
|
||||
if (apiRequestParamJsonType === 'ARRAY') {
|
||||
//array
|
||||
result = `[${result}]`
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'XML': {
|
||||
//xml
|
||||
if (!params[0]?.hasOwnProperty('value')) keyRefs.value = 'name'
|
||||
result = tranformXml(params, keyRefs, typeRefs)
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user