# Conflicts: # react-ui/config/proxy.ts # ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.javamaster-arm
| @@ -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 | |||
| @@ -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 | |||
| @@ -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[*]}" | |||
| @@ -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 | |||
| @@ -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; | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -5,4 +5,5 @@ | |||
| public | |||
| dist | |||
| .umi | |||
| mock | |||
| mock | |||
| /src/iconfont/ | |||
| @@ -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', | |||
| }, | |||
| }; | |||
| @@ -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 | |||
| @@ -0,0 +1 @@ | |||
| v18.20.7 | |||
| @@ -20,3 +20,4 @@ yarn-error.log | |||
| CNAME | |||
| /build | |||
| /public | |||
| /src/iconfont/ | |||
| @@ -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'; | |||
| } | |||
| } | |||
| }, | |||
| }, | |||
| }; | |||
| } | |||
| @@ -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>; | |||
| } | |||
| } | |||
| }; | |||
| @@ -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; | |||
| @@ -0,0 +1,6 @@ | |||
| import { addons } from '@storybook/manager-api'; | |||
| import theme from './theme'; | |||
| addons.setConfig({ | |||
| theme: theme, | |||
| }); | |||
| @@ -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; | |||
| @@ -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', | |||
| ], | |||
| ], | |||
| }, | |||
| ], | |||
| }; | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| import { create } from '@storybook/theming'; | |||
| export default create({ | |||
| base: 'light', | |||
| brandTitle: '组件库文档', | |||
| brandUrl: 'https://storybook.js.org/docs', | |||
| brandTarget: '_blank', | |||
| }); | |||
| @@ -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": "./" | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -0,0 +1,3 @@ | |||
| # Dockerfile | |||
| FROM nginx:alpine | |||
| COPY storybook-static/ /usr/share/nginx/html | |||
| @@ -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. | |||
| [](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master)   | |||
| [](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](http://umijs.org/)  | |||
|  | |||
| </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 | |||
| @@ -75,7 +75,7 @@ export default defineConfig({ | |||
| * @name layout 插件 | |||
| * @doc https://umijs.org/docs/max/layout-menu | |||
| */ | |||
| title: '复杂智能软件', | |||
| title: '智能材料科研平台', | |||
| layout: { | |||
| ...defaultSettings, | |||
| }, | |||
| @@ -17,7 +17,7 @@ const Settings: ProLayoutProps & { | |||
| fixSiderbar: false, | |||
| splitMenus: false, | |||
| colorWeak: false, | |||
| title: '复杂智能软件', | |||
| title: '智能材料科研平台', | |||
| pwa: true, | |||
| token: { | |||
| // 参见ts声明,demo 见文档,通过token 修改样式 | |||
| @@ -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, | |||
| }, | |||
| }, | |||
| @@ -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, | |||
| @@ -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" | |||
| ] | |||
| } | |||
| } | |||
| @@ -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; /* 优化页面加载时的字体显示 */ | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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: { | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -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> | |||
| ); | |||
| } | |||
| @@ -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 : '--'; | |||
| }; | |||
| }; | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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> | |||
| @@ -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; | |||
| }; | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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" | |||
| /> | |||
| ))} | |||
| @@ -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> | |||
| ); | |||
| } | |||
| @@ -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} | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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> | |||
| )} | |||
| @@ -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; | |||
| @@ -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('复制失败'); | |||
| }); | |||
| }); | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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> | |||
| @@ -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> | |||
| ); | |||
| } | |||
| @@ -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; | |||
| @@ -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}> | |||
| @@ -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; | |||
| } | |||
| @@ -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) { | |||
| @@ -33,7 +33,7 @@ | |||
| margin-top: 20px; | |||
| margin-bottom: 30px; | |||
| &__back-btn { | |||
| &__button { | |||
| height: 32px; | |||
| } | |||
| } | |||
| @@ -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> | |||
| )} | |||
| @@ -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, | |||
| @@ -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; | |||
| }; | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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; | |||
| @@ -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> | |||
| ); | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||