Browse Source

Merge remote-tracking branch 'origin/dev'

# Conflicts:
#	react-ui/config/proxy.ts
#	ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java
master-arm
chenzhihang 1 year ago
parent
commit
b8677eec62
100 changed files with 1700 additions and 1047 deletions
  1. +7
    -0
      .gitignore
  2. +0
    -0
      k8s/build-java.sh
  3. +0
    -0
      k8s/build-node.sh
  4. +1
    -1
      k8s/build.sh
  5. +1
    -1
      k8s/build_and_deploy.sh
  6. +3
    -1
      k8s/deploy.sh
  7. +15
    -1
      k8s/dockerfiles/conf/nginx.conf
  8. +0
    -36
      k8s/k8s-10gen.yaml
  9. +0
    -36
      k8s/k8s-11visual.yaml
  10. +0
    -36
      k8s/k8s-12front.yaml
  11. +0
    -62
      k8s/k8s-3nacos.yaml
  12. +0
    -36
      k8s/k8s-4gateway.yaml
  13. +0
    -36
      k8s/k8s-5auth.yaml
  14. +0
    -36
      k8s/k8s-6system.yaml
  15. +0
    -44
      k8s/k8s-7management.yaml
  16. +0
    -36
      k8s/k8s-8file.yaml
  17. +0
    -36
      k8s/k8s-9job.yaml
  18. +0
    -36
      k8s/template-yaml/deploy/k8s-12front.yaml
  19. +0
    -53
      k8s/template-yaml/deploy/k8s-7management.yaml
  20. +12
    -2
      k8s/template-yaml/k8s-6system.yaml
  21. +2
    -1
      react-ui/.eslintignore
  22. +7
    -1
      react-ui/.eslintrc.js
  23. +2
    -7
      react-ui/.gitignore
  24. +1
    -0
      react-ui/.nvmrc
  25. +1
    -0
      react-ui/.prettierignore
  26. +15
    -0
      react-ui/.storybook/babel-plugin-auto-css-modules.js
  27. +19
    -0
      react-ui/.storybook/blocks/StoryName.tsx
  28. +121
    -0
      react-ui/.storybook/main.ts
  29. +6
    -0
      react-ui/.storybook/manager.ts
  30. +19
    -0
      react-ui/.storybook/mock/umijs.mock.tsx
  31. +92
    -0
      react-ui/.storybook/mock/websocket.mock.js
  32. +112
    -0
      react-ui/.storybook/preview.tsx
  33. +19
    -0
      react-ui/.storybook/storybook.css
  34. +7
    -0
      react-ui/.storybook/theme.ts
  35. +27
    -0
      react-ui/.storybook/tsconfig.json
  36. +20
    -0
      react-ui/.storybook/typings.d.ts
  37. +3
    -0
      react-ui/Dockerfile
  38. +1
    -133
      react-ui/README.md
  39. +1
    -1
      react-ui/config/config.ts
  40. +1
    -1
      react-ui/config/defaultSettings.ts
  41. +2
    -2
      react-ui/config/proxy.ts
  42. +153
    -41
      react-ui/config/routes.ts
  43. +39
    -4
      react-ui/package.json
  44. BIN
      react-ui/public/favicon-cc.ico
  45. BIN
      react-ui/public/favicon-cl.ico
  46. BIN
      react-ui/public/favicon.ico
  47. BIN
      react-ui/public/fonts/DingTalk-JinBuTi.ttf
  48. BIN
      react-ui/public/fonts/DingTalk-JinBuTi.woff
  49. BIN
      react-ui/public/fonts/DingTalk-JinBuTi.woff2
  50. +12
    -4
      react-ui/public/fonts/font.css
  51. +307
    -0
      react-ui/public/mockServiceWorker.js
  52. +17
    -15
      react-ui/src/app.tsx
  53. BIN
      react-ui/src/assets/img/confirm-icon.png
  54. +0
    -0
      react-ui/src/assets/img/copy-icon.png
  55. BIN
      react-ui/src/assets/img/logo-cc.png
  56. BIN
      react-ui/src/assets/img/logo-cl.png
  57. BIN
      react-ui/src/assets/img/logo.png
  58. BIN
      react-ui/src/assets/img/popover-bg.png
  59. BIN
      react-ui/src/assets/img/user-points-bg.png
  60. BIN
      react-ui/src/assets/img/workspace-experiment.png
  61. BIN
      react-ui/src/assets/img/workspace-pipeline.png
  62. +86
    -0
      react-ui/src/components/BasicInfo/BasicInfoItem.tsx
  63. +58
    -0
      react-ui/src/components/BasicInfo/BasicInfoItemValue.tsx
  64. +0
    -113
      react-ui/src/components/BasicInfo/components.tsx
  65. +0
    -48
      react-ui/src/components/BasicInfo/format.ts
  66. +26
    -2
      react-ui/src/components/BasicInfo/index.less
  67. +35
    -6
      react-ui/src/components/BasicInfo/index.tsx
  68. +2
    -2
      react-ui/src/components/BasicInfo/types.ts
  69. +10
    -3
      react-ui/src/components/BasicTableInfo/index.less
  70. +10
    -10
      react-ui/src/components/BasicTableInfo/index.tsx
  71. +0
    -0
      react-ui/src/components/CodeConfigItem/index.less
  72. +13
    -2
      react-ui/src/components/CodeConfigItem/index.tsx
  73. +36
    -13
      react-ui/src/components/CodeSelect/index.tsx
  74. +49
    -0
      react-ui/src/components/CodeSelectorModal/index.less
  75. +25
    -21
      react-ui/src/components/CodeSelectorModal/index.tsx
  76. +41
    -0
      react-ui/src/components/ConfigInfo/index.tsx
  77. +2
    -2
      react-ui/src/components/CopyingText/clipboard.js
  78. +0
    -0
      react-ui/src/components/CopyingText/index.less
  79. +26
    -0
      react-ui/src/components/CopyingText/index.tsx
  80. +0
    -9
      react-ui/src/components/DisabledInput/index.less
  81. +0
    -20
      react-ui/src/components/DisabledInput/index.tsx
  82. +19
    -0
      react-ui/src/components/FormInfo/index.less
  83. +74
    -0
      react-ui/src/components/FormInfo/index.tsx
  84. +13
    -5
      react-ui/src/components/FullScreenFrame/index.tsx
  85. +48
    -24
      react-ui/src/components/IFramePage/index.tsx
  86. +2
    -1
      react-ui/src/components/InfoGroup/InfoGroupTitle.less
  87. +7
    -1
      react-ui/src/components/InfoGroup/InfoGroupTitle.tsx
  88. +1
    -1
      react-ui/src/components/InfoGroup/index.less
  89. +12
    -3
      react-ui/src/components/InfoGroup/index.tsx
  90. +1
    -1
      react-ui/src/components/KFEmpty/index.less
  91. +16
    -6
      react-ui/src/components/KFEmpty/index.tsx
  92. +8
    -2
      react-ui/src/components/KFIcon/index.tsx
  93. +0
    -0
      react-ui/src/components/KFModal/KFModalTitle.less
  94. +5
    -1
      react-ui/src/components/KFModal/KFModalTitle.tsx
  95. +6
    -3
      react-ui/src/components/KFModal/index.tsx
  96. +12
    -4
      react-ui/src/components/KFRadio/index.tsx
  97. +1
    -1
      react-ui/src/components/KFSpin/index.less
  98. +11
    -5
      react-ui/src/components/KFSpin/index.tsx
  99. +0
    -19
      react-ui/src/components/LabelValue/index.less
  100. +0
    -20
      react-ui/src/components/LabelValue/index.tsx

+ 7
- 0
.gitignore View File

@@ -58,3 +58,10 @@ mvnw

# web
**/node_modules

*storybook.log

/react-ui/docs
/react-ui/types/tsconfig.tsbuildinfo
/react-ui/storybook-static
/react-ui/.storybook/scripts

+ 0
- 0
k8s/build-java.sh View File


+ 0
- 0
k8s/build-node.sh View File


+ 1
- 1
k8s/build.sh View File

@@ -54,7 +54,7 @@ compile_front() {
# 编译前端
docker run -v ${baseDir}:${baseDir} \
-e http_proxy=http://172.20.32.253:3128 -e https_proxy=http://172.20.32.253:3128 \
172.20.32.187/ci4s/node:16.16.0 ${baseDir}/k8s/build-node.sh
172.20.32.187/tempimagefile/node:18.16.0 ${baseDir}/k8s/build-node.sh
if [ $? -ne 0 ]; then
echo "编译失败,请检查代码!"
exit 1


+ 1
- 1
k8s/build_and_deploy.sh View File

@@ -61,7 +61,7 @@ if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then
exit 1
fi

valid_envs=("dev" "test")
valid_envs=("dev" "test" "test1")
if [[ ! " ${valid_envs[@]} " =~ " $env " ]]; then
echo "Invalid environment: $env" >&2
echo "Valid environments are: ${valid_envs[*]}"


+ 3
- 1
k8s/deploy.sh View File

@@ -34,7 +34,7 @@ if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then
exit 1
fi

valid_envs=("dev" "test")
valid_envs=("dev" "test" "test1")
if [[ ! " ${valid_envs[@]} " =~ " $env " ]]; then
echo "Invalid environment: $env" >&2
echo "Valid environments are: ${valid_envs[*]}"
@@ -46,6 +46,8 @@ if [ "$env" == "dev" ]; then
remote_ip="172.20.32.197"
elif [ "$env" == "test" ]; then
remote_ip="172.20.32.185"
elif [ "$env" == "test1" ]; then
remote_ip="172.20.32.235"
else
echo "Invalid environment - $env"
exit 1


+ 15
- 1
k8s/dockerfiles/conf/nginx.conf View File

@@ -29,11 +29,25 @@ http {
location /label-studio/ {
# rewrite ^/label-studio/(.*)$ /$1 break;
proxy_pass http://label-studio-service.argo.svc:8080/projects/;
proxy_pass http://label-studio-service.argo.svc:9000/projects/;
proxy_hide_header X-Frame-Options;
add_header X-Frame-Options ALLOWALL;
}
location /minio/ {
# rewrite ^/label-studio/(.*)$ /$1 break;
proxy_pass http://juicefs-s3-gateway.juicefs.svc:9000/;
proxy_hide_header X-Frame-Options;
add_header X-Frame-Options ALLOWALL;
}
location /neo4j/ {
# rewrite ^/label-studio/(.*)$ /$1 break;
proxy_pass http://172.20.20.88:7474/;
proxy_hide_header X-Frame-Options;
add_header X-Frame-Options ALLOWALL;
}
location / {
rewrite ^/prod-api/(.*)$ /$1 break;
root /home/ruoyi/projects/ruoyi-ui;


+ 0
- 36
k8s/k8s-10gen.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-gen-deployment
namespace: ci4s-test
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-gen
template:
metadata:
labels:
app: ci4s-gen
spec:
containers:
- name: ci4s-gen
image: ci4s-gen:v1.0
ports:
- containerPort: 9202

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-gen-service
namespace: ci4s-test
spec:
type: NodePort
ports:
- port: 9202
nodePort: 31211
protocol: TCP
selector:
app: ci4s-gen


+ 0
- 36
k8s/k8s-11visual.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-visual-deployment
namespace: ci4s-test
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-visual
template:
metadata:
labels:
app: ci4s-visual
spec:
containers:
- name: ci4s-visual
image: ci4s-visual:v1.0
ports:
- containerPort: 9100

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-visual-service
namespace: ci4s-test
spec:
type: NodePort
ports:
- port: 9100
nodePort: 31212
protocol: TCP
selector:
app: ci4s-visual


+ 0
- 36
k8s/k8s-12front.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-front-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-front
template:
metadata:
labels:
app: ci4s-front
spec:
containers:
- name: ci4s-front
image: 172.20.32.187/ci4s/ci4s-front:20240401
ports:
- containerPort: 8000

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-front-service
namespace: argo
spec:
type: NodePort
ports:
- port: 8000
nodePort: 31213
protocol: TCP
selector:
app: ci4s-front


+ 0
- 62
k8s/k8s-3nacos.yaml View File

@@ -1,62 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: argo
name: nacos-ci4s
labels:
app: nacos-ci4s
spec:
replicas: 1
selector:
matchLabels:
app: nacos-ci4s
template:
metadata:
labels:
app: nacos-ci4s
spec:
containers:
- name: nacos-ci4s
image: nacos/nacos-server:v2.2.0
env:
- name: SPRING_DATASOURCE_PLATFORM
value: mysql
- name: MODE
value: standalone
- name: MYSQL_SERVICE_HOST
value: mysql.argo.svc
- name: MYSQL_SERVICE_PORT
value: "3306"
- name: MYSQL_SERVICE_DB_NAME
value: nacos-ci4s-config
- name: MYSQL_SERVICE_USER
value: root
- name: MYSQL_SERVICE_PASSWORD
value: qazxc123456.
ports:
- containerPort: 8848
- containerPort: 9848
restartPolicy: Always

---

apiVersion: v1
kind: Service
metadata:
namespace: argo
name: nacos-ci4s
labels:
app: nacos-ci4s
spec:
type: NodePort
selector:
app: nacos-ci4s
ports:
- port: 8848
targetPort: 8848
nodePort: 31203
name: web
- port: 9848
targetPort: 9848
nodePort: 31204
name: podsa

+ 0
- 36
k8s/k8s-4gateway.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-gateway-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-gateway
template:
metadata:
labels:
app: ci4s-gateway
spec:
containers:
- name: ci4s-gateway
image: 172.20.32.187/ci4s/ci4s-gateway:20240401
ports:
- containerPort: 8082

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-gateway-service
namespace: argo
spec:
type: NodePort
ports:
- port: 8082
nodePort: 31205
protocol: TCP
selector:
app: ci4s-gateway


+ 0
- 36
k8s/k8s-5auth.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-auth-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-auth
template:
metadata:
labels:
app: ci4s-auth
spec:
containers:
- name: ci4s-auth
image: 172.20.32.187/ci4s/ci4s-auth:20240401
ports:
- containerPort: 9200

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-auth-service
namespace: argo
spec:
type: NodePort
ports:
- port: 9200
nodePort: 31206
protocol: TCP
selector:
app: ci4s-auth


+ 0
- 36
k8s/k8s-6system.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-system-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-system
template:
metadata:
labels:
app: ci4s-system
spec:
containers:
- name: ci4s-system
image: 172.20.32.187/ci4s/ci4s-system:20240401
ports:
- containerPort: 9201

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-system-service
namespace: argo
spec:
type: NodePort
ports:
- port: 9201
nodePort: 31207
protocol: TCP
selector:
app: ci4s-system


+ 0
- 44
k8s/k8s-7management.yaml View File

@@ -1,44 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-management-platform-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-management-platform
template:
metadata:
labels:
app: ci4s-management-platform
spec:
containers:
- name: ci4s-management-platform
image: 172.20.32.187/ci4s/managent:20240401
ports:
- containerPort: 9213
volumeMounts:
- name: resource
mountPath: /home/resource/
volumes:
- name: resource
hostPath:
path: /home/resource/
type: DirectoryOrCreate

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-management-platform-service
namespace: argo
spec:
type: NodePort
ports:
- port: 9213
nodePort: 31208
protocol: TCP
selector:
app: ci4s-management-platform


+ 0
- 36
k8s/k8s-8file.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-file-deployment
namespace: ci4s-test
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-file
template:
metadata:
labels:
app: ci4s-file
spec:
containers:
- name: ci4s-file
image: ci4s-file:v1.0
ports:
- containerPort: 9300

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-file-service
namespace: ci4s-test
spec:
type: NodePort
ports:
- port: 9300
nodePort: 31209
protocol: TCP
selector:
app: ci4s-file


+ 0
- 36
k8s/k8s-9job.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-job-deployment
namespace: ci4s-test
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-job
template:
metadata:
labels:
app: ci4s-job
spec:
containers:
- name: ci4s-job
image: ci4s-job:v1.0
ports:
- containerPort: 9203

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-job-service
namespace: ci4s-test
spec:
type: NodePort
ports:
- port: 9203
nodePort: 31210
protocol: TCP
selector:
app: ci4s-job


+ 0
- 36
k8s/template-yaml/deploy/k8s-12front.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-front-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-front
template:
metadata:
labels:
app: ci4s-front
spec:
containers:
- name: ci4s-front
image: 172.20.32.187/ci4s/ci4s-front:202406120836
ports:
- containerPort: 8000

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-front-service
namespace: argo
spec:
type: NodePort
ports:
- port: 8000
nodePort: 31213
protocol: TCP
selector:
app: ci4s-front


+ 0
- 53
k8s/template-yaml/deploy/k8s-7management.yaml View File

@@ -1,53 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-management-platform-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-management-platform
template:
metadata:
labels:
app: ci4s-management-platform
spec:
containers:
- name: ci4s-management-platform
image: 172.20.32.187/ci4s/ci4s-managent:202409201355
env:
- name: TZ
value: Asia/Shanghai
- name: JAVA_TOOL_OPTIONS
value: "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"
ports:
- containerPort: 9213
volumeMounts:
- name: resource-volume
mountPath: /home/resource/
volumes:
- name: resource-volume
persistentVolumeClaim:
claimName: platform-data-pvc-nfs
---
apiVersion: v1
kind: Service
metadata:
name: ci4s-management-platform-service
namespace: argo
spec:
type: NodePort
ports:
- name: http
port: 9213
nodePort: 31208
protocol: TCP
- name: debug
nodePort: 34567
port: 5005
protocol: TCP
targetPort: 5005
selector:
app: ci4s-management-platform


+ 12
- 2
k8s/template-yaml/k8s-6system.yaml View File

@@ -18,7 +18,11 @@ spec:
image: ${k8s-6system-image}
ports:
- containerPort: 9201

env:
- name: TZ
value: Asia/Shanghai
- name: JAVA_TOOL_OPTIONS
value: "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"
---
apiVersion: v1
kind: Service
@@ -28,9 +32,15 @@ metadata:
spec:
type: NodePort
ports:
- port: 9201
- name: http
port: 9201
nodePort: 31207
protocol: TCP
- name: debug
nodePort: 31220
port: 5005
protocol: TCP
targetPort: 5005
selector:
app: ci4s-system


+ 2
- 1
react-ui/.eslintignore View File

@@ -5,4 +5,5 @@
public
dist
.umi
mock
mock
/src/iconfont/

+ 7
- 1
react-ui/.eslintrc.js View File

@@ -1,10 +1,16 @@
module.exports = {
extends: [require.resolve('@umijs/lint/dist/config/eslint')],
extends: [
require.resolve('@umijs/lint/dist/config/eslint'),
'plugin:react/recommended',
'plugin:react-hooks/recommended',
],
globals: {
page: true,
REACT_APP_ENV: true,
},
rules: {
'@typescript-eslint/no-use-before-define': 'off',
'react/react-in-jsx-scope': 'off',
'react/display-name': 'off',
},
};

+ 2
- 7
react-ui/.gitignore View File

@@ -41,10 +41,5 @@ screenshot
build

pnpm-lock.yaml
/src/services/codeConfig/index.js
/src/pages/CodeConfig/components/AddCodeConfigModal/index.less
/src/pages/CodeConfig/List/index.less
/src/pages/Dataset/components/ResourceItem/index.less
/src/pages/CodeConfig/components/AddCodeConfigModal/index.tsx
/src/pages/CodeConfig/components/CodeConfigItem/index.tsx
/src/pages/Dataset/components/ResourceItem/index.tsx

*storybook.log

+ 1
- 0
react-ui/.nvmrc View File

@@ -0,0 +1 @@
v18.20.7

+ 1
- 0
react-ui/.prettierignore View File

@@ -20,3 +20,4 @@ yarn-error.log
CNAME
/build
/public
/src/iconfont/

+ 15
- 0
react-ui/.storybook/babel-plugin-auto-css-modules.js View File

@@ -0,0 +1,15 @@
export default function (babel) {
const { types: t } = babel;
return {
visitor: {
ImportDeclaration(path) {
const source = path.node.source.value;
if (source.endsWith('.less')) {
if (path.node.specifiers.length > 0) {
path.node.source.value += '?modules';
}
}
},
},
};
}

+ 19
- 0
react-ui/.storybook/blocks/StoryName.tsx View File

@@ -0,0 +1,19 @@
import { Of, useOf } from '@storybook/blocks';

/**
* A block that displays the story name or title from the of prop
* - if a story reference is passed, it renders the story name
* - if a meta reference is passed, it renders the stories' title
* - if nothing is passed, it defaults to the primary story
*/
export const StoryName = ({ of }: { of?: Of }) => {
const resolvedOf = useOf(of || 'story', ['story', 'meta']);
switch (resolvedOf.type) {
case 'story': {
return <h3 className="css-wzniqs">{resolvedOf.story.name}</h3>;
}
case 'meta': {
return <h3 className="css-wzniqs">{resolvedOf.preparedMeta.title}</h3>;
}
}
};

+ 121
- 0
react-ui/.storybook/main.ts View File

@@ -0,0 +1,121 @@
import type { StorybookConfig } from '@storybook/react-webpack5';
import path from 'path';
import webpack from 'webpack';

const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
// '@storybook/addon-webpack5-compiler-swc',
'@storybook/addon-webpack5-compiler-babel',
'@storybook/addon-onboarding',
'@storybook/addon-essentials',
'@chromatic-com/storybook',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-webpack5',
options: {},
},
staticDirs: [
'../public',
{ from: '../docs', to: '/docs' },
{ from: '../docs/index.html', to: '/docs/index.html' },
],
docs: {
defaultName: 'Documentation',
},
webpackFinal: async (config) => {
if (config.resolve) {
config.resolve.alias = {
...config.resolve.alias,
'@': path.resolve(__dirname, '../src'),
'@umijs/max$': path.resolve(__dirname, './mock/umijs.mock.tsx'),
};
}
if (config.module && config.module.rules) {
config.module.rules.push(
{
test: /\.less$/,
oneOf: [
{
resourceQuery: /modules/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
import: true,
esModule: true,
modules: {
localIdentName: '[local]___[hash:base64:5]',
},
},
},
{
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true, // 如果需要支持 Ant Design 的 Less 变量,开启此项
modifyVars: {
hack: 'true; @import "@/styles/theme.less";',
},
},
},
},
],
include: path.resolve(__dirname, '../src'), // 限制范围,避免处理 node_modules
},
{
use: [
'style-loader',
'css-loader',
{
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true, // 如果需要支持 Ant Design 的 Less 变量,开启此项
modifyVars: {
hack: 'true; @import "@/styles/theme.less";',
},
},
},
},
],
include: path.resolve(__dirname, '../src'), // 限制范围,避免处理 node_modules
},
],
},
{
test: /\.(tsx?|jsx?)$/,
loader: 'ts-loader',
options: {
transpileOnly: true,
},
include: [
path.resolve(__dirname, '../src'), // 限制范围,避免处理 node_modules
path.resolve(__dirname, './'),
],
},
);
}
if (config.plugins) {
config.plugins.push(
new webpack.ProvidePlugin({
React: 'react', // 全局注入 React
}),
);
}

return config;
},
babel: async (config: any) => {
if (!config.plugins) {
config.plugins = [];
}

config.plugins.push(path.resolve(__dirname, './babel-plugin-auto-css-modules.js'));
return config;
},
};
export default config;

+ 6
- 0
react-ui/.storybook/manager.ts View File

@@ -0,0 +1,6 @@
import { addons } from '@storybook/manager-api';
import theme from './theme';

addons.setConfig({
theme: theme,
});

+ 19
- 0
react-ui/.storybook/mock/umijs.mock.tsx View File

@@ -0,0 +1,19 @@
export const Link = ({ to, children, ...props }: any) => (
<a href={typeof to === 'string' ? to : '#'} {...props}>
{children}
</a>
);

export const request = (url: string, options: any) => {
return fetch(url, options)
.then((res) => {
if (!res.ok) {
throw new Error(res.statusText);
}
return res;
})
.then((res) => res.json());
};

export { useNavigate, useParams, useSearchParams } from 'react-router-dom';
export const history = window.history;

+ 92
- 0
react-ui/.storybook/mock/websocket.mock.js View File

@@ -0,0 +1,92 @@
export const createWebSocketMock = () => {
class WebSocketMock {
constructor(url) {
this.url = url;
this.readyState = WebSocket.OPEN;
this.listeners = {};
this.count = 0;

console.log('Mock WebSocket connected to:', url);

// 模拟服务器推送消息
this.intervalId = setInterval(() => {
this.count += 1;
if (this.count > 5) {
this.count = 0;
clearInterval(this.intervalId);
return;
}
this.sendMessage(JSON.stringify(logStreamData));
}, 3000);
}

sendMessage(data) {
if (this.listeners['message']) {
this.listeners['message'].forEach((callback) => callback({ data }));
}
}

addEventListener(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}

removeEventListener(event, callback) {
if (this.listeners[event]) {
this.listeners[event] = this.listeners[event].filter((cb) => cb !== callback);
}
}

close() {
this.readyState = WebSocket.CLOSED;
console.log('Mock WebSocket closed');
}
}

return WebSocketMock;
};

export const logStreamData = {
streams: [
{
stream: {
workflows_argoproj_io_completed: 'false',
workflows_argoproj_io_workflow: 'workflow-p2ddj',
container: 'init',
filename:
'/var/log/pods/argo_workflow-p2ddj-git-clone-f33abcda-3988047653_e31cf6be-e013-4885-9eb6-ec84f83b9ba9/init/0.log',
job: 'argo/workflow-p2ddj-git-clone-f33abcda-3988047653',
namespace: 'argo',
pod: 'workflow-p2ddj-git-clone-f33abcda-3988047653',
stream: 'stderr',
},
values: [
[
'1742179591969785990',
'time="2025-03-17T02:46:31.969Z" level=info msg="Starting Workflow Executor" version=v3.5.10\n',
],
],
},
{
stream: {
filename:
'/var/log/pods/argo_workflow-p2ddj-git-clone-f33abcda-3988047653_e31cf6be-e013-4885-9eb6-ec84f83b9ba9/init/0.log',
job: 'argo/workflow-p2ddj-git-clone-f33abcda-3988047653',
namespace: 'argo',
pod: 'workflow-p2ddj-git-clone-f33abcda-3988047653',
stream: 'stderr',
workflows_argoproj_io_completed: 'false',
workflows_argoproj_io_workflow: 'workflow-p2ddj',
container: 'init',
},
values: [
[
'1742179591973414064',
'time="2025-03-17T02:46:31.973Z" level=info msg="Using executor retry strategy" Duration=1s Factor=1.6 Jitter=0.5 Steps=5\n',
],
],
},
],
};

+ 112
- 0
react-ui/.storybook/preview.tsx View File

@@ -0,0 +1,112 @@
import '@/global.less';
import '@/overrides.less';
import themes from '@/styles/theme.less';
import type { Preview } from '@storybook/react';
import { App, ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import { initialize, mswLoader } from 'msw-storybook-addon';
import { createWebSocketMock } from './mock/websocket.mock';
import './storybook.css';

/*
* Initializes MSW
* See https://github.com/mswjs/msw-storybook-addon#configuring-msw
* to learn how to customize it
*/
initialize();

// 替换全局 WebSocket 为 Mock 版本
// @ts-ignore
global.WebSocket = createWebSocketMock();

const preview: Preview = {
parameters: {
controls: {
expanded: true,
sort: 'requiredFirst',
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
backgrounds: {
values: [
{ name: 'Dark', value: '#000' },
{ name: 'Gray', value: '#f9fafb' },
{ name: 'Light', value: '#FFF' },
],
default: 'Light',
},
options: {
storySort: {
method: 'alphabetical',
order: ['Documentation', 'Components'],
},
},
},
decorators: [
(Story) => (
<ConfigProvider
locale={zhCN}
theme={{
cssVar: true,
token: {
colorPrimary: themes['primaryColor'],
colorSuccess: themes['successColor'],
colorError: themes['errorColor'],
colorWarning: themes['warningColor'],
colorLink: themes['primaryColor'],
colorText: themes['textColor'],
controlHeightLG: 46,
},
components: {
Button: {
defaultBg: 'rgba(22, 100, 255, 0.06)',
defaultBorderColor: 'rgba(22, 100, 255, 0.11)',
defaultColor: themes['textColor'],
defaultHoverBg: 'rgba(22, 100, 255, 0.06)',
defaultHoverBorderColor: 'rgba(22, 100, 255, 0.5)',
defaultHoverColor: '#3F7FFF ',
defaultActiveBg: 'rgba(22, 100, 255, 0.12)',
defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)',
defaultActiveColor: themes['primaryColor'],
contentFontSize: parseInt(themes['fontSize']),
},
Input: {
inputFontSize: parseInt(themes['fontSizeInput']),
inputFontSizeLG: parseInt(themes['fontSizeInputLg']),
paddingBlockLG: 10,
},
Select: {
singleItemHeightLG: 46,
optionSelectedColor: themes['primaryColor'],
},
Table: {
headerBg: 'rgba(242, 244, 247, 0.36)',
headerBorderRadius: 4,
rowSelectedBg: 'rgba(22, 100, 255, 0.05)',
},
Tabs: {
titleFontSize: 16,
},
Form: {
labelColor: 'rgba(29, 29, 32, 0.8);',
},
Breadcrumb: {
iconFontSize: parseInt(themes['fontSize']),
linkColor: 'rgba(29, 29, 32, 0.7)',
separatorColor: 'rgba(29, 29, 32, 0.7)',
},
},
}}
>
<App message={{ maxCount: 3 }}>
<Story />
</App>
</ConfigProvider>
),
],
loaders: [mswLoader], // 👈 Add the MSW loader to all stories
};

export default preview;

+ 19
- 0
react-ui/.storybook/storybook.css View File

@@ -0,0 +1,19 @@
html,
body,
#root {
min-width: unset;
height: 100%;
margin: 0;
padding: 0;
overflow-y: visible;
}

.ant-input-search-large .ant-input-affix-wrapper, .ant-input-search-large .ant-input-search-button {
height: 46px;
}

*,
*::before,
*::after {
box-sizing: border-box;
}

+ 7
- 0
react-ui/.storybook/theme.ts View File

@@ -0,0 +1,7 @@
import { create } from '@storybook/theming';
export default create({
base: 'light',
brandTitle: '组件库文档',
brandUrl: 'https://storybook.js.org/docs',
brandTarget: '_blank',
});

+ 27
- 0
react-ui/.storybook/tsconfig.json View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "esnext", // 指定ECMAScript目标版本
"lib": ["dom", "dom.iterable", "esnext"], // 要包含在编译中的库文件列表
"allowJs": true, // 允许编译JavaScript文件
"skipLibCheck": true, // 跳过所有声明文件的类型检查
"esModuleInterop": true, // 禁用命名空间导入(import * as fs from "fs"),并启用CJS/AMD/UMD样式的导入(import fs from "fs")
"allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块进行默认导入
"strict": true, // 启用所有严格类型检查选项
"forceConsistentCasingInFileNames": false, // 允许对同一文件的引用使用不一致的大小写
"module": "esnext", // 指定模块代码生成
"moduleResolution": "bundler", // 使用bundlers样式解析模块
"isolatedModules": true, // 无条件地为未解析的文件发出导入
"resolveJsonModule": true, // 包含.json扩展名的模块
"noEmit": true, // 不发出输出(即不编译代码,只进行类型检查)
"jsx": "react-jsx", // 在.tsx文件中支持JSX
"sourceMap": true, // 生成相应的.map文件
"declaration": true, // 生成相应的.d.ts文件
"noUnusedLocals": true, // 报告未使用的局部变量错误
"noUnusedParameters": true, // 报告未使用的参数错误
"incremental": true, // 通过读写磁盘上的文件来启用增量编译
"noFallthroughCasesInSwitch": true, // 报告switch语句中的fallthrough案例错误
"strictNullChecks": true, // 启用严格的null检查
"importHelpers": true,
"baseUrl": "./"
}
}

+ 20
- 0
react-ui/.storybook/typings.d.ts View File

@@ -0,0 +1,20 @@
declare module 'slash2';
declare module '*.css';
declare module '*.less';
declare module '*.scss';
declare module '*.sass';
declare module '*.svg';
declare module '*.png';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.gif';
declare module '*.bmp';
declare module '*.tiff';
declare module 'omit.js';
declare module 'numeral';
declare module '@antv/data-set';
declare module 'mockjs';
declare module 'react-fittext';
declare module 'bizcharts-plugin-slider';

declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;

+ 3
- 0
react-ui/Dockerfile View File

@@ -0,0 +1,3 @@
# Dockerfile
FROM nginx:alpine
COPY storybook-static/ /usr/share/nginx/html

+ 1
- 133
react-ui/README.md View File

@@ -1,133 +1 @@
Language : 🇺🇸 | [🇨🇳](./README.zh-CN.md) | [🇷🇺](./README.ru-RU.md) | [🇹🇷](./README.tr-TR.md) | [🇯🇵](./README.ja-JP.md) | [🇫🇷](./README.fr-FR.md) | [🇵🇹](./README.pt-BR.md) | [🇸🇦](./README.ar-DZ.md)

<h1 align="center">Ant Design Pro</h1>

<div align="center">

An out-of-box UI solution for enterprise applications as a React boilerplate.

[![Build Status](https://dev.azure.com/ant-design/ant-design-pro/_apis/build/status/ant-design.ant-design-pro?branchName=master)](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master) ![Github Action](https://github.com/ant-design/ant-design-pro/workflows/Node%20CI/badge.svg) ![Deploy](https://github.com/ant-design/ant-design-pro/workflows/Deploy%20CI/badge.svg)

[![Gitter](https://img.shields.io/gitter/room/ant-design/pro-english.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjEyMzUiIGhlaWdodD0iNjUwIiB2aWV3Qm94PSIwIDAgNzQxMCAzOTAwIj4NCjxyZWN0IHdpZHRoPSI3NDEwIiBoZWlnaHQ9IjM5MDAiIGZpbGw9IiNiMjIyMzQiLz4NCjxwYXRoIGQ9Ik0wLDQ1MEg3NDEwbTAsNjAwSDBtMCw2MDBINzQxMG0wLDYwMEgwbTAsNjAwSDc0MTBtMCw2MDBIMCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjMwMCIvPg0KPHJlY3Qgd2lkdGg9IjI5NjQiIGhlaWdodD0iMjEwMCIgZmlsbD0iIzNjM2I2ZSIvPg0KPGcgZmlsbD0iI2ZmZiI%2BDQo8ZyBpZD0iczE4Ij4NCjxnIGlkPSJzOSI%2BDQo8ZyBpZD0iczUiPg0KPGcgaWQ9InM0Ij4NCjxwYXRoIGlkPSJzIiBkPSJNMjQ3LDkwIDMxNy41MzQyMzAsMzA3LjA4MjAzOSAxMzIuODczMjE4LDE3Mi45MTc5NjFIMzYxLjEyNjc4MkwxNzYuNDY1NzcwLDMwNy4wODIwMzl6Ii8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB5PSI0MjAiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHk9Ijg0MCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTI2MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTY4MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjczQiIHg9IjI0NyIgeT0iMjEwIi8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzOSIgeD0iNDk0Ii8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzMTgiIHg9Ijk4OCIvPg0KPHVzZSB4bGluazpocmVmPSIjczkiIHg9IjE5NzYiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3M1IiB4PSIyNDcwIi8%2BDQo8L2c%2BDQo8L3N2Zz4%3D)](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Join the chat at https://gitter.im/ant-design/ant-design-pro](https://img.shields.io/gitter/room/ant-design/ant-design-pro.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg?style=flat-square)](http://umijs.org/) ![](https://badgen.net/badge/icon/Ant%20Design?icon=https://gw.alipayobjects.com/zos/antfincdn/Pp4WPgVDB3/KDpgvguMpGfqaHPjicRK.svg&label)

![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png)

</div>

- Preview: http://preview.pro.ant.design
- Home Page: http://pro.ant.design
- Documentation: http://pro.ant.design/docs/getting-started
- ChangeLog: http://pro.ant.design/docs/changelog
- FAQ: http://pro.ant.design/docs/faq
- Mirror Site in China: http://ant-design-pro.gitee.io

## 5.0 is out! 🎉🎉🎉

[Ant Design Pro 5.0.0](https://github.com/ant-design/ant-design-pro/issues/8656)

## Translation Recruitment :loudspeaker:

We need your help: https://github.com/ant-design/ant-design-pro/issues/120

## Features

- :bulb: **TypeScript**: A language for application-scale JavaScript
- :scroll: **Blocks**: Build page with block template
- :gem: **Neat Design**: Follow [Ant Design specification](http://ant.design/)
- :triangular_ruler: **Common Templates**: Typical templates for enterprise applications
- :rocket: **State of The Art Development**: Newest development stack of React/umi/dva/antd
- :iphone: **Responsive**: Designed for variable screen sizes
- :art: **Theming**: Customizable theme with simple config
- :globe_with_meridians: **International**: Built-in i18n solution
- :gear: **Best Practices**: Solid workflow to make your code healthy
- :1234: **Mock development**: Easy to use mock development solution
- :white_check_mark: **UI Test**: Fly safely with unit and e2e tests

## Templates

```
- Dashboard
- Analytic
- Monitor
- Workspace
- Form
- Basic Form
- Step Form
- Advanced From
- List
- Standard Table
- Standard List
- Card List
- Search List (Project/Applications/Article)
- Profile
- Simple Profile
- Advanced Profile
- Account
- Account Center
- Account Settings
- Result
- Success
- Failed
- Exception
- 403
- 404
- 500
- User
- Login
- Register
- Register Result
```

## Usage

### Use bash

We provide pro-cli to quickly initialize scaffolding.

```bash
# use npm
npm i @ant-design/pro-cli -g
pro create myapp
```

select umi version

```shell
🐂 Use umi@4 or umi@3 ? (Use arrow keys)
❯ umi@4
umi@3
```

> If the umi@4 version is selected, full blocks are not yet supported.

If you choose umi@3, you can also choose the pro template. Pro is the basic template, which only provides the basic content of the framework operation. Complete contains all blocks, which is not suitable for secondary development as a basic template.

```shell
? 🚀 Full or a simple scaffold? (Use arrow keys)
❯ simple
complete
```

Install dependencies:

```shell
$ cd myapp && tyarn
// or
$ cd myapp && npm install
```

## Browsers support

Modern browsers.

| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
| --- | --- | --- | --- | --- |
| Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |

## Contributing

Any type of contribution is welcome, here are some examples of how you may contribute to this project:

- Use Ant Design Pro in your daily work.
- Submit [issues](http://github.com/ant-design/ant-design-pro/issues) to report bugs or ask questions.
- Propose [pull requests](http://github.com/ant-design/ant-design-pro/pulls) to improve our code.
# Documentation

+ 1
- 1
react-ui/config/config.ts View File

@@ -75,7 +75,7 @@ export default defineConfig({
* @name layout 插件
* @doc https://umijs.org/docs/max/layout-menu
*/
title: '复杂智能软件',
title: '智能材料科研平台',
layout: {
...defaultSettings,
},


+ 1
- 1
react-ui/config/defaultSettings.ts View File

@@ -17,7 +17,7 @@ const Settings: ProLayoutProps & {
fixSiderbar: false,
splitMenus: false,
colorWeak: false,
title: '复杂智能软件',
title: '智能材料科研平台',
pwa: true,
token: {
// 参见ts声明,demo 见文档,通过token 修改样式


+ 2
- 2
react-ui/config/proxy.ts View File

@@ -26,10 +26,10 @@ export default {
// 配置了这个可以从 http 代理到 https
// 依赖 origin 的功能可能需要这个,比如 cookie
changeOrigin: true,
// pathRewrite: { '^/api': '' },
pathRewrite: { '^/api': '' },
},
'/profile/avatar/': {
target: 'http://172.20.32.185:31213',
target: 'http://172.20.32.235:31213',
changeOrigin: true,
},
},


+ 153
- 41
react-ui/config/routes.ts View File

@@ -44,7 +44,7 @@ export default [
{
name: 'login',
path: '/user/login',
component: './User/Login/login',
component: process.env.NO_SSO ? './User/Login/login' : './User/Login',
},
],
},
@@ -143,6 +143,11 @@ export default [
path: 'compare',
component: './Experiment/Comparison/index',
},
{
name: '实验可视化对比',
path: 'compare-visual',
component: './Experiment/Aim/index',
},
],
},
{
@@ -176,11 +181,83 @@ export default [
},
{
name: '实验实例详情',
path: 'instance/:autoMLId/:id',
path: 'instance/:experimentId/:id',
component: './AutoML/Instance/index',
},
],
},
{
name: '超参数自动寻优',
path: 'hyperparameter',
routes: [
{
name: '超参数寻优',
path: '',
component: './HyperParameter/List/index',
},
{
name: '实验详情',
path: 'info/:id',
component: './HyperParameter/Info/index',
},
{
name: '创建实验',
path: 'create',
component: './HyperParameter/Create/index',
},
{
name: '编辑实验',
path: 'edit/:id',
component: './HyperParameter/Create/index',
},
{
name: '复制实验',
path: 'copy/:id',
component: './HyperParameter/Create/index',
},
{
name: '实验实例详情',
path: 'instance/:experimentId/:id',
component: './HyperParameter/Instance/index',
},
],
},
{
name: '主动学习',
path: 'active-learn',
routes: [
{
name: '超参数寻优',
path: '',
component: './ActiveLearn/List/index',
},
{
name: '实验详情',
path: 'info/:id',
component: './ActiveLearn/Info/index',
},
{
name: '创建实验',
path: 'create',
component: './ActiveLearn/Create/index',
},
{
name: '编辑实验',
path: 'edit/:id',
component: './ActiveLearn/Create/index',
},
{
name: '复制实验',
path: 'copy/:id',
component: './ActiveLearn/Create/index',
},
{
name: '实验实例详情',
path: 'instance/:experimentId/:id',
component: './ActiveLearn/Instance/index',
},
],
},
],
},
{
@@ -235,7 +312,18 @@ export default [
{
name: '镜像详情',
path: 'info/:id',
component: './Mirror/Info',
routes: [
{
name: '镜像详情',
path: '',
component: './Mirror/Info',
},
{
name: '新增镜像版本',
path: 'add-version',
component: './Mirror/Create',
},
],
},
{
name: '创建镜像',
@@ -255,55 +343,55 @@ export default [
},
],
},
],
},
{
name: '模型部署',
path: '/modelDeployment',
routes: [
{
name: '模型部署',
path: '',
component: './ModelDeployment/List',
},
{
name: '创建推理服务',
path: 'createService',
component: './ModelDeployment/CreateService',
},
{
name: '编辑推理服务',
path: 'editService/:serviceId',
component: './ModelDeployment/CreateService',
},
{
name: '服务详情',
path: 'serviceInfo/:serviceId',
path: 'modelDeployment',
routes: [
{
name: '服务详情',
name: '模型部署',
path: '',
component: './ModelDeployment/ServiceInfo',
},
{
name: '新增服务版本',
path: 'createVersion',
component: './ModelDeployment/CreateVersion',
component: './ModelDeployment/List',
},
{
name: '更新服务版本',
path: 'updateVersion',
component: './ModelDeployment/CreateVersion',
name: '创建推理服务',
path: 'createService',
component: './ModelDeployment/CreateService',
},
{
name: '重启服务版本',
path: 'restartVersion',
component: './ModelDeployment/CreateVersion',
name: '编辑推理服务',
path: 'editService/:serviceId',
component: './ModelDeployment/CreateService',
},
{
name: '服务版本详情',
path: 'versionInfo/:id',
component: './ModelDeployment/VersionInfo',
name: '服务详情',
path: 'serviceInfo/:serviceId',
routes: [
{
name: '服务详情',
path: '',
component: './ModelDeployment/ServiceInfo',
},
{
name: '新增服务版本',
path: 'createVersion',
component: './ModelDeployment/CreateVersion',
},
{
name: '更新服务版本',
path: 'updateVersion',
component: './ModelDeployment/CreateVersion',
},
{
name: '重启服务版本',
path: 'restartVersion',
component: './ModelDeployment/CreateVersion',
},
{
name: '服务版本详情',
path: 'versionInfo/:id',
component: './ModelDeployment/VersionInfo',
},
],
},
],
},
@@ -461,6 +549,30 @@ export default [
},
],
},
{
name: '算力积分',
path: '/points',
routes: [
{
name: '算力积分',
path: '',
key: 'points',
component: './Points/index',
},
],
},
{
name: '知识图谱',
path: '/knowledge',
routes: [
{
name: '知识图谱',
path: '',
key: 'knowledge',
component: './Knowledge/index',
},
],
},
{
path: '*',
layout: false,


+ 39
- 4
react-ui/package.json View File

@@ -1,13 +1,14 @@
{
"name": "ant-design-pro",
"version": "6.0.0",
"name": "cl-model",
"version": "1.0.0",
"private": true,
"description": "An out-of-box UI solution for enterprise applications",
"description": "",
"scripts": {
"analyze": "cross-env ANALYZE=1 max build",
"build": "max build",
"deploy": "npm run build && npm run gh-pages",
"dev": "npm run start:dev",
"dev-no-sso": "cross-env NO_SSO=true npm run start:dev",
"docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./",
"docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build",
"docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up",
@@ -15,6 +16,7 @@
"docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up",
"docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro",
"docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro",
"docs": "typedoc",
"gh-pages": "gh-pages -d dist",
"i18n-remove": "pro i18n-remove --locale=zh-CN --write",
"postinstall": "max setup",
@@ -36,6 +38,11 @@
"start:mock": "cross-env REACT_APP_ENV=dev UMI_ENV=dev max dev",
"start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev",
"start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev",
"storybook": "storybook dev -p 6006",
"storybook-build": "storybook build",
"storybook-deploy": "./.storybook/scripts/upload-deploy.sh",
"storybook-docs": "storybook dev --docs",
"storybook-docs-build": "storybook build --docs",
"test": "jest",
"test:coverage": "npm run jest -- --coverage",
"test:update": "npm run jest -- -u",
@@ -61,6 +68,7 @@
"@types/crypto-js": "^4.2.2",
"@umijs/route-utils": "^4.0.1",
"antd": "~5.21.4",
"caniuse-lite": "~1.0.30001707",
"classnames": "^2.3.2",
"crypto-js": "^4.2.0",
"echarts": "^5.5.0",
@@ -83,6 +91,19 @@
},
"devDependencies": {
"@ant-design/pro-cli": "^3.1.0",
"@chromatic-com/storybook": "~3.2.4",
"@storybook/addon-essentials": "~8.5.3",
"@storybook/addon-interactions": "~8.5.3",
"@storybook/addon-onboarding": "~8.5.3",
"@storybook/addon-styling-webpack": "~1.0.1",
"@storybook/addon-webpack5-compiler-babel": "~3.0.5",
"@storybook/addon-webpack5-compiler-swc": "~2.0.0",
"@storybook/blocks": "~8.5.3",
"@storybook/manager-api": "~8.6.0",
"@storybook/react": "~8.5.3",
"@storybook/react-webpack5": "~8.5.3",
"@storybook/test": "~8.5.3",
"@storybook/theming": "~8.6.0",
"@testing-library/react": "^14.0.0",
"@types/antd": "^1.0.0",
"@types/express": "^4.17.14",
@@ -96,21 +117,30 @@
"@umijs/max": "^4.0.66",
"cross-env": "^7.0.3",
"eslint": "^8.39.0",
"eslint-plugin-react-hooks": "~5.2.0",
"eslint-plugin-storybook": "~0.11.2",
"express": "^4.18.2",
"gh-pages": "^5.0.0",
"husky": "^8.0.3",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"less": "~4.2.2",
"less-loader": "~12.2.0",
"lint-staged": "^13.2.0",
"mockjs": "^1.1.0",
"msw": "~2.7.0",
"msw-storybook-addon": "~2.0.4",
"prettier": "^2.8.1",
"storybook": "~8.5.3",
"swagger-ui-dist": "^4.18.2",
"ts-loader": "~9.5.2",
"ts-node": "^10.9.1",
"typedoc": "~0.28.1",
"typescript": "^5.0.4",
"umi-presets-pro": "^2.0.0"
},
"engines": {
"node": ">=16.14.0"
"node": ">=18.18.0"
},
"create-umi": {
"ignoreScript": [
@@ -140,5 +170,10 @@
"CNAME",
"create-umi"
]
},
"msw": {
"workerDirectory": [
"public"
]
}
}

BIN
react-ui/public/favicon-cc.ico View File

Before After

BIN
react-ui/public/favicon-cl.ico View File

Before After

BIN
react-ui/public/favicon.ico View File

Before After

BIN
react-ui/public/fonts/DingTalk-JinBuTi.ttf View File


BIN
react-ui/public/fonts/DingTalk-JinBuTi.woff View File


BIN
react-ui/public/fonts/DingTalk-JinBuTi.woff2 View File


+ 12
- 4
react-ui/public/fonts/font.css View File

@@ -1,5 +1,13 @@
@font-face {
font-family: Alibaba;
src: url('./ALIBABA-PUHUITI-MEDIUM.TTF');
font-display: swap;
}
font-family: Alibaba;
src: url('./ALIBABA-PUHUITI-MEDIUM.TTF');
font-display: swap;
}

@font-face {
font-family: 'DingTalk-JinBuTi';
src: url('./DingTalk-JinBuTi.woff2') format('woff2'), /* 最优先使用 woff2 */
url('./DingTalk-JinBuTi.woff') format('woff'), /* 兼容性较好的 woff */
url('./DingTalk-JinBuTi.ttf') format('truetype'); /* ttf 作为备选 */
font-display: swap; /* 优化页面加载时的字体显示 */
}

+ 307
- 0
react-ui/public/mockServiceWorker.js View File

@@ -0,0 +1,307 @@
/* eslint-disable */
/* tslint:disable */

/**
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/

const PACKAGE_VERSION = '2.7.0'
const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()

self.addEventListener('install', function () {
self.skipWaiting()
})

self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})

self.addEventListener('message', async function (event) {
const clientId = event.source.id

if (!clientId || !self.clients) {
return
}

const client = await self.clients.get(clientId)

if (!client) {
return
}

const allClients = await self.clients.matchAll({
type: 'window',
})

switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}

case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: {
packageVersion: PACKAGE_VERSION,
checksum: INTEGRITY_CHECKSUM,
},
})
break
}

case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)

sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: {
client: {
id: client.id,
frameType: client.frameType,
},
},
})
break
}

case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId)
break
}

case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)

const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})

// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}

break
}
}
})

self.addEventListener('fetch', function (event) {
const { request } = event

// Bypass navigation requests.
if (request.mode === 'navigate') {
return
}

// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return
}

// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}

// Generate unique request ID.
const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId))
})

async function handleRequest(event, requestId) {
const client = await resolveMainClient(event)
const response = await getResponse(event, client, requestId)

// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
;(async function () {
const responseClone = response.clone()

sendToClient(
client,
{
type: 'RESPONSE',
payload: {
requestId,
isMockedResponse: IS_MOCKED_RESPONSE in response,
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
body: responseClone.body,
headers: Object.fromEntries(responseClone.headers.entries()),
},
},
[responseClone.body],
)
})()
}

return response
}

// Resolve the main client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)

if (activeClientIds.has(event.clientId)) {
return client
}

if (client?.frameType === 'top-level') {
return client
}

const allClients = await self.clients.matchAll({
type: 'window',
})

return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible'
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id)
})
}

async function getResponse(event, client, requestId) {
const { request } = event

// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = request.clone()

function passthrough() {
// Cast the request headers to a new Headers instance
// so the headers can be manipulated with.
const headers = new Headers(requestClone.headers)

// Remove the "accept" header value that marked this request as passthrough.
// This prevents request alteration and also keeps it compliant with the
// user-defined CORS policies.
const acceptHeader = headers.get('accept')
if (acceptHeader) {
const values = acceptHeader.split(',').map((value) => value.trim())
const filteredValues = values.filter(
(value) => value !== 'msw/passthrough',
)

if (filteredValues.length > 0) {
headers.set('accept', filteredValues.join(', '))
} else {
headers.delete('accept')
}
}

return fetch(requestClone, { headers })
}

// Bypass mocking when the client is not active.
if (!client) {
return passthrough()
}

// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return passthrough()
}

// Notify the client that a request has been intercepted.
const requestBuffer = await request.arrayBuffer()
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: requestBuffer,
keepalive: request.keepalive,
},
},
[requestBuffer],
)

switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data)
}

case 'PASSTHROUGH': {
return passthrough()
}
}

return passthrough()
}

function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()

channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}

resolve(event.data)
}

client.postMessage(
message,
[channel.port2].concat(transferrables.filter(Boolean)),
)
})
}

async function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error()
}

const mockedResponse = new Response(response.body, response)

Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
})

return mockedResponse
}

+ 17
- 15
react-ui/src/app.tsx View File

@@ -1,14 +1,17 @@
import RightContent from '@/components/RightContent';
import themes from '@/styles/theme.less';
import { type GlobalInitialState } from '@/types';
import { menuItemRender } from '@/utils/menuRender';
import type { Settings as LayoutSettings } from '@ant-design/pro-components';
import { RuntimeConfig, history } from '@umijs/max';
import { RuntimeAntdConfig } from 'umi';
import defaultSettings from '../config/defaultSettings';
import '../public/fonts/font.css';
import { getAccessToken } from './access';
import ErrorBoundary from './components/ErrorBoundary';
import './dayjsConfig';
import './global.less';
import { removeAllPageCacheState } from './hooks/pageCacheState';
import { removeAllPageCacheState } from './hooks/useCacheState';
import { globalGetSeverTime } from './hooks/useServerTime';
import {
getRemoteMenu,
getRoutersInfo,
@@ -17,21 +20,17 @@ import {
setRemoteMenu,
} from './services/session';
import './styles/menu.less';
export { requestConfig as request } from './requestConfig';
// const isDev = process.env.NODE_ENV === 'development';
import { type GlobalInitialState } from '@/types';
// import '@/utils/clipboard';
import { menuItemRender } from '@/utils/menuRender';
import ErrorBoundary from './components/ErrorBoundary';
import { needAuth } from './utils';
import { gotoLoginPage } from './utils/ui';
export { requestConfig as request } from './requestConfig';

/**
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
* */
*/
export async function getInitialState(): Promise<GlobalInitialState> {
const fetchUserInfo = async () => {
try {
globalGetSeverTime();
const response = await getUserInfo();
return {
...response.user,
@@ -41,7 +40,7 @@ export async function getInitialState(): Promise<GlobalInitialState> {
roleNames: response.user.roles,
} as API.CurrentUser;
} catch (error) {
console.error('1111', error);
console.error('getInitialState', error);
gotoLoginPage();
}
return undefined;
@@ -140,7 +139,6 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
onClick: () => {
// 点击菜单项,删除所有的页面 state 缓存
removeAllPageCacheState();
// console.log('click menu');
},
},
...initialState?.settings,
@@ -169,7 +167,7 @@ export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => {
}
};

export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => {
export const patchRoutes: RuntimeConfig['patchRoutes'] = () => {
//console.log('patchRoutes', e);
};

@@ -215,7 +213,7 @@ export const antd: RuntimeAntdConfig = (memo) => {
defaultColor: themes['textColor'],
defaultHoverBg: 'rgba(22, 100, 255, 0.06)',
defaultHoverBorderColor: 'rgba(22, 100, 255, 0.5)',
defaultHoverColor: '#3F7FFF ',
defaultHoverColor: '#3F7FFF',
defaultActiveBg: 'rgba(22, 100, 255, 0.12)',
defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)',
defaultActiveColor: themes['primaryColor'],
@@ -228,11 +226,12 @@ export const antd: RuntimeAntdConfig = (memo) => {
};
memo.theme.components.Select = {
singleItemHeightLG: 46,
optionSelectedColor: themes['primaryColor'],
};
memo.theme.components.Table = {
headerBg: 'rgba(242, 244, 247, 0.36)',
headerBorderRadius: 4,
rowSelectedBg: 'rgba(22, 100, 255, 0.05)',
// rowSelectedBg: 'rgba(22, 100, 255, 0.05)', 固定列时,横向滑动导致重叠
};
memo.theme.components.Tabs = {
titleFontSize: 16,
@@ -245,9 +244,12 @@ export const antd: RuntimeAntdConfig = (memo) => {
linkColor: 'rgba(29, 29, 32, 0.7)',
separatorColor: 'rgba(29, 29, 32, 0.7)',
};
memo.theme.components.Tree = {
directoryNodeSelectedBg: 'rgba(22, 100, 255, 0.7)',
};

memo.theme.cssVar = true;
// memo.theme.hashed = false;
memo.theme.hashed = false;

memo.appConfig = {
message: {


BIN
react-ui/src/assets/img/confirm-icon.png View File

Before After
Width: 462  |  Height: 276  |  Size: 45 kB

react-ui/src/assets/img/comfirm-icon.png → react-ui/src/assets/img/copy-icon.png View File


BIN
react-ui/src/assets/img/logo-cc.png View File

Before After
Width: 104  |  Height: 132  |  Size: 9.2 kB

BIN
react-ui/src/assets/img/logo-cl.png View File

Before After
Width: 112  |  Height: 112  |  Size: 5.3 kB

BIN
react-ui/src/assets/img/logo.png View File

Before After
Width: 104  |  Height: 132  |  Size: 9.2 kB Width: 112  |  Height: 112  |  Size: 5.3 kB

BIN
react-ui/src/assets/img/popover-bg.png View File

Before After
Width: 1200  |  Height: 452  |  Size: 49 kB

BIN
react-ui/src/assets/img/user-points-bg.png View File

Before After
Width: 981  |  Height: 672  |  Size: 220 kB

BIN
react-ui/src/assets/img/workspace-experiment.png View File

Before After
Width: 147  |  Height: 148  |  Size: 17 kB Width: 216  |  Height: 216  |  Size: 24 kB

BIN
react-ui/src/assets/img/workspace-pipeline.png View File

Before After
Width: 169  |  Height: 159  |  Size: 21 kB Width: 219  |  Height: 216  |  Size: 31 kB

+ 86
- 0
react-ui/src/components/BasicInfo/BasicInfoItem.tsx View File

@@ -0,0 +1,86 @@
/*
* @Author: 赵伟
* @Date: 2024-11-29 09:27:19
* @Description: 用于 BasicInfo 和 BasicTableInfo 组件的子组件
*/

import { Typography } from 'antd';
import React from 'react';
import BasicInfoItemValue from './BasicInfoItemValue';
import { type BasicInfoData, type BasicInfoLink } from './types';

type BasicInfoItemProps = {
/** 基础信息 */
data: BasicInfoData;
/** 标题宽度 */
labelWidth: number;
/** 自定义类名前缀 */
classPrefix: string;
/** 标题是否显示省略号 */
labelEllipsis?: boolean;
/** 标签对齐方式 */
labelAlign?: 'start' | 'end' | 'justify';
};

function BasicInfoItem({
data,
labelWidth,
classPrefix,
labelEllipsis = true,
labelAlign = 'start',
}: BasicInfoItemProps) {
const { label, value, format, ellipsis } = data;
const formatValue = format ? format(value) : value;
const myClassName = `${classPrefix}__item`;
let valueComponent = undefined;
if (React.isValidElement(formatValue)) {
valueComponent = <div className={`${myClassName}__node`}>{formatValue}</div>;
} else if (Array.isArray(formatValue)) {
valueComponent = (
<div className={`${myClassName}__value-container`}>
{formatValue.map((item: BasicInfoLink) => (
<BasicInfoItemValue
key={item.value}
value={item.value}
link={item.link}
url={item.url}
ellipsis={ellipsis}
classPrefix={classPrefix}
/>
))}
</div>
);
} else if (typeof formatValue === 'object' && formatValue) {
valueComponent = (
<BasicInfoItemValue
value={formatValue.value}
link={formatValue.link}
url={formatValue.url}
ellipsis={ellipsis}
classPrefix={classPrefix}
/>
);
} else {
valueComponent = (
<BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} />
);
}
return (
<div className={myClassName} key={label}>
<div
className={`${myClassName}__label`}
style={{ width: labelWidth, textAlign: labelAlign, textAlignLast: labelAlign }}
>
<Typography.Text
ellipsis={labelEllipsis !== false ? { tooltip: label } : false}
style={{ width: labelAlign === 'justify' ? '100%' : 'auto' }}
>
{label}
</Typography.Text>
</div>
{valueComponent}
</div>
);
}

export default BasicInfoItem;

+ 58
- 0
react-ui/src/components/BasicInfo/BasicInfoItemValue.tsx View File

@@ -0,0 +1,58 @@
/*
* @Author: 赵伟
* @Date: 2024-11-29 09:27:19
* @Description: 用于 BasicInfoItem 的组件
*/

import { isEmpty } from '@/utils';
import { Link } from '@umijs/max';
import { Typography } from 'antd';

type BasicInfoItemValueProps = {
/** 值是否显示省略号 */
ellipsis?: boolean;
/** 自定义类名前缀 */
classPrefix: string;
/** 值 */
value?: string;
/** 内部链接 */
link?: string;
/** 外部链接 */
url?: string;
};

function BasicInfoItemValue({
value,
link,
url,
classPrefix,
ellipsis = true,
}: BasicInfoItemValueProps) {
const myClassName = `${classPrefix}__item__value`;
let component = undefined;
if (url && value) {
component = (
<a className={`${myClassName}__link`} href={url} target="_blank" rel="noopener noreferrer">
{value}
</a>
);
} else if (link && value) {
component = (
<Link to={link} className={`${myClassName}__link`}>
{value}
</Link>
);
} else {
component = <span className={`${myClassName}__text`}>{!isEmpty(value) ? value : '--'}</span>;
}

return (
<div className={myClassName}>
<Typography.Text ellipsis={ellipsis !== false ? { tooltip: value } : false}>
{component}
</Typography.Text>
</div>
);
}

export default BasicInfoItemValue;

+ 0
- 113
react-ui/src/components/BasicInfo/components.tsx View File

@@ -1,113 +0,0 @@
/*
* @Author: 赵伟
* @Date: 2024-11-29 09:27:19
* @Description: 用于 BasicInfo 和 BasicTableInfo 组件的子组件
*/

import { Link } from '@umijs/max';
import { Typography } from 'antd';
import React from 'react';
import { type BasicInfoData, type BasicInfoLink } from './types';

type BasicInfoItemProps = {
data: BasicInfoData;
labelWidth: number;
classPrefix: string;
};

export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemProps) {
const { label, value, format, ellipsis } = data;
const formatValue = format ? format(value) : value;
const myClassName = `${classPrefix}__item`;
let valueComponent = undefined;
if (Array.isArray(formatValue)) {
valueComponent = (
<div className={`${myClassName}__value-container`}>
{formatValue.map((item: BasicInfoLink) => (
<BasicInfoItemValue
key={item.value}
value={item.value}
link={item.link}
url={item.url}
ellipsis={ellipsis}
classPrefix={classPrefix}
/>
))}
</div>
);
} else if (React.isValidElement(formatValue)) {
// 这个判断必须在下面的判断之前
valueComponent = (
<BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} />
);
} else if (typeof formatValue === 'object' && formatValue) {
valueComponent = (
<BasicInfoItemValue
value={formatValue.value}
link={formatValue.link}
url={formatValue.url}
ellipsis={ellipsis}
classPrefix={classPrefix}
/>
);
} else {
valueComponent = (
<BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} />
);
}
return (
<div className={myClassName} key={label}>
<div className={`${myClassName}__label`} style={{ width: labelWidth }}>
{label}
</div>
{valueComponent}
</div>
);
}

type BasicInfoItemValueProps = {
ellipsis?: boolean;
classPrefix: string;
value: string | React.ReactNode;
link?: string;
url?: string;
};

export function BasicInfoItemValue({
value,
link,
url,
ellipsis,
classPrefix,
}: BasicInfoItemValueProps) {
const myClassName = `${classPrefix}__item__value`;
let component = undefined;
if (url && value) {
component = (
<a className={`${myClassName}__link`} href={url} target="_blank" rel="noopener noreferrer">
{value}
</a>
);
} else if (link && value) {
component = (
<Link to={link} className={`${myClassName}__link`}>
{value}
</Link>
);
} else if (React.isValidElement(value)) {
return value;
} else {
component = <span className={`${myClassName}__text`}>{value ?? '--'}</span>;
}

return (
<div className={myClassName}>
<Typography.Text
ellipsis={ellipsis ? { tooltip: value } : false}
style={{ fontSize: 'inherit' }}
>
{component}
</Typography.Text>
</div>
);
}

+ 0
- 48
react-ui/src/components/BasicInfo/format.ts View File

@@ -1,48 +0,0 @@
/*
* @Author: 赵伟
* @Date: 2024-11-29 09:27:19
* @Description: 用于 BasicInfo 和 BasicTableInfo 组件的常用转化格式
*/

// 格式化日期
export { formatDate } from '@/utils/date';

/**
* 格式化字符串数组
* @param value - 字符串数组
* @returns 逗号分隔的字符串
*/
export const formatList = (value: string[] | null | undefined): string => {
if (
value === undefined ||
value === null ||
Array.isArray(value) === false ||
value.length === 0
) {
return '--';
}
return value.join(',');
};

/**
* 格式化布尔值
* @param value - 布尔值
* @returns "是" 或 "否"
*/
export const formatBoolean = (value: boolean): string => {
return value ? '是' : '否';
};

type FormatEnum = (value: string | number) => string;

/**
* 格式化枚举
* @param options - 枚举选项
* @returns 格式化枚举函数
*/
export const formatEnum = (options: { value: string | number; label: string }[]): FormatEnum => {
return (value: string | number) => {
const option = options.find((item) => item.value === value);
return option ? option.label : '--';
};
};

+ 26
- 2
react-ui/src/components/BasicInfo/index.less View File

@@ -17,8 +17,6 @@
color: @text-color-secondary;
font-size: @font-size-content;
line-height: 1.6;
text-align: justify;
text-align-last: justify;

&::after {
position: absolute;
@@ -31,10 +29,12 @@
flex: 1;
flex-direction: column;
gap: 5px 0;
min-width: 0;
}

&__value {
flex: 1;
min-width: 0;
margin-left: 16px;
font-size: @font-size-content;
line-height: 1.6;
@@ -49,5 +49,29 @@
text-underline-offset: 3px;
}
}

&__node {
flex: 1;
min-width: 0;
margin-left: 16px;
font-size: @font-size-content;
line-height: 1.6;
word-break: break-all;
}
}
}

.kf-basic-info--three-columns {
width: 100%;

.kf-basic-info__item {
width: calc((100% - 80px) / 3);

&__label {
font-size: @font-size;
}
&__value {
font-size: @font-size;
}
}
}

+ 35
- 6
react-ui/src/components/BasicInfo/index.tsx View File

@@ -1,27 +1,56 @@
import classNames from 'classnames';
import React from 'react';
import { BasicInfoItem } from './components';
import BasicInfoItem from './BasicInfoItem';
import './index.less';
import type { BasicInfoData, BasicInfoLink } from './types';
export * from './format';
export type { BasicInfoData, BasicInfoLink };

type BasicInfoProps = {
export type BasicInfoProps = {
/** 基础信息 */
datas: BasicInfoData[];
/** 标题宽度 */
labelWidth: number;
/** 标题是否显示省略号 */
labelEllipsis?: boolean;
/** 是否一行三列 */
threeColumns?: boolean;
/** 标签对齐方式 */
labelAlign?: 'start' | 'end' | 'justify';
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
labelWidth: number;
};

export default function BasicInfo({ datas, className, style, labelWidth }: BasicInfoProps) {
/**
* 基础信息展示组件,用于展示基础信息,支持一行两列或一行三列,支持数据格式化
*/
export default function BasicInfo({
datas,
labelWidth,
labelEllipsis = true,
labelAlign = 'start',
threeColumns = false,
className,
style,
}: BasicInfoProps) {
return (
<div className={classNames('kf-basic-info', className)} style={style}>
<div
className={classNames(
'kf-basic-info',
{ 'kf-basic-info--three-columns': threeColumns },
className,
)}
style={style}
>
{datas.map((item) => (
<BasicInfoItem
key={item.label}
data={item}
labelWidth={labelWidth}
classPrefix="kf-basic-info"
labelEllipsis={labelEllipsis}
labelAlign={labelAlign}
/>
))}
</div>


+ 2
- 2
react-ui/src/components/BasicInfo/types.ts View File

@@ -3,12 +3,12 @@ export type BasicInfoData = {
label: string;
value?: any;
ellipsis?: boolean;
format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined;
format?: (_value?: any) => string | React.ReactNode | BasicInfoLink | BasicInfoLink[] | undefined;
};

// 值为链接的类型
export type BasicInfoLink = {
value: string;
value?: string;
link?: string;
url?: string;
};

+ 10
- 3
react-ui/src/components/BasicTableInfo/index.less View File

@@ -4,7 +4,7 @@
flex-wrap: wrap;
align-items: stretch;
width: 100%;
border: 1px solid @border-color-base;
border: 1px solid @border-color;
border-bottom: none;
border-radius: 4px;

@@ -12,7 +12,7 @@
display: flex;
align-items: stretch;
width: 25%;
border-bottom: 1px solid @border-color-base;
border-bottom: 1px solid @border-color;

&__label {
flex: none;
@@ -34,7 +34,6 @@
&__value {
flex: 1;
min-width: 0;
margin: 0 !important;
padding: 12px 20px 4px;
font-size: @font-size;
word-break: break-all;
@@ -56,5 +55,13 @@
text-underline-offset: 3px;
}
}

&__node {
flex: 1;
min-width: 0;
padding: 12px 20px;
font-size: @font-size;
word-break: break-all;
}
}
}

+ 10
- 10
react-ui/src/components/BasicTableInfo/index.tsx View File

@@ -1,22 +1,21 @@
import classNames from 'classnames';
import { BasicInfoItem } from '../BasicInfo/components';
import { BasicInfoProps } from '../BasicInfo';
import BasicInfoItem from '../BasicInfo/BasicInfoItem';
import { type BasicInfoData, type BasicInfoLink } from '../BasicInfo/types';
import './index.less';
export * from '../BasicInfo/format';
export type { BasicInfoData, BasicInfoLink };

type BasicTableInfoProps = {
datas: BasicInfoData[];
className?: string;
style?: React.CSSProperties;
labelWidth: number;
};
export type BasicTableInfoProps = Omit<BasicInfoProps, 'labelAlign' | 'threeColumns'>;

/**
* 表格基础信息展示组件,用于展示基础信息,一行四列,支持数据格式化
*/
export default function BasicTableInfo({
datas,
labelWidth,
labelEllipsis,
className,
style,
labelWidth,
}: BasicTableInfoProps) {
const remainder = datas.length % 4;
const array = [];
@@ -24,7 +23,7 @@ export default function BasicTableInfo({
for (let i = 0; i < 4 - remainder; i++) {
array.push({
label: '',
value: '',
value: false, // 用于区分是否是空数据,不能是空字符串、null、undefined
});
}
}
@@ -37,6 +36,7 @@ export default function BasicTableInfo({
key={`${item.label}-${index}`}
data={item}
labelWidth={labelWidth}
labelEllipsis={labelEllipsis}
classPrefix="kf-basic-table-info"
/>
))}


react-ui/src/pages/Pipeline/components/CodeConfigItem/index.less → react-ui/src/components/CodeConfigItem/index.less View File


react-ui/src/pages/Pipeline/components/CodeConfigItem/index.tsx → react-ui/src/components/CodeConfigItem/index.tsx View File

@@ -2,6 +2,7 @@ import { AvailableRange } from '@/enums';
import { type CodeConfigData } from '@/pages/CodeConfig/List';
import { Flex, Typography } from 'antd';
import classNames from 'classnames';
import { useState } from 'react';
import styles from './index.less';

type CodeConfigItemProps = {
@@ -10,6 +11,7 @@ type CodeConfigItemProps = {
};

function CodeConfigItem({ item, onClick }: CodeConfigItemProps) {
const [isEllipsis, setIsEllipsis] = useState(false);
return (
<div className={styles['code-config-item']} onClick={() => onClick?.(item)}>
<Flex justify="space-between" align="center" style={{ marginBottom: '15px' }}>
@@ -32,11 +34,20 @@ function CodeConfigItem({ item, onClick }: CodeConfigItemProps) {
</Flex>
<Typography.Paragraph
className={styles['code-config-item__url']}
ellipsis={{ rows: 2, tooltip: item.git_url }}
ellipsis={{
rows: 2,
tooltip: isEllipsis ? item.git_url : false, // 仅当省略时显示 tooltip
onEllipsis: (ellipsis) => setIsEllipsis(ellipsis),
}}
>
{item.git_url}
</Typography.Paragraph>
<div className={styles['code-config-item__branch']}>{item.git_branch}</div>
<Typography.Paragraph
className={styles['code-config-item__branch']}
ellipsis={{ tooltip: item.git_branch }}
>
{item.git_branch}
</Typography.Paragraph>
</div>
);
}

+ 36
- 13
react-ui/src/components/CodeSelect/index.tsx View File

@@ -4,59 +4,82 @@
* @Description: 流水线选择代码配置表单
*/

import CodeSelectorModal from '@/components/CodeSelectorModal';
import KFIcon from '@/components/KFIcon';
import CodeSelectorModal from '@/pages/Pipeline/components/CodeSelectorModal';
import { openAntdModal } from '@/utils/modal';
import { Button } from 'antd';
import classNames from 'classnames';
import ParameterInput, { type ParameterInputProps } from '../ParameterInput';
import './index.less';

export { requiredValidator, type ParameterInputObject } from '../ParameterInput';
export {
requiredValidator,
type ParameterInputObject,
type ParameterInputValue,
} from '../ParameterInput';

type CodeSelectProps = ParameterInputProps;

function CodeSelect({ value, onChange, disabled, ...rest }: CodeSelectProps) {
/** 代码配置选择表单组件 */
function CodeSelect({
value,
size,
disabled,
className,
style,
onChange,
...rest
}: CodeSelectProps) {
// 选择代码配置
const selectResource = () => {
const { close } = openAntdModal(CodeSelectorModal, {
onOk: (res) => {
if (res) {
const { git_url, git_branch, code_repo_name } = res;
const { id, code_repo_name, git_url, git_branch, git_user_name, git_password, ssh_key } =
res;
const jsonObj = {
id,
name: code_repo_name,
code_path: git_url,
branch: git_branch,
username: git_user_name,
password: git_password,
ssh_private_key: ssh_key,
};
const jsonObjStr = JSON.stringify(jsonObj);
const showValue = code_repo_name;
onChange?.({
value: jsonObjStr,
showValue,
showValue: code_repo_name,
fromSelect: true,
...jsonObj,
});
} else {
onChange?.({
value: undefined,
showValue: undefined,
fromSelect: false,
});
onChange?.(undefined);
}
close();
},
});
};

// 删除
const handleRemove = () => {
onChange?.(undefined);
};

return (
<div className="kf-code-select">
<div className={classNames('kf-code-select', className)} style={style}>
<ParameterInput
{...rest}
size={size}
disabled={disabled}
value={value}
onChange={onChange}
onClick={selectResource}
onRemove={handleRemove}
></ParameterInput>
<Button
className="kf-code-select__button"
size="large"
size={size}
type="link"
icon={<KFIcon type="icon-xuanzedaimapeizhi" font={16} />}
disabled={disabled}


+ 49
- 0
react-ui/src/components/CodeSelectorModal/index.less View File

@@ -0,0 +1,49 @@
.kf-code-selector-modal {
width: 100%;
height: 100%;

&__search {
width: 100%;
}

&__content {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 10px;
width: 100%;
max-height: 50vh;
margin-top: 24px;
margin-bottom: 30px;
overflow-x: hidden;
overflow-y: auto;
}

&__empty {
padding-top: 40px;
}

// 覆盖 antd 样式
.ant-input-affix-wrapper {
border-radius: 23px !important;
.ant-input-prefix {
margin-inline-end: 12px;
}
.ant-input-suffix {
margin-inline-end: 12px;
}
.ant-input-clear-icon {
font-size: 16px;
}
}

.ant-input-group-addon {
display: none;
}

.ant-pagination {
.ant-select-single {
height: 32px !important;
}
}
}

react-ui/src/pages/Pipeline/components/CodeSelectorModal/index.tsx → react-ui/src/components/CodeSelectorModal/index.tsx View File

@@ -4,16 +4,16 @@
* @Description: 选择代码
*/

import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { type CodeConfigData } from '@/pages/CodeConfig/List';
import { getCodeConfigListReq } from '@/services/codeConfig';
import { to } from '@/utils/promise';
import { Icon } from '@umijs/max';
import type { ModalProps, PaginationProps } from 'antd';
import { Empty, Input, Pagination } from 'antd';
import { useEffect, useState } from 'react';
import CodeConfigItem from '../CodeConfigItem';
import styles from './index.less';
import './index.less';

export { type CodeConfigData };

@@ -21,6 +21,7 @@ export interface CodeSelectorModalProps extends Omit<ModalProps, 'onOk'> {
onOk?: (params: CodeConfigData | undefined) => void;
}

/** 选择代码配置的弹窗,推荐使用函数的方式打开 */
function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
const [dataList, setDataList] = useState<CodeConfigData[]>([]);
const [total, setTotal] = useState(0);
@@ -32,23 +33,23 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
const [inputText, setInputText] = useState<string | undefined>(undefined);

useEffect(() => {
// 获取数据请求
const getDataList = async () => {
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
code_repo_name: searchText || undefined,
};
const [res] = await to(getCodeConfigListReq(params));
if (res && res.data && res.data.content) {
setDataList(res.data.content);
setTotal(res.data.totalElements);
}
};

getDataList();
}, [pagination, searchText]);

// 获取数据请求
const getDataList = async () => {
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
code_repo_name: searchText || undefined,
};
const [res] = await to(getCodeConfigListReq(params));
if (res && res.data && res.data.content) {
setDataList(res.data.content);
setTotal(res.data.totalElements);
}
};

// 搜索
const handleSearch = (value: string) => {
setSearchText(value);
@@ -79,9 +80,9 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
footer={null}
destroyOnClose
>
<div className={styles['code-selector']}>
<div className="kf-code-selector-modal">
<Input.Search
className={styles['code-selector__search']}
className="kf-code-selector-modal__search"
placeholder="按代码仓库名称筛选"
allowClear
onSearch={handleSearch}
@@ -90,12 +91,15 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
suffix={null}
value={inputText}
prefix={
<Icon icon="local:magnifying-glass" style={{ marginLeft: '10px', marginTop: '2px' }} />
<KFIcon type="icon-sousuo" color="rgba(22,100,255,0.4" style={{ marginLeft: '10px' }} />
}
// prefix={
// <Icon icon="local:magnifying-glass" style={{ marginLeft: '10px', marginTop: '2px' }} />
// }
/>
{dataList?.length !== 0 ? (
<>
<div className={styles['code-selector__content']}>
<div className="kf-code-selector-modal__content">
{dataList?.map((item) => (
<CodeConfigItem item={item} key={item.id} onClick={handleClick} />
))}
@@ -112,7 +116,7 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
/>
</>
) : (
<div className={styles['code-selector__empty']}>
<div className="kf-code-selector-modal__empty">
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>
</div>
)}

+ 41
- 0
react-ui/src/components/ConfigInfo/index.tsx View File

@@ -0,0 +1,41 @@
import BasicInfo, { type BasicInfoData, type BasicInfoProps } from '@/components/BasicInfo';
import InfoGroup from '@/components/InfoGroup';
import classNames from 'classnames';
export type { BasicInfoData };

interface ConfigInfoProps extends BasicInfoProps {
/** 标题 */
title: string;
/** 子元素 */
children?: React.ReactNode;
}

/** 详情基本信息块,目前主要用于主动机器学习、超参数寻优、自主学习详情中 */
function ConfigInfo({
title,
datas,
labelWidth,
labelAlign = 'start',
labelEllipsis = true,
threeColumns = true,
className,
style,
children,
}: ConfigInfoProps) {
return (
<InfoGroup title={title} className={classNames('kf-config-info', className)} style={style}>
<div className={'kf-config-info__content'}>
<BasicInfo
datas={datas}
labelWidth={labelWidth}
labelAlign={labelAlign}
labelEllipsis={labelEllipsis}
threeColumns={threeColumns}
/>
{children}
</div>
</InfoGroup>
);
}

export default ConfigInfo;

react-ui/src/utils/clipboard.js → react-ui/src/components/CopyingText/clipboard.js View File

@@ -1,5 +1,5 @@
import { message } from 'antd';
import ClipboardJS from 'clipboard';
import { message } from "antd";

const clipboard = new ClipboardJS('#copying');

@@ -9,4 +9,4 @@ clipboard.on('success', () => {

clipboard.on('error', () => {
message.error('复制失败');
});
});

react-ui/src/pages/AutoML/components/CopyingText/index.less → react-ui/src/components/CopyingText/index.less View File


+ 26
- 0
react-ui/src/components/CopyingText/index.tsx View File

@@ -0,0 +1,26 @@
import KFIcon from '@/components/KFIcon';
import { Tooltip } from 'antd';
import styles from './index.less';

export type CopyingTextProps = {
text: string;
};

function CopyingText({ text }: CopyingTextProps) {
return (
<div className={styles['copying-text']}>
<span className={styles['copying-text__text']}>{text}</span>
<Tooltip title="复制">
<KFIcon
id="copying"
data-clipboard-text={text}
type="icon-fuzhi2"
className={styles['copying-text__icon']}
color="#606b7a"
/>
</Tooltip>
</div>
);
}

export default CopyingText;

+ 0
- 9
react-ui/src/components/DisabledInput/index.less View File

@@ -1,9 +0,0 @@
.disabled-input {
padding: 4px 11px;
color: rgba(0, 0, 0, 0.25);
font-size: @font-size-input;
background-color: rgba(0, 0, 0, 0.04);
border: 1px solid #d9d9d9;
border-radius: 6px;
cursor: not-allowed;
}

+ 0
- 20
react-ui/src/components/DisabledInput/index.tsx View File

@@ -1,20 +0,0 @@
import { Typography } from 'antd';
import styles from './index.less';

type DisabledInputProps = {
value?: any;
valuePropName?: string;
};

function DisabledInput({ value, valuePropName }: DisabledInputProps) {
const data = valuePropName ? value[valuePropName] : value;
return (
<div className={styles['disabled-input']}>
<Typography.Text ellipsis={{ tooltip: data }} style={{ color: 'inherit' }}>
{data}
</Typography.Text>
</div>
);
}

export default DisabledInput;

+ 19
- 0
react-ui/src/components/FormInfo/index.less View File

@@ -0,0 +1,19 @@
.form-info {
min-height: 32px;
padding: 4px 11px;
color: @text-disabled-color;
font-size: @font-size-input;
background-color: rgba(0, 0, 0, 0.04);
border: 1px solid #d9d9d9;
border-radius: 6px;

.ant-typography {
margin: 0 !important;
}
}

.form-info--multiline {
.ant-typography {
white-space: pre-wrap;
}
}

+ 74
- 0
react-ui/src/components/FormInfo/index.tsx View File

@@ -0,0 +1,74 @@
import { formatEnum } from '@/utils/format';
import { Typography, type SelectProps } from 'antd';
import classNames from 'classnames';
import './index.less';

type FormInfoProps = {
/** 值 */
value?: any;
/** 如果 `value` 是对象,取对象的哪个属性作为值 */
valuePropName?: string;
/** 是否是多行文本 */
textArea?: boolean;
/** 是否是下拉框 */
select?: boolean;
/** 下拉框数据 */
options?: SelectProps['options'];
/** 自定义节点 label、value 的字段 */
fieldNames?: SelectProps['fieldNames'];
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
};

/**
* 模拟禁用的输入框,但是内容超长时,hover 时显示所有内容
*/
function FormInfo({
value,
valuePropName,
textArea = false,
select = false,
options,
fieldNames,
className,
style,
}: FormInfoProps) {
let showValue = value;
if (value && typeof value === 'object' && valuePropName) {
showValue = value[valuePropName];
} else if (select === true && options) {
let _options: SelectProps['options'] = options;
if (fieldNames) {
_options = options.map((v) => {
return {
...v,
label: fieldNames.label && v[fieldNames.label],
value: fieldNames.value && v[fieldNames.value],
options: fieldNames.options && v[fieldNames.options],
};
});
}
showValue = formatEnum(_options)(value);
}

return (
<div
className={classNames(
'form-info',
{
'form-info--multiline': textArea,
},
className,
)}
style={style}
>
<Typography.Paragraph ellipsis={textArea ? false : { tooltip: showValue }}>
{showValue}
</Typography.Paragraph>
</div>
);
}

export default FormInfo;

+ 13
- 5
react-ui/src/components/FullScreenFrame/index.tsx View File

@@ -2,22 +2,30 @@ import classNames from 'classnames';
import './index.less';

type FullScreenFrameProps = {
/** URL */
url: string;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
onload?: (e?: React.SyntheticEvent<HTMLIFrameElement, Event>) => void;
onerror?: (e?: React.SyntheticEvent<HTMLIFrameElement, Event>) => void;
/** 加载完成回调 */
onLoad?: (e?: React.SyntheticEvent<HTMLIFrameElement, Event>) => void;
/** 加载失败回调 */
onError?: (e?: React.SyntheticEvent<HTMLIFrameElement, Event>) => void;
};

function FullScreenFrame({ url, className, style, onload, onerror }: FullScreenFrameProps) {
/**
* 全屏 iframe,IFramePage 组件的子组件,开发中应该使用 IFramePage
*/
function FullScreenFrame({ url, className, style, onLoad, onError }: FullScreenFrameProps) {
return (
<div className={classNames('kf-full-screen-frame', className ?? '')} style={style}>
{url && (
<iframe
src={url}
className="kf-full-screen-frame__iframe"
onLoad={onload}
onError={onerror}
onLoad={onLoad}
onError={onError}
></iframe>
)}
</div>


+ 48
- 24
react-ui/src/components/IFramePage/index.tsx View File

@@ -1,8 +1,9 @@
import FullScreenFrame from '@/components/FullScreenFrame';
import KFSpin from '@/components/KFSpin';
// import { getLabelStudioUrl } from '@/services/developmentEnvironment';
import { getKnowledgeGraphUrl, getLabelStudioUrl } from '@/services/developmentEnvironment';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import { FloatButton } from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
@@ -12,52 +13,74 @@ export enum IframePageType {
DatasetAnnotation = 'DatasetAnnotation', // 数据标注
AppDevelopment = 'AppDevelopment', // 应用开发
DevEnv = 'DevEnv', // 开发环境
GitLink = 'GitLink',
GitLink = 'GitLink', // git link
Aim = 'Aim', // 实验对比
Knowledge = 'Knowledge', // 知识图谱
TensorBoard = 'TensorBoard', // 可视化结果
}

const getRequestAPI = (type: IframePageType): (() => Promise<any>) => {
switch (type) {
case IframePageType.DatasetAnnotation:
return () => Promise.resolve({ code: 200, data: 'http://172.20.32.181:18888/oauth/login' }); //getLabelStudioUrl;
case IframePageType.AppDevelopment:
return () => Promise.resolve({ code: 200, data: 'http://172.20.32.185:30080/' });
case IframePageType.DevEnv:
case IframePageType.DatasetAnnotation: // 数据标注
return getLabelStudioUrl;
case IframePageType.AppDevelopment: // 应用开发
return () => Promise.resolve({ code: 200, data: 'http://172.20.32.197:30080/' });
case IframePageType.DevEnv: // 开发环境
return () =>
Promise.resolve({
code: 200,
data: SessionStorage.getItem(SessionStorage.editorUrlKey) || '',
});
case IframePageType.GitLink:
case IframePageType.GitLink: // git link
return () => Promise.resolve({ code: 200, data: 'http://172.20.32.201:4000' });
case IframePageType.Aim: // Aim
return () =>
Promise.resolve({
code: 200,
data: SessionStorage.getItem(SessionStorage.aimUrlKey),
});
case IframePageType.Knowledge:
// 知识图谱
return getKnowledgeGraphUrl;
case IframePageType.TensorBoard:
// TensorBoard
return () =>
Promise.resolve({
code: 200,
data: SessionStorage.getItem(SessionStorage.tensorBoardUrlKey),
});
}
};

type IframePageProps = {
/** 子系统 */
type: IframePageType;
/** 是否可以在页签上打开 */
openInTab?: boolean;
/** 自定义样式类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
};

function IframePage({ type, className, style }: IframePageProps) {
/** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */
function IframePage({ type, openInTab = false, className, style }: IframePageProps) {
const [iframeUrl, setIframeUrl] = useState('');
const [loading, setLoading] = useState(false);

useEffect(() => {
requestIframeUrl();
return () => {
if (type === IframePageType.DevEnv) {
SessionStorage.removeItem(SessionStorage.editorUrlKey);
const requestIframeUrl = async () => {
setLoading(true);
const [res] = await to(getRequestAPI(type)());
if (res && res.data) {
setIframeUrl(res.data);
} else {
setLoading(false);
}
};
}, []);
const requestIframeUrl = async () => {
setLoading(true);
const [res] = await to(getRequestAPI(type)());
if (res && res.data) {
setIframeUrl(res.data);
} else {
setLoading(false);
}
};

requestIframeUrl();
}, [type]);

const hideLoading = () => {
setLoading(false);
@@ -66,7 +89,8 @@ function IframePage({ type, className, style }: IframePageProps) {
return (
<div className={classNames('kf-iframe-page', className)} style={style}>
{loading && createPortal(<KFSpin size="large" />, document.body)}
<FullScreenFrame url={iframeUrl} onload={hideLoading} onerror={hideLoading} />
<FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} />
{openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />}
</div>
);
}


react-ui/src/components/InfoGroupTitle/index.less → react-ui/src/components/InfoGroup/InfoGroupTitle.less View File

@@ -1,7 +1,7 @@
.kf-info-group-title {
width: 100%;
height: 56px;
padding-left: @content-padding;
padding: 0 @content-padding;
background: linear-gradient(
179.03deg,
rgba(199, 223, 255, 0.12) 0%,
@@ -21,6 +21,7 @@
color: @text-color;
font-weight: 500;
font-size: @font-size-title;
.singleLine();

&::after {
position: absolute;

react-ui/src/components/InfoGroupTitle/index.tsx → react-ui/src/components/InfoGroup/InfoGroupTitle.tsx View File

@@ -1,13 +1,19 @@
import { Flex } from 'antd';
import classNames from 'classnames';
import './index.less';
import './InfoGroupTitle.less';

type InfoGroupTitleProps = {
/** 标题 */
title: string;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
};

/**
* 信息组标题
*/
function InfoGroupTitle({ title, style, className }: InfoGroupTitleProps) {
return (
<Flex align="center" className={classNames('kf-info-group-title', className)} style={style}>

+ 1
- 1
react-ui/src/components/InfoGroup/index.less View File

@@ -4,7 +4,7 @@
&__content {
padding: 20px @content-padding;
background-color: white;
border: 1px solid @border-color-base;
border: 1px solid @border-color;
border-top: none;
border-radius: 0 0 4px 4px;
}


+ 12
- 3
react-ui/src/components/InfoGroup/index.tsx View File

@@ -1,16 +1,25 @@
import classNames from 'classnames';
import InfoGroupTitle from '../InfoGroupTitle';
import InfoGroupTitle from './InfoGroupTitle';
import './index.less';

type InfoGroupProps = {
/** 标题 */
title: string;
height?: string | number; // 如果要纵向滚动,需要设置高度
width?: string | number; // 如果要横向滚动,需要设置宽度
/** 高度, 如果要纵向滚动,需要设置高度 */
height?: string | number;
/** 宽度, 如果要横向滚动,需要设置宽度 */
width?: string | number;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
/** 子元素 */
children?: React.ReactNode;
};

/**
* 信息组,用于展示基本信息,支持横向、纵向滚动。自动机器学习、超参数寻优都是使用这个组件
*/
function InfoGroup({ title, height, width, className, style, children }: InfoGroupProps) {
const contentStyle: React.CSSProperties = {};
if (height) {


+ 1
- 1
react-ui/src/components/KFEmpty/index.less View File

@@ -33,7 +33,7 @@
margin-top: 20px;
margin-bottom: 30px;

&__back-btn {
&__button {
height: 32px;
}
}


+ 16
- 6
react-ui/src/components/KFEmpty/index.tsx View File

@@ -9,15 +9,24 @@ export enum EmptyType {
}

type EmptyProps = {
className?: string;
style?: React.CSSProperties;
/** 类型 */
type: EmptyType;
/** 标题 */
title?: string;
/** 内容 */
content?: string;
/** 是否有页脚,如果有默认是一个按钮 */
hasFooter?: boolean;
footer?: () => React.ReactNode;
/** 按钮标题,默认是"刷新" */
buttonTitle?: string;
onRefresh?: () => void;
/** 按钮点击回调 */
onButtonClick?: () => void;
/** 自定义页脚内容 */
footer?: () => React.ReactNode;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
};

function getEmptyImage(type: EmptyType) {
@@ -31,6 +40,7 @@ function getEmptyImage(type: EmptyType) {
}
}

/** 空状态 */
function KFEmpty({
className,
style,
@@ -40,7 +50,7 @@ function KFEmpty({
hasFooter = true,
footer,
buttonTitle = '刷新',
onRefresh,
onButtonClick,
}: EmptyProps) {
const image = getEmptyImage(type);

@@ -54,7 +64,7 @@ function KFEmpty({
{footer ? (
footer()
) : (
<Button className="kf-empty__footer__back-btn" type="primary" onClick={onRefresh}>
<Button className="kf-empty__footer__button" type="primary" onClick={onButtonClick}>
{buttonTitle}
</Button>
)}


+ 8
- 2
react-ui/src/components/KFIcon/index.tsx View File

@@ -14,14 +14,20 @@ const Icon = createFromIconfontCN({
type IconFontProps = Parameters<typeof Icon>[0];

interface KFIconProps extends IconFontProps {
/** 图标 */
type: string;
/** 字体大小 */
font?: number;
/** 字体颜色 */
color?: string;
style?: React.CSSProperties;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
}

function KFIcon({ type, font = 15, color = '', style = {}, className, ...rest }: KFIconProps) {
/** 封装 iconfont 图标 */
function KFIcon({ type, font = 15, color, className, style, ...rest }: KFIconProps) {
const iconStyle = {
...style,
fontSize: font,


react-ui/src/components/ModalTitle/index.less → react-ui/src/components/KFModal/KFModalTitle.less View File


react-ui/src/components/ModalTitle/index.tsx → react-ui/src/components/KFModal/KFModalTitle.tsx View File

@@ -6,12 +6,16 @@

import classNames from 'classnames';
import React from 'react';
import './index.less';
import './KFModalTitle.less';

type ModalTitleProps = {
/** 标题 */
title: React.ReactNode;
/** 图片 */
image?: string;
/** 自定义样式 */
style?: React.CSSProperties;
/** 自定义类名 */
className?: string;
};


+ 6
- 3
react-ui/src/components/KFModal/index.tsx View File

@@ -4,19 +4,22 @@
* @Description: 自定义 Modal
*/

import ModalTitle from '@/components/ModalTitle';
import { Modal, type ModalProps } from 'antd';
import classNames from 'classnames';
import KFModalTitle from './KFModalTitle';
import './index.less';

export interface KFModalProps extends ModalProps {
/** 标题图片 */
image?: string;
}

/** 自定义 Modal,应用中的业务 Modal 应该使用它进行封装,推荐使用函数的方式打开 */
function KFModal({
title,
image,
children,
className = '',
className,
centered,
maskClosable,
...rest
@@ -27,7 +30,7 @@ function KFModal({
{...rest}
centered={centered ?? true}
maskClosable={maskClosable ?? false}
title={<ModalTitle title={title} image={image}></ModalTitle>}
title={<KFModalTitle title={title} image={image} />}
>
{children}
</Modal>


+ 12
- 4
react-ui/src/components/KFRadio/index.tsx View File

@@ -8,32 +8,40 @@ import classNames from 'classnames';
import './index.less';

export type KFRadioItem = {
key: string;
title: string;
value: string;
icon?: React.ReactNode;
};

type KFRadioProps = {
/** 选项 */
items: KFRadioItem[];
/** 当前选中项 */
value?: string;
/** 自定义样式 */
style?: React.CSSProperties;
/** 自定义类名 */
className?: string;
/** 选中回调 */
onChange?: (value: string) => void;
};

/**
* 自定义 Radio
*/
function KFRadio({ items, value, style, className, onChange }: KFRadioProps) {
return (
<span className={classNames('kf-radio', className)} style={style}>
{items.map((item) => {
return (
<span
key={item.key}
key={item.value}
className={
value === item.key
value === item.value
? classNames('kf-radio__item', 'kf-radio__item--active')
: 'kf-radio__item'
}
onClick={() => onChange?.(item.key)}
onClick={() => onChange?.(item.value)}
>
{item.icon}
<span style={{ marginLeft: '5px' }}>{item.title}</span>


+ 1
- 1
react-ui/src/components/KFSpin/index.less View File

@@ -4,7 +4,7 @@
right: 0;
bottom: 0;
left: 0;
z-index: 1001;
z-index: 1001; // 设置大于 Modal 的 z-index
display: flex;
flex-direction: column;
align-items: center;


+ 11
- 5
react-ui/src/components/KFSpin/index.tsx View File

@@ -5,13 +5,19 @@
*/

import { Spin, SpinProps } from 'antd';
import styles from './index.less';
import './index.less';

function KFSpin(props: SpinProps) {
interface KFSpinProps extends SpinProps {
/** 加载文本 */
label?: string;
}

/** 自定义 Spin */
function KFSpin({ label = '加载中', ...rest }: KFSpinProps) {
return (
<div className={styles['kf-spin']}>
<Spin {...props} />
<div className={styles['kf-spin__label']}>加载中</div>
<div className={'kf-spin'}>
<Spin {...rest} />
<div className={'kf-spin__label'}>{label}</div>
</div>
);
}


+ 0
- 19
react-ui/src/components/LabelValue/index.less View File

@@ -1,19 +0,0 @@
.kf-label-value {
display: flex;
align-items: flex-start;
font-size: 16px;
line-height: 1.6;

&__label {
flex: none;
width: 80px;
color: @text-color-secondary;
}

&__value {
flex: 1;
color: @text-color;
white-space: pre-line;
word-break: break-all;
}
}

+ 0
- 20
react-ui/src/components/LabelValue/index.tsx View File

@@ -1,20 +0,0 @@
import classNames from 'classnames';
import './index.less';

type labelValueProps = {
label: string;
value?: any;
className?: string;
style?: React.CSSProperties;
};

function LabelValue({ label, value, className, style }: labelValueProps) {
return (
<div className={classNames('kf-label-value', className)} style={style}>
<div className="kf-label-value__label">{label}</div>
<div className="kf-label-value__value">{value ?? '--'}</div>
</div>
);
}

export default LabelValue;

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

Loading…
Cancel
Save