valentichu 1 éve
commit
3865b45f29
71 módosított fájl, 7416 hozzáadás és 0 törlés
  1. 3 0
      .babelrc
  2. 1 0
      .env.development
  3. 6 0
      .eslintignore
  4. 24 0
      .eslintrc.json
  5. 31 0
      .gitignore
  6. 8 0
      .npmignore
  7. 1 0
      .npmrc
  8. 15 0
      config/webpack.base.js
  9. 96 0
      config/webpack.dev.config.js
  10. 92 0
      config/webpack.prod.config.js
  11. 26 0
      example/src/app.js
  12. 12 0
      example/src/index.html
  13. 0 0
      lib/index.js
  14. 61 0
      package.json
  15. 15 0
      src/App.jsx
  16. 27 0
      src/api/alarm.js
  17. 97 0
      src/entry.jsx
  18. 5 0
      src/entry.module.scss
  19. 0 0
      src/index.css
  20. 21 0
      src/index.jsx
  21. 376 0
      src/pages/Alarm/components/complex.jsx
  22. 68 0
      src/pages/Alarm/components/complex.module.less
  23. 359 0
      src/pages/Alarm/components/components/Formular.jsx
  24. 161 0
      src/pages/Alarm/components/components/HintBox.jsx
  25. 55 0
      src/pages/Alarm/components/components/fomular.module.less
  26. 76 0
      src/pages/Alarm/components/components/hintbox.module.less
  27. 59 0
      src/pages/Alarm/components/components/oneNode.js
  28. 21 0
      src/pages/Alarm/components/components/scroll.module.less
  29. 190 0
      src/pages/Alarm/components/components/utils.js
  30. 826 0
      src/pages/Alarm/components/easy.jsx
  31. 29 0
      src/pages/Alarm/components/easy.module.less
  32. 28 0
      src/pages/Alarm/index.jsx
  33. 0 0
      src/pages/Alarm/style/index.module.less
  34. 324 0
      src/style/antd.less
  35. 87 0
      src/style/base.less
  36. 99 0
      src/style/colors.js
  37. 82 0
      src/style/common.less
  38. 54 0
      src/style/components/ScrollBar/index.module.scss
  39. 15 0
      src/style/components/ScrollBar/scroll.module.less
  40. 102 0
      src/style/components/table.less
  41. 4 0
      src/style/global.less
  42. 5 0
      src/style/index.module.less
  43. 237 0
      src/utils/constant/ChartConfig/Bar/index.jsx
  44. 199 0
      src/utils/constant/ChartConfig/CompareSortBar/index.jsx
  45. 23 0
      src/utils/constant/ChartConfig/ComplexChart/index.jsx
  46. 180 0
      src/utils/constant/ChartConfig/Frequency/index.jsx
  47. 236 0
      src/utils/constant/ChartConfig/Line/index.jsx
  48. 123 0
      src/utils/constant/ChartConfig/Numerical/index.jsx
  49. 90 0
      src/utils/constant/ChartConfig/Ring/index.jsx
  50. 64 0
      src/utils/constant/ChartConfig/Sankey/index.jsx
  51. 79 0
      src/utils/constant/ChartConfig/Scatter/index.jsx
  52. 26 0
      src/utils/constant/ChartConfig/SimpleChart/index.jsx
  53. 730 0
      src/utils/constant/ChartConfig/utils.jsx
  54. 194 0
      src/utils/constant/MultiSingleColorsOptions/index.js
  55. 331 0
      src/utils/constant/index.js
  56. 451 0
      src/utils/index.js
  57. 11 0
      src/utils/index2.js
  58. 88 0
      src/utils/request.js
  59. 12 0
      src/utils/store/index.js
  60. 45 0
      src/utils/store/slice.js
  61. 25 0
      src/utils/theme/ThemeWrapper.jsx
  62. 87 0
      src/utils/theme/customStyle.less
  63. 247 0
      src/utils/theme/index.js
  64. 60 0
      src/utils/theme/style/blue.js
  65. 53 0
      src/utils/theme/style/dark.js
  66. 58 0
      src/utils/theme/style/green.js
  67. 6 0
      src/utils/theme1/ThemeWrapper.jsx
  68. 37 0
      src/utils/theme1/applyTheme.js
  69. 53 0
      src/utils/theme1/blue/index.js
  70. 69 0
      src/utils/theme1/index.js
  71. 41 0
      src/utils/themes/index.js

+ 3 - 0
.babelrc

@@ -0,0 +1,3 @@
+{
+  "presets": ["@babel/preset-env", "@babel/preset-react"]
+}

+ 1 - 0
.env.development

@@ -0,0 +1 @@
+REACT_APP_PUBLIC_PATH=http://192.168.1.109:32080

+ 6 - 0
.eslintignore

@@ -0,0 +1,6 @@
+src/components/GridModules/Numerical
+src/components/GridModules/Ring
+src/components/GridModules/Scatter
+
+src/components/Settings/Scatter*
+src/components/Settings/Ring*

+ 24 - 0
.eslintrc.json

@@ -0,0 +1,24 @@
+{
+    "env": {
+        "browser": true,
+        "es2021": true
+    },
+    "extends": [
+        "react-app"
+    ],
+    "overrides": [],
+    "parserOptions": {
+        "ecmaVersion": "latest",
+        "sourceType": "module"
+    },
+    "plugins": [],
+    "rules": {
+        "import/no-anonymous-default-export": "off",
+        "no-unused-vars": "off"
+    },
+    "settings": {
+        "react": {
+            "version": "detect"
+        }
+    }
+}

+ 31 - 0
.gitignore

@@ -0,0 +1,31 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+point-2.1.run.xml
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+*.run.xml
+yarn.lock

+ 8 - 0
.npmignore

@@ -0,0 +1,8 @@
+# 指定发布 npm 的时候需要忽略的文件和文件夹
+# npm 默认不会把 node_modules 发上去
+config # webpack配置
+example # 开发时预览代码
+src # 组件源代码目录
+.babelrc # babel 配置
+.gitignore
+.git

+ 1 - 0
.npmrc

@@ -0,0 +1 @@
+registry=http://192.168.1.103:4873/

+ 15 - 0
config/webpack.base.js

@@ -0,0 +1,15 @@
+module.exports = {
+    module: {
+        rules: [
+            {
+                // 使用 babel-loader 来编译处理 js 和 jsx 文件
+                test: /\.(js|jsx)$/,
+                use: "babel-loader",
+                exclude: /node_modules/
+            }
+        ]
+    },
+    resolve: {
+        extensions: ['.js','.jsx'],
+    }
+};

+ 96 - 0
config/webpack.dev.config.js

@@ -0,0 +1,96 @@
+const path = require("path");
+const merge = require("webpack-merge");
+const baseConfig = require("./webpack.base.js"); // 引用公共配置
+const webpack = require("webpack");
+
+const devConfig = {
+  mode: "development", // 开发模式
+  entry: path.join(__dirname, "../src/index.jsx"), // 项目入口,处理资源文件的依赖关系
+  output: {
+    path: path.join(__dirname, "../example/src/"),
+    filename: "bundle.js", // 使用webpack-dev-sevrer启动开发服务时,并不会实际在`src`目录下生成bundle.js,打包好的文件是在内存中的,但并不影响我们使用。
+  },
+  module: {
+    rules: [
+      {
+        test: /\.css$/,
+        exclude: /\.min\.css$/,
+        loader: ["style-loader", "css-loader?modules"],
+      },
+      // {
+      //     test: /\.s[ac]ss$/i,
+      //     use: ['style-loader','css-loader','sass-loader'],
+      // },
+      {
+        test: /\.min\.css$/,
+        loader: ["style-loader", "css-loader"],
+      },
+      {
+        test: /(\.module)?.(sass|scss)$/,
+        use: [
+          "style-loader",
+          {
+            loader: "css-loader",
+            options: {
+              modules: {
+                localIdentName: "[path][name]__[local]--[hash:base64:5]",
+              },
+              sourceMap: true,
+            },
+          },
+          "sass-loader",
+        ],
+      },
+      {
+        test: /\.less$/,
+        use: [
+          "style-loader",
+          {
+            loader: "css-loader",
+            options: {
+              modules: true, // 确保启用了 CSS Modules
+            },
+          },
+          "less-loader",
+        ],
+      },
+      // {
+      //     // 匹配 icon 文件
+      //     test: /\.(icon|ttf|bin|eot|woff|woff2|svg)$/,
+      //     // 使用那些 loader 进行处理
+      //     loader: 'url-loader'
+      // },
+      {
+        test: /\.(png|svg|jpg|gif)$/,
+        use: ["file-loader"],
+      },
+      // {
+      //     test: /\.svg$/,
+      //     use:{
+      //         loader: "svg-sprite-loader",
+      //         include: path.resolve(__dirname,'../src/components/Icons'),
+      //         options:{
+      //             symbolId: "icon-[name]"
+      //         }
+      //     },
+      //     // include: path.iconsPath,
+      //     // options: {
+      //     //     symbolId: "icon-[name]"
+      //     // }
+      // },
+    ],
+  },
+  devServer: {
+    contentBase: path.join(__dirname, "../example/src/"),
+    compress: true,
+    host: "127.0.0.1", // webpack-dev-server启动时要指定ip,不能直接通过localhost启动,不指定会报错
+    port: 3001, // 启动端口为 3001 的服务
+    open: true, // 自动打开浏览器
+  },
+  plugins: [
+    new webpack.ProvidePlugin({
+      React: "react",
+    }),
+  ],
+};
+module.exports = merge(devConfig, baseConfig); // 将baseConfig和devConfig合并为一个配置

+ 92 - 0
config/webpack.prod.config.js

@@ -0,0 +1,92 @@
+const path = require('path');
+const merge = require('webpack-merge');
+const baseConfig = require('./webpack.base.js'); // 引用公共的配置
+const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 用于将组件的css打包成单独的文件输出到`lib`目录中
+const webpack = require('webpack')
+
+const prodConfig = {
+    mode: 'production', // 开发模式
+    entry: path.join(__dirname, "../src/entry.jsx"),
+    output: {
+        path: path.join(__dirname, "../lib/"),
+        filename: "index.js",
+        libraryTarget: 'umd', // 采用通用模块定义
+        libraryExport: 'default', // 兼容 ES6 的模块系统、CommonJS 和 AMD 模块规范
+    },
+    module: {
+        rules: [
+            {
+                test: /\.css$/,
+                loader: [MiniCssExtractPlugin.loader,'css-loader?modules'],
+            },
+            {
+                test: /\.min\.css$/,
+                loader: ['style-loader','css-loader'],
+            },
+            {
+                test: /(\.module)?.(sass|scss)$/,
+                use: [
+                    'style-loader',
+                    {
+                        loader: 'css-loader',
+                        options: {
+                            modules: {
+                                localIdentName: '[path][name]__[local]--[hash:base64:5]',
+                            },
+                            sourceMap: true,
+                        },
+                    },
+                    'sass-loader',
+                ],
+            },
+            // {
+            //     test: /\.less$/,
+            //     use: [
+            //         'style-loader',
+            //         {
+            //             loader: 'css-loader',
+            //             options: {
+            //                 modules: {
+            //                     localIdentName: '[path][name]__[local]--[hash:base64:5]',
+            //                 },
+            //                 sourceMap: true,
+            //             },
+            //         },
+            //         'less-loader',
+            //     ],
+            // },
+            {
+                test: /\.less$/,
+                loader: ['style-loader','css-loader','less-loader'],
+            },
+            {
+                test: /\.(png|svg|jpg|gif)$/,
+                use: ['file-loader'],
+            }
+        ]
+    },
+    plugins: [
+        new MiniCssExtractPlugin({
+            filename: "main.min.css" // 提取后的css的文件名
+        }),
+        new webpack.ProvidePlugin({
+            "React": "react",
+        })
+    ],
+    externals: { // 定义外部依赖,避免把react和react-dom打包进去
+        react: {
+            root: "React",
+            commonjs2: "react",
+            commonjs: "react",
+            amd: "react"
+        },
+        "react-dom": {
+            root: "ReactDOM",
+            commonjs2: "react-dom",
+            commonjs: "react-dom",
+            amd: "react-dom"
+        }
+    }
+};
+
+module.exports = merge(prodConfig, baseConfig); // 将baseConfig和prodConfig合并为一个配置

+ 26 - 0
example/src/app.js

@@ -0,0 +1,26 @@
+// import React from 'react'
+// import { render } from 'react-dom'
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+// import App from './App';
+import { Provider } from 'react-redux'
+import store from '../../src/utils/store'
+import ReactDemo from '../../src' // 引入组件
+const App = (props) =>{
+    return <ReactDemo />
+}
+// render(<App />, document.getElementById('root'))
+
+let a = null
+const render = (props) => {
+    const { container, locHash, pageType,url } = props ?? {}
+    const root = document.getElementById('root')
+    a = ReactDOM.createRoot(root)
+    a.render(
+        <Provider store={store}>
+            <App></App>
+        </Provider>
+    )
+}
+
+render()

+ 12 - 0
example/src/index.html

@@ -0,0 +1,12 @@
+<!-- examples/src/index.html -->
+<html>
+<head>
+    <title>My First React Component</title>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+</head>
+<body style="margin:0;">
+<div id="root"></div>
+<script src="bundle.js"></script> <!-- 这句十分重要 -->
+</body>
+</html>

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
lib/index.js


+ 61 - 0
package.json

@@ -0,0 +1,61 @@
+{
+  "name": "dt-alarm-component",
+  "version": "0.0.1",
+  "description": "",
+  "main": "lib/index.js",
+  "scripts": {
+    "start": "webpack-dev-server --config config/webpack.dev.config.js",
+    "build": "webpack --config config/webpack.prod.config.js",
+    "pub": "yarn publish",
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC",
+  "devDependencies": {
+    "@babel/cli": "^7.5.5",
+    "@babel/core": "^7.5.5",
+    "@babel/preset-env": "^7.5.5",
+    "@babel/preset-react": "^7.0.0",
+    "babel-loader": "^8.0.6",
+    "css-loader": "^3.2.0",
+    "customize-cra": "^1.0.0",
+    "eslint": "^8.47.0",
+    "eslint-config-react-app": "^7.0.1",
+    "file-loader": "^6.2.0",
+    "jszip": "^3.10.1",
+    "less": "^4.2.0",
+    "less-loader": "4.1.0",
+    "mini-css-extract-plugin": "^0.8.0",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
+    "sass": "^1.66.1",
+    "sass-loader": "10.3.1",
+    "style-loader": "^1.0.0",
+    "svg-sprite-loader": "^6.0.11",
+    "url-loader": "^4.1.1",
+    "webpack": "^4.39.3",
+    "webpack-cli": "^3.3.7",
+    "webpack-dev-server": "^3.8.0",
+    "webpack-merge": "^4.2.2"
+  },
+  "dependencies": {
+    "@reduxjs/toolkit": "^1.9.5",
+    "antd": "^5.15.4",
+    "axios": "^1.4.0",
+    "dayjs": "^1.11.9",
+    "echarts": "^5.4.3",
+    "file-saver": "^2.0.5",
+    "html2canvas": "^1.4.1",
+    "lodash": "^4.17.21",
+    "path": "^0.12.7",
+    "ramda": "^0.29.0",
+    "react-redux": "^8.1.2",
+    "react-router-dom": "^6.15.0",
+    "react-sortablejs": "6.1.1",
+    "sortablejs": "^1.15.0",
+    "web-vitals": "^3.4.0"
+  },
+  "files": [
+    "lib"
+  ]
+}

+ 15 - 0
src/App.jsx

@@ -0,0 +1,15 @@
+import React, { useEffect, useMemo, useState } from "react";
+import { EasyConfig, ComplexConfig } from "./entry";
+
+function Home(props) {
+  return (
+    <ComplexConfig
+      editId={-1}
+      onConfirm={() => {
+        console.log(1);
+      }}
+    ></ComplexConfig>
+  );
+}
+
+export default Home;

+ 27 - 0
src/api/alarm.js

@@ -0,0 +1,27 @@
+import axios from "axios";
+const config = {
+  baseURL:
+    process.env.NODE_ENV === "development"
+      ? "http://192.168.1.109:32080"
+      : window.location.origin,
+  // baseURL: '/api/aragron',
+  noNeedInterceptor: false,
+};
+
+export function addRule(data) {
+  return axios.post("/api/aragron/unialarm/add_rule", data, config);
+}
+
+export function editRule(data) {
+  return axios.post("/api/aragron/unialarm/edit_rule", data, config);
+}
+
+export function searchPoint(data) {
+  return axios.post("/api/aragron/unialarm/search_point", data, config);
+}
+
+export default {
+  editRule,
+  addRule,
+  searchPoint,
+};

+ 97 - 0
src/entry.jsx

@@ -0,0 +1,97 @@
+import React, {
+  useMemo,
+  useState,
+  forwardRef,
+  useImperativeHandle,
+  useRef,
+} from "react";
+import styles from "./entry.module.scss";
+import "./style/global.less";
+import Easy from "./pages/Alarm/components/easy";
+import Complex from "./pages/Alarm/components/complex";
+import { getAntdToken } from "./utils/theme";
+import ThemeWrapper from "./utils/theme/ThemeWrapper";
+import { ConfigProvider, App } from "antd";
+import zhCN from "antd/es/locale/zh_CN";
+import "./utils/theme/customStyle.less";
+import "dayjs/locale/zh-cn";
+import dayjs from "dayjs";
+dayjs.locale("zh-cn");
+
+export const EasyConfig = forwardRef((props, ref) => {
+  const { editId = -1, onConfirm } = props;
+  const compRef = useRef(null);
+
+  const [themeName, setThemeName] = useState();
+
+  const antdToken = useMemo(() => {
+    if (themeName) {
+      return getAntdToken(themeName);
+    }
+  }, [themeName]);
+
+  useImperativeHandle(
+    ref,
+    () => {
+      return {
+        fetchData: compRef.ok(),
+      };
+    },
+    []
+  );
+
+  return (
+    <ThemeWrapper setThemeName={setThemeName}>
+      {themeName && (
+        <ConfigProvider
+          locale={zhCN}
+          theme={antdToken}
+          autoInsertSpaceInButton={false}
+        >
+          <App className={styles.App}>
+            <Easy ref={compRef} alarmId={editId} onConfirm={onConfirm} />
+          </App>
+        </ConfigProvider>
+      )}
+    </ThemeWrapper>
+  );
+});
+
+export const ComplexConfig = forwardRef((props, ref) => {
+  const { editId = -1, onConfirm } = props;
+  const compRef = useRef(null);
+
+  const [themeName, setThemeName] = useState();
+
+  const antdToken = useMemo(() => {
+    if (themeName) {
+      return getAntdToken(themeName);
+    }
+  }, [themeName]);
+
+  useImperativeHandle(
+    ref,
+    () => {
+      return {
+        fetchData: compRef.ok(),
+      };
+    },
+    []
+  );
+
+  return (
+    <ThemeWrapper setThemeName={setThemeName}>
+      {themeName && (
+        <ConfigProvider
+          locale={zhCN}
+          theme={antdToken}
+          autoInsertSpaceInButton={false}
+        >
+          <App className={styles.App}>
+            <Complex ref={compRef} alarmId={editId} onConfirm={onConfirm} />
+          </App>
+        </ConfigProvider>
+      )}
+    </ThemeWrapper>
+  );
+});

+ 5 - 0
src/entry.module.scss

@@ -0,0 +1,5 @@
+:global{
+  :local(.App){
+    height: 100%;
+  }
+}

+ 0 - 0
src/index.css


+ 21 - 0
src/index.jsx

@@ -0,0 +1,21 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { Provider } from 'react-redux'
+import App from './App'
+import store from './utils/store'
+
+
+let a = null
+const render = (props) => {
+    const root = document.getElementById('root')
+
+    a = ReactDOM.createRoot(root)
+
+    a.render(
+        <Provider store={store}>
+            <App></App>
+        </Provider>
+    )
+}
+
+render()

+ 376 - 0
src/pages/Alarm/components/complex.jsx

@@ -0,0 +1,376 @@
+import React, {
+  useRef,
+  useState,
+  useEffect,
+  forwardRef,
+  useImperativeHandle,
+} from "react";
+import { Form, Input, Select, message } from "antd";
+import { Modal, Button, Table } from "antd";
+import Formular from "./components/Formular";
+import API2 from "../../../api/alarm";
+import styles from "./complex.module.less";
+import oneNode from "./components/oneNode";
+
+const { Search } = Input;
+
+function TranslateText(arr) {
+  const dtLanguage = localStorage.getItem("dtLanguage");
+  if (!Array.isArray(arr)) {
+    return arr;
+  }
+
+  if (arr.length < 2 && arr.length > 0) {
+    return arr?.[0];
+  }
+
+  if (dtLanguage === "en") {
+    return arr?.[1];
+  }
+
+  return arr?.[0];
+}
+
+const Complex = (props, ref) => {
+  const { alarmId, groupList = [] } = props;
+
+  const [form] = Form.useForm();
+  const formuRef = useRef();
+  // const [alarmId, setAlarmId] = useState(searchParams.get('alarm_id'))
+  // const [token, setToken] = useState(searchParams.get('token'))
+  // const [apiUrl, setApiUrl] = useState(decodeURIComponent(searchParams.get('api_url')) + '/')
+  const [data, setData] = useState();
+
+  const [loading, setLoading] = useState(false);
+  const [deviceList, setDeviceList] = useState([]);
+  const [paginationProps, setPaginationProps] = useState({
+    pageSize: 10,
+    current: 1,
+    showTotal: (total) => `共${total}条`,
+    // showQuickJumper: true,
+    // showSizeChanger:true,
+    // size:'Large',
+  });
+
+  const handleTableChange = async (pagination) => {
+    setPaginationProps({ ...pagination, showTotal: (total) => `共${total}条` });
+    // setCurrentPage(pagination.current);
+    // setPageSize(pagination.pageSize)
+  };
+
+  useEffect(() => {
+    onSearch("");
+  }, []);
+
+  useEffect(() => {
+    async function run() {
+      if (alarmId && alarmId != -1) {
+        form.setFieldsValue({
+          name: alarmId.name,
+          alarm_level: alarmId.alarm_level,
+          group_id: alarmId.group_id,
+        });
+        console.log(alarmId.formula);
+        formuRef.current.backEndInput(alarmId.formula);
+      }
+    }
+    run();
+  }, [alarmId]);
+
+  useEffect(() => {
+    if (data) {
+      form.setFieldsValue({
+        name: data.name,
+        alarm_level: data.alarm_level,
+        group_id: data.group_id,
+      });
+    }
+  }, [data]);
+
+  const onModalOk = async () => {
+    if (alarmId == -1) {
+      const value = await form.validateFields();
+      const obj = {
+        ...value,
+        formula: formuRef.current.getFinalStr(),
+        ext_notify_list: [],
+        is_complex: true,
+        type: 1,
+        sub_type: 12,
+        status: 1,
+        defer_unit: 2,
+      };
+
+      const { state } = await API2.addRule(obj);
+      if (state === 0) {
+        message.success("保存成功");
+        props.onConfirm();
+      }
+    } else {
+      const value = await form.validateFields();
+
+      const obj = {
+        ...alarmId,
+        ...data,
+        ...value,
+        formula: formuRef.current.getFinalStr(),
+        ext_notify_list: [],
+      };
+      const { state } = await API2.editRule(obj);
+      if (state === 0) {
+        message.success("保存成功");
+        props.onConfirm();
+      }
+    }
+    form.resetFields();
+    props.onConfirm();
+  };
+
+  useImperativeHandle(
+    ref,
+    () => {
+      return {
+        ok: onModalOk(),
+      };
+    },
+    [onModalOk]
+  );
+
+  const symbClick = (e) => {
+    let innerText = e.target.innerText;
+    if (innerText === "≥") {
+      innerText = ">=";
+    } else if (innerText === "≤") {
+      innerText = "<=";
+    } else if (innerText === "&&(and)") {
+      innerText = "&&";
+    } else if (innerText === "||(or)") {
+      innerText = "||";
+    }
+    formuRef.current.dtInsertFormular(innerText);
+    if (innerText === "()") {
+      //判断为括号则把光标移到括号中间
+      formuRef.current.moveInTextNode();
+    }
+  };
+
+  const insertToFomular = (poi) => {
+    const { point_id, name } = poi;
+    const res = oneNode(point_id, name);
+    formuRef.current.dtInsertFormular(res);
+  };
+
+  const columns = [
+    {
+      title: TranslateText(["点位编号", "Point id"]),
+      dataIndex: "point_id",
+      ellipsis: true,
+    },
+    {
+      title: TranslateText(["点位名称", "Point name"]),
+      dataIndex: "name",
+      ellipsis: true,
+    },
+  ];
+
+  const onSearch = async (v) => {
+    const res = await API2.searchPoint({
+      key_word: v ?? "",
+      count: 50,
+      type: 1,
+      no_child: true,
+    });
+    if (res.state === 0) {
+      setDeviceList(res.data || []);
+      return;
+    }
+    setDeviceList([]);
+  };
+
+  const clickTableRow = (record) => {
+    // child.current.handlePickPoint({name:record.name, point_id:record.point_id})
+    insertToFomular({ name: record.name, point_id: record.point_id });
+  };
+  const onModalCancel = () => {
+    props.onConfirm();
+    form.resetFields();
+  };
+
+  console.log(styles);
+
+  return (
+    <div className={styles.wrapper}>
+      <div className="main">
+        <div className="left">
+          <Form
+            name="basic"
+            autoComplete="off"
+            form={form}
+            requiredMark={false}
+            labelAlign="right"
+            colon={false}
+            labelCol={{
+              span: 4,
+            }}
+            wrapperCol={{
+              span: 15,
+            }}
+          >
+            <Form.Item
+              label={TranslateText(["告警名称", "Alarm name"])}
+              name="name"
+              rules={[
+                {
+                  required: true,
+                  message: TranslateText(["请输入告警名称", "Please input"]),
+                },
+              ]}
+            >
+              <Input
+                placeholder={TranslateText(["请输入告警名称", "Please input"])}
+                style={{ width: "240px" }}
+              />
+            </Form.Item>
+
+            <Form.Item
+              label={TranslateText(["条件符号", "Symbol"])}
+              // name="symb"
+            >
+              <div className="symb" style={{ width: "500px" }}>
+                <Button type="primary" onClick={symbClick}>
+                  ()
+                </Button>
+                <Button type="primary" onClick={symbClick}>
+                  {"&&(and)"}
+                </Button>
+                <Button type="primary" onClick={symbClick}>
+                  {"||(or)"}
+                </Button>
+                <Button type="primary" onClick={symbClick}>
+                  ≥
+                </Button>
+                <Button type="primary" onClick={symbClick}>
+                  {">"}
+                </Button>
+                <Button type="primary" onClick={symbClick}>
+                  =
+                </Button>
+                <Button type="primary" onClick={symbClick}>{`<`}</Button>
+                <Button type="primary" onClick={symbClick}>
+                  ≤
+                </Button>
+                <Button type="primary" onClick={symbClick}>
+                  +
+                </Button>
+                <Button type="primary" onClick={symbClick}>
+                  -
+                </Button>
+                <Button type="primary" onClick={symbClick}>
+                  ×
+                </Button>
+                <Button type="primary" onClick={symbClick}>
+                  ÷
+                </Button>
+              </div>
+            </Form.Item>
+
+            <Form.Item
+              label={TranslateText(["条件编辑", "Edit"])}
+              // name="formu"
+            >
+              <Formular ref={formuRef} />
+            </Form.Item>
+
+            <Form.Item
+              label={TranslateText(["告警组", "Alarm group"])}
+              name={"group_id"}
+            >
+              <Select
+                style={{ width: 240 }}
+                options={[
+                  ...[
+                    {
+                      label: "未分组",
+                      value: 0,
+                    },
+                  ],
+                  ...(groupList?.map((item) => {
+                    return {
+                      label: item?.name,
+                      value: item?.id,
+                    };
+                  }) ?? []),
+                ]}
+                placeholder={TranslateText(["请选择", "Please choose"])}
+              />
+            </Form.Item>
+
+            <Form.Item
+              label={TranslateText(["告警级别", "Alarm level"])}
+              name="alarm_level"
+              rules={[
+                {
+                  required: true,
+                  message: TranslateText(["请输入告警级别", "Please input"]),
+                },
+              ]}
+            >
+              <Select
+                placeholder={TranslateText(["请选择", "Please choose"])}
+                style={{ width: "240px" }}
+              >
+                <Select.Option value={1}>
+                  {TranslateText(["低", "Low"])}
+                </Select.Option>
+                <Select.Option value={2}>
+                  {TranslateText(["中", "Medium"])}
+                </Select.Option>
+                <Select.Option value={3}>
+                  {TranslateText(["高", "High"])}
+                </Select.Option>
+              </Select>
+            </Form.Item>
+          </Form>
+        </div>
+
+        <div
+          style={{
+            borderRight: "1px solid rgba(255, 255, 255, 0.12)",
+            marginLeft: "16px",
+          }}
+        ></div>
+
+        <div className="right">
+          <div className="header">
+            <Search
+              placeholder={TranslateText(["请输入点位名称", "Please input"])}
+              onSearch={onSearch}
+              style={{ width: "240px" }}
+              // className={styles.deviceListTreeSearch}
+            />
+          </div>
+          <Table
+            onRow={(record) => {
+              return {
+                onClick: (event) => {
+                  clickTableRow(record);
+                }, // 点击行
+              };
+            }}
+            key={deviceList}
+            loading={loading}
+            columns={columns}
+            dataSource={deviceList}
+            rowKey="point_id"
+            // className="deviceListTable"
+            pagination={paginationProps}
+            onChange={handleTableChange}
+            size="middle"
+          />
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default forwardRef(Complex);

+ 68 - 0
src/pages/Alarm/components/complex.module.less

@@ -0,0 +1,68 @@
+:global {
+  :local(.wrapper) {
+    // width: 100%;
+    // height: 100%;
+    // background-color: #212b38;
+    // color: var(--dt-text-color1);
+    // background-color: var(--dt-fill-color1);
+    // padding: 16px;
+    // padding-top: 24px;
+    width: 100%;
+    overflow: hidden;
+
+    .ant-input {
+      // background-color: #212b38;
+    }
+
+    .symb {
+      button {
+        margin-right: 8px;
+        margin-bottom: 8px;
+        min-width: 28px;
+        padding: 4px 0px;
+      }
+    }
+
+    .bottom {
+      display: flex;
+      align-items: center;
+      margin-top: 16px;
+
+      .confirmgroup {
+        margin-left: auto;
+
+        button {
+          margin-left: 16px;
+        }
+      }
+    }
+
+    .main {
+      display: flex;
+      width: 100%;
+      padding-left: 16px;
+      padding-right: 16px;
+
+      .left {
+        width: 60%;
+      }
+
+      .right {
+        width: 40%;
+        margin-left: 16px;
+
+        .header {
+          margin-bottom: 16px;
+        }
+      }
+    }
+
+    .ant-form-item-label>label {
+      color: var(--dt-text-color1);
+    }
+
+    .ant-table-cell:before {
+      background-color: transparent !important;
+    }
+  }
+}

+ 359 - 0
src/pages/Alarm/components/components/Formular.jsx

@@ -0,0 +1,359 @@
+import React, {
+  useState,
+  useCallback,
+  useRef,
+  useEffect,
+  forwardRef,
+  useImperativeHandle,
+} from "react";
+import HintBox from "./HintBox";
+import styles from "./fomular.module.less";
+import { moveInTextNode, isFirefox } from "./utils";
+import oneNode from "./oneNode";
+import API2 from "../../../../api/alarm";
+
+const Formular = (props, ref) => {
+  const formulaRef = useRef();
+  const [hintVisible, setHintVisible] = useState(false);
+  const [cursorTextRange, setCursorTextRange] = useState();
+  const [cursorHtmlRange, setCursorHtmlRange] = useState();
+  const [formularText, setFormularText] = useState();
+  const [formularHtml, setFormularHtml] = useState();
+  // const [globalRange, setGlobalRange] = useState();
+  const rangeCurrent = useRef(); //历史光标range位置
+  const globalRangeRef = useRef(); //全局range的记录
+  const outStrRef = useRef(); //最终传给后端的公式str
+
+  const saveRange = function () {
+    var selection = window.getSelection
+      ? window.getSelection()
+      : document.selection;
+    var range = selection.createRange
+      ? selection.createRange()
+      : selection.getRangeAt(0);
+    // console.log('更新全局range', range);
+    // setGlobalRange(range)
+    globalRangeRef.current = range;
+    // _range = range;
+  };
+
+  const dtInsertFormular = function (str) {
+    if (!window.getSelection) {
+      // document.getElementById('formulaId').focus();
+      var selection = window.getSelection
+        ? window.getSelection()
+        : document.selection;
+      var range = selection.createRange
+        ? selection.createRange()
+        : selection.getRangeAt(0);
+      range.pasteHTML(str);
+      range.collapse(false);
+      range.select();
+    } else {
+      // document.getElementById('formulaId').focus();
+      var selection = window.getSelection
+        ? window.getSelection()
+        : document.selection;
+      // console.log(globalRangeRef.current)
+      if (!globalRangeRef.current) {
+        console.log("输入框没有焦点");
+        return;
+      }
+      selection.addRange(globalRangeRef.current);
+      range = globalRangeRef.current;
+      range.collapse(false);
+      var hasR = range.createContextualFragment(str);
+      var hasR_lastChild = hasR.lastChild;
+      while (
+        hasR_lastChild &&
+        hasR_lastChild.nodeName.toLowerCase() == "br" &&
+        hasR_lastChild.previousSibling &&
+        hasR_lastChild.previousSibling.nodeName.toLowerCase() == "br"
+      ) {
+        var e = hasR_lastChild;
+        hasR_lastChild = hasR_lastChild.previousSibling;
+        hasR.removeChild(e);
+      }
+      range.insertNode(hasR);
+      if (hasR_lastChild) {
+        range.setEndAfter(hasR_lastChild);
+        range.setStartAfter(hasR_lastChild);
+      }
+      selection.removeAllRanges();
+      selection.addRange(range);
+    }
+    updateCursorLocation();
+  };
+
+  const cleanBr = () => {
+    const inHtml = document.getElementById("formulaId").innerHTML;
+    document.getElementById("formulaId").innerHTML = inHtml.replace("<br>", ""); //清理无用的br
+  };
+
+  const [pointData, setPointData] = useState([]); //点位信息
+  const backEndInput = useCallback(
+    async (inputStr) => {
+      // const inputStr = '([P_NB4_CW_1_bo_7_AQ]>15)-([P_NB4_CW_1_bo_7_AQ_DIFF_H]+1)';
+      var vars = {};
+      pointData.forEach((item, index) => {
+        vars[item.point_id] = item.name;
+      });
+      // console.log(vars)
+      const regx = new RegExp(`\\[([^\\[\\]]*)\\]`, "g");
+      var match = inputStr.match(regx);
+      match = match.map((item) => item.replace("[", "").replace("]", ""));
+      // const res = await API.energySearchByPointsIds(match)
+      const res = await API2.searchPoint({
+        point_ids: match,
+        count: 20,
+        type: 1,
+      });
+      if (res.state === 0) {
+        res.data?.forEach((item) => {
+          vars[item.point_id] = item.name;
+        });
+      } else {
+        vars = {};
+      }
+      console.log(vars);
+      initDisplay({ vars: vars, formula: inputStr });
+      outStrRef.current = inputStr;
+    },
+    [pointData]
+  );
+
+  const initDisplay = (value) => {
+    const { vars, formula } = value;
+    console.log(value);
+    let result = formula;
+    Reflect.ownKeys(vars).forEach((key) => {
+      const rule = new RegExp(`\\[${key}\\]`, "g");
+      // const name = `<div contenteditable="false" data-point_id='${key}'>${vars[key]}</div>`
+      const name = oneNode(key, vars[key]);
+      result = result.replace(rule, (v, index, string) => {
+        const { length } = v;
+        const str = string.slice(index - 1, length + index + 1);
+        if (str.startsWith("_") || str.endsWith("_")) {
+          return key;
+        } else {
+          return name;
+        }
+      });
+      // result = result.replace('&&', 'and').replace('||', 'or')
+    });
+    result = result.replace("<br>", ""); //去除br标签
+    formulaRef.current.innerHTML = result;
+    // const target = formulaRef.current
+    // setFocus(target, result.length)
+    // setLastHtmlEditRange();
+  };
+
+  // 获取光标位置,但这个函数是不足够的,因为如果插入img元素,光标位置会被分割。只能判断字符串在自身text元素的位置
+  const getCursorIndex = () => {
+    const selection = window.getSelection();
+    return selection?.focusOffset;
+  };
+
+  const updateCursorLocation = () => {
+    // console.log('HTML位置: ', getCursorHtmlPosition(document.getElementById('formulaId')))
+    // console.log('Text位置: ', getCursorTextPosition(document.getElementById('formulaId')))
+    saveRange();
+    // console.log(getCursorIndex());
+    // setCursorTextRange(getCursorIndex())
+  };
+
+  const handleClick = () => {
+    updateCursorLocation();
+  };
+
+  const getRangeRect = () => {
+    //三层逻辑,首先判断能不能找到光标的位置,再次判断历史光标有没有位置,最后默认一个位置。
+    const selection = window.getSelection();
+    const range = selection?.getRangeAt(0);
+    console.log(range);
+    let rect = range.getClientRects()[0];
+    console.log(range.getClientRects());
+    if (rect) {
+      rangeCurrent.current = rect;
+    } else {
+      if (rangeCurrent.current) {
+        rect = rangeCurrent.current;
+      } else {
+        rect = { x: 110, y: 160.5 };
+      }
+    }
+    const LINE_HEIGHT = 30;
+    return {
+      x: rect.x,
+      y: rect.y + LINE_HEIGHT,
+    };
+  };
+
+  // 获取节点
+  const getRangeNode = () => {
+    const selection = window.getSelection();
+    return selection?.focusNode;
+  };
+
+  // 匹配@后的内容
+  const getAtContent = () => {
+    const content = getRangeNode()?.textContent || "";
+    const regx = /@([^@\s]*)$/;
+    const match = regx.exec(content.slice(0, getCursorIndex()));
+    if (match && match.length === 2) {
+      return match[1];
+    }
+    return undefined;
+  };
+
+  const [position, setPosition] = useState({
+    x: 0,
+    y: 0,
+  }); //提示框出现的位置
+  const [queryString, setQueryString] = useState(""); //查询字符串
+
+  const handleAt = (e) => {
+    document.getElementById("formulaId").focus();
+    const position = getRangeRect();
+    setPosition(position);
+    const user = getAtContent();
+    setQueryString(user || "");
+    setHintVisible(true);
+  };
+
+  const handleKeyUp = (e) => {
+    updateCursorLocation(); //按下任意按键都会更新最新的光标位置
+    const { key } = e;
+    switch (key) {
+      case "ArrowUp":
+      case "ArrowDown":
+      case "ArrowLeft":
+      case "ArrowRight":
+        // updateCursorLocation();
+        break;
+      case "Backspace":
+      case "Delete":
+        if (
+          document.getElementById("formulaId").innerHTML.startsWith("<br>") &&
+          isFirefox()
+        ) {
+          cleanBr();
+        }
+        break;
+      default:
+    }
+  };
+
+  const handleKeyDown = (e) => {
+    if (hintVisible) {
+      if (
+        e.code === "ArrowUp" ||
+        e.code === "ArrowDown" ||
+        e.code === "Enter"
+      ) {
+        e.preventDefault();
+      }
+      if (e.code === "Backspace") {
+        e.preventDefault();
+        setHintVisible(false);
+      }
+    } else {
+      const { key } = e;
+      switch (key) {
+        case "Enter":
+          e.preventDefault();
+          break;
+        case "@":
+          // case 'Process': // 中文输入法的 @
+          handleAt();
+          e.preventDefault();
+          break;
+        default:
+      }
+    }
+  };
+
+  useImperativeHandle(
+    ref,
+    () => {
+      return {
+        backEndInput: backEndInput,
+        dtInsertFormular: dtInsertFormular,
+        moveInTextNode: moveInTextNode,
+        getFinalStr: () => outStrRef.current,
+      };
+    },
+    [backEndInput]
+  );
+
+  const handlePickPoint = (poi) => {
+    const { name, point_id } = poi;
+    // const res = `<div contenteditable="false" data-point_id='${point_id}'>${name}</div> `
+    const res = oneNode(point_id, name);
+    dtInsertFormular(res);
+    setHintVisible(false);
+    updateCursorLocation();
+  };
+
+  const divBlur = () => {
+    setValue();
+  };
+
+  const setValue = () => {
+    let formula = "";
+    const vars = {};
+    formulaRef.current.childNodes.forEach((item) => {
+      if (item.dataset?.point_id) {
+        formula += `[${item.dataset?.point_id}]`;
+        vars[item.dataset?.point_id] = item.innerText;
+      } else if (item.localName === "br") {
+      } else {
+        formula += item.data;
+      }
+    });
+    const res = {
+      formula,
+      vars,
+    };
+    outStrRef.current = formula;
+    console.log(outStrRef.current);
+  };
+
+  const stopNativeEvent = useCallback((e) => {
+    e.preventDefault();
+    return false;
+  }, []);
+
+  return (
+    <div className={styles.wrapper}>
+      <div
+        id="formulaId"
+        ref={formulaRef}
+        className="editor"
+        contentEditable
+        onClick={handleClick}
+        onKeyUp={handleKeyUp}
+        onKeyDown={handleKeyDown}
+        onCut={stopNativeEvent}
+        onBlur={divBlur}
+        onPaste={stopNativeEvent}
+        onCopy={stopNativeEvent}
+      ></div>
+      <div>
+        <HintBox
+          visible={hintVisible}
+          setVis={() => setHintVisible(false)}
+          // hintData={pointData}
+          queryString={queryString}
+          position={position}
+          onPickPoint={handlePickPoint}
+          onClickPoint={handlePickPoint}
+        ></HintBox>
+      </div>
+      {/* <button onClick={backEndInput}>后端输入</button>
+      <button onClick={()=>dtInsertFormular('()')}>输入字符</button> */}
+    </div>
+  );
+};
+
+export default forwardRef(Formular);

+ 161 - 0
src/pages/Alarm/components/components/HintBox.jsx

@@ -0,0 +1,161 @@
+import React, { useState, useEffect, useRef, forwardRef } from "react";
+import { Tooltip } from "antd";
+import API2 from "../../../../api/alarm";
+import styles from "./hintbox.module.less";
+
+const HintBox = (props, ref) => {
+  const { hintData, visible, setVis, onPickPoint, onClickPoint } = props;
+  const [data, setData] = useState([]);
+  const [index, setIndex] = useState(-1);
+  const visibleRef = useRef();
+
+
+  visibleRef.current = visible;
+  const usersRef = useRef();
+  // usersRef.current = hintData;
+  const indexRef = useRef();
+  indexRef.current = index;
+
+  function useDebounce(fn, delay) {
+    const refTimer = useRef();
+
+    return function f(...args) {
+      if (refTimer.current) {
+        clearTimeout(refTimer.current);
+      }
+      refTimer.current = setTimeout(() => {
+        fn(args);
+      }, delay);
+    };
+  }
+
+  const getRes = useDebounce(async (queryString) => {
+    const res = await API2.searchPoint({
+      key_word: queryString[0],
+      count: 50,
+      type: 1,
+    });
+    // console.log(res);
+    if (res.state === 0) {
+      if (res.data) {
+        const dat = res.data.filter((item, index) => index < 100);
+        setData(dat);
+        usersRef.current = dat;
+        return;
+      }
+    }
+    setData([]);
+    usersRef.current = [];
+  }, 100);
+
+  // const searchUser = (queryString) => {
+  //   getRes();
+  //   return queryString
+  //     ? hintData.filter(({ name }) => name.startsWith(queryString))
+  //     : hintData.slice(0);
+  // };
+
+  useEffect(() => {
+    const filterdUsers = getRes(props.queryString);
+    // console.log(data)
+    setIndex(0);
+    if (!data?.length) {
+      // props.onHide();
+      setVis();
+    }
+  }, [props.queryString]);
+
+  const srollDiv = useRef();
+  useEffect(() => {
+    const keyDownHandler = (e) => {
+      if (visibleRef.current) {
+        if (e.code === "Escape") {
+          setVis();
+          setIndex(-1);
+          return;
+        }
+        if (e.code === "ArrowDown") {
+          setIndex((oldIndex) => {
+            return Math.min(oldIndex + 1, (usersRef.current?.length || 0) - 1);
+          });
+          srollDiv.current.scrollTop += 32;
+          return;
+        }
+        if (e.code === "ArrowUp") {
+          setIndex((oldIndex) => Math.max(0, oldIndex - 1));
+          srollDiv.current.scrollTop -= 32;
+          return;
+        }
+        if (e.code === "Enter") {
+          if (
+            indexRef.current !== undefined &&
+            usersRef.current?.[indexRef.current]
+          ) {
+            // console.log(usersRef.current)
+            // console.log(indexRef.current)
+            // console.log(usersRef.current?.[indexRef.current])
+            onPickPoint(usersRef.current?.[indexRef.current]);
+            setIndex(-1);
+          }
+          return;
+        }
+      }
+    };
+    document.addEventListener("keyup", keyDownHandler);
+    return () => {
+      document.removeEventListener("keyup", keyDownHandler);
+    };
+  }, []);
+
+  const clickItem = (e) => {
+    onClickPoint(e);
+    // console.log(e);
+  };
+
+  return (
+    <>
+      {visible ? (
+        <div
+          style={{
+            position: "fixed",
+            top: props.position.y,
+            left: props.position.x,
+            zIndex: 999,
+          }}
+          className={styles.wrapper}
+        >
+          {data?.length ? "" : "无搜索结果"}
+          <div className="title">
+            <div className="name">Point_Name</div>
+            <div className="id">Point_ID</div>
+          </div>
+          <div className="scorllDiv" ref={srollDiv}>
+            {data?.map((user, i) => {
+              return (
+                <div
+                  key={user.point_id}
+                  className={i === index ? "active item" : "item"}
+                  onClick={(e) => clickItem(user)}
+                >
+                  <Tooltip
+                    title={user.name}
+                    getPopupContainer={(triggerNode) => triggerNode.parentNode}
+                  >
+                    <div className="name">{user.name}</div>
+                  </Tooltip>
+                  <Tooltip
+                    title={user.point_id}
+                    getPopupContainer={(triggerNode) => triggerNode.parentNode}
+                  >
+                    <div className="id">{user.point_id}</div>
+                  </Tooltip>
+                </div>
+              );
+            })}
+          </div>
+        </div>
+      ) : null}
+    </>
+  );
+};
+export default forwardRef(HintBox);

+ 55 - 0
src/pages/Alarm/components/components/fomular.module.less

@@ -0,0 +1,55 @@
+:global {
+  :local(.wrapper) {
+    @theme-color: #1890ff;
+    @input-border: 1px solid #dcdfe6;
+    width: 500px;
+
+    .editor {
+      @import "./scroll.module.less";
+      margin: 0 auto;
+      width: 100%;
+      height: 236px;
+      background: var(--dt-fill-color1);
+      border: 1px solid rgba(255, 255, 255, 0.12);
+      text-align: left;
+      padding: 10px;
+      overflow: auto;
+      line-height: 30px;
+      color: var(--dt-text-color1);
+      word-wrap: break-all;
+
+      &:focus {
+        outline: none;
+      }
+
+      br {
+        display: none;
+      }
+
+      & * {
+        display: inline;
+        white-space: nowrap;
+        vertical-align: text-bottom;
+      }
+
+      &:focus {
+        border: 1px solid @theme-color;
+      }
+
+      div {
+        padding: 0 3px;
+        background-color: var(--dt-primary-color7);
+
+        span {
+          display: none;
+        }
+      }
+
+      img {
+        margin: 0 1px;
+        cursor: pointer;
+      }
+
+    }
+  }
+}

+ 76 - 0
src/pages/Alarm/components/components/hintbox.module.less

@@ -0,0 +1,76 @@
+:global {
+  :local(.wrapper) {
+    width: 480px;
+    // text-align: center;
+    background-color: #ffffff;
+
+    .title {
+      background-color: var(--dt-fill-color1);
+      display: flex;
+      color: var(--dt-text-color1);
+      height: 32px;
+      font-weight: 600;
+      padding-left: 10px;
+      padding-right: 10px;
+      line-height: 32px;
+
+      >.name {
+        width: 50%;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        padding-left: 10px;
+        padding-right: 10px;
+      }
+
+      >.id {
+        width: 50%;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        padding-left: 10px;
+        padding-right: 10px;
+      }
+    }
+
+    .scorllDiv {
+      @import "./scroll.module.less";
+      max-height: 350px;
+      overflow: auto;
+      position: relative;
+    }
+
+    .item {
+      display: flex;
+      color: var(--dt-text-color2);
+      height: 32px;
+      padding-left: 10px;
+      padding-right: 10px;
+      line-height: 32px;
+      cursor: pointer;
+      background: var(--dt-fill-color1);
+
+      &.active {
+        background: var(--dt-fill-color3);
+      }
+
+      >.name {
+        width: 50%;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        padding-left: 10px;
+        padding-right: 10px;
+      }
+
+      >.id {
+        width: 50%;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        padding-left: 10px;
+        padding-right: 10px;
+      }
+    }
+  }
+}

+ 59 - 0
src/pages/Alarm/components/components/oneNode.js

@@ -0,0 +1,59 @@
+import { message } from 'antd';
+window.dtmessage = message
+window.copyToClipboard = copyToClipboard
+export default function oneNode(point_id, name) {
+  // return `<div contenteditable="false" data-point_id='${point_id}'>${name}</div>`;
+  // window.navigator.clipboard.writeText("${point_id}");
+  return `<img 
+  title='${point_id}' 
+  onclick='
+  window.copyToClipboard("${point_id}");
+  window.dtmessage.success("已复制");
+  ' 
+  data-point_id='${point_id}' 
+  src=${drawPoint(name)}
+    >`
+}
+
+function copyToClipboard(textToCopy) {
+  // navigator clipboard 需要https等安全上下文
+  if (window.navigator.clipboard && window.isSecureContext) {
+    // navigator clipboard 向剪贴板写文本
+    return window.navigator.clipboard.writeText(textToCopy);
+  } else {
+    // 创建text area
+    let textArea = document.createElement("textarea");
+    textArea.value = textToCopy;
+    // 使text area不在viewport,同时设置不可见
+    textArea.style.position = "absolute";
+    textArea.style.opacity = 0;
+    textArea.style.left = "-999999px";
+    textArea.style.top = "-999999px";
+    document.body.appendChild(textArea);
+    textArea.focus();
+    textArea.select();
+    // 执行复制命令并移除文本框
+    document.execCommand('copy');
+    textArea.remove();
+  }
+}
+
+const drawPoint = (text) => {
+  let canvas = document.createElement('canvas');
+  let context = canvas.getContext('2d');
+  const font = "14px '微软雅黑'";
+  context.font = font;
+  let width = context.measureText(text).width; //获取字体宽度
+  width = width + 6; //加一些左右宽度
+  canvas.width = width;
+  canvas.height = 22;
+  context.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--dt-primary-color7'); //背景颜色
+  context.fillRect(0, 0, canvas.width, canvas.height);
+  context.font = font;
+  context.textAlign = 'center';
+  context.textBaseline = 'middle';
+  context.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--dt-text-color1'); // 设置字体颜色
+  context.fillText(text, canvas.width / 2, canvas.height / 2 + 2);
+  let dataUrl = canvas.toDataURL('image/png');
+  return dataUrl;
+}

+ 21 - 0
src/pages/Alarm/components/components/scroll.module.less

@@ -0,0 +1,21 @@
+&::-webkit-scrollbar {
+  width: 6px;
+  background-color: #1d242f;
+}
+
+&::-webkit-scrollbar-thumb {
+  background-color: #757980;
+
+  &:hover {
+    background-color: #62666d;
+  }
+
+  &:active {
+    background-color: #41454c;
+  }
+}
+
+& {
+  scrollbar-width: thin;
+  // scrollbar-color: #c1c1c1 #eee;
+}

+ 190 - 0
src/pages/Alarm/components/components/utils.js

@@ -0,0 +1,190 @@
+export const getCursorHtmlPosition = (node) => {
+  var range = window.getSelection().getRangeAt(0),
+    preCaretRange = range.cloneRange(),
+    caretPosition,
+    tmp = document.createElement("div");
+
+  preCaretRange.selectNodeContents(node);
+  preCaretRange.setEnd(range.endContainer, range.endOffset);
+  tmp.appendChild(preCaretRange.cloneContents());
+  caretPosition = tmp.innerHTML.length;
+  return caretPosition;
+}
+
+export const getCursorTextPosition = (node) => {
+  var range = window.getSelection().getRangeAt(0),
+    preCaretRange = range.cloneRange(),
+    caretPosition,
+    tmp = document.createElement("div");
+
+  preCaretRange.selectNodeContents(node);
+  preCaretRange.setEnd(range.endContainer, range.endOffset);
+  tmp.appendChild(preCaretRange.cloneContents());
+
+  var regx = /<img.*?>/g //匹配img,替换为1个字符,如#,这样可以近似得到光标在文本中的位置
+  let val = tmp.innerHTML
+  var match = val.match(regx);
+  if (match) {//如果开头到光标的位置能匹配到img,才进行替换
+    // console.log(match);
+    match = match.map((item) => val = val.replace(item, '#'))
+  }
+
+
+  caretPosition = val.length;
+  return caretPosition;
+}
+
+export const getCaretPosition = (node) => {
+  var range = window.getSelection().getRangeAt(0),
+    preCaretRange = range.cloneRange(),
+    caretPosition,
+    tmp = document.createElement("div");
+
+  preCaretRange.selectNodeContents(node);
+  preCaretRange.setEnd(range.endContainer, range.endOffset);
+  tmp.appendChild(preCaretRange.cloneContents());
+  caretPosition = tmp.innerHTML.length;
+  return caretPosition;
+}
+
+export const getCaretPositionOverRange = (node, range1) => {
+  var range = range1,
+    preCaretRange = range.cloneRange(),
+    caretPosition,
+    tmp = document.createElement("div");
+
+  preCaretRange.selectNodeContents(node);
+  preCaretRange.setEnd(range.endContainer, range.endOffset);
+  tmp.appendChild(preCaretRange.cloneContents());
+  caretPosition = tmp.innerHTML.length;
+  return caretPosition;
+}
+
+export const getHTMLList = ({ text, prefix, suffix }) => text
+  .replace(new RegExp(prefix, 'g'), ',')
+  .replace(new RegExp(suffix, 'g'), ',')
+  .split(',')
+  .filter(v => !!v)
+
+export const str2dom = v => {
+  const objE = document.createElement('div')
+  objE.innerHTML = v
+  return [...objE.childNodes]
+}
+
+export const dom2str = node => {
+  let tmpNode = document.createElement('div')
+  tmpNode.appendChild(node)
+  const str = tmpNode.innerHTML
+  tmpNode = null
+  node = null
+  return str
+}
+
+export const isHTML = v => Object.prototype.toString.call(v) === '[object HTMLDivElement]'
+export const isIMG = v => Object.prototype.toString.call(v) === '[object HTMLImageElement]'
+
+export const validKeys = '0123456789+-*/@.()'
+
+export const getDiffIndex = (s1, s2) => {
+  const l1 = s1.split('')
+  const l2 = s2.split('')
+  const max = Math.max(l1.length, l2.length)
+  let index = 0
+  for (let i = 0; i < max; i++) {
+    if (l1[i] === l2[i]) {
+      index += 1
+    } else {
+      break
+    }
+  }
+  return index
+}
+
+export const moveInTextNode = (num) => {//适配括号需要退格一下
+  var selection = window.getSelection ? window.getSelection() : document.selection;
+  var range = selection.createRange ? selection.createRange() : selection.getRangeAt(0);
+  const list = document.getElementById('formulaId').childNodes
+  // console.log(list);
+  let nodeIndex = -1;//确定属于哪一个textNode
+  let nodeWhere = -1;//确定括号在textNode的哪个地方
+  for (let i = 0; i < list.length; i++) {
+    const v = list[i]
+    // console.log(v);
+    if ((!isIMG(v)) && v.data.indexOf('()') > -1) {
+      nodeIndex = i
+      nodeWhere = v.data.indexOf('()')
+    }
+  }
+  if (nodeIndex === -1) {
+    return;
+  }
+  range.setStart(document.getElementById('formulaId').childNodes[nodeIndex], 1)
+  range.collapse(true)
+  selection.removeAllRanges()
+  selection.addRange(range)
+}
+
+
+export const setFocus = (el, index) => {
+  const range = document.createRange()
+  const sel = window.getSelection()
+  let nodeIndex = 0
+  let offsetIndex = 0
+  let innerIndex = index
+  const list = str2dom(el.innerHTML)
+  for (let i = 0; i < list.length; i++) {
+    const v = list[i]
+    if (!innerIndex) break
+    if (isIMG(v)) {
+      const { length } = dom2str(v)
+      if (innerIndex >= length) {
+        innerIndex -= dom2str(v).length
+      }
+    } else if (innerIndex >= v.length) {
+      innerIndex -= v.length
+    } else {
+      offsetIndex = innerIndex
+      break
+    }
+    nodeIndex += 1
+  }
+
+  range.selectNodeContents(el)
+  range.collapse(false)
+
+  if (el.childNodes[nodeIndex] && isIMG(el.childNodes[nodeIndex])) {
+    let currIndex = nodeIndex - 1
+    let currDom = el.childNodes[currIndex].cloneNode()
+    while (isIMG(currDom) && currIndex !== 0) {
+      currIndex -= 1
+      currDom = el.childNodes[currIndex].cloneNode()
+    }
+    if (currIndex >= 0) {
+      const { length } = el.childNodes[currIndex]
+      range.setStart(el.childNodes[currIndex], length)
+    }
+  } else if (el.childNodes[nodeIndex] && !isIMG(el.childNodes[nodeIndex])) {
+    range.setStart(el.childNodes[nodeIndex], offsetIndex > 0 ? offsetIndex : 0)
+  }
+
+  range.collapse(true)
+  sel.removeAllRanges()
+  sel.addRange(range)
+}
+
+export const getParentNode = (el, className) => {
+  if (!el) return null
+  do {
+    el = el.parentNode
+  } while (el && !el.className.includes(className))
+  return el
+}
+
+export const isFirefox = () => {
+  const browserAgent = navigator.userAgent.toLowerCase();
+  if (browserAgent.indexOf('firefox') !== -1) {
+    return true
+  }
+  return false;
+}

+ 826 - 0
src/pages/Alarm/components/easy.jsx

@@ -0,0 +1,826 @@
+import React, {
+  useEffect,
+  useState,
+  forwardRef,
+  useImperativeHandle,
+} from "react";
+import { Form, Input, Select, message, Switch, Space, Col } from "antd";
+import { Modal, Button, Radio } from "antd";
+import styles from "./easy.module.less";
+import API2 from "../../../api/alarm";
+
+function TranslateText(arr) {
+  const dtLanguage = localStorage.getItem("dtLanguage");
+  if (!Array.isArray(arr)) {
+    return arr;
+  }
+
+  if (arr.length < 2 && arr.length > 0) {
+    return arr?.[0];
+  }
+
+  if (dtLanguage === "en") {
+    return arr?.[1];
+  }
+
+  return arr?.[0];
+}
+
+const Easy = (props, ref) => {
+  const { alarmId, groupList = [] } = props;
+
+  const [form] = Form.useForm();
+  // const [alarmId, setAlarmId] = useState(searchParams.get('alarm_id'))
+  // const [token, setToken] = useState(searchParams.get('token'))
+  // const [apiUrl, setApiUrl] = useState(decodeURIComponent(searchParams.get('api_url')) + '/')
+  const [data, setData] = useState();
+  const [deferUnit, setDeferUnit] = useState();
+  const [status, setStatus] = useState(1);
+  const [dateTime, setDateTime] = useState(null);
+  const [subType, setSubType] = useState();
+  const [isDisabled, setIsDisabled] = useState(true);
+  const [loading, setLoading] = useState(false);
+  const [ahhEnable, setAhhEnable] = useState(false);
+  const [ahEnable, setAhEnable] = useState(false);
+  const [alEnable, setAlEnable] = useState(false);
+  const [allEnable, setAllEnable] = useState(false);
+
+  const onModalOk = async () => {
+    const isNull = (val, bool) => {
+      if (bool) {
+        return Number(val);
+      } else {
+        return ["", undefined, null].indexOf(val) === -1 ? Number(val) : -9999;
+      }
+    };
+
+    const isBool = (bool) => {
+      return bool ? bool : false;
+    };
+
+    if (alarmId == -1) {
+      let bool = false;
+      if (["", undefined, null].indexOf(dateTime) !== -1 && status === 1) {
+        setDateTime(undefined);
+        bool = true;
+      }
+      const value = await form.validateFields();
+      if (bool) {
+        return;
+      }
+
+      if (
+        (value?.ahh_enable && value?.ahh_value === "-9999") ||
+        (value?.ah_enable && value?.ah_value === "-9999") ||
+        (value?.al_enable && value?.al_value === "-9999") ||
+        (value?.all_enable && value?.all_value === "-9999")
+      ) {
+        message.error(`${
+          value?.ahh_enable && value?.ahh_value === "-9999" ? "上上限;" : ""
+        }
+        ${value?.ah_enable && value?.ah_value === "-9999" ? "上限;" : ""}
+        ${value?.al_enable && value?.al_value === "-9999" ? "下限;" : ""}
+        ${value?.all_enable && value?.all_value === "-9999" ? "下下限;" : ""}
+        禁止输入-9999`);
+        return;
+      }
+
+      setLoading(true);
+      let details = {};
+
+      if (value?.sub_type === 13) {
+        details = {
+          alarm_value: isNull(value?.alarm_value),
+        };
+      } else {
+        details = {
+          ahh_value: isNull(value?.ahh_value, isBool(value?.ahh_enable)),
+          ahh_enable: isBool(value?.ahh_enable),
+          ah_value: isNull(value?.ah_value, isBool(value?.ah_enable)),
+          ah_enable: isBool(value?.ah_enable),
+          al_value: isNull(value?.al_value, isBool(value?.al_enable)),
+          al_enable: isBool(value?.al_enable),
+          all_value: isNull(value?.all_value, isBool(value?.all_enable)),
+          all_enable: isBool(value?.all_enable),
+        };
+      }
+      delete value.alarm_value;
+      delete value?.ahh_value;
+      delete value?.ahh_enable;
+      delete value?.ah_value;
+      delete value?.ah_enable;
+      delete value?.al_value;
+      delete value?.al_enable;
+      delete value?.all_value;
+      delete value?.all_enable;
+
+      const obj = {
+        ...value,
+        point_name: value?.point_name?.split("<==>")[0],
+        threshold_high: value.threshold_high?.toString().trim()
+          ? Number(value.threshold_high?.toString().trim())
+          : null,
+        threshold_low: value.threshold_low?.toString().trim()
+          ? Number(value.threshold_low?.toString().trim())
+          : null,
+        ext_notify_list: [],
+        high_enabled: value.threshold_high?.toString().trim() ? true : false,
+        low_enabled: value.threshold_low?.toString().trim() ? true : false,
+        status: 1,
+        defer_unit: deferUnit,
+        defer_seconds: Number(dateTime) ?? 0,
+        type: 1,
+        sub_type: value?.sub_type,
+        group_id: value?.group_id ?? 0,
+        details,
+      };
+
+      API2.addRule(obj)
+        .then((res) => {
+          if (res?.state === 0) {
+            message.success("保存成功");
+            form.resetFields();
+            props.onConfirm();
+          }
+        })
+        .finally(() => {
+          setLoading(false);
+        });
+    } else {
+      let bool = false;
+      if (["", undefined, null].indexOf(dateTime) !== -1 && status === 1) {
+        setDateTime(undefined);
+        bool = true;
+      }
+      const value = await form.validateFields();
+
+      if (bool) {
+        return;
+      }
+
+      if (
+        (value?.ahh_enable && value?.ahh_value === "-9999") ||
+        (value?.ah_enable && value?.ah_value === "-9999") ||
+        (value?.al_enable && value?.al_value === "-9999") ||
+        (value?.all_enable && value?.all_value === "-9999")
+      ) {
+        message.error(`${
+          value?.ahh_enable && value?.ahh_value === "-9999" ? "上上限;" : ""
+        }
+        ${value?.ah_enable && value?.ah_value === "-9999" ? "上限;" : ""}
+        ${value?.al_enable && value?.al_value === "-9999" ? "下限;" : ""}
+        ${value?.all_enable && value?.all_value === "-9999" ? "下下限;" : ""}
+        禁止输入-9999`);
+        return;
+      }
+
+      setLoading(true);
+      let details = {};
+
+      if (value?.sub_type === 13) {
+        console.log(value?.alarm_value);
+        details = {
+          alarm_value: isNull(value?.alarm_value),
+        };
+      } else {
+        details = {
+          ahh_value: isNull(value?.ahh_value, isBool(value?.ahh_enable)),
+          ahh_enable: isBool(value?.ahh_enable),
+          ah_value: isNull(value?.ah_value, isBool(value?.ah_enable)),
+          ah_enable: isBool(value?.ah_enable),
+          al_value: isNull(value?.al_value, isBool(value?.al_enable)),
+          al_enable: isBool(value?.al_enable),
+          all_value: isNull(value?.all_value, isBool(value?.all_enable)),
+          all_enable: isBool(value?.all_enable),
+        };
+      }
+      delete value.alarm_value;
+      delete value?.ahh_value;
+      delete value?.ahh_enable;
+      delete value?.ah_value;
+      delete value?.ah_enable;
+      delete value?.al_value;
+      delete value?.al_enable;
+      delete value?.all_value;
+      delete value?.all_enable;
+      delete data?.Details;
+
+      const obj = {
+        ...data,
+        ...value,
+        point_name: value?.point_name?.split("<==>")[0],
+        threshold_high: value.threshold_high?.toString().trim()
+          ? Number(value.threshold_high?.toString().trim())
+          : null,
+        threshold_low: value.threshold_low?.toString().trim()
+          ? Number(value.threshold_low?.toString().trim())
+          : null,
+        high_enabled: value.threshold_high?.toString().trim() ? true : false,
+        low_enabled: value.threshold_low?.toString().trim() ? true : false,
+        defer_unit: deferUnit,
+        defer_seconds: dateTime ? Number(dateTime) : 0,
+        type: 1,
+        sub_type: value?.sub_type,
+        group_id: value?.group_id ?? 0,
+        details,
+      };
+
+      API2.editRule(obj)
+        .then((res) => {
+          if (res?.state === 0) {
+            message.success("保存成功");
+            form.resetFields();
+            props.onConfirm();
+          }
+        })
+        .finally(() => {
+          setLoading(false);
+        });
+    }
+  };
+
+  useImperativeHandle(
+    ref,
+    () => {
+      return {
+        ok: onModalOk(),
+      };
+    },
+    [onModalOk]
+  );
+
+  useEffect(() => {
+    onSearch("");
+  }, []);
+
+  useEffect(() => {
+    async function run() {
+      if (alarmId && alarmId != -1) {
+        // const { state, data } = await API.detailAlarm(Number(alarmId))
+        setData(alarmId);
+        setDeferUnit(alarmId?.defer_unit);
+        setStatus(alarmId.defer_seconds > 0 ? 1 : 2);
+        setDateTime(alarmId?.defer_seconds);
+      }
+    }
+    run();
+  }, [alarmId]);
+
+  useEffect(() => {
+    if (data) {
+      setSubType(data.sub_type);
+      setDeferUnit(data?.defer_unit);
+      setStatus(data.defer_seconds > 0 ? 1 : 2);
+      setDateTime(data.defer_seconds);
+      setIsDisabled(false);
+      setAllEnable(data?.Details.all_enable);
+      setAlEnable(data?.Details.al_enable);
+      setAhEnable(data?.Details.ah_enable);
+      setAhhEnable(data?.Details.ahh_enable);
+      form.setFieldsValue({
+        threshold_high: data.threshold_high,
+        threshold_low: data.threshold_low,
+        point_name: data.point_name,
+        point_id: data.point_id,
+        name: data.name,
+        unit: data.unit,
+        alarm_level: data.alarm_level,
+        sub_type: data.sub_type,
+        group_id: data.group_id,
+        defer_unit: data.defer_unit ? data.defer_unit : undefined,
+        defer_seconds: data.defer_seconds,
+        ...(data.sub_type === 13
+          ? {
+              alarm_value: data?.Details.alarm_value,
+            }
+          : {
+              ahh_value:
+                data?.Details.ahh_value !== -9999
+                  ? data?.Details.ahh_value
+                  : null,
+              ahh_enable: data?.Details.ahh_enable,
+              ah_value:
+                data?.Details.ah_value !== -9999
+                  ? data?.Details.ah_value
+                  : null,
+              ah_enable: data?.Details.ah_enable,
+              al_value:
+                data?.Details.al_value !== -9999
+                  ? data?.Details.al_value
+                  : null,
+              al_enable: data?.Details.al_enable,
+              all_value:
+                data?.Details.all_value !== -9999
+                  ? data?.Details.all_value
+                  : null,
+              all_enable: data?.Details.all_enable,
+            }),
+      });
+    }
+  }, [data]);
+
+  const onChange = (e) => {
+    // console.log(`selected ${e}`);
+    console.log(e);
+    setIsDisabled(false);
+    setDeferUnit(Number(e.value.split("<==>")[1]));
+    form.setFieldsValue({
+      name: e.value.split("<==>")[0],
+      point_id: e.key,
+    });
+  };
+
+  const [pointList, setPointList] = useState([]);
+  const onSearch = async (value) => {
+    const res = await API2.searchPoint({
+      key_word: value ?? "",
+      count: 50,
+      type: 1,
+      no_child: true,
+    });
+    if (res.state === 0) {
+      setPointList(res.data || []);
+      return;
+    }
+    setPointList([]);
+  };
+
+  const onModalCancel = () => {
+    props.onConfirm();
+    form.resetFields();
+  };
+
+  const onValuesChange = (changedValues, allValues) => {
+    console.log(changedValues, allValues, "changedValues, allValues");
+    if (changedValues.hasOwnProperty("sub_type")) {
+      setSubType(changedValues?.sub_type);
+
+      if (
+        changedValues?.sub_type === 14 &&
+        allValues.hasOwnProperty("ahh_enable") === false &&
+        allValues.hasOwnProperty("ah_enable") === false &&
+        allValues.hasOwnProperty("al_enable") === false &&
+        allValues.hasOwnProperty("all_enable") === false
+      ) {
+        setAllEnable(true);
+        setAlEnable(true);
+        setAhEnable(true);
+        setAhhEnable(true);
+        form.setFieldsValue({
+          ahh_enable: true,
+          ah_enable: true,
+          al_enable: true,
+          all_enable: true,
+        });
+      }
+    }
+    if (changedValues.hasOwnProperty("all_enable")) {
+      setAllEnable(changedValues?.all_enable);
+    }
+    if (changedValues.hasOwnProperty("al_enable")) {
+      setAlEnable(changedValues?.al_enable);
+    }
+    if (changedValues.hasOwnProperty("ah_enable")) {
+      setAhEnable(changedValues?.ah_enable);
+    }
+    if (changedValues.hasOwnProperty("ahh_enable")) {
+      setAhhEnable(changedValues?.ahh_enable);
+    }
+  };
+
+  return (
+    <Form
+      className={styles.wrapper}
+      name="basic"
+      autoComplete="off"
+      form={form}
+      // requiredMark={false}
+      labelAlign="right"
+      colon={false}
+      labelCol={{
+        span: 7,
+      }}
+      onValuesChange={onValuesChange}
+      wrapperCol={{
+        span: 17,
+      }}
+    >
+      <Form.Item label={TranslateText(["告警模式", "Mode"])} name={"sub_type"}>
+        <Select
+          style={{ width: 240 }}
+          disabled={alarmId !== -1}
+          options={[
+            {
+              label: "离散报警",
+              value: 13,
+            },
+            {
+              label: "限值报警",
+              value: 14,
+            },
+          ]}
+          placeholder={TranslateText(["请选择告警模式", "Please choose"])}
+        />
+      </Form.Item>
+      <Form.Item
+        label={TranslateText(["点位名称", "Point name"])}
+        name="point_name"
+        rules={[
+          {
+            required: true,
+            message: TranslateText(["请输入点位名称", "Please input"]),
+          },
+        ]}
+        getValueFromEvent={(e) => e.value}
+      >
+        <Select
+          showSearch
+          disabled={alarmId !== -1}
+          filterOption={false}
+          dropdownMatchSelectWidth={400}
+          labelInValue={true}
+          onChange={(e) => {
+            onChange(e);
+          }}
+          onSearch={onSearch}
+          placeholder={TranslateText(["请输入点位名称", "Please input"])}
+          style={{ width: "240px" }}
+        >
+          {pointList.map((item, index) => {
+            return (
+              <Select.Option
+                value={
+                  item.name + "<==>" + item.defer_unit + "<==>" + item.point_id
+                }
+                key={item.point_id}
+              >
+                {item.name}({item.point_id})(
+                {item.defer_unit === 1 ? "秒级" : "分钟级"})
+              </Select.Option>
+            );
+          })}
+        </Select>
+      </Form.Item>
+
+      <Form.Item
+        label={TranslateText(["点位编号", "Point id"])}
+        name="point_id"
+        style={{ display: alarmId === -1 ? "none" : "" }}
+      >
+        <Input
+          placeholder={TranslateText(["请输入point_id", "Please input"])}
+          disabled={alarmId !== -1}
+          style={{ width: "240px" }}
+        />
+      </Form.Item>
+
+      <Form.Item
+        label={TranslateText(["单位", "Unit"])}
+        name="unit"
+        rules={[
+          {
+            required: false,
+            message: TranslateText(["请输入单位", "Please input"]),
+          },
+        ]}
+      >
+        <Input
+          placeholder={TranslateText(["请输入", "Please input"])}
+          style={{ width: "240px" }}
+        />
+      </Form.Item>
+
+      <Form.Item
+        label={TranslateText(["告警名称", "Alarm name"])}
+        name="name"
+        rules={[
+          {
+            required: true,
+            message: TranslateText(["请输入告警名称", "Please input"]),
+          },
+        ]}
+      >
+        <Input
+          placeholder={TranslateText(["请输入告警名称", "Please input"])}
+          style={{ width: "240px" }}
+        />
+      </Form.Item>
+
+      <Form.Item label={TranslateText(["告警组", "Group"])} name={"group_id"}>
+        <Select
+          style={{ width: 240 }}
+          options={[
+            ...[
+              {
+                label: "未分组",
+                value: 0,
+              },
+            ],
+            ...(groupList?.map((item) => {
+              return {
+                label: item?.name,
+                value: item?.id,
+              };
+            }) ?? []),
+          ]}
+          placeholder={TranslateText(["请选择", "Please choose"])}
+        />
+      </Form.Item>
+
+      {subType === 13 && (
+        <Form.Item
+          label={TranslateText(["告警值", "Alarm value"])}
+          name="alarm_value"
+          required={false}
+          rules={[
+            {
+              required: true,
+              message: TranslateText(["请输入告警值", "Please input"]),
+            },
+            () => ({
+              validator(rule, value) {
+                let regPos =
+                  /^(([^0][0-9]+|0)\.([0-9])$)|^(([^0][0-9]+|0)$)|^(([1-9]+)\.([0-9])$)|^(([1-9]+)$)/;
+                let regNeg =
+                  /^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/;
+                if (regPos.test(value) || regNeg.test(value)) {
+                  //if中是正则表达是,判断是否是6位数字
+                  return Promise.resolve();
+                } else {
+                  if (!Boolean(value)) return Promise.resolve();
+                  return Promise.reject("只能输入整数、小数或负数"); //如果违反规则,就会给出提示
+                }
+              },
+            }),
+          ]}
+        >
+          <Input
+            placeholder={TranslateText(["请输入告警值", "Please input"])}
+            style={{ width: "240px" }}
+          />
+        </Form.Item>
+      )}
+
+      {subType === 14 && (
+        <>
+          <Form.Item label={TranslateText(["上上限", "Ultra upper limit"])}>
+            <Space size={20}>
+              <Form.Item
+                name="ahh_value"
+                key={ahhEnable}
+                noStyle
+                rules={[
+                  {
+                    required: ahhEnable,
+                    message: TranslateText(["请输入上上限", "Please input"]),
+                  },
+                  () => ({
+                    validator(rule, value) {
+                      let regPos =
+                        /^(([^0][0-9]+|0)\.([0-9])$)|^(([^0][0-9]+|0)$)|^(([1-9]+)\.([0-9])$)|^(([1-9]+)$)/;
+                      let regNeg =
+                        /^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/;
+                      if (regPos.test(value) || regNeg.test(value)) {
+                        //if中是正则表达是,判断是否是6位数字
+                        return Promise.resolve();
+                      } else {
+                        if (!Boolean(value)) return Promise.resolve();
+                        return Promise.reject("只能输入整数、小数或负数"); //如果违反规则,就会给出提示
+                      }
+                    },
+                  }),
+                ]}
+              >
+                <Input
+                  placeholder={TranslateText(["请输入", "Please input"])}
+                  style={{ width: "240px" }}
+                />
+              </Form.Item>
+              <Form.Item
+                label={""}
+                name="ahh_enable"
+                valuePropName="checked"
+                noStyle
+              >
+                <Switch size={"small"} />
+              </Form.Item>
+            </Space>
+          </Form.Item>
+          <Form.Item label={TranslateText(["上限", "Upper limit"])}>
+            <Space size={20}>
+              <Form.Item
+                name="ah_value"
+                key={ahEnable}
+                noStyle
+                rules={[
+                  {
+                    required: ahEnable,
+                    message: TranslateText(["请输入上限", "Please input"]),
+                  },
+                  () => ({
+                    validator(rule, value) {
+                      let regPos =
+                        /^(([^0][0-9]+|0)\.([0-9])$)|^(([^0][0-9]+|0)$)|^(([1-9]+)\.([0-9])$)|^(([1-9]+)$)/;
+                      let regNeg =
+                        /^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/;
+                      if (regPos.test(value) || regNeg.test(value)) {
+                        //if中是正则表达是,判断是否是6位数字
+                        return Promise.resolve();
+                      } else {
+                        if (!Boolean(value)) return Promise.resolve();
+                        return Promise.reject("只能输入整数、小数或负数"); //如果违反规则,就会给出提示
+                      }
+                    },
+                  }),
+                ]}
+              >
+                <Input
+                  placeholder={TranslateText(["请输入", "Please input"])}
+                  style={{ width: "240px" }}
+                />
+              </Form.Item>
+              <Form.Item name="ah_enable" valuePropName="checked" noStyle>
+                <Switch size={"small"} />
+              </Form.Item>
+            </Space>
+          </Form.Item>
+          <Form.Item label={TranslateText(["下限", "Lower limit"])}>
+            <Space size={20}>
+              <Form.Item
+                name="al_value"
+                key={alEnable}
+                noStyle
+                rules={[
+                  {
+                    required: alEnable,
+                    message: TranslateText(["请输入下限", "Please input"]),
+                  },
+                  () => ({
+                    validator(rule, value) {
+                      let regPos =
+                        /^(([^0][0-9]+|0)\.([0-9])$)|^(([^0][0-9]+|0)$)|^(([1-9]+)\.([0-9])$)|^(([1-9]+)$)/;
+                      let regNeg =
+                        /^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/;
+                      if (regPos.test(value) || regNeg.test(value)) {
+                        //if中是正则表达是,判断是否是6位数字
+                        return Promise.resolve();
+                      } else {
+                        if (!Boolean(value)) return Promise.resolve();
+                        return Promise.reject("只能输入整数、小数或负数"); //如果违反规则,就会给出提示
+                      }
+                    },
+                  }),
+                ]}
+              >
+                <Input
+                  placeholder={TranslateText(["请输入", "Please input"])}
+                  style={{ width: "240px" }}
+                />
+              </Form.Item>
+              <Form.Item name="al_enable" valuePropName="checked" noStyle>
+                <Switch size={"small"} />
+              </Form.Item>
+            </Space>
+          </Form.Item>
+          <Form.Item label={TranslateText(["下下限", "Ultra lower limit"])}>
+            <Space size={20}>
+              <Form.Item
+                name="all_value"
+                key={allEnable}
+                noStyle
+                rules={[
+                  {
+                    required: allEnable,
+                    message: TranslateText(["请输入下下限", "Please input"]),
+                  },
+                  () => ({
+                    validator(rule, value) {
+                      let regPos =
+                        /^(([^0][0-9]+|0)\.([0-9])$)|^(([^0][0-9]+|0)$)|^(([1-9]+)\.([0-9])$)|^(([1-9]+)$)/;
+                      let regNeg =
+                        /^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/;
+                      if (regPos.test(value) || regNeg.test(value)) {
+                        //if中是正则表达是,判断是否是6位数字
+                        return Promise.resolve();
+                      } else {
+                        if (!Boolean(value)) return Promise.resolve();
+                        return Promise.reject("只能输入整数、小数或负数"); //如果违反规则,就会给出提示
+                      }
+                    },
+                  }),
+                ]}
+              >
+                <Input
+                  placeholder={TranslateText(["请输入", "Please input"])}
+                  style={{ width: "240px" }}
+                />
+              </Form.Item>
+              <Form.Item name="all_enable" valuePropName="checked" noStyle>
+                <Switch size={"small"} />
+              </Form.Item>
+            </Space>
+          </Form.Item>
+        </>
+      )}
+      <Form.Item
+        label={TranslateText(["告警级别", "Level"])}
+        required={false}
+        name="alarm_level"
+        rules={[
+          {
+            required: true,
+            message: TranslateText(["请输入告警级别", "Please input"]),
+          },
+        ]}
+      >
+        <Select
+          placeholder={TranslateText(["请选择", "Please choose"])}
+          style={{ width: "240px" }}
+        >
+          <Select.Option value={1}>
+            {TranslateText(["低", "Low"])}
+          </Select.Option>
+          <Select.Option value={2}>
+            {TranslateText(["中", "Medium"])}
+          </Select.Option>
+          <Select.Option value={3}>
+            {TranslateText(["高", "High"])}
+          </Select.Option>
+        </Select>
+      </Form.Item>
+
+      <Form.Item
+        name={isDisabled ? "" : "defer_seconds"}
+        disabled={isDisabled}
+        required={true}
+        label={TranslateText(["延时告警", "Delayed alarm"])}
+      >
+        <div style={{ marginTop: 6 }}>
+          <Radio.Group
+            defaultValue={alarmId !== -1 ? (dateTime > 0 ? 1 : 2) : 1}
+            disabled={isDisabled}
+            onChange={(e) => {
+              setStatus(e.target.value);
+              if (e.target.value === 1) {
+                setDateTime(null);
+              } else {
+                setDateTime(0);
+              }
+            }}
+          >
+            <Radio value={1}>{TranslateText(["开启", "on"])}</Radio>
+            <Radio value={2}>{TranslateText(["关闭", "off"])}</Radio>
+          </Radio.Group>
+          <div
+            style={{
+              display: "flex",
+              marginTop: 10,
+              gap: 8,
+              alignItems: "center",
+            }}
+          >
+            <span
+              style={{
+                color: "var(--dt-text-color1)",
+                fontWeight: 400,
+                fontSize: 14,
+              }}
+            >
+              {TranslateText(["持续", "Alarm after"])}
+            </span>
+            <Input
+              disabled={status !== 1 || isDisabled}
+              value={dateTime}
+              onChange={(e) => {
+                console.log(e.target.value);
+                setDateTime(e.target.value?.replace(/\D/g, ""));
+              }}
+              placeholder={TranslateText(["请输入", "Please input"])}
+              style={{ width: 100 }}
+            />
+            <span
+              style={{
+                color: "var(--dt-text-color1)",
+                fontWeight: 400,
+                fontSize: 14,
+              }}
+            >
+              {deferUnit === 1
+                ? TranslateText(["秒", "seconds"])
+                : TranslateText(["分钟", "minutes"])}{" "}
+              {TranslateText(["后报警", "of duration"])}
+            </span>
+          </div>
+          {dateTime !== null &&
+            ["", undefined, null].indexOf(dateTime) !== -1 &&
+            status === 1 &&
+            !isDisabled && (
+              <div style={{ color: "var(--dt-error-color1)" }}>请输入数字</div>
+            )}
+        </div>
+      </Form.Item>
+    </Form>
+  );
+};
+
+export default forwardRef(Easy);

+ 29 - 0
src/pages/Alarm/components/easy.module.less

@@ -0,0 +1,29 @@
+:global {
+  :local(.wrapper) {
+    // background-color: #212b38;
+    // padding-top: 24px;
+    width: 100%;
+
+    .ant-input {
+      // background-color: #212b38;
+    }
+
+    .bottom {
+      display: flex;
+      align-items: center;
+      margin-top: 16px;
+
+      .confirmgroup {
+        margin-left: auto;
+
+        button {
+          margin-left: 16px;
+        }
+      }
+    }
+
+    .ant-form-item-label>label {
+      color: var(--dt-text-color1);
+    }
+  }
+}

+ 28 - 0
src/pages/Alarm/index.jsx

@@ -0,0 +1,28 @@
+import styles from "./style/index.module.less";
+import { Tree, Input } from "antd";
+import React, { useState, useEffect, useMemo } from "react";
+import Complex from "./components/complex";
+import Easy from "./components/easy";
+
+import _ from "lodash";
+
+export default function Index(props) {
+  const componentMap = {
+    easy: (
+      <Easy
+        alarmId={props.alarmId}
+        onConfirm={props.onConfirm}
+        groupList={props.groupList}
+      ></Easy>
+    ),
+    complex: (
+      <Complex
+        alarmId={props.alarmId}
+        onConfirm={props.onConfirm}
+        groupList={props.groupList}
+      ></Complex>
+    ),
+  };
+
+  return <>{componentMap[props.type]}</>;
+}

+ 0 - 0
src/pages/Alarm/style/index.module.less


+ 324 - 0
src/style/antd.less

@@ -0,0 +1,324 @@
+// antd覆盖 切换到dtd以后去掉
+:root {
+  .ant-layout-sider-trigger {
+    border-top: 1px solid var(--dt-line-color2);
+    justify-content: flex-end;
+    padding-right: 20px;
+    display: flex;
+    align-items: center;
+    background: var(--dt-fill-color3);
+  }
+
+  .ant-layout-sider-collapsed .ant-layout-sider-trigger {
+    justify-content: center;
+    text-align: center;
+    padding-right: 0;
+  }
+
+  .ant-menu,
+  .ant-layout-sider,
+  .ant-layout-sider-children {
+    background-color: var(--dt-fill-color3) !important;
+  }
+
+  .ant-layout-sider {
+    box-shadow: 2px 0px 8px rgba(255, 255, 255, 0.02);
+  }
+
+  .ant-menu {
+    border-right: none;
+    font-weight: 400 !important;
+  }
+
+  .ant-menu-inline {
+    border-right: none;
+  }
+
+  .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected,
+  .ant-menu-item:active,
+  .ant-menu-submenu-title:active {
+    background-color: var(--dt-primary-color7);
+    color: var(--dt-primary-color);
+  }
+
+  // .ant-menu-item:hover,
+  // .ant-menu-submenu-selected,
+  // .ant-menu-submenu-title:hover,
+  // .ant-menu-submenu:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow,
+  // .ant-menu-light .ant-menu-item-active,
+  // .ant-menu-light .ant-menu-item:hover,
+  // .ant-menu-light .ant-menu-submenu-active,
+  // .ant-menu-light .ant-menu-submenu-title:hover,
+  // .ant-menu-light .ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open {
+  //   color: var(--dt-primary-color) !important;
+  // }
+
+  .ant-menu-inline .ant-menu-item::after {
+    border-color: var(--dt-primary-color);
+  }
+
+  .ant-menu-inline .ant-menu-item::after {
+    border-width: 4px;
+  }
+
+  .ant-dropdown-menu {
+    background-color: var(--dt-fill-color3);
+  }
+
+  .ant-menu-item-icon {
+    fill: currentColor;
+  }
+
+  .ant-switch-checked {
+    background-color: var(--dt-primary-color) !important;
+  }
+
+  // 全局已配置
+  // .ant-pagination {
+  //   .ant-pagination-item {
+  //     border-color: var(--dt-line-color2) !important;
+  //   }
+  //   .ant-pagination-total-text,
+  //   .ant-select-selection-item,
+  //   .ant-pagination-options-quick-jumper,
+  //   .ant-pagination-item a,
+  //   .ant-pagination-item-link {
+  //     font-size: 12px;
+  //     font-weight: 400;
+  //     color: var(--dt-text-color1) !important;
+  //   }
+  //   .ant-pagination-item-active a {
+  //     color: var(--dt-primary-color) !important;
+  //   }
+  //   .ant-pagination-item-active {
+  //     background: var(--dt-fill-color3);
+  //   }
+  // }
+
+  // 全局已配置
+  // .ant-btn-primary {
+  //   background-color: var(--dt-primary-color) !important;
+  //   border-color: var(--dt-primary-color) !important;
+  // }
+
+  // .ant-modal-content,
+  // .ant-modal-header {
+  //   background-color: var(--dt-fill-color3);
+  // }
+
+  // .ant-modal-close {
+  //   top: 20px;
+  // }
+
+  // .ant-modal-footer {
+  //   margin-top: 16px;
+  // }
+
+  // .ant-modal-close-icon {
+  //   color: var(--dt-text-color1);
+  // }
+
+  .ant-form-item {
+    margin-bottom: 20px;
+  }
+
+  .ant-form-item-explain-error {
+    color: var(--dt-error-color1) !important;
+  }
+
+  .ant-select-status-error.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input) .ant-select-selector,
+  .ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless).ant-input,
+  .ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless).ant-input:hover {
+    border-color: var(--dt-error-color1) !important;
+  }
+
+  .ant-form-item-label>label:after {
+    content: '';
+  }
+
+  .ant-cascader-menu {
+    scrollbar-width: none;
+
+    &::-webkit-scrollbar {
+      width: 0 !important;
+    }
+  }
+
+  .ant-menu-item-icon,
+  .ant-menu-item-icon path {
+    fill: currentColor !important;
+  }
+
+  .ant-menu {
+    scrollbar-width: none !important;
+
+    &::-webkit-scrollbar {
+      width: 0px !important;
+    }
+  }
+
+  .ant-menu-sub.ant-menu-inline>.ant-menu-item {
+    height: 40px;
+    line-height: 40px;
+  }
+
+  .ant-layout-sider-collapsed {
+    max-width: 40px !important;
+    min-width: 40px !important;
+    width: 40px !important;
+
+    .ant-layout-sider-trigger {
+      max-width: 40px !important;
+      min-width: 40px !important;
+      width: 40px !important;
+    }
+  }
+
+  .ant-select-dropdown {
+    background-color: var(--dt-fill-color1);
+  }
+
+  .ant-tooltip-inner {
+    background: var(--dt-fill-color3) !important;
+    color: var(--dt-text-color1) !important;
+  }
+
+  .ant-tooltip-arrow,
+  .ant-tooltip-arrow .ant-tooltip-arrow-content {
+    --antd-arrow-background-color: var(--dt-fill-color3) !important;
+  }
+
+  .ant-layout{
+    background: transparent;
+  }
+
+  // .ant-input-affix-wrapper,
+  // .ant-input {
+  //   background-color: transparent;
+  // }
+  // .ant-input {
+  //   color: var(--dt-text-color1);
+  //   &::placeholder {
+  //     color: var(--dt-text-color4);
+  //   }
+  // }
+
+  // .ant-input {
+  //   color: black;
+  //   &::placeholder {
+  //     color: var(--dt-text-color2);
+  //   }
+  // }
+
+  // .ant-select-selector,
+  // .ant-picker,
+  // .ant-btn-default {
+  //   background-color: transparent !important;
+  // }
+
+  // .ant-select-arrow svg,
+  // .ant-picker-range-separator svg,
+  // .ant-picker-suffix svg {
+  //   color: var(--dt-text-color3);
+  // }
+
+  // .ant-select-dropdown,
+  // .ant-picker-panel {
+  //   background-color: var(--dt-fill-color3) !important;
+  // }
+
+  // .ant-select-item-option-active,
+  // .ant-select-item-option-selected {
+  //   background-color: inherit;
+  //   color: var(--dt-primary-color);
+  // }
+
+  // .ant-picker-cell {
+  //   color: var(--dt-text-color2);
+  // }
+
+  // .ant-picker-cell-in-view {
+  //   color: var(--dt-text-color1);
+  // }
+
+  // .ant-picker-super-prev-icon,
+  // .ant-picker-prev-icon,
+  // .ant-picker-super-next-icon,
+  // .ant-picker-next-icon {
+  //   color: var(--dt-text-color2);
+  // }
+
+  // .ant-picker-header,
+  // .ant-picker-panel {
+  //   border-color: var(--dt-line-color2);
+  // }
+
+  // .ant-picker-cell:hover:not(.ant-picker-cell-selected):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end):not(.ant-picker-cell-range-hover-start):not(.ant-picker-cell-range-hover-end)
+  //   .ant-picker-cell-inner {
+  //   background-color: inherit;
+  //   color: var(--dt-primary-color);
+  // }
+
+  // .ant-switch-checked:focus {
+  //   box-shadow: inherit;
+  // }
+
+  // .ant-modal-content,
+  // .ant-modal-header,
+  // .ant-modal-footer {
+  //   background-color: var(--dt-fill-color3);
+  //   border-color: var(--dt-line-color2);
+  // }
+
+  // .ant-modal-title {
+  //   font-weight: 600;
+  //   font-size: 14px;
+  // }
+
+  // .ant-modal-close-x svg {
+  //   color: #606266;
+  // }
+
+  // .ant-progress-inner {
+  //   width: 100px !important;
+  //   height: 100px !important;
+  // }
+
+  // .ant-progress-inner:not(.ant-progress-circle-gradient)
+  //   .ant-progress-circle-path {
+  //   stroke: var(--dt-line-color1);
+  // }
+
+  // .ant-progress-circle-trail {
+  //   stroke: rgba(234, 241, 254, 0.08);
+  // }
+
+  // .ant-tree {
+  //   background: var(--dt-fill-color3);
+  // }
+
+  // .ant-tree-treenode {
+  //   margin: 0 8px;
+  //   display: flex;
+  //   align-items: center;
+  //   border-radius: 2px;
+  // }
+
+  // .ant-tree-treenode-selected {
+  //   background-color: var(--dt-line-color2);
+  // }
+
+  // .ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected {
+  //   background-color: transparent;
+  // }
+
+  // .ant-tree .ant-tree-node-content-wrapper:hover {
+  //   background-color: transparent;
+  //   color: var(--dt-primary-color);
+  // }
+
+  // .ant-tree-switcher-icon {
+  //   color: var(--dt-text-color3);
+  // }
+
+}

+ 87 - 0
src/style/base.less

@@ -0,0 +1,87 @@
+html,
+body,
+#m2root {
+  width: 100%;
+  height: 100%;
+  margin: 0;
+  padding: 0;
+  background: var(--dt-fill-color1);
+  color: var(--dt-text-color1);
+  font-size: 14px;
+  font-style: normal;
+  font-weight: 400;
+  scrollbar-width: thin;
+
+  ::-webkit-scrollbar-corner {
+    background: rgba(0, 0, 0, 0);
+  }
+
+  ::-webkit-scrollbar {
+    height: 8px;
+    width: 8px;
+  }
+
+  ::-webkit-scrollbar-thumb {
+    background-color: var(--dt-fill-color5) !important;
+    border-radius: 19px;
+
+    &:hover {
+      background-color: #a8a8a8;
+    }
+
+    &:active {
+      background-color: #787878;
+    }
+  }
+
+  ::-webkit-scrollbar-track {
+    background-color: transparent;
+  }
+}
+
+.base-margin-left {
+  margin-left: 16px;
+}
+
+.base-margin-right {
+  margin-right: 16px;
+}
+
+.base-margin-top {
+  margin-top: 16px;
+}
+
+.base-margin-bottom {
+  margin-bottom: 16px;
+}
+
+.base-margin {
+  margin: 16px;
+}
+
+.space-between {
+  justify-content: space-between;
+}
+
+.center {
+  justify-content: center;
+}
+
+.flex-1 {
+  flex: 1;
+}
+
+.flex-2 {
+  flex: 2;
+}
+
+.icon {
+  fill: currentColor;
+}
+
+iframe {
+  display: block;
+}
+
+micro-app, micro-app-body {height:100%;}
+

+ 99 - 0
src/style/colors.js

@@ -0,0 +1,99 @@
+import * as echarts from 'echarts';
+
+export const LineColors = [
+  {
+    color: '#2A6FF6',
+    areaStyle: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      {
+        offset: 0,
+        color: 'rgba(42, 111, 246, 0.2)',
+      },
+      {
+        offset: 1,
+        color: 'rgba(42, 111, 246, 0)',
+      },
+    ]),
+  },
+  {
+    color: '#19AFFA',
+    areaStyle: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      {
+        offset: 0,
+        color: 'rgba(25,175,250, 0.2)',
+      },
+      {
+        offset: 1,
+        color: 'rgba(25,175,250, 0)',
+      },
+    ]),
+  },
+  {
+    color: '#2DA641',
+    areaStyle: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      {
+        offset: 0,
+        color: 'rgba(45,166,65, 0.2)',
+      },
+      {
+        offset: 1,
+        color: 'rgba(45,166,65, 0)',
+      },
+    ]),
+  },
+  {
+    color: '#FAD20C',
+    areaStyle: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      {
+        offset: 0,
+        color: 'rgba(250,210,12, 0.2)',
+      },
+      {
+        offset: 1,
+        color: 'rgba(250,210,12, 0)',
+      },
+    ]),
+  },
+  {
+    color: '#ED6A0C',
+    areaStyle: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      {
+        offset: 0,
+        color: 'rgba(237,106,12, 0.2)',
+      },
+      {
+        offset: 1,
+        color: 'rgba(237,106,12, 0)',
+      },
+    ]),
+  },
+  {
+    color: '#BA257C',
+    areaStyle: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      {
+        offset: 0,
+        color: 'rgba(186,37,124, 0.2)',
+      },
+      {
+        offset: 1,
+        color: 'rgba(186,37,124, 0)',
+      },
+    ]),
+  },
+  {
+    color: '#7031FF',
+    areaStyle: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      {
+        offset: 0,
+        color: 'rgba(112,49,255, 0.2)',
+      },
+      {
+        offset: 1,
+        color: 'rgba(112,49,255, 0)',
+      },
+    ]),
+  },
+];
+
+export const PieColor = LineColors.map((item) => item.color);
+
+export const BarColors = ['#235DCE', '#658DDD', '#1593D2', '#5BB3DF'];

+ 82 - 0
src/style/common.less

@@ -0,0 +1,82 @@
+.custom-top {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 16px;
+}
+
+.toolbox {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  margin-bottom: 16px;
+}
+
+.legend-group {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  margin: 16px 0;
+  width: 100%;
+  .legend {
+    width: calc(50% - 4px);
+    display: flex;
+    align-items: center;
+    font-size: 12px;
+    color: var(--dt-text-color3);
+    font-weight: 400;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    .dot {
+      width: 8px;
+      height: 8px;
+      border-radius: 50%;
+      margin-right: 8px;
+    }
+    .legend-label {
+      max-width: 40x;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+}
+
+.diff {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: var(--dt-text-color3);
+  margin-bottom: 16px;
+  height: 22px;
+}
+
+.site-tree-search-value {
+  color: var(--dt-error-color1);
+}
+
+.card-main {
+  width: 100%;
+}
+
+.card {
+  background: var(--dt-fill-color3);
+  display: flex;
+  flex-direction: column;
+  &-header {
+    padding: 20px;
+    font-weight: 400;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    > div {
+      display: flex;
+      align-items: center;
+    }
+  }
+  &-content {
+    height: 0;
+    flex: 1;
+  }
+}

+ 54 - 0
src/style/components/ScrollBar/index.module.scss

@@ -0,0 +1,54 @@
+:global {
+  :local(.wrapper) {
+    //hardcode
+
+    //滚动条背景
+    &::-webkit-scrollbar {
+      height: 5px;
+      width: 5px;
+    }
+
+    // //滚动条前景
+    &::-webkit-scrollbar-thumb {
+      background-color: var(--dt-line-color3) !important;
+      border-radius: 10px;
+    }
+
+    &::-webkit-scrollbar-track {
+      background-color: transparent;
+      // border-radius: 5px;
+    }
+    // firefox
+    scrollbar-color: rgba(144, 147, 153, 0.3) transparent;
+    scrollbar-width: thin;
+  }
+  :local(.inner) {
+    .ant-table-body,
+    .ant-table-tbody,
+    .ant-table-content,
+    .scroll_bar_restyle {
+      //hardcode
+
+      //滚动条背景
+      &::-webkit-scrollbar {
+        height: 5px;
+        width: 5px;
+        // border-radius: 5px;
+      }
+
+      // //滚动条前景
+      &::-webkit-scrollbar-thumb {
+        background-color: var(--dt-line-color2);
+        border-radius: 10px;
+      }
+
+      &::-webkit-scrollbar-track {
+        background-color: transparent;
+        // border-radius: 5px;
+      }
+      // firefox
+      scrollbar-color: rgba(144, 147, 153, 0.3) transparent;
+      scrollbar-width: thin;
+    }
+  }
+}

+ 15 - 0
src/style/components/ScrollBar/scroll.module.less

@@ -0,0 +1,15 @@
+//滚动条背景
+&::-webkit-scrollbar {
+  height: 5px;
+  width: 5px;
+}
+
+// //滚动条前景
+&::-webkit-scrollbar-thumb {
+  background-color: var(--dt-line-color2);
+  border-radius: 10px;
+}
+
+&::-webkit-scrollbar-track {
+  background-color: transparent;
+}

+ 102 - 0
src/style/components/table.less

@@ -0,0 +1,102 @@
+:root {
+  .ant-table {
+    background-color: var(--dt-fill-color3);
+  }
+
+  .ant-table-placeholder:hover .ant-table-cell {
+    background: var(--dt-fill-color3);
+  }
+
+  .ant-table-cell:before {
+    background-color: transparent !important;
+  }
+
+  // .ant-table-cell {
+  //   &:first-child {
+  //     padding-left: 20px !important;
+  //   }
+  // }
+
+  .ant-table-thead>tr>th {
+    font-style: normal;
+    font-weight: 600;
+    background-color: var(--dt-fill-color1);
+    color: var(--dt-text-color1);
+  }
+
+  .ant-table-tbody {
+    background-color: var(--dt-fill-color3);
+    background: var(--dt-fill-color3);
+    color: var(--dt-text-color2);
+  }
+
+  .ant-table-tbody>tr>td {
+    border-bottom: 1px solid var(--dt-line-color1);
+  }
+
+  .ant-table-cell-row-hover {
+    background: var(--dt-fill-color1) !important;
+  }
+
+  .ant-table-tbody>tr.ant-table-row-selected td {
+    background-color: var(--dt-fill-color3);
+  }
+
+  .ant-table-tbody>tr>td:hover {
+    background-color: var(--dt-fill-color3);
+  }
+
+  .ant-empty-normal {
+    color: var(--dt-text-color2);
+  }
+
+  .ant-pagination {
+    .ant-pagination-item-link {
+      background-color: var(--dt-fill-color3);
+      color: var(--dt-text-color2);
+    }
+
+    .ant-pagination-item-active {
+      background: var(--dt-fill-color3);
+    }
+  }
+
+  .ant-table-cell-scrollbar {
+    box-shadow: 0 1px 0 1px --dt-fill-color3;
+  }
+
+  // .ant-table.ant-table-small .ant-table-footer,
+  // .ant-table.ant-table-small .ant-table-tbody>tr>td,
+  // .ant-table.ant-table-small .ant-table-thead>tr>th,
+  // .ant-table.ant-table-small .ant-table-title,
+  // .ant-table.ant-table-small tfoot>tr>td,
+  // .ant-table.ant-table-small tfoot>tr>th {
+  //   padding: 5px; //small模式的行高设计稿为32px
+  // }
+
+
+  //滚动条样式修改
+  .ant-table-body,
+  .ant-table-content,
+  .scroll_bar_restyle {
+    //hardcode
+
+    //滚动条背景
+    &::-webkit-scrollbar {
+      height: 5px;
+      width: 5px;
+      // border-radius: 5px;
+    }
+
+    // //滚动条前景
+    &::-webkit-scrollbar-thumb {
+      background-color: var(--dt-line-color3) !important;
+      border-radius: 10px;
+    }
+
+    &::-webkit-scrollbar-track {
+      background-color: transparent;
+      // border-radius: 5px;
+    }
+  }
+}

+ 4 - 0
src/style/global.less

@@ -0,0 +1,4 @@
+// @import '~antd/dist/antd.less';
+@import './antd.less';
+@import './base.less';
+@import './common.less';

+ 5 - 0
src/style/index.module.less

@@ -0,0 +1,5 @@
+:global {
+  :local(.app) {
+    height: 100%;
+  }
+}

+ 237 - 0
src/utils/constant/ChartConfig/Bar/index.jsx

@@ -0,0 +1,237 @@
+import { MULTI_COLORS_OPTIONS as MULTI_SINGLE_COLORS_OPTIONS } from '../../MultiSingleColorsOptions'
+import { BarChart } from '../../../../components/GridModules'
+import {
+  getYAxisSettingConf,
+  chartTypeInLineOptions,
+  barChartChangeableChartTypeOptions as changeableChartTypes,
+  precisionOptions, contrastPositionOptions,
+  getCompareSettingConf, getLegendSettingConf
+} from '../utils'
+
+const config = {
+  name: '柱状图',
+  code: 'BargraphChart',
+  comp: BarChart,
+  changeableChartTypes,
+  custom_settings: [
+    {
+      // id: 13, //基础设置
+      renderCompName: 'DTFormRender',// default use IdRender
+      props: {
+        wrapperStyle: {
+          type: 'title',
+          props: {
+            title: '基础设置'
+          }
+        },
+        onChangeInterceptor: (nData, data, changedVals) => {
+          if ((changedVals ?? {}).hasOwnProperty('chart-type')) {
+            const rData = { ...(nData ?? {}) }
+            const val = changedVals['chart-type']
+            if (val === 'line') {
+              rData['item-width'] = 2
+            } else if (val === 'bar') {
+              rData['item-width'] = null
+            } else {
+            }
+            return rData
+          }
+          return nData
+        }
+      },
+      keys: [
+        {
+          key: 'chart-type',
+          default: 'bar', format: 'select', props: {
+            title: '图表类型',
+            options: chartTypeInLineOptions
+          }
+        },
+        {
+          // 继承外部
+          key: 'chart-color',
+          // default: { type: 'Single', colors: [0, 0, 0, 1] }
+          default: null, format: 'single-color-select', props: {
+            title: '颜色设定'
+          }
+        },
+        {
+          // 继承外部
+          key: 'item-width',
+          default: null, format: 'number-input', props: {
+            title: '大小/线条粗细'
+          }
+        },
+        {
+          key: 'bar-chart-stack',
+          default: null,
+          format: 'input',
+          hidden: data => data?.['chart-type'] === 'line',
+          props: {
+            title: 'Stack值',
+            width: 90
+          }
+        },
+        {
+          key: 'area-chart', default: false, format: 'switch',
+          hidden: data => data?.['chart-type'] === 'bar',
+          props: {
+            title: '设置为面积图'
+          }
+        },
+        {
+          key: 'area-gradient', default: true, format: 'switch',
+          hidden: data => data?.['chart-type'] === 'bar',
+          props: {
+            title: '面积图颜色渐变'
+          }
+        },
+        {
+          key: 'area-gradient-opacity', default: 80, format: 'slider',
+          hidden: data => data?.['chart-type'] === 'bar',
+          props: {
+            title: '堆积/面积颜色渐变'
+          }
+        }
+      ]
+    },
+    {
+      // id: 8, //对比设置
+      ...getCompareSettingConf()
+    }
+  ],
+  basic_settings: [
+    {
+      // id: 9, //柱状图基础设置
+      renderCompName: 'DTFormRender',// default use IdRender
+      props: {
+        wrapperStyle: {
+          type: 'padding'
+        }
+      },
+      keys: [
+        {
+          key: 'title',
+          default: '',
+          format: 'input',
+          props: {
+            title: '标题'
+          }
+        },
+        {
+          key: 'precision', default: 1, format: 'select', props: {
+            title: '精度配置',
+            options: precisionOptions
+          }
+        },
+        {
+          key: 'changeable-chart-types', default: [], format: 'select', props: {
+            title: '图表切换',
+            options: changeableChartTypes,
+            width: 240,
+            allowClear: true,
+            mode: 'multiple'
+          }
+        },
+        {
+          key: 'color',
+          default: {
+            type: MULTI_SINGLE_COLORS_OPTIONS[0].type,
+            colors: MULTI_SINGLE_COLORS_OPTIONS[0].colors
+          }, format: 'multi-color-select', props: {
+            title: '色系'
+          }
+        },
+        {
+          key: 'bar-color-gradient', default: false, format: 'switch', props: {
+            title: '填充颜色渐变'
+          }
+        },
+        {
+          key: 'bar-color-gradient-opacity', default: 100, format: 'slider', props: {
+            title: '堆积/面积颜色透明度'
+          }
+        },
+        {
+          key: 'bar-width', default: 20, format: 'number-input', props: {
+            title: '柱体粗细'
+          }
+        },
+        {
+          key: 'bar-show-label', default: false, format: 'switch', props: {
+            title: '柱体显示数据'
+          }
+        },
+        {
+          key: 'custom-grid', default: false, format: 'switch', props: {
+            title: '轴距自定义'
+          }
+        },
+        {
+          key: ['grid-left', 'grid-top'], default: [20, 20], format: 'two-number-input', props: {
+            title: ['左间距', '上间距']
+          }
+        },
+        {
+          key: 'compare-list-show', default: false, format: 'switch', props: {
+            title: '对比内容设置'
+          }
+        },
+        {
+          key: 'compare-font-size', default: 12, format: 'number-input', props: {
+            title: '字体大小'
+          }
+        },
+        {
+          key: 'compare-list-position', default: 'right-top', format: 'select', props: {
+            title: '对比内容位置',
+            options: contrastPositionOptions
+          }
+        }
+      ]
+    },
+    {
+      ...getYAxisSettingConf()
+    },
+    {
+      // id: 3, //X轴线设置框
+      renderCompName: 'DTFormRender',// default use IdRender
+      props: {
+        wrapperStyle: {
+          type: 'foldable',
+          props: {
+            title: 'X轴线'
+          }
+        }
+      },
+      keys: [
+        {
+          key: 'x-axis-title-show', default: false, format: 'switch', props: {
+            title: '轴标题'
+          }
+        },
+        {
+          key: 'x-title', default: '',
+          format: 'input',
+          props: {
+            title: TranslateText(['名称', 'Name']),
+          }
+        },
+        {
+          key: 'x-label-font-size', default: 12, format: 'number-input', props: {
+            title: '字体大小'
+          }
+        }
+      ]
+    },
+    {
+      ...getLegendSettingConf()
+    },
+    {
+      id: 6, //阈值设置框 thresholds
+      keys: [{ key: 'thresholds', default: [] }]
+    }
+  ]
+}
+
+export default config

+ 199 - 0
src/utils/constant/ChartConfig/CompareSortBar/index.jsx

@@ -0,0 +1,199 @@
+import { MULTI_COLORS_OPTIONS as MULTI_SINGLE_COLORS_OPTIONS } from '../../MultiSingleColorsOptions'
+import { CompareSortBar } from '../../../../components/GridModules'
+import {
+  getYAxisSettingConf,
+  CompareSortChangeableChartTypeOptions as changeableChartTypes,
+  precisionOptions,
+  getLegendSettingConf
+} from '../utils'
+
+const config = {
+  name: '对比排序图',
+  code: 'CompareSortChart',
+  comp: CompareSortBar,
+  changeableChartTypes,
+  pointCommId: 1004,
+  customCommId: 2002,
+  custom_settings: [
+    {
+      // id: 26, //基础设置
+      renderCompName: 'DTFormRender',// default use IdRender
+      props: {
+        wrapperStyle: {
+          type: 'title',
+          props: {
+            title: '基础设置'
+          }
+        }
+      },
+      keys: [
+        {
+          // 继承外部
+          key: 'chart-color',
+          // default: { type: 'Single', colors: [0, 0, 0, 1] }
+          default: null, format: 'single-color-select', props: {
+            title: '颜色'
+          }
+        },
+        {
+          // 继承外部
+          key: 'item-width',
+          default: null, format: 'number-input', props: {
+            title: '柱体粗细'
+          }
+        },
+        {
+          key: 'bar-chart-stack',
+          default: null, format: 'input', props: {
+            title: 'Stack值',
+            width: 90
+          }
+        }
+      ]
+    }
+  ],
+  basic_settings: [
+    {
+      // id: 27, //基础设置
+      // needConfig: true,
+      renderCompName: 'DTFormRender',// default use IdRender
+      props: {
+        wrapperStyle: {
+          type: 'padding'
+        }
+      },
+      keys: [
+        {
+          key: 'title',
+          default: '',
+          format: 'input',
+          props: {
+            title: '标题'
+          }
+        },
+        {
+          key: 'color',
+          default: {
+            type: MULTI_SINGLE_COLORS_OPTIONS[0].type,
+            colors: MULTI_SINGLE_COLORS_OPTIONS[0].colors
+          }, format: 'multi-color-select', props: {
+            title: '色系'
+          }
+        },
+        {
+          key: 'changeable-chart-types', default: [], format: 'select', props: {
+            title: '图表切换',
+            options: changeableChartTypes,
+            width: 240,
+            allowClear: true,
+            mode: 'multiple'
+          }
+        },
+        {
+          key: 'bar-color-gradient', default: false, format: 'switch', props: {
+            title: '填充颜色渐变'
+          }
+        },
+        {
+          key: 'bar-color-gradient-opacity', default: 100, format: 'slider', props: {
+            title: '堆积/面积颜色透明度'
+          }
+        },
+        // {
+        //   key: 'direction',
+        //   default: 1
+        // },
+        {
+          key: 'bar-width', default: 20, format: 'number-input', props: {
+            title: '柱体粗细'
+          }
+        },
+        {
+          key: 'bar-show-label', default: false, format: 'switch', props: {
+            title: '柱体显示数据'
+          }
+        },
+        {
+          key: 'compare-sort', default: null, format: 'select', props: (cardInfo) => {
+            return {
+              title: '排序设定',
+              options: [
+                {
+                  label: '从大到小',
+                  value: 'desc'
+                },
+                {
+                  label: '从小到大',
+                  value: 'asc'
+                }
+              ],
+              allowClear: true,
+              // disabled: cardInfo?.config?.pointClasses?.length !== 1
+            }
+          }
+        },
+        {
+          key: 'precision', default: 1, format: 'select', props: {
+            title: '精度配置',
+            options: precisionOptions
+          }
+        },
+        {
+          key: 'custom-grid', default: false, format: 'switch', props: {
+            title: '轴距自定义'
+          }
+        },
+        {
+          key: ['grid-left', 'grid-top'], default: [20, 20], format: 'two-number-input', props: {
+            title: ['左间距', '上间距']
+          }
+        }
+      ]
+    },
+    {
+      ...getYAxisSettingConf()
+    },
+    {
+      // id: 3, //X轴线设置框
+      renderCompName: 'DTFormRender',// default use IdRender
+      props: {
+        wrapperStyle: {
+          type: 'foldable',
+          props: {
+            title: 'X轴线'
+          }
+        }
+      },
+      keys: [
+        {
+          key: 'x-axis-title-show', default: false, format: 'switch', props: {
+            title: '轴标题'
+          }
+        },
+        {
+          key: 'x-title', default: '',
+          format: 'input',
+          props: {
+            title: TranslateText(['名称', 'Name']),
+          }
+        },
+        {
+          key: 'x-label-font-size', default: 12, format: 'number-input', props: {
+            title: '字体大小'
+          }
+        }
+      ]
+    },
+    {
+      ...getLegendSettingConf()
+    },
+    {
+      id: 31, //阈值设置框 thresholds
+      // 由于阈值设置需要选择列,列数得从cardInfo中取
+      needConfig: true,
+      keys: [{ key: 'thresholds', default: [] }]
+    }
+  ]
+}
+
+export default config

+ 23 - 0
src/utils/constant/ChartConfig/ComplexChart/index.jsx

@@ -0,0 +1,23 @@
+import { ComplexTable } from '../../../../components/GridModules'
+import { getTableBasicSettingConf, complexTableChangeableChartTypeOptions as changeableChartTypes } from '../utils'
+
+const config = {
+  name: '复杂表格图',
+  code: 'ComplexChart',
+  comp: ComplexTable,
+  changeableChartTypes,
+  custom_settings: [],
+  pointCommId: 1004,
+  basic_settings: [
+    {
+      ...getTableBasicSettingConf({ changeableChartTypes })
+    },
+    {
+      id: 25, //阈值设置框 thresholds
+      needConfig: true,
+      keys: [{ key: 'thresholds', default: [] }]
+    }
+  ]
+}
+
+export default config

+ 180 - 0
src/utils/constant/ChartConfig/Frequency/index.jsx

@@ -0,0 +1,180 @@
+import { MULTI_COLORS_OPTIONS as MULTI_SINGLE_COLORS_OPTIONS } from '../../MultiSingleColorsOptions'
+import { FrequencyHistogramChart } from '../../../../components/GridModules'
+import {
+  precisionOptions, contrastPositionOptions,
+  getCompareSettingConf, getLegendSettingConf
+} from '../utils'
+
+const config = {
+  name: '频次直方图',
+  code: 'FrequencyChart',
+  comp: FrequencyHistogramChart,
+  custom_settings: [
+    {
+      id: 21, //基础设置
+      keys: [
+        {
+          // 继承外部
+          key: 'chart-color',
+          // default: { type: 'Single', colors: [0, 0, 0, 1] }
+          default: null
+        },
+        {
+          key: 'use-custom-opacity',
+          default: false
+        },
+        { key: 'custom-opacity', default: 100 },
+        { key: 'ref-lines', default: null },
+        { key: 'show-function-line', default: false }
+      ]
+    },
+    {
+      // id: 8, //对比设置
+      ...getCompareSettingConf()
+    }
+  ],
+  basic_settings: [
+    {
+      // id: 17, //频次直方图基础设置
+      renderCompName: 'DTFormRender',// default use IdRender
+      props: {
+        wrapperStyle: {
+          type: 'padding'
+        }
+      },
+      keys: [
+        {
+          key: 'title',
+          default: '',
+          format: 'input',
+          props: {
+            title: '标题'
+          }
+        },
+        {
+          key: 'color',
+          default: {
+            type: MULTI_SINGLE_COLORS_OPTIONS[0].type,
+            colors: MULTI_SINGLE_COLORS_OPTIONS[0].colors
+          }, format: 'multi-color-select', props: {
+            title: '色系'
+          }
+        },
+        {
+          key: 'opacity', default: 70, format: 'slider', props: {
+            title: '直方柱体颜色透明度'
+          }
+        },
+        {
+          key: 'precision', default: 1, format: 'select', props: {
+            title: '精度配置',
+            options: precisionOptions
+          }
+        },
+        {
+          key: 'custom-grid', default: false, format: 'switch', props: {
+            title: '轴距自定义'
+          }
+        },
+        {
+          key: ['grid-left', 'grid-top'], default: [20, 20], format: 'two-number-input', props: {
+            title: ['左间距', '上间距']
+          }
+        },
+        {
+          key: 'compare-list-show', default: false, format: 'switch', props: {
+            title: '对比内容设置'
+          }
+        },
+        {
+          key: 'compare-font-size', default: 12, format: 'number-input', props: {
+            title: '字体大小'
+          }
+        },
+        {
+          key: 'compare-list-position', default: 'right-top', format: 'select', props: {
+            title: '位置',
+            options: contrastPositionOptions
+          }
+        }
+      ]
+    },
+    {
+      // id: 18, //Y轴线设置框
+      renderCompName: 'DTFormRender',// default use IdRender
+      props: {
+        wrapperStyle: {
+          type: 'foldable',
+          props: {
+            title: 'Y轴线'
+          }
+        }
+      },
+      keys: [
+        {
+          key: 'y-axis-title-show', default: false, format: 'switch', props: {
+            title: '轴标题'
+          }
+        },
+        {
+          key: 'y-title', default: '',
+          format: 'input',
+          props: {
+            title: 'Y轴名称'
+          }
+        },
+        {
+          key: 'y-label-font-size', default: 12, format: 'number-input', props: {
+            title: 'Y轴字体大小'
+          }
+        },
+        {
+          key: 'y-hide-split-line', default: true, format: 'switch', props: {
+            title: '隐藏Y轴网格线'
+          }
+        }
+      ]
+    },
+    {
+      // id: 19, //X轴线设置框
+      renderCompName: 'DTFormRender',// default use IdRender
+      props: {
+        wrapperStyle: {
+          type: 'foldable',
+          props: {
+            title: 'X轴线'
+          }
+        }
+      },
+      keys: [
+        {
+          key: 'x-axis-title-show', default: false, format: 'switch', props: {
+            title: '轴标题'
+          }
+        },
+        {
+          key: 'x-title', default: '',
+          format: 'input',
+          props: {
+            title: TranslateText(['名称', 'Name']),
+          }
+        },
+        {
+          key: 'x-label-font-size', default: 12, format: 'number-input', props: {
+            title: '字体大小'
+          }
+        },
+        {
+          key: 'group-number', default: 10, format: 'number-input', props: {
+            title: 'Bins分段数'
+          }
+        }
+      ]
+    },
+    {
+      ...getLegendSettingConf()
+    }
+  ]
+}
+
+export default config

+ 236 - 0
src/utils/constant/ChartConfig/Line/index.jsx

@@ -0,0 +1,236 @@
+import { MULTI_COLORS_OPTIONS as MULTI_SINGLE_COLORS_OPTIONS } from '../../MultiSingleColorsOptions'
+import { LineChart } from '../../../../components/GridModules'
+import {
+  getYAxisSettingConf,
+  lineChartChangeableChartTypeOptions as changeableChartTypes,
+  precisionOptions, contrastPositionOptions, chartTypeInLineOptions,
+  getCompareSettingConf, getLegendSettingConf
+} from '../utils'
+
+const config = {
+  name: '折线图',
+  code: 'LineChart',
+  comp: LineChart,
+  changeableChartTypes,
+  custom_settings: [
+    {
+      // id: 7, //基础设置
+      renderCompName: 'DTFormRender',// default use IdRender
+      props: {
+        wrapperStyle: {
+          type: 'title',
+          props: {
+            title: '基础设置'
+          }
+        },
+        onChangeInterceptor: (nData, data, changedVals) => {
+          if ((changedVals ?? {}).hasOwnProperty('chart-type')) {
+            const rData = { ...(nData ?? {}) }
+            const val = changedVals['chart-type']
+            if (val === 'line') {
+              rData['item-width'] = null
+            } else if (val === 'bar') {
+              rData['item-width'] = 10
+            } else {
+            }
+            return rData
+          }
+          return nData
+        }
+      },
+      keys: [
+        {
+          key: 'chart-type',
+          default: 'line', format: 'select', props: {
+            title: '图表类型',
+            options: chartTypeInLineOptions
+          }
+        },
+        {
+          // 继承外部
+          key: 'item-width',
+          default: null, format: 'number-input', props: {
+            title: '图形主体粗细'
+          }
+        },
+        {
+          // 继承外部
+          key: 'chart-color',
+          // default: { type: 'Single', colors: [0, 0, 0, 1] }
+          default: null, format: 'single-color-select', props: {
+            title: '颜色设定'
+          }
+        },
+        {
+          key: 'bar-chart-stack',
+          default: null,
+          format: 'input',
+          props: {
+            title: 'Stack值',
+            width: 90
+          }
+        }
+      ]
+    },
+    {
+      // id: 8, //对比设置
+      ...getCompareSettingConf()
+    }
+  ],
+  basic_settings: [
+    {
+      // id: 28, //新基础设置
+      renderCompName: 'DTFormRender',// default use IdRender
+      props: {
+        wrapperStyle: {
+          type: 'padding'
+        }
+      },
+      keys: [
+        {
+          key: 'title',
+          default: '',
+          format: 'input',
+          props: {
+            title: '标题'
+          }
+        },
+        {
+          key: 'precision', default: 1, format: 'select', props: {
+            title: '精度配置',
+            options: precisionOptions
+          }
+        },
+        {
+          key: 'changeable-chart-types', default: [], format: 'select', props: {
+            title: '图表切换',
+            options: changeableChartTypes,
+            width: 240,
+            allowClear: true,
+            mode: 'multiple'
+          }
+        },
+        {
+          key: 'color',
+          default: {
+            type: MULTI_SINGLE_COLORS_OPTIONS[0].type,
+            colors: MULTI_SINGLE_COLORS_OPTIONS[0].colors
+          }, format: 'multi-color-select', props: {
+            title: '色系'
+          }
+        },
+        {
+          key: 'line-width', default: 2, format: 'number-input', props: {
+            title: '折线粗细'
+          }
+        },
+        {
+          key: 'line-symbol-show', default: true, format: 'switch', props: {
+            title: '折线圆点'
+          }
+        },
+        {
+          key: 'line-symbol-width', default: 8, format: 'number-input', props: {}
+        },
+        {
+          key: 'area-chart', default: false, format: 'switch', props: {
+            title: '设置为面积图'
+          }
+        },
+        {
+          key: 'area-gradient', default: true, format: 'switch', props: {
+            title: '面积图颜色渐变'
+          }
+        },
+        {
+          key: 'area-gradient-opacity', default: 15, format: 'slider', props: {
+            title: '堆积/面积颜色渐变'
+          }
+        },
+        {
+          key: 'custom-grid', default: false, format: 'switch', props: {
+            title: '轴距自定义'
+          }
+        },
+        {
+          key: ['grid-left', 'grid-top'], default: [20, 20], format: 'two-number-input', props: {
+            title: ['左间距', '上间距']
+          }
+        },
+        {
+          key: 'compare-list-show', default: false, format: 'switch', props: {
+            title: '对比内容设置'
+          }
+        },
+        {
+          key: 'compare-font-size', default: 12, format: 'number-input', props: {
+            title: '字体大小'
+          }
+        },
+        {
+          key: 'compare-list-position', default: 'right-top', format: 'select', props: {
+            title: '对比内容位置',
+            options: contrastPositionOptions
+          }
+        }
+      ]
+    },
+    {
+      ...getYAxisSettingConf()
+    },
+    {
+      // id: 3, //X轴线设置框
+      renderCompName: 'DTFormRender',// default use IdRender
+      props: {
+        wrapperStyle: {
+          type: 'foldable',
+          props: {
+            title: 'X轴线'
+          }
+        }
+      },
+      keys: [
+        {
+          key: 'x-axis-title-show', default: false, format: 'switch', props: {
+            title: '轴标题'
+          }
+        },
+        {
+          key: 'x-title', default: '',
+          format: 'input',
+          props: {
+            title: TranslateText(['名称', 'Name']),
+          }
+        },
+        {
+          key: 'x-label-font-size', default: 12, format: 'number-input', props: {
+            title: '字体大小'
+          }
+        }
+      ]
+    },
+    {
+      ...getLegendSettingConf()
+    },
+    {
+      renderCompName: 'DTFormRender',// default use IdRender
+      props: {
+        wrapperStyle: {
+          type: 'foldable',
+          props: {
+            title: '阈值'
+          }
+        }
+      },
+      keys: [
+        {
+          key: 'thresholds-v2', default: [], format: 'thresholds-v2', props: {
+            // title: '显示图例'
+          }
+        }
+      ]
+    }
+  ]
+}
+
+export default config

+ 123 - 0
src/utils/constant/ChartConfig/Numerical/index.jsx

@@ -0,0 +1,123 @@
+import { MULTI_COLORS_OPTIONS as MULTI_SINGLE_COLORS_OPTIONS } from '../../MultiSingleColorsOptions'
+import { NumericalChart } from '../../../../components/GridModules'
+
+const config = {
+  name: '数值图',
+  code: 'NumericalChart',
+  comp: NumericalChart,
+  custom_settings: [
+    {
+      id: 12, //基础设置
+      keys: [
+        {
+          key: 'chart-type',
+          default: 'numerical'
+        },
+        { key: 'chart-main-color', default: null },
+        {
+          key: 'meter-size',
+          default: 70
+        },
+        {
+          key: 'meter-line-size',
+          default: 10
+        },
+        {
+          key: 'show-as-ratio',
+          default: true
+        },
+        {
+          key: 'ratio-large',
+          default: 1
+        },
+        {
+          key: 'ratio-small',
+          default: 0
+        },
+        {
+          key: 'ratio-ring-color',
+          default: null
+        },
+      ]
+    },
+    {
+      id: 20, //对比设置
+      keys: [
+        {
+          key: 'compare-method',
+          default: 'LAST'
+        },
+        {
+          key: 'compare-show',
+          default: false
+        },
+        {
+          key: 'compare-origin-name',
+          default: ''
+        },
+        {
+          key: 'compare-name',
+          default: ''
+        },
+        {
+          key: 'compare-delta-show',
+          default: false
+        },
+        {
+          key: 'compare-delta-name',
+          default: ''
+        },
+        {
+          key: 'compare-rate-show',
+          default: false
+        },
+        {
+          key: 'compare-rate-name',
+          default: ''
+        },
+        {
+          key: 'ori-show',
+          default: true
+        },
+      ]
+    }
+  ],
+  basic_settings: [
+    {
+      id: 10, //标题设置框
+      needConfig: true,
+      keys: [
+        {
+          key: 'title',
+          default: ''
+        },
+        { key: 'precision', default: 1 },
+        { key: 'content-font-size', default: 26 },
+        { key: 'content-font-color', default: null },
+        { key: 'custom-grid', default: false },
+        { key: 'grid-left', default: 20 },
+        { key: 'grid-top', default: 20 },
+        { key: 'compare-list-show', default: false },
+        { key: 'compare-font-size', default: 26 },
+        { key: 'compare-list-position', default: 'right-top' },
+        { key: 'compare-item-margin-enabled', default: true },
+        { key: 'compare-item-margin', default: 150 },
+      ]
+    },
+    // {
+    //   id: 14, //仪表图设置
+    //   keys: [
+    //     {
+    //       key: 'show-meter',
+    //       default: false
+    //     },
+    //   ]
+    // },
+    {
+      id: 11, //阈值设置框 thresholds
+      keys: [{ key: 'thresholds', default: [] }]
+    }
+  ]
+}
+
+export default config

+ 90 - 0
src/utils/constant/ChartConfig/Ring/index.jsx

@@ -0,0 +1,90 @@
+import { MULTI_COLORS_OPTIONS as MULTI_SINGLE_COLORS_OPTIONS } from '../../MultiSingleColorsOptions'
+import { RingChart } from '../../../../components/GridModules'
+
+const config =   {
+  name: '环形图',
+  code: 'RingChart',
+  comp: RingChart,
+  custom_settings: [
+    {
+      id: 16, //ringChartCustomize
+      keys: [
+        { key: 'ring-color', default: null },
+      ]
+    },
+  ],
+  basic_settings: [
+    {
+      id: 15, //基础设置
+      keys: [
+        {
+          key: 'title',
+          default: ''
+        },
+        {
+          key: 'color',
+          default: {
+            type: MULTI_SINGLE_COLORS_OPTIONS[0].type,
+            colors: MULTI_SINGLE_COLORS_OPTIONS[0].colors
+          }
+        },
+        { key: 'ring-out-r', default: 60 },
+        { key: 'ring-in-r', default: 50 },
+        { key: 'precision', default: 1 },
+        { key: 'custom-grid', default: false },
+        { key: 'grid-left', default: 20 },
+        { key: 'grid-top', default: 20 },
+        { key: 'compare-font-size', default: 14 },
+        { key: 'compare-list-position', default: 'right-top' },
+        {
+          key: 'compare-method',
+          default: 'LAST'
+        },
+        {
+          key: 'ori-show',
+          default: false
+        },
+        {
+          key: 'compare-origin-name',
+          default: ''
+        },
+        {
+          key: 'compare-show',
+          default: false
+        },
+        {
+          key: 'compare-name',
+          default: ''
+        },
+        {
+          key: 'compare-delta-show',
+          default: false
+        },
+        {
+          key: 'compare-delta-name',
+          default: ''
+        },
+        {
+          key: 'compare-rate-show',
+          default: false
+        },
+        {
+          key: 'compare-rate-name',
+          default: ''
+        },
+        {
+          key: 'legend-show',
+          default: true
+        },
+        {
+          key: 'percent-show',
+          default: false
+        },
+        { key: 'legend-font-size', default: 12 },
+        { key: 'legend-position', default: 'right-top' },
+      ]
+    },
+  ]
+}
+
+export default config

+ 64 - 0
src/utils/constant/ChartConfig/Sankey/index.jsx

@@ -0,0 +1,64 @@
+import { MULTI_COLORS_OPTIONS as MULTI_SINGLE_COLORS_OPTIONS } from '../../MultiSingleColorsOptions'
+import { SankeyChart } from '../../../../components/GridModules'
+
+const config = {
+  name: '桑基图',
+  code: 'SankeyChart',
+  comp: SankeyChart,
+  pointCommId: 1005,
+  customCommId: 2003,
+  custom_settings: [
+    {
+      id: 30, //基础设置
+      keys: [
+        { key: 'line-option', default: 3 },
+        { key: 'line-color', default: null },
+        // { key: 'line-gradient', default: false },
+        { key: 'line-opacity', default: 0.2 },
+        { key: 'line-curveness', default: 0.5 },
+        { key: 'border-color', default: null },
+        { key: 'border-size', default: 1 },
+        { key: 'border-opacity', default: 1 },
+      ]
+    }
+  ],
+  basic_settings: [
+    {
+      id: 29, //标题设置框
+      needConfig: true,
+      keys: [
+        {
+          key: 'title',
+          default: ''
+        },
+        {
+          key: 'color',
+          default: {
+            type: MULTI_SINGLE_COLORS_OPTIONS[0].type,
+            colors: MULTI_SINGLE_COLORS_OPTIONS[0].colors
+          }
+        },
+        { key: 'precision', default: 1 },
+        { key: 'custom-grid', default: false },
+        { key: 'grid-left', default: 20 },
+        { key: 'grid-top', default: 20 },
+        { key: 'label-show', default: true },
+        { key: 'label-font-color', default: null },
+        { key: 'label-font-size', default: 12 },
+        { key: 'label-position', default: 'right' },
+        { key: 'line-option', default: 3 },
+        { key: 'line-color', default: null },
+        // { key: 'line-gradient', default: false },
+        { key: 'line-opacity', default: 0.2 },
+        { key: 'line-curveness', default: 0.5 },
+        { key: 'border-color', default: null },
+        { key: 'border-size', default: 1 },
+        { key: 'border-opacity', default: 1 },
+        { key: 'border-node-width', default: 20 },
+        { key: 'border-node-space', default: 8 },
+      ]
+    }
+  ]
+}
+
+export default config

+ 79 - 0
src/utils/constant/ChartConfig/Scatter/index.jsx

@@ -0,0 +1,79 @@
+import { MULTI_COLORS_OPTIONS as MULTI_SINGLE_COLORS_OPTIONS } from '../../MultiSingleColorsOptions'
+import { ScatterChart } from '../../../../components/GridModules'
+
+const config = {
+  name: '散点图',
+  code: 'ScatterChart',
+  comp: ScatterChart,
+  pointCommId: 1002,
+  custom_settings: [
+    {
+      id: 22, //基础设置
+      keys: [
+        {
+          key: 'chart-color',
+          // default: { type: 'Single', colors: [0, 0, 0, 1] }
+          default: null
+        },
+        {
+          key: 'symbol-size',
+          default: 10
+        },
+        {
+          key: 'guide-line-type',
+          default: 'trend'
+        },
+        {
+          key: 'formula-show',
+          default: true
+        },
+        {
+          key: 'R-squared-show',
+          default: true
+        },
+        {
+          key: 'guide-line-legend-name',
+          default: ''
+        },
+        {
+          key: 'guide-line-color',
+          default: null
+        }
+      ]
+    },
+  ],
+  basic_settings: [
+    {
+      id: 23, //标题设置框
+      keys: [
+        { key: 'title', default: '' },
+        {
+          key: 'color',
+          default: {
+            type: MULTI_SINGLE_COLORS_OPTIONS[0].type,
+            colors: MULTI_SINGLE_COLORS_OPTIONS[0].colors
+          }
+        },
+        { key: 'symbol-size', default: 8 },
+        { key: 'precision', default: 1 },
+        { key: 'custom-grid', default: false },
+        { key: 'grid-left', default: 20 },
+        { key: 'grid-top', default: 20 },
+        { key: 'y-axis-title-show', default: false },
+        { key: 'y-title', default: '' },
+        { key: 'y-label-font-size', default: 12 },
+        { key: 'y-hide-split-line', default: true },
+        { key: 'x-axis-title-show', default: false },
+        { key: 'x-title', default: '' },
+        { key: 'x-label-font-size', default: 12 },
+        { key: 'legend-show', default: true },
+        { key: 'legend-font-size', default: 12 },
+        { key: 'legend-position', default: 'top-center' },
+        { key: 'unit1', default: '' },
+        { key: 'unit2', default: '' },
+      ]
+    },
+  ]
+}
+
+export default config

+ 26 - 0
src/utils/constant/ChartConfig/SimpleChart/index.jsx

@@ -0,0 +1,26 @@
+import { NormalTable } from '../../../../components/GridModules'
+import { getTableBasicSettingConf, normalTableChangeableChartTypeOptions as changeableChartTypes } from '../utils'
+
+const config = {
+  name: '普通表格图',
+  code: 'SimpleChart',
+  comp: NormalTable,
+  ...changeableChartTypes ? {
+    changeableChartTypes
+  } : {},
+  custom_settings: [],
+  basic_settings: [
+    {
+      ...getTableBasicSettingConf({
+        changeableChartTypes
+      })
+    },
+    {
+      id: 25, //阈值设置框 thresholds
+      needConfig: true,
+      keys: [{ key: 'thresholds', default: [] }]
+    }
+  ]
+}
+
+export default config

+ 730 - 0
src/utils/constant/ChartConfig/utils.jsx

@@ -0,0 +1,730 @@
+import { IconLine, IconBar, IconTable, IconCompareSort } from '../../../components/Icons'
+
+const precisionOptions = [
+  {
+    label: '保留1位小数',
+    value: 1
+  },
+  {
+    label: '保留2位小数',
+    value: 2
+  },
+  {
+    label: '保留3位小数',
+    value: 3
+  },
+  {
+    label: '整数',
+    value: 0
+  }
+]
+
+const contrastPositionOptions = [
+  {
+    label: '右上',
+    value: 'right-top'
+  },
+  {
+    label: '左上',
+    value: 'left-top'
+  }
+]
+
+const legendPositionOptions = [
+  {
+    label: '上左',
+    value: 'top-left'
+  },
+  {
+    label: '上中',
+    value: 'top-center'
+  },
+  {
+    label: '上右',
+    value: 'top-right'
+  },
+  {
+    label: '下左',
+    value: 'bottom-left'
+  },
+  {
+    label: '下中',
+    value: 'bottom-center'
+  },
+  {
+    label: '下右',
+    value: 'bottom-right'
+  },
+]
+
+const chartTypeInLineOptions = [
+  {
+    label: '折线图',
+    value: 'line'
+  },
+  {
+    label: '柱状图',
+    value: 'bar'
+  }
+]
+
+const compareMethodOptions = [
+  {
+    label: '原始值',
+    value: 'LAST'
+  },
+  {
+    label: '加和',
+    value: 'SUM'
+  },
+  {
+    label: '平均值',
+    value: 'AVERAGE'
+  },
+  {
+    label: '最大值',
+    value: 'MAX'
+  },
+  {
+    label: '最小值',
+    value: 'MIN'
+  }
+]
+
+const tableDirectionOptions = [
+  {
+    label: '横向',
+    value: 1
+  },
+  {
+    label: '纵向',
+    value: 2
+  }
+]
+
+const getCompareSettingConf = () => {
+  return {
+    // id: 8, //对比设置
+    renderCompName: 'DTFormRender',// default use IdRender
+    props: {
+      wrapperStyle: {
+        type: 'title',
+        props: {
+          title: '对比设置'
+        }
+      }
+    },
+    keys: [
+      {
+        key: 'compare-method',
+        default: 'SUM', format: 'select', props: {
+          title: '对比方式',
+          options: compareMethodOptions,
+          width: 132
+        }
+      },
+      {
+        key: 'compare-origin-name',
+        default: '',
+        format: 'input',
+        props: {
+          title: '原数据名称'
+        }
+      },
+      {
+        key: 'compare-show',
+        default: false, format: 'switch', props: {
+          title: '显示对比数据'
+        }
+      },
+      {
+        key: 'compare-name',
+        default: '',
+        format: 'input',
+        props: {
+          title: '对比数据名称'
+        }
+      },
+      {
+        key: 'compare-delta-show',
+        default: false, format: 'switch', props: {
+          title: '显示对比数据差值'
+        }
+      },
+      {
+        key: 'compare-delta-name',
+        default: '',
+        format: 'input',
+        props: {
+          title: '对比数据差值名称'
+        }
+      },
+      {
+        key: 'compare-rate-show',
+        default: false, format: 'switch', props: {
+          title: '显示对比数据差值比例'
+        }
+      },
+      {
+        key: 'compare-rate-name',
+        default: '',
+        format: 'input',
+        props: {
+          title: '对比数据差值比例名称'
+        }
+      }
+    ]
+  }
+}
+
+const getLegendSettingConf = () => {
+  return {
+    // id: 4, //图例设置框
+    renderCompName: 'DTFormRender',// default use IdRender
+    props: {
+      wrapperStyle: {
+        type: 'foldable',
+        props: {
+          title: '图例'
+        }
+      }
+    },
+    keys: [
+      {
+        key: 'legend-show', default: true, format: 'switch', props: {
+          title: '显示图例'
+        }
+      },
+      {
+        key: 'legend-font-size', default: 12, format: 'number-input', props: {
+          title: '字体大小'
+        }
+      },
+      {
+        key: 'legend-position', default: 'top-center', format: 'select', props: {
+          title: '图例位置',
+          options: legendPositionOptions
+        }
+      }
+    ]
+  }
+}
+
+const getTableBasicSettingConf = (conf) => {
+  const { changeableChartTypes } = conf ?? {}
+  return {
+    // id: 24, //普通表格图基础设置
+    renderCompName: 'DTFormRender',// default use IdRender
+    props: {
+      wrapperStyle: {
+        type: 'padding'
+      }
+    },
+    keys: [
+      {
+        key: 'title',
+        default: '', format: 'input', props: {
+          title: '标题'
+        }
+      },
+      {
+        key: 'precision',
+        default: 1, format: 'select', props: {
+          title: '精度配置',
+          options: precisionOptions
+        }
+      },
+      ...changeableChartTypes ? [{
+        key: 'changeable-chart-types', default: [], format: 'select', props: {
+          title: '图表切换',
+          options: changeableChartTypes,
+          width: 240,
+          allowClear: true,
+          mode: 'multiple'
+        }
+      }] : [],
+      {
+        key: 'use-custom-lineheight', default: false, format: 'switch', props: {
+          title: '自定义行高'
+        }
+      },
+      {
+        key: ['col-width', 'line-height'], default: [20, 20], format: 'two-number-input', props: {
+          title: ['最小列宽', '最小行高'],
+          width: 90
+        }
+      },
+      {
+        key: 'table-direction', default: 1, format: 'select', props: {
+          title: '横向/纵向',
+          options: tableDirectionOptions
+        }
+      },
+      {
+        key: 'table-color', default: null, format: 'single-color-select', props: {
+          title: '表格字体颜色',
+          placeholder: ''
+        }
+      },
+      {
+        key: 'first-row-color', default: null, format: 'single-color-select', props: {
+          title: '首行',
+          placeholder: ''
+        }
+      },
+      {
+        key: 'first-col-color', default: null, format: 'single-color-select', props: {
+          title: '首列',
+          placeholder: ''
+        }
+      }
+    ]
+  }
+}
+
+const getYAxisSettingConf = () => {
+  return {
+    id: 2, //Y轴线设置框
+    keys: [
+      { key: 'y-axis-title-show', default: true },
+      { key: 'y-hide-split-left', default: true },
+      { key: 'y-label-font-size', default: 12 },
+      { key: 'left-y-show', default: true },
+      { key: 'left-y-title', default: '' },
+      { key: 'left-y-max', default: null },
+      { key: 'left-y-min', default: null },
+      { key: 'right-y-show', default: true },
+      { key: 'right-y-title', default: '' },
+      { key: 'right-y-max', default: null },
+      { key: 'right-y-min', default: null },
+      { key: 'y-hide-split-right', default: true }
+    ]
+  }
+}
+
+const lineChartChangeableChartTypeOptions = [
+  {
+    label: '柱状图',
+    value: 1,
+    icon: IconBar,
+    getConfig: (chartTypes, info) => {
+      const barConfig = (chartTypes ?? []).find(({ value }) => value === 2) ?? null
+      if (barConfig) {
+        let pInfo = info
+        // console.log(barConfig, pInfo)
+        const { defaultBasicSettings, defaultCustomSettings } = barConfig
+        if (pInfo) {
+          pInfo = {
+            ...pInfo,
+            chart_type: 2,
+            ...(!!(pInfo?.config) ? {
+              config: {
+                ...(pInfo?.config),
+                basic_settings: {
+                  ...(pInfo?.config?.basic_settings ?? {}),
+                  ...(defaultBasicSettings ?? {}),
+                  ...(() => {
+                    if (pInfo?.config?.basic_settings) {
+                      // 保留属性
+                      // 精度、色系、轴距、对比、XY轴、阈值跟随折线图
+                      // +图例
+
+                      //  阈值显示开启
+                      // if (!('thresholds' in pInfo.config.basic_settings)) {
+                      //   pInfo.config.basic_settings['thresholds'] = pInfo.config.basic_settings?.['thresholds-v2'] ?? []
+                      // }
+                      // (pInfo.config.basic_settings?.['thresholds'] ?? []).map(item => { 
+                      //   item.highlight = true;
+                      //   return item; 
+                      // })
+
+                      const remainKeys = [
+                        'precision',
+                        'color',
+                        'custom-grid', 'grid-left', 'grid-top',
+                        'compare-list-show', 'compare-font-size', 'compare-list-position',
+                        'x-axis-title-show', 'x-title', 'x-label-font-size',
+                        'y-axis-title-show', 'y-label-font-size',
+                        'left-y-show', 'left-y-title', 'left-y-max', 'left-y-min', 'y-hide-split-left',
+                        'right-y-show', 'right-y-title', 'right-y-max', 'right-y-min', 'y-hide-split-right',
+                        'thresholds',
+                        'legend-show', 'legend-font-size', 'legend-position'
+                      ]
+                      const transKeys = {}
+                      return remainKeys.reduce((pV, cV) => {
+                        const targetKey = transKeys[cV] ?? cV
+                        pV[targetKey] = pInfo?.config?.basic_settings?.[cV]
+                        return pV
+                      }, {})
+                    }
+                    return {}
+                  })()
+                },
+                ...(!!(pInfo?.config?.points) ? {
+                  points: (pInfo?.config?.points ?? [])
+                    .map(item => ({
+                      ...item,
+                      custom_settings: {
+                        ...(item?.custom_settings ?? {}),
+                        ...(defaultCustomSettings ?? {}),
+                        ...(() => {
+                          if (item?.custom_settings) {
+                            // 保留属性
+                            // 颜色、对比跟随折线图
+                            const remainKeys = [
+                              'chart-color',
+                              'compare-method', 'compare-origin-name', 'compare-show', 'compare-name', 'compare-delta-show', 'compare-delta-name', 'compare-rate-show', 'compare-rate-name'
+                            ]
+                            const transKeys = {}
+                            return remainKeys.reduce((pV, cV) => {
+                              const targetKey = transKeys[cV] ?? cV
+                              pV[targetKey] = item?.custom_settings?.[cV]
+                              return pV
+                            }, {})
+                          }
+                          return {}
+                        })()
+                      }
+                    }))
+                } : {})
+              }
+            } : {})
+          }
+        }
+        return {
+          config: barConfig,
+          props: {
+            tableView: false
+          },
+          pInfo
+        }
+      } else {
+        return null
+      }
+    }
+  },
+  {
+    label: '表视图',
+    value: 2,
+    icon: IconTable,
+    getConfig: (chartTypes, info) => {
+      const config = (chartTypes ?? []).find(({ value }) => value === 1) ?? null
+      if (config) {
+        return {
+          config,
+          props: {
+            tableView: true
+          },
+          pInfo: info
+        }
+      } else {
+        return null
+      }
+    }
+  }
+]
+
+const barChartChangeableChartTypeOptions = [
+  {
+    label: '折线图',
+    value: 1,
+    icon: IconLine,
+    getConfig: (chartTypes, info) => {
+      const config = (chartTypes ?? []).find(({ value }) => value === 1) ?? null
+      if (config) {
+        let pInfo = info
+        // console.log(barConfig, pInfo)
+        const { defaultBasicSettings, defaultCustomSettings } = config
+        if (pInfo) {
+          pInfo = {
+            ...pInfo,
+            chart_type: 1,
+            ...(!!(pInfo?.config) ? {
+              config: {
+                ...(pInfo?.config),
+                basic_settings: {
+                  ...(pInfo?.config?.basic_settings ?? {}),
+                  ...(defaultBasicSettings ?? {}),
+                  ...(() => {
+                    if (pInfo?.config?.basic_settings) {
+                      // 保留属性
+                      // 精度、色系、轴距、对比、XY轴、阈值跟随折线图
+                      // +图例
+                      const remainKeys = [
+                        'precision',
+                        'color',
+                        'custom-grid', 'grid-left', 'grid-top',
+                        'compare-list-show', 'compare-font-size', 'compare-list-position',
+                        'x-axis-title-show', 'x-title', 'x-label-font-size',
+                        'y-axis-title-show', 'y-label-font-size',
+                        'left-y-show', 'left-y-title', 'left-y-max', 'left-y-min', 'y-hide-split-left',
+                        'right-y-show', 'right-y-title', 'right-y-max', 'right-y-min', 'y-hide-split-right',
+                        'thresholds',
+                        'legend-show', 'legend-font-size', 'legend-position'
+                      ]
+                      const transKeys = {}
+                      return remainKeys.reduce((pV, cV) => {
+                        const targetKey = transKeys[cV] ?? cV
+                        pV[targetKey] = pInfo?.config?.basic_settings?.[cV]
+                        return pV
+                      }, {})
+                    }
+                    return {}
+                  })()
+                },
+                ...(!!(pInfo?.config?.points) ? {
+                  points: (pInfo?.config?.points ?? [])
+                    .map(item => ({
+                      ...item,
+                      custom_settings: {
+                        ...(item?.custom_settings ?? {}),
+                        ...(defaultCustomSettings ?? {}),
+                        ...(() => {
+                          if (item?.custom_settings) {
+                            // 保留属性
+                            // 颜色、对比跟随折线图
+                            const remainKeys = [
+                              'chart-color',
+                              'compare-method', 'compare-origin-name', 'compare-show', 'compare-name', 'compare-delta-show', 'compare-delta-name', 'compare-rate-show', 'compare-rate-name'
+                            ]
+                            const transKeys = {}
+                            return remainKeys.reduce((pV, cV) => {
+                              const targetKey = transKeys[cV] ?? cV
+                              pV[targetKey] = item?.custom_settings?.[cV]
+                              return pV
+                            }, {})
+                          }
+                          return {}
+                        })()
+                      }
+                    }))
+                } : {})
+              }
+            } : {})
+          }
+        }
+        return {
+          config,
+          props: {
+            tableView: false
+          },
+          pInfo
+        }
+      } else {
+        return null
+      }
+    }
+  },
+  {
+    label: '表视图',
+    value: 2,
+    icon: IconTable,
+    getConfig: (chartTypes, info) => {
+      const config = (chartTypes ?? []).find(({ value }) => value === 2) ?? null
+      if (config) {
+        return {
+          config,
+          props: {
+            tableView: true
+          },
+          pInfo: info
+        }
+      } else {
+        return null
+      }
+    }
+  }
+]
+
+const normalTableChangeableChartTypeOptions = [
+  {
+    label: '折线图',
+    value: 1,
+    icon: IconLine,
+    getConfig: (chartTypes, info) => {
+      const config = (chartTypes ?? []).find(({ value }) => value === 1) ?? null
+      if (config) {
+        let pInfo = info
+        // console.log(barConfig, pInfo)
+        const { defaultBasicSettings, defaultCustomSettings } = config
+        if (pInfo) {
+          pInfo = {
+            ...pInfo,
+            chart_type: 1,
+            ...(!!(pInfo?.config) ? {
+              config: {
+                ...(pInfo?.config),
+                basic_settings: {
+                  ...(pInfo?.config?.basic_settings ?? {}),
+                  ...(defaultBasicSettings ?? {}),
+                  ...(() => {
+                    if (pInfo?.config?.basic_settings) {
+                      // 保留属性
+                      // 精度
+                      const remainKeys = [
+                        'precision'
+                      ]
+                      const transKeys = {}
+                      return remainKeys.reduce((pV, cV) => {
+                        const targetKey = transKeys[cV] ?? cV
+                        pV[targetKey] = pInfo?.config?.basic_settings?.[cV]
+                        return pV
+                      }, {})
+                    }
+                    return {}
+                  })()
+                },
+                ...(!!(pInfo?.config?.points) ? {
+                  points: (pInfo?.config?.points ?? []).map(item => ({ ...item, custom_settings: { ...(item?.custom_settings ?? {}), ...(defaultCustomSettings ?? {}) } }))
+                } : {})
+              }
+            } : {})
+          }
+        }
+        return {
+          config,
+          props: {
+            tableView: false
+          },
+          pInfo
+        }
+      } else {
+        return null
+      }
+    }
+  },
+  {
+    label: '柱状图',
+    value: 2,
+    icon: IconBar,
+    getConfig: (chartTypes, info) => {
+      const barConfig = (chartTypes ?? []).find(({ value }) => value === 2) ?? null
+      if (barConfig) {
+        let pInfo = info
+        // console.log(barConfig, pInfo)
+        const { defaultBasicSettings, defaultCustomSettings } = barConfig
+        if (pInfo) {
+          pInfo = {
+            ...pInfo,
+            chart_type: 2,
+            ...(!!(pInfo?.config) ? {
+              config: {
+                ...(pInfo?.config),
+                basic_settings: {
+                  ...(pInfo?.config?.basic_settings ?? {}),
+                  ...(defaultBasicSettings ?? {}),
+                  ...(() => {
+                    if (pInfo?.config?.basic_settings) {
+                      // 保留属性
+                      // 精度
+                      const remainKeys = [
+                        'precision'
+                      ]
+                      const transKeys = {}
+                      return remainKeys.reduce((pV, cV) => {
+                        const targetKey = transKeys[cV] ?? cV
+                        pV[targetKey] = pInfo?.config?.basic_settings?.[cV]
+                        return pV
+                      }, {})
+                    }
+                    return {}
+                  })()
+                },
+                ...(!!(pInfo?.config?.points) ? {
+                  points: (pInfo?.config?.points ?? []).map(item => ({ ...item, custom_settings: { ...(item?.custom_settings ?? {}), ...(defaultCustomSettings ?? {}) } }))
+                } : {})
+              }
+            } : {})
+          }
+        }
+        return {
+          config: barConfig,
+          props: {
+            tableView: false
+          },
+          pInfo
+        }
+      } else {
+        return null
+      }
+    }
+  }
+]
+
+const complexTableChangeableChartTypeOptions = [
+  {
+    label: '对比排序图',
+    value: 1,
+    icon: IconCompareSort,
+    getConfig: (chartTypes, info) => {
+      const config = (chartTypes ?? []).find(({ value }) => value === 9) ?? null
+      if (config) {
+        let pInfo = info
+        // console.log(barConfig, pInfo)
+        const { defaultBasicSettings, defaultCustomSettings } = config
+        if (pInfo) {
+          pInfo = {
+            ...pInfo,
+            chart_type: 9,
+            ...(!!(pInfo?.config) ? {
+              config: {
+                ...(pInfo?.config),
+                basic_settings: { ...(pInfo?.config?.basic_settings ?? {}), ...(defaultBasicSettings ?? {}) },
+                ...(!!(pInfo?.config?.pointClasses) ? {
+                  pointClasses: (pInfo?.config?.pointClasses ?? []).map(item => ({ ...item, custom_settings: { ...(item?.custom_settings ?? {}), ...(defaultCustomSettings ?? {}) } }))
+                } : {})
+              }
+            } : {})
+          }
+        }
+        return {
+          config,
+          pInfo
+        }
+      } else {
+        return null
+      }
+    }
+  }
+]
+
+const CompareSortChangeableChartTypeOptions = [
+  {
+    label: '表视图',
+    value: 1,
+    icon: IconTable,
+    getConfig: (chartTypes, info) => {
+      const config = (chartTypes ?? []).find(({ value }) => value === 9) ?? null
+      if (config) {
+        return {
+          config,
+          props: {
+            tableView: true
+          },
+          pInfo: info
+        }
+      } else {
+        return null
+      }
+    }
+  }
+]
+
+export {
+  CompareSortChangeableChartTypeOptions,
+  complexTableChangeableChartTypeOptions,
+  normalTableChangeableChartTypeOptions,
+  barChartChangeableChartTypeOptions,
+  lineChartChangeableChartTypeOptions,
+
+  getTableBasicSettingConf,
+  getLegendSettingConf,
+  getCompareSettingConf,
+  getYAxisSettingConf,
+
+  compareMethodOptions,
+  chartTypeInLineOptions,
+  legendPositionOptions,
+  contrastPositionOptions,
+  precisionOptions
+}

+ 194 - 0
src/utils/constant/MultiSingleColorsOptions/index.js

@@ -0,0 +1,194 @@
+const colors1 = [
+  [42, 111, 246, 1],
+  [20, 201, 201, 1],
+  [45, 166, 65, 1],
+  [250, 210, 12, 1],
+  [237, 106, 12, 1],
+  [195, 232, 47, 1],
+  [212, 42, 141, 1],
+  [112, 49, 255, 1]
+]
+// [212, 0, 0, 1]
+// [195, 232, 47, 1]
+const colors2 = [
+  [116, 170, 250, 1],
+  [94, 223, 214, 1],
+  [106, 202, 116, 1],
+  [252, 237, 99, 1],
+  [244, 168, 96, 1],
+  [221, 249, 111, 1],
+  [229, 111, 172, 1],
+  [171, 122, 255, 1]
+].reduce((pV, cV, index) => {
+  return [...pV, ...(colors1[index] ? [colors1[index]] : []), cV]
+}, [])
+// 229, 93, 83, 1
+// 221, 249, 111, 1
+const colors3 = [
+  [193, 221, 253, 1],
+  [183, 244, 236, 1],
+  [186, 237, 187, 1],
+  [254, 252, 187, 1],
+  [251, 222, 186, 1],
+  [237, 254, 168, 1],
+  [246, 189, 215, 1],
+  [221, 195, 255, 1]
+].reduce((pV, cV, index) => {
+  return [
+    ...pV,
+    ...(colors2[index * 2] ? [colors2[index * 2]] : []),
+    ...(colors2[index * 2 + 1] ? [colors2[index * 2 + 1]] : []),
+    cV
+  ]
+}, [])
+// 246, 188, 179, 1
+// 237, 254, 168, 1
+const colors1d = [
+  [80, 141, 248, 1],
+  [63, 212, 207, 1],
+  [87, 184, 100, 1],
+  [251, 224, 50, 1],
+  [241, 135, 48, 1],
+  [212, 239, 105, 1],
+  [221, 77, 156, 1],
+  [143, 87, 255, 1]
+]
+// 221, 39, 33, 1
+// 212, 239, 105, 1
+const colors2d = [
+  [80, 141, 248, 0.55],
+  [63, 212, 207, 0.55],
+  [87, 184, 100, 0.55],
+  [251, 224, 50, 0.55],
+  [241, 135, 48, 0.55],
+  [212, 239, 105, 0.55],
+  [221, 77, 156, 0.55],
+  [143, 87, 255, 0.55]
+].reduce((pV, cV, index) => {
+  return [...pV, ...(colors1d[index] ? [colors1d[index]] : []), cV]
+}, [])
+// 221, 39, 33, 0.55
+const colors3d = [
+  [80, 141, 248, 0.3],
+  [63, 212, 207, 0.3],
+  [87, 184, 100, 0.3],
+  [251, 224, 50, 0.3],
+  [241, 135, 48, 0.3],
+  [212, 239, 105, 0.3],
+  [221, 77, 156, 0.3],
+  [143, 87, 255, 0.3]
+].reduce((pV, cV, index) => {
+  return [
+    ...pV,
+    ...(colors2d[index * 2] ? [colors2d[index * 2]] : []),
+    ...(colors2d[index * 2 + 1] ? [colors2d[index * 2 + 1]] : []),
+    cV
+  ]
+}, [])
+
+const dColor1 = [
+  [42, 111, 246, 1],
+  [20, 201, 201, 1],
+  [45, 166, 65, 1],
+  [250, 210, 12, 1],
+  [241, 143, 73, 1],
+  [212, 0, 0, 1],
+  [212, 42, 141, 1],
+  [112, 49, 255, 1]
+]
+
+const dColor2 = [
+  [2, 93, 244, 1],
+  [219, 107, 207, 1],
+  [36, 152, 209, 1],
+  [187, 189, 230, 1],
+  [64, 69, 178, 1],
+  [33, 169, 122, 1],
+  [255, 116, 90, 1],
+  [0, 126, 153, 1]
+]
+
+const dColor3 = [
+  [26, 175, 139, 1],
+  [255, 69, 0, 1],
+  [246, 189, 22, 1],
+  [64, 108, 133, 1],
+  [47, 184, 252, 1],
+  [180, 15, 15, 1],
+  [255, 92, 162, 1],
+  [68, 53, 255, 1]
+]
+
+const dColor4 = [
+  [91, 143, 249, 1],
+  [97, 221, 170, 1],
+  [101, 120, 155, 1],
+  [246, 189, 22, 1],
+  [114, 98, 253, 1],
+  [120, 211, 248, 1],
+  [150, 97, 188, 1],
+  [0, 134, 133, 1]
+]
+
+const dColor5 = [
+  [252, 178, 135, 1],
+  [189, 119, 228, 1],
+  [115, 207, 130, 1],
+  [125, 204, 195, 1],
+  [129, 167, 202, 1],
+  [126, 129, 217, 1],
+  [212, 137, 191, 1],
+  [205, 113, 113, 1]
+]
+
+const MULTI_COLORS_OPTIONS = [
+  {
+    label: '8色系(浅色系)',
+    colors: colors1
+  },
+  {
+    label: '16色系(浅色系)',
+    colors: colors2
+  },
+  {
+    label: '24色系(浅色系)',
+    colors: colors3
+  },
+  {
+    label: '邻近色板',
+    colors: dColor1
+  },
+  {
+    label: '马卡龙色板',
+    colors: dColor2
+  },
+  {
+    label: '对比色板',
+    colors: dColor3
+  },
+  {
+    label: '轻快色板',
+    colors: dColor4
+  },
+  {
+    label: '淡雅色板',
+    colors: dColor5
+  },
+  {
+    label: '8色系(深色系)',
+    colors: colors1d
+  },
+  {
+    label: '16色系(深色系)',
+    colors: colors2d
+  },
+  {
+    label: '24色系(深色系)',
+    colors: colors3d
+  }
+].map(item => ({
+  ...item,
+  type: 'SeveralSingle'
+}))
+
+export { MULTI_COLORS_OPTIONS }

+ 331 - 0
src/utils/constant/index.js

@@ -0,0 +1,331 @@
+import { MULTI_COLORS_OPTIONS as MULTI_SINGLE_COLORS_OPTIONS } from './MultiSingleColorsOptions'
+import LineChartConfig from './ChartConfig/Line'
+import BarChartConfig from './ChartConfig/Bar'
+import NumericalChartConfig from './ChartConfig/Numerical'
+import RingChartConfig from './ChartConfig/Ring'
+import FrequencyHistogramChartConfig from './ChartConfig/Frequency'
+import ScatterConfig from './ChartConfig/Scatter'
+import SimpleChart from './ChartConfig/SimpleChart'
+import CompareSortBarChart from './ChartConfig/CompareSortBar'
+import ComplexChartConfig from './ChartConfig/ComplexChart'
+import SankeyChart from './ChartConfig/Sankey'
+import dayjs from 'dayjs'
+
+const rangeAndIntervalOptions = [
+  {
+    label: '小时',
+    value: 'TIME',
+    child:[
+      {
+        label: '小时',
+        value: 'TIME-TIME',
+      }
+    ]
+  },
+  {
+    label: '日',
+    value: 'DAY',
+    child:[
+      {
+        label: '小时',
+        value: 'DAY-TIME',
+      },
+      {
+        label: '日',
+        value: 'DAY-DAY',
+      }
+    ]
+  },
+  {
+    label: '周',
+    value: 'WEEK',
+    child:[
+      {
+        label: '小时',
+        value: 'WEEK-TIME',
+      },
+      {
+        label: '日',
+        value: 'WEEK-DAY',
+      },
+      {
+        label: '周',
+        value: 'WEEK-WEEK'
+      }
+    ]
+  },
+  {
+    label: '月',
+    value: 'MONTH',
+    child:[
+      {
+        label: '小时',
+        value: 'MONTH-TIME',
+      },
+      {
+        label: '日',
+        value: 'MONTH-DAY',
+      },
+      {
+        label: '周',
+        value: 'MONTH-WEEK'
+      },
+      {
+        label: '月',
+        value: 'MONTH-MONTH'
+      }
+    ]
+  },
+  {
+    label: '年',
+    value: 'YEAR',
+    child:[
+      {
+        label: '小时',
+        value: 'YEAR-TIME',
+      },
+      {
+        label: '日',
+        value: 'YEAR-DAY',
+      },
+      {
+        label: '周',
+        value: 'YEAR-WEEK'
+      },
+      {
+        label: '月',
+        value: 'YEAR-MONTH'
+      },
+      {
+        label: '年',
+        value: 'YEAR-YEAR'
+      }
+    ]
+  }
+]
+const useLATEST_OPTIONS = () => {
+  const LATEST_OPTIONS = [
+    {
+      param: '1h',
+      value: 'last-1-hours',
+      label: TranslateText(['最近1小时', 'Last 1hr']),
+    },
+    {
+      param: '8h',
+      value: 'last-8-hours',
+      label: TranslateText(['最近8小时', 'Last 8hrs']),
+    },
+    {
+      param: '16h',
+      value: 'last-16-hours',
+      label: TranslateText(['最近16小时', 'Last 16hrs']),
+    },
+    {
+      param: '24h',
+      value: 'last-24-hours',
+      label: TranslateText(['最近24小时', 'Last 24hrs']),
+    },
+    {
+      param: '48h',
+      value: 'last-48-hours',
+      label: TranslateText(['最近48小时', 'Last 48hrs']),
+    },
+    {
+      param: '72h',
+      value: 'last-72-hours',
+      label: TranslateText(['最近72小时', 'Last 72hrs']),
+    },
+    {
+      param: 'today',
+      value: 'last-1-day',
+      label: TranslateText(['当天', 'Today']),
+    }
+  ]
+  return [LATEST_OPTIONS]
+}
+const rangeOptions = [
+  {
+    label: '小时',
+    value: 'TIME',
+  },
+  {
+    label: '日',
+    value: 'DAY',
+  },
+  {
+    label: '周',
+    value: 'WEEK'
+  },
+  {
+    label: '月',
+    value: 'MONTH'
+  },
+  {
+    label: '年',
+    value: 'YEAR'
+  }
+]
+
+const pageTypes = [
+  {
+    name: '自由页面',
+    value: 1
+  },
+  {
+    name: '累计值页面',
+    value: 2
+  },
+  {
+    name: '瞬时值页面',
+    value: 3
+  }
+]
+
+const MULTI_COLORS_OPTIONS = [
+  {
+    label: '红色渐变',
+    type: 'Gradient',
+    colors: [
+      [183, 61, 102, 1],
+      [254, 142, 131, 1]
+    ]
+  },
+  {
+    label: '橙黄渐变',
+    type: 'Gradient',
+    colors: [
+      [250, 64, 13, 1],
+      [250, 206, 45, 1]
+    ]
+  },
+  {
+    label: '蓝色渐变',
+    type: 'Gradient',
+    colors: [
+      [14, 101, 234, 1],
+      [10, 196, 247, 1]
+    ]
+  },
+  {
+    label: '绿黄渐变',
+    type: 'Gradient',
+    colors: [
+      [31, 149, 61, 1],
+      [244, 241, 78, 1]
+    ]
+  },
+  {
+    label: '紫红渐变',
+    type: 'Gradient',
+    colors: [
+      [88, 8, 253, 1],
+      [160, 52, 234, 1]
+    ]
+  }
+]
+
+const chartTypes = [
+  { ...(LineChartConfig ?? {}), value: 1 },
+  { ...(BarChartConfig ?? {}), value: 2 },
+  { ...(NumericalChartConfig ?? {}), value: 3 },
+  { ...(RingChartConfig ?? {}), value: 5 },
+  { ...(FrequencyHistogramChartConfig ?? {}), value: 4 },
+  { ...(ScatterConfig ?? {}), value: 6 },
+  { ...(SimpleChart ?? {}), value: 7 },
+  { ...(ComplexChartConfig ?? {}), value: 8 },
+  { ...(CompareSortBarChart ?? {}), value: 9 },
+  { ...(SankeyChart ?? {}), value: 10 },
+]
+  .filter(({ comp }) => !!comp)
+  .map(item => {
+    const { basic_settings, custom_settings } = item
+    const [defaultBasicSettings, defaultCustomSettings] = [
+      basic_settings,
+      custom_settings
+    ].map(settings => {
+      return settings.reduce((pV, cV) => {
+        const { keys } = cV
+        return Object.assign(
+          pV,
+          keys.reduce((kPV, kCV) => {
+            return Object.assign(kPV, { [kCV.key]: kCV.default })
+          }, {})
+        )
+      }, {})
+    })
+    return Object.assign(item, { defaultBasicSettings, defaultCustomSettings })
+  })
+
+const cardTypes = [
+  {
+    name: '累计值卡片',
+    value: 2
+  },
+  {
+    name: '瞬时值卡片',
+    value: 3
+  },
+  {
+    name: '固定时间段',
+    value: 4
+  }
+]
+
+
+const createPageMethods = [
+  {
+    name: '普通创建',
+    value: 1
+  },
+  {
+    name: '模板套用',
+    value: 3
+  },
+  {
+    name: '从模板复刻',
+    value: 2
+  }
+]
+
+
+const getUnixTimeFunc = (delta, unit) => {
+  return (relativeUnix, timeStr) => {
+    // timeStr 时:分
+    let ret = null
+    if (typeof relativeUnix === 'number' && typeof timeStr === 'string' && timeStr !== '') {
+      ret = dayjs(dayjs.unix(relativeUnix).add(delta, unit).format(`YYYY-MM-DD ${timeStr}:00`)).unix()
+    }
+    return ret
+  }
+}
+
+const relativeTimeOptions = [
+  {
+    label: '前日',
+    value: 1,
+    getUnixTime: getUnixTimeFunc(-1, 'days')
+  },
+  {
+    label: '当天',
+    value: 2,
+    getUnixTime: getUnixTimeFunc(0, 'days')
+  },
+  {
+    label: '次日',
+    value: 3,
+    getUnixTime: getUnixTimeFunc(1, 'days')
+  }
+]
+
+export {
+  relativeTimeOptions,
+  createPageMethods,
+  rangeOptions,
+  useLATEST_OPTIONS,
+  rangeAndIntervalOptions,
+  pageTypes,
+  cardTypes,
+  chartTypes,
+  MULTI_COLORS_OPTIONS,
+  MULTI_SINGLE_COLORS_OPTIONS
+}

+ 451 - 0
src/utils/index.js

@@ -0,0 +1,451 @@
+import {
+  rangeOptions,
+  pageTypes,
+  cardTypes,
+  chartTypes,
+  MULTI_COLORS_OPTIONS,
+  MULTI_SINGLE_COLORS_OPTIONS,
+  rangeAndIntervalOptions,
+  useLATEST_OPTIONS
+} from './constant'
+import * as echarts from 'echarts'
+import { useEffect, useMemo, useState, useCallback } from 'react'
+import html2canvas from 'html2canvas'
+import { useSearchParams } from 'react-router-dom'
+import dayjs from 'dayjs'
+
+function childValue(arr) {
+  return (arr ?? []).reduce((acc, cur) => {
+    const childValues = (cur?.['child'] ?? []).map((item) => item?.value
+    )
+    return acc.concat(childValues)
+  }, [])
+}
+function hasValue(val) {
+  return val !== null && val != void 0
+}
+
+function debounce(fn, delay = 1000) {
+  // 实现防抖函数的核心是使用setTimeout
+  // time变量用于保存setTimeout返回的Id
+
+  let time = null
+
+  // 将回调接收的参数保存到args数组中
+  function _debounce(...args) {
+    // 如果time不为0,也就是说有定时器存在,将该定时器清除
+    if (time !== null) {
+      clearTimeout(time)
+    }
+
+    time = setTimeout(() => {
+      // 使用apply改变fn的this,同时将参数传递给fn
+      fn.apply(this, args)
+    }, delay)
+  }
+
+  // 防抖函数会返回另一个函数,该函数才是真正被调用的函数
+  return _debounce
+}
+function throttle(fn, interval = 100) {
+  //该变量用于记录上一次函数的执行事件
+  let lastTime = 0
+
+  const _throttle = function (...args) {
+    // 获取当前时间
+    const nowTime = new Date().getTime()
+
+    // cd剩余时间
+    const remainTime = nowTime - lastTime
+    // 如果剩余时间大于间隔时间,也就是说可以再次执行函数
+    if (remainTime - interval >= 0) {
+      fn.apply(this, args)
+      // 将上一次函数执行的时间设置为nowTime,这样下次才能重新进入cd
+      lastTime = nowTime
+    }
+  }
+  // 返回_throttle函数
+  return _throttle
+}
+
+const useResizeEchart = dom => {
+  useEffect(() => {
+    if (dom) {
+      const resizeObserver = new ResizeObserver(() => {
+        let instance = echarts.getInstanceByDom(dom)
+        if (instance) {
+          instance.resize()
+        }
+      })
+      resizeObserver.observe(dom)
+      return () => {
+        resizeObserver.unobserve(dom)
+      }
+    }
+  }, [dom])
+  return []
+}
+
+const useObjVals = (vals, onChange, minObjectNum = 0) => {
+  const arr = useMemo(() => {
+    return vals
+      .map((val, index) => {
+        return {
+          value: val,
+          onChange: nVal =>
+            onChange(Object.assign(vals.concat(), { [index]: nVal }))
+        }
+      })
+      .concat(
+        vals.length < minObjectNum
+          ? new Array(minObjectNum - vals.length).fill(null).map(() => {
+            return {
+              value: null,
+              onChange: () => { }
+            }
+          })
+          : []
+      )
+  }, [vals, onChange, minObjectNum])
+  return arr
+}
+
+const downloadCanvas = title => {
+  return canvas => {
+    if (canvas) {
+      let dataURL = canvas.toDataURL('image/png')
+      // dataURL = dataURL.replace('data:image/png;base64,', '')
+      const a = document.createElement('a')
+      document.body.appendChild(a)
+      a.href = dataURL
+      a.download = title
+      a.click()
+      document.body.removeChild(a)
+    }
+  }
+}
+
+const parseDom = targetDom => {
+  return html2canvas(targetDom, {
+    width: targetDom.offsetWidth, //画布的宽
+    height: targetDom.offsetHeight, //画布的高
+    scale: 1, //处理模糊问题
+    useCORS: true //开启跨域,这个是必须的
+    //scrollX:0,//图片x轴的位置
+    //scrollY:0,//图片Y轴的位置
+    //x:0,//x轴的偏移量
+    //Y:0//Y轴的便宜量
+  })
+}
+
+function downloadDomImg(targetDom, title) {
+  if (targetDom) {
+    parseDom(targetDom).then(downloadCanvas(title))
+  }
+}
+
+const usePopupContainer = () => {
+  const [props, setProps] = useState(null)
+  const refFunc = useCallback(d => {
+    setProps(
+      d
+        ? {
+          getPopupContainer: () => d
+        }
+        : null
+    )
+  }, [])
+  return [refFunc, props]
+}
+
+const useDomRect = dom => {
+  const [rect, setRect] = useState(null)
+  useEffect(() => {
+    if (dom) {
+      const resizeObserver = new ResizeObserver(
+        throttle(entries => {
+          entries.forEach(entry => {
+            // console.log('大小位置', entry.contentRect);
+            setRect({
+              height: entry?.contentRect?.height ?? 0,
+              width: entry?.contentRect?.width ?? 0
+            })
+          })
+        })
+      )
+      resizeObserver.observe(dom)
+      return () => {
+        resizeObserver.unobserve(dom)
+      }
+    }
+  }, [dom])
+  return [rect]
+}
+
+const useTableBodyRect = d => {
+  const headD = useMemo(() => {
+    if (d) {
+      return d.querySelector('.ant-table-header') ?? null
+    }
+    return null
+  }, [d])
+  const [headerRect] = useDomRect(headD)
+  const deltaHeight = useMemo(() => {
+    return headerRect?.height ?? 0
+  }, [headerRect])
+  const [wrapperRect] = useDomRect(d)
+  const pRect = useMemo(() => {
+    if (wrapperRect && typeof deltaHeight === 'number' && !isNaN(deltaHeight)) {
+      let nHeight = (wrapperRect?.height ?? 0) - deltaHeight
+      if (nHeight < 0) {
+        nHeight = 0
+      }
+      return {
+        ...wrapperRect,
+        height: nHeight
+      }
+    }
+    return null
+  }, [deltaHeight, wrapperRect])
+  return [pRect]
+}
+
+const handleNan = (val, config, defaultRet = '-') => {
+  if (typeof val === 'number') {
+    const precision = config?.basic_settings?.precision ?? 1
+    return isNaN(val) ? defaultRet : val.toLocaleString('zh-cn', { 'minimumFractionDigits': precision, 'maximumFractionDigits': precision })
+  }
+  return defaultRet
+}
+
+const fetchVariablesFromStr = str => {
+  let ret = []
+  if (typeof str === 'string' && str !== '') {
+    const reg_g = /\$\{(.+?)\}/g;
+    let result = null
+    do {
+      result = reg_g.exec(str);
+      // console.log("result=", result);
+      result && ret.push(result[1]);
+    } while (result)
+  }
+  return ret
+}
+
+const fetchVariablesFromInfo = info => {
+  let ret = []
+  if ([1, 2, 3, 4, 5, 7].indexOf(info?.chart_type) !== -1) {
+    const pointIds = (info?.config?.points ?? []).map(({ point_id }) => point_id)
+    for (let id of pointIds) {
+      ret.push(...fetchVariablesFromStr(id))
+    }
+  } else { }
+  ret = [...new Set(ret)].filter(str => typeof str === 'string' && str !== '')
+  return ret
+}
+
+function postDownloadFile(url, params, method='post') {
+  let form = document.createElement('form');
+  form.setAttribute('style', 'display:none;');
+  form.setAttribute('method', method);
+  form.setAttribute('action', process.env.REACT_APP_BASE_URL + url);
+
+  Object.keys(params).forEach((key) => {
+    if (Array.isArray(params[key])) {
+      // value_list = []
+      params[key].forEach((value) => {
+        let input_item = document.createElement('input');
+        input_item.name = key;
+        input_item.value = value;
+        form.appendChild(input_item);
+      });
+    } else {
+      let input_item = document.createElement('input');
+      input_item.name = key;
+      input_item.value = params[key];
+      form.appendChild(input_item);
+    }
+  });
+
+  document.body.appendChild(form);
+  // let windowName = 'Download(' + (new Date().getTime()) + ')';
+  // let w = window.open('', windowName);
+  // '_blank'; 打开新页面,注释则不打开新页面
+  form.target = '_blank';
+  form.submit();
+  form.remove();
+  // w.close();
+}
+
+const useUrlTs = () => {
+  const [searchParams] = useSearchParams()
+  const ts = useMemo(() => {
+    const t = searchParams.get('ts')
+    let ret = null
+    try {
+      const mT = dayjs.unix(parseInt(t))
+      if (mT.isValid()) {
+        ret = mT.unix()
+      }
+    } catch (e) { }
+    return ret
+  }, [searchParams])
+  return [ts]
+}
+
+const tickMap = {
+  'minute': {
+    formatStr: 'YYYY-MM-DD HH:mm:00',
+    waitFunc: mTime => {
+      const mS = cCurr.millisecond()
+      if (mS === 0) {
+      }
+    }
+  }
+}
+
+const useSystemTick = unit => {
+  // 跟随系统时间tick,暂时做一版 分钟tick
+  // "YYYY-MM-DD HH:mm:00"
+  const [tickInfo, setTickInfo] = useState(null)
+  useEffect(() => {
+    let myTimeout = null
+    let prev = ''
+    const curr = dayjs()
+    if (tickInfo) {
+      prev = tickInfo?.timeStr ?? ''
+    } else {
+      // 无参考时间以当前为准
+      prev = curr.format("YYYY-MM-DD HH:mm:00")
+    }
+    const currTimeStr = curr.format("YYYY-MM-DD HH:mm:00")
+    if (currTimeStr !== prev) {
+      setTickInfo({
+        type: 'SYSTEM_MINUTE_CHANGE',
+        timeStr: currTimeStr
+      })
+    } else {
+      let cCurr = curr
+      const func = () => {
+        const cCurrStr = cCurr.format("YYYY-MM-DD HH:mm:00")
+        // console.log(cCurrStr)
+        if (cCurrStr !== prev) {
+          setTickInfo({
+            type: 'SYSTEM_MINUTE_CHANGE',
+            timeStr: cCurrStr
+          })
+        } else {
+          myTimeout = setTimeout(func, 1000 - cCurr.millisecond())
+          cCurr = dayjs()
+        }
+      }
+      func()
+    }
+
+    return () => {
+      if (myTimeout) {
+        clearTimeout(myTimeout)
+      }
+    }
+  }, [tickInfo])
+  return [tickInfo]
+}
+
+function setComma(num){
+
+  if(num===undefined||num===null){
+    return ''
+  }
+
+  let str = String(num)
+  let str2 = []
+  let suffix = ''
+  let isNegative = false
+
+  if(str.indexOf('-')===0){
+    isNegative = true
+  }
+
+  if(str.indexOf('.')!==-1){
+    str2 = str.split('.')
+    suffix = '.'+str2[1]
+  }else if(str.indexOf(' ')!==-1){
+    str2 = str.split(' ')
+    suffix = ' '+str2[1]
+  }else{
+    str2 = [str,'']
+  }
+
+  let str3 = str2[0]
+  if(isNegative){
+    str3 = str2[0].substring(1)
+  }
+
+  if(str3.length>3){
+    str3 = str3.split('').reverse()
+    let newNum = 0
+    for(var i = 0;i < str3.length;i++){
+      if(newNum!==0&&newNum%3===0){
+        str3.splice(i,0,',')
+        i++
+      }
+      newNum++
+    }
+    str3 = str3.reverse().join('')
+  }
+
+  if(isNegative){
+    return '-'+str3+suffix
+  }
+
+  return str3+suffix
+}
+
+function RGBToHex(rgba) {
+  const [r, g, b, o] = rgba
+  const hex = `#${(
+    ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1) +
+    (o < 1 && o >= 0
+      ? ((1 << 8) + Math.floor(o * 255)).toString(16).slice(1)
+      : '')
+  ).toUpperCase()}`
+  return hex
+}
+
+function hexToRgb(hexValue) {
+  let a = 1
+  if (hexValue.length > 7) {
+    a = Math.ceil(10000 * parseInt(hexValue.slice(7), 16) / 255) / 10000;
+  }
+  return { r: parseInt(hexValue.slice(1, 3), 16), g: parseInt(hexValue.slice(3, 5), 16), b: parseInt(hexValue.slice(5, 7), 16), a };
+}
+
+
+
+export {
+  useSystemTick,
+  useUrlTs,
+  rangeOptions,
+  rangeAndIntervalOptions,
+  useLATEST_OPTIONS,
+  pageTypes,
+  cardTypes,
+  chartTypes,
+  MULTI_COLORS_OPTIONS,
+  MULTI_SINGLE_COLORS_OPTIONS,
+  useResizeEchart,
+  useObjVals,
+  downloadDomImg,
+  usePopupContainer,
+  useDomRect,
+  useTableBodyRect,
+  handleNan,
+  fetchVariablesFromInfo,
+  fetchVariablesFromStr,
+  postDownloadFile,
+  setComma,
+  RGBToHex,
+  hexToRgb,
+  childValue,
+  hasValue
+}

+ 11 - 0
src/utils/index2.js

@@ -0,0 +1,11 @@
+const mapRelaStage = {
+    gt: 1,
+    ge: 2,
+    eq: 3,
+    lt: 4,
+    le: 5
+}
+
+export {
+    mapRelaStage
+}

+ 88 - 0
src/utils/request.js

@@ -0,0 +1,88 @@
+import { message } from 'antd'
+import axios from 'axios' // 引入axios
+// import { getToken } from '@/utils/auth'
+
+const baseURL = process.env.REACT_APP_BASE_URL
+const tokenName =  'token'
+
+const service = axios.create({
+  baseURL
+  // timeout: 99999
+})
+
+// request拦截器
+service.interceptors.request.use(
+  config => {
+    if (!config.headers) {
+      config.headers = {};
+    }
+    // config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
+
+    // config.headers.Authorization = `${localStorage.getItem(tokenName)}`;
+    return config
+  },
+  error => {
+    // Do something with request error
+    console.log(error) // for debug
+    throw error
+  }
+)
+
+const logout = () => {
+  // 如果判断在iframe内,仅向iframe外部通知
+  if (window.self !== window.top) {
+    // console.log('在iframe中');
+    if (window.parent) {
+      window.parent.postMessage({ type: 'logout' }, '*');
+    }
+  } else {
+    window.postMessage({ type: 'logout' }, '*');
+  };
+}
+
+// response 拦截器
+service.interceptors.response.use(
+  response => {
+    /**
+     * code为非0是抛错 可结合自己业务进行修改
+     */
+    const res = response?.data
+
+    if (response.status === 401 || res?.errcode === 401) {
+      message.warning('权限校验失败!')
+      return;
+    } else if (response.status === 440) {
+      logout()
+      return;
+    }else if(res?.type==='application/msexcel'||res?.type==='application/zip'){
+        return response
+    }
+    // 17: token失效
+    if (res?.state == 17) {
+      logout()
+      return;
+    }
+
+    return res
+  },
+  error => {
+    console.log(`err${error}`) // for debug
+    if (error?.response?.status === 440) {
+      logout();
+      return;
+    }
+    if (error?.response?.status === 401) {
+      message.warning('权限校验失败!')
+      return;
+    }
+    // Message({
+    //   message: error.message,
+    //   type: 'error',
+    //   duration: 5 * 1000
+    // })
+    return Promise.reject(new Error('网络异常,请联系管理员'));
+  }
+)
+
+export default service
+export { baseURL }

+ 12 - 0
src/utils/store/index.js

@@ -0,0 +1,12 @@
+import { configureStore } from "@reduxjs/toolkit";
+import counterReducer from './slice'
+ 
+ const store=configureStore({
+  reducer:{
+       counter:counterReducer
+  }
+  // middleware: [],
+  // 启用Redux DevTools,默认true
+  // devTools: true,
+})
+export default store

+ 45 - 0
src/utils/store/slice.js

@@ -0,0 +1,45 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+const initialState = {
+  value: 0,
+  config: {},
+  isFullScreen: false,
+}
+export const counterSlice = createSlice({
+  /**
+   * createSlice接收的:一个字符串名来标识该片也就是name,
+   * 一个初始状态值initialState,
+   * 以及多个reducer函数。
+   * 并且为每个 reducer 函数生成动作创建器。
+   *  */
+  name: 'counter',
+  initialState, //相当于state
+  reducers: { //注意这里加了S,后面导出没加S
+    increment: (state) => {
+      /** 
+        * Redux Toolkit 允许我们在还原器中编写“可变的(mutable)”逻辑。
+        * 它实际上并没有改变状态,因为它使用 Immer 库,
+        * 它将检测对"draft state" 的更改,并根据这些更改生成
+        * 一个全新的不可变状态
+        */
+      state.value += 1
+    },
+    decrement: (state) => {
+      state.value -= 1
+    },
+    incrementByAmount: (state, action) => {
+      state.value += action.payload
+    },
+    setStoreConfig: (state, action) => {
+      state.config = action.payload
+    },
+    setFullScreenConfig: (state, action) => {
+      state.isFullScreen = action.payload
+    }
+  },
+
+})
+// 为每个 reducer 函数生成动作创建器(Action creators)
+// 相当action
+export const { increment, decrement, incrementByAmount, setStoreConfig, setFullScreenConfig } = counterSlice.actions
+export default counterSlice.reducer

+ 25 - 0
src/utils/theme/ThemeWrapper.jsx

@@ -0,0 +1,25 @@
+import { useTheme, useLocalStorageThemeName } from './index.js';
+import { useEffect } from 'react';
+
+export default (props) => {
+  const { setThemeName } = props
+  const [] = useTheme();
+  // const [themeName] = useThemeKey()
+  const [themeNameInLC] = useLocalStorageThemeName()
+
+  // useEffect(() => {
+  //   setThemeName(themeName)
+  // }, [themeName])
+
+  useEffect(() => {
+    setThemeName(themeNameInLC)
+  }, [themeNameInLC])
+  console.log(11,props)
+  if(props.children){
+    return props.children;
+  }else{
+    return (
+        <>加载中...</>
+    );
+  }
+};

+ 87 - 0
src/utils/theme/customStyle.less

@@ -0,0 +1,87 @@
+:root {
+
+  .ant-table-cell {
+    &:first-child {
+      padding-left: 20px !important;
+
+      &.ant-table-selection-column {
+        padding-left: 8px !important;
+      }
+    }
+  }
+
+  .ant-table-measure-row{
+    td{
+      padding: 0 !important;
+    }
+  }
+
+  .ant-table-cell:before {
+    width: 0px !important;
+  }
+
+  //滚动条样式修改
+  .ant-table-body,
+  .ant-table-content,
+  .scroll_bar_restyle {
+    //hardcode
+
+    //滚动条背景
+    &::-webkit-scrollbar {
+      height: 5px;
+      width: 5px;
+      // border-radius: 5px;
+    }
+
+    // //滚动条前景
+    &::-webkit-scrollbar-thumb {
+      background-color: var(--dt-bg-color4);
+      border-radius: 10px;
+    }
+
+    &::-webkit-scrollbar-track {
+      background-color: transparent;
+      // border-radius: 5px;
+    }
+  }
+
+  .fr-container .fr-label-title {
+    color: var(--dt-text-color1);
+  }
+
+  .ant-tooltip-inner {
+    background: var(--dt-fill-color3) !important;
+    color: var(--dt-text-color1) !important;
+  }
+
+  .ant-tooltip-arrow,
+  .ant-tooltip-arrow .ant-tooltip-arrow-content {
+    --antd-arrow-background-color: var(--dt-fill-color3) !important;
+  }
+
+  // Hover with in range
+  .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover::before,
+  .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start::before,
+  .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end::before,
+  .ant-picker-cell-in-view.ant-picker-cell-range-start.ant-picker-cell-range-hover::before,
+  .ant-picker-cell-in-view.ant-picker-cell-range-end.ant-picker-cell-range-hover::before,
+  .ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single).ant-picker-cell-range-hover-start::before,
+  .ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single).ant-picker-cell-range-hover-end::before,
+  .ant-picker-panel> :not(.ant-picker-date-panel) .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start::before,
+  .ant-picker-panel> :not(.ant-picker-date-panel) .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end::before {
+    background: var(--dt-primary-color6);
+  }
+
+  .ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start .ant-picker-cell-inner,
+  .ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end .ant-picker-cell-inner {
+    // background: var(--dt-primary-color6);
+
+    &::after {
+      background: var(--dt-primary-color6);
+    }
+  }
+
+  .dtShadow {
+    box-shadow: 1px 2px 5px 2px var(--dt-shadow-color1);
+  }
+}

+ 247 - 0
src/utils/theme/index.js

@@ -0,0 +1,247 @@
+import { useEffect, useMemo, useState } from 'react';
+import { useSearchParams } from 'react-router-dom';
+import { myTheme as blueTheme } from './style/blue'
+import { myTheme as greenTheme } from './style/green'
+import { myTheme as darkBlueTheme } from './style/dark'
+
+function TranslateText(arr) {
+  const dtLanguage = localStorage.getItem('dtLanguage')
+  if (!Array.isArray(arr)) {
+    return arr
+  }
+
+  if (arr.length < 2 && arr.length > 0) {
+    return arr?.[0]
+  }
+
+  if (dtLanguage === 'en') {
+    return arr?.[1]
+  }
+
+  return arr?.[0]
+}
+if(!window.TranslateText){
+  window.TranslateText = TranslateText;
+}
+
+const useThemeKey = () => {
+  const [searchParams] = useSearchParams();
+  const theme = useMemo(() => {
+    return (
+      searchParams.get('theme') ??
+      localStorage.getItem('--dt-theme') ??
+      'dark'
+    );
+  }, [searchParams]);
+  return [theme];
+};
+
+const isDarkTheme = (themeKey) => {
+  if ((themeKey ?? '').indexOf('dark') !== -1) {
+    return 1
+  }
+  return 0
+}
+
+const antdTokenCorRes = {
+  colorBgBase: "--dt-fill-color3",
+  "colorInfo": "--dt-primary-color1",
+  "colorPrimary": "--dt-primary-color1",
+  "colorPrimaryBg": "--dt-primary-color7",
+  "colorPrimaryBgHover": "--dt-primary-color4",
+  "colorPrimaryBorder": "--dt-primary-color1",
+  "colorPrimaryBorderHover": "--dt-primary-color2",
+  "colorPrimaryHover": "--dt-primary-color2",
+  "colorPrimaryActive": "--dt-primary-color3",
+  "colorPrimaryTextHover": "--dt-primary-color2",
+  "colorPrimaryTextActive": "--dt-primary-color3",
+  "colorSuccess": "--dt-success-color1",
+  "colorSuccessBg": "--dt-success-color6",
+  "colorSuccessBgHover": "--dt-success-color4",
+  "colorSuccessBorder": "--dt-success-color1",
+  "colorSuccessBorderHover": "--dt-success-color2",
+  "colorSuccessHover": "--dt-success-color2",
+  "colorSuccessActive": "--dt-success-color3",
+  "colorSuccessTextHover": "--dt-success-color2",
+  "colorSuccessTextActive": "--dt-success-color3",
+  "colorWarning": "--dt-warning-color1",
+  "colorWarningBg": "--dt-warning-color6",
+  "colorWarningBgHover": "--dt-warning-color5",
+  "colorWarningBorder": "--dt-warning-color1",
+  "colorWarningBorderHover": "--dt-warning-color2",
+  "colorWarningHover": "--dt-warning-color2",
+  "colorWarningActive": "--dt-warning-color3",
+  "colorWarningTextActive": "--dt-warning-color3",
+  "colorWarningText": "--dt-warning-color1",
+  "colorWarningTextHover": "--dt-warning-color2",
+  "colorError": "--dt-error-color1",
+  "colorErrorBg": "--dt-error-color6",
+  "colorErrorBgHover": "--dt-error-color5",
+  "colorErrorBorder": "--dt-error-color1",
+  "colorErrorBorderHover": "--dt-error-color2",
+  "colorErrorHover": "--dt-error-color2",
+  "colorErrorActive": "--dt-error-color3",
+  "colorErrorTextActive": "--dt-error-color3",
+  "colorErrorTextHover": "--dt-error-color2",
+  "colorTextBase": "--dt-text-color1",
+  "colorText": "--dt-text-color1",
+  "colorTextSecondary": "--dt-text-color2",
+  "colorTextTertiary": "--dt-text-color3",
+  "colorTextQuaternary": "--dt-text-color4",
+  "colorBorder": "--dt-line-color2",
+  "colorBorderSecondary": "--dt-line-color1",
+  "colorFill": "--dt-fill-color2",
+  "colorFillSecondary": "--dt-fill-color5",
+  "colorFillTertiary": "--dt-line-color1",
+  "colorFillQuaternary": "--dt-fill-color1",
+  "colorBgLayout": "--dt-fill-color1",
+  borderRadius: 6,
+  components: {
+    "Button": {
+      // "colorTextDisabled": "#ffffff",
+      // "colorBgContainerDisabled": "--dt-primary-color5",
+      // "colorBorder": "--dt-primary-color5",
+      controlOutlineWidth: 0,
+    },
+    "Menu": {
+      "colorSubItemBg": "--dt-fill-color3",
+      "colorBgTextHover": "--dt-fill-color5",
+      "colorItemBgHover": "--dt-line-color1"
+    },
+    "Steps": {
+      "colorBorderSecondary": "--dt-line-color2",
+      "colorFillContent": "--dt-line-color1",
+      "colorError": "--dt-error-color1"
+    },
+    "Segmented": {
+      "colorFillSecondary": "--dt-fill-color5",
+      "colorTextDisabled": "--dt-text-color4",
+      "colorTextLabel": "--dt-text-color2",
+      "colorText": "--dt-text-color1"
+    },
+    "Select": {
+      "colorIcon": "--dt-text-color2",
+      "colorFillSecondary": "--dt-line-color1",
+      "colorText": "--dt-text-color1"
+    },
+    "Pagination": {
+      "colorBgTextHover": "--dt-line-color1"
+    },
+    "Checkbox": {
+      "colorBorder": "--dt-line-color2"
+    },
+    "Table": {
+      "controlItemBgActive": "transparent",
+      "controlItemBgActiveHover": "transparent",
+    },
+  }
+}
+
+export function getAntdToken(theme) {
+
+  function recur(antdTokenCorRes) {
+    const colors = Object.keys(antdTokenCorRes).map(key => {
+      const tokenName = antdTokenCorRes[key]
+      if (key.indexOf('Width') !== -1 || key.indexOf('Radius') !== -1) {
+        return { [key]: tokenName };
+      }
+      return { [key]: themeKeys(theme)?.[tokenName]?.[isDarkTheme(theme)] ?? '' };
+    })
+    return colors
+  }
+
+  const colors = recur(antdTokenCorRes)
+
+  // 各组件的遍历
+  const components = antdTokenCorRes?.components
+  const componentsToken = Object.keys(components ?? {}).map(item => {
+    // console.log(item, components[item])
+    let colors = recur(components[item])
+    return { [item]: Object.assign(...colors) }
+  })
+
+  const token = Object.assign(...colors)
+  const comp = Object.assign(...componentsToken)
+  const res = { token: token, components: comp }
+  console.log(res)
+  return res;
+}
+
+const themeKeys = (themeKey) => {
+  if (themeKey === 'light' || themeKey === 'dark') {
+    return blueTheme
+  }
+  else if (themeKey === 'green' || themeKey === 'darkGreen') {
+    return greenTheme
+  }
+  else if (themeKey === 'darkBlue') {
+    return darkBlueTheme
+  }
+  return blueTheme
+}
+
+const useTheme = () => {
+  // const [theme] = useThemeKey();
+  const [themeName] = useLocalStorageThemeName();
+
+  // useEffect(() => {
+  //   changeTheme(theme);
+  //   localStorage.setItem('--dt-theme', theme);
+  // }, [theme]);
+
+  useEffect(() => {
+    changeTheme(themeName);
+    localStorage.setItem('--dt-theme', themeName);
+    // console.log(themeName, 'useEffect')
+  }, [themeName])
+
+  return [themeName];
+};
+
+const useColors = (colors) => {
+  const theme = localStorage.getItem('--dt-theme') ?? process.env.REACT_APP_THEME ?? 'dark'
+  const colorsStr = useMemo(() => {
+    return JSON.stringify(colors ?? []);
+  }, [colors]);
+  const pColors = useMemo(() => {
+    return JSON.parse(colorsStr);
+  }, [colorsStr]);
+  let [retColors] = useVariable(theme, pColors);
+  return [retColors];
+};
+
+export function getVariable(key) {
+  const theme = localStorage.getItem('--dt-theme');
+  return themeKeys(theme)[key][isDarkTheme(theme)];
+}
+
+export function useVariable(theme, keys) {
+  const colors = useMemo(() => {
+    return (keys ?? []).map((key) => themeKeys(theme)?.[key]?.[isDarkTheme(theme)] ?? '');
+  }, [theme, keys]);
+  return [colors];
+}
+
+export function changeTheme(theme) {
+  const myTheme = themeKeys(theme)
+  Object.keys(myTheme).forEach((key) => {
+    const val = myTheme[key][isDarkTheme(theme)];
+    if (typeof val === 'string' && val !== '') {
+      document.documentElement.style.setProperty(key, val);
+    }
+  });
+}
+
+const useLocalStorageThemeName = () => {
+  const ThemeName = useMemo(() => {
+    return (
+      // isChange ??
+      localStorage.getItem('--dt-theme') ??
+        process.env.REACT_APP_THEME ??
+      'dark'
+    )
+  }, [localStorage.getItem('--dt-theme')])
+  return [ThemeName]
+}
+
+export { useTheme, useColors, useLocalStorageThemeName };

+ 60 - 0
src/utils/theme/style/blue.js

@@ -0,0 +1,60 @@
+//蓝色主题
+// light蓝 dark蓝
+import { useMemo } from 'react';
+
+const myTheme = {
+  '--dt-primary-color1': ['#2a6ff6', '#508df8'],
+  '--dt-primary-color2': ['#558cf8', '#4387ff'],
+  '--dt-primary-color3': ['#2259c5', '#73a4f9'],
+  '--dt-primary-color4': ['#709FF9', 'rgba(80, 141, 248, 0.4)'],
+  '--dt-primary-color5': ['#9DBDFB', 'rgba(80, 141, 248, 0.5)'],
+  '--dt-primary-color6': ['#BDD2FC', 'rgba(80, 141, 248, 0.3)'],
+  '--dt-primary-color7': ['#EAF1FE', 'rgba(80, 141, 248, 0.2)'],
+  '--dt-primary-color': ['var(--dt-primary-color1)', 'var(--dt-primary-color1)'],
+  '--dt-primary-color-hover': ['var(--dt-primary-color2)', 'var(--dt-primary-color2)'],
+  '--dt-primary-color-click': ['var(--dt-primary-color3)', 'var(--dt-primary-color3)'],
+  '--dt-primary-color-disabled': ['var(--dt-primary-color5)', 'var(--dt-primary-color5)'],
+  '--dt-primary-color-label': ['var(--dt-primary-color7)', 'var(--dt-primary-color7)'],
+  '--dt-text-color1': ['#1F232B', 'rgba(255, 255, 255, 0.85)'],
+  '--dt-text-color2': ['#4D596A', 'rgba(255, 255, 255, 0.6)'],
+  '--dt-text-color3': ['#848e9b', 'rgba(255, 255, 255, 0.45)'],
+  '--dt-text-color4': ['#c9ced6', 'rgba(255, 255, 255, 0.3)'],
+  '--dt-line-color1': ['#F2F3F5', 'rgba(255, 255, 255, 0.08)'],
+  '--dt-line-color2': ['#E5E6EA', 'rgba(255, 255, 255, 0.12)'],
+  '--dt-line-color3': ['#C9CDD4', 'rgba(255, 255, 255, 0.16)'],
+  '--dt-fill-color1': ['#f7f8fa', '#1f2729'],
+  '--dt-fill-color2': ['#C9CDD4', 'rgba(255, 255, 255, 0.24)'],
+  '--dt-fill-color3': ['#ffffff', '#293133'],
+  '--dt-fill-color4': ['#F2F3F5', 'rgba(255, 255, 255, 0.08)'],
+  '--dt-fill-color5': ['#E5E6EB', 'rgba(255, 255, 255, 0.16)'],
+  '--dt-fill-color6': ['#4E5969', '#C5C5C5'],
+  '--dt-success-color1': ['#2da641', '#27C346'],
+  '--dt-success-color2': ['#57B867', '#23AE3F'],
+  '--dt-success-color3': ['#29973B', '#67D57D'],
+  '--dt-success-color4': ['#9ED6A8', 'rgba(39, 195, 70, 0.5)'],
+  '--dt-success-color5': ['#BEE3C4', 'rgba(39, 195, 70, 0.4)'],
+  '--dt-success-color6': ['#EAF6EC', 'rgba(39, 195, 70, 0.2)'],
+  '--dt-warning-color1': ['#ED6A0C', '#FF9626'],
+  '--dt-warning-color2': ['#F1883D', '#FF890A'],
+  '--dt-warning-color3': ['#D8600B', '#FFB567'],
+  '--dt-warning-color4': ['#F7BA8F', 'rgba(255, 150, 38, 0.5)'],
+  '--dt-warning-color5': ['#F9D1B4', 'rgba(255, 150, 38, 0.4)'],
+  '--dt-warning-color6': ['#FDF0E7', 'rgba(255, 150, 38, 0.2)'],
+  '--dt-error-color1': ['#f24a3f', '#F76965'],
+  '--dt-error-color2': ['#f56e65', '#F6504C'],
+  '--dt-error-color3': ['#dc4339', '#F99693'],
+  '--dt-error-color4': ['#f9aca7', 'rgba(247, 105, 101, 0.5)'],
+  '--dt-error-color5': ['#fbc7c3', 'rgba(247, 105, 101, 0.4)'],
+  '--dt-error-color6': ['#feedec', 'rgba(247, 105, 101, 0.2)'],
+  '--dt-white-color1': ['rgba(255, 255, 255, 1)', 'rgba(255, 255, 255, 1)'],
+  '--dt-white-color2': ['rgba(255, 255, 255, 1)', 'rgba(255, 255, 255, 1)'],
+  '--dt-cyan-color6': ['#14C9C9', '#14C9C9'],
+  '--dt-special-color1': ['#ffffff', '#ffffff'],
+  '--dt-special-color2': ['#ffffff', '#ffffff'],
+  '--dt-special-color3': ['#000000', '#000000'],
+  '--dt-special-color4': ['#1f232b', '#2e3739'],
+  '--dt-special-color5': ['#3a4143', '#3a4143'],
+  '--dt-shadow-color1':['rgba(0, 0, 0, 0.10)','rgba(0, 0, 0, 0.25)'],
+};
+
+export { myTheme };

+ 53 - 0
src/utils/theme/style/dark.js

@@ -0,0 +1,53 @@
+import { useMemo } from 'react';
+
+const myTheme = {
+  '--dt-primary-color1': ['#2a6ff6', '#508df8'],
+  '--dt-primary-color2': ['#558cf8', '#4387ff'],
+  '--dt-primary-color3': ['#2259c5', '#73a4f9'],
+  '--dt-primary-color4': ['#709FF9', 'rgba(80, 141, 248, 0.4)'],
+  '--dt-primary-color5': ['#9DBDFB', 'rgba(80, 141, 248, 0.5)'],
+  '--dt-primary-color6': ['#BDD2FC', 'rgba(80, 141, 248, 0.3)'],
+  '--dt-primary-color7': ['#EAF1FE', 'rgba(80, 141, 248, 0.2)'],
+  '--dt-primary-color': ['var(--dt-primary-color1)', 'var(--dt-primary-color1)'],
+  '--dt-primary-color-hover': ['var(--dt-primary-color2)', 'var(--dt-primary-color2)'],
+  '--dt-primary-color-click': ['var(--dt-primary-color3)', 'var(--dt-primary-color3)'],
+  '--dt-primary-color-disabled': ['var(--dt-primary-color5)', 'var(--dt-primary-color5)'],
+  '--dt-primary-color-label': ['var(--dt-primary-color7)', 'var(--dt-primary-color7)'],
+  '--dt-text-color1': ['#1F232B', '#cce8ff'],
+  '--dt-text-color2': ['#4D596A', '#80baea'],
+  '--dt-text-color3': ['#848e9b', '#2d76b2'],
+  '--dt-text-color4': ['#c9ced6', '#125a95'],
+  '--dt-line-color1': ['#F2F3F5', 'rgba(255, 255, 255, 0.12)'],
+  '--dt-line-color2': ['#E5E6EA', '#0077db'],
+  '--dt-line-color3': ['#C9CDD4', '#0077db'],
+  '--dt-fill-color1': ['#f7f8fa', '#020e24'],
+  '--dt-fill-color2': ['#C9CDD4', 'rgba(255, 255, 255, 0.24)'],
+  '--dt-fill-color3': ['#ffffff', '#071a36'],
+  '--dt-fill-color4': ['#F2F3F5', '#0d274f'],
+  '--dt-fill-color5': ['#E5E6EB', 'rgba(255, 255, 255, 0.16)'],
+  '--dt-fill-color6': ['#4E5969', '#0077db'],
+  '--dt-success-color1': ['#2da641', '#27C346'],
+  '--dt-success-color2': ['#57B867', '#23AE3F'],
+  '--dt-success-color3': ['#29973B', '#67D57D'],
+  '--dt-success-color4': ['#9ED6A8', 'rgba(39, 195, 70, 0.5)'],
+  '--dt-success-color5': ['#BEE3C4', 'rgba(39, 195, 70, 0.4)'],
+  '--dt-success-color6': ['#EAF6EC', 'rgba(39, 195, 70, 0.2)'],
+  '--dt-warning-color1': ['#ED6A0C', '#FF9626'],
+  '--dt-warning-color2': ['#F1883D', '#FF890A'],
+  '--dt-warning-color3': ['#D8600B', '#FFB567'],
+  '--dt-warning-color4': ['#F7BA8F', 'rgba(255, 150, 38, 0.5)'],
+  '--dt-warning-color5': ['#F9D1B4', 'rgba(255, 150, 38, 0.4)'],
+  '--dt-warning-color6': ['#FDF0E7', 'rgba(255, 150, 38, 0.2)'],
+  '--dt-error-color1': ['#f24a3f', '#F76965'],
+  '--dt-error-color2': ['#f56e65', '#F6504C'],
+  '--dt-error-color3': ['#dc4339', '#F99693'],
+  '--dt-error-color4': ['#f9aca7', 'rgba(247, 105, 101, 0.5)'],
+  '--dt-error-color5': ['#fbc7c3', 'rgba(247, 105, 101, 0.4)'],
+  '--dt-error-color6': ['#feedec', 'rgba(247, 105, 101, 0.2)'],
+  '--dt-white-color1': ['rgba(255, 255, 255, 1)', 'rgba(255, 255, 255, 1)'],
+  '--dt-white-color2': ['rgba(255, 255, 255, 1)', 'rgba(255, 255, 255, 1)'],
+  '--dt-cyan-color6': ['#14C9C9', '#14C9C9'],
+  '--dt-shadow-color1':['rgba(0, 0, 0, 0.10)','rgba(0, 0, 0, 0.25)'],
+};
+
+export { myTheme };

+ 58 - 0
src/utils/theme/style/green.js

@@ -0,0 +1,58 @@
+import { useMemo } from 'react';
+
+const myTheme = {
+  '--dt-primary-color1': ['#3DCD58', 'rgba(60, 126, 255, 1)'],
+  '--dt-primary-color2': ['#64D779', 'rgba(67, 135, 255, 1)'],
+  '--dt-primary-color3': ['#38BB50', 'rgba(115, 164, 249, 1)'],
+  '--dt-primary-color4': ['#7DDE8F', 'rgba(80, 141, 248, 0.4)'],
+  '--dt-primary-color5': ['#A6E8B2', 'rgba(80, 141, 248, 0.5)'],
+  '--dt-primary-color6': ['#C3F0CB', 'rgba(80, 141, 248, 0.3)'],
+  '--dt-primary-color7': ['#ECFAEE', 'rgba(80, 141, 248, 0.2)'],
+  '--dt-primary-color': ['var(--dt-primary-color1)', 'var(--dt-primary-color1)'],
+  '--dt-primary-color-hover': ['var(--dt-primary-color2)', 'var(--dt-primary-color2)'],
+  '--dt-primary-color-click': ['var(--dt-primary-color3)', 'var(--dt-primary-color3)'],
+  '--dt-primary-color-disabled': ['var(--dt-primary-color5)', 'var(--dt-primary-color5)'],
+  '--dt-primary-color-label': ['var(--dt-primary-color7)', 'var(--dt-primary-color7)'],
+  '--dt-text-color1': ['#1F232B', 'rgba(255, 255, 255, 0.85)'],
+  '--dt-text-color2': ['#4D596A', 'rgba(255, 255, 255, 0.6)'],
+  '--dt-text-color3': ['#848e9b', 'rgba(255, 255, 255, 0.45)'],
+  '--dt-text-color4': ['#c9ced6', 'rgba(255, 255, 255, 0.3)'],
+  '--dt-line-color1': ['#F2F3F5', 'rgba(255, 255, 255, 0.08)'],
+  '--dt-line-color2': ['#E5E6EA', 'rgba(255, 255, 255, 0.12)'],
+  '--dt-line-color3': ['#C9CDD4', 'rgba(255, 255, 255, 0.16)'],
+  '--dt-fill-color1': ['#f7f8fa', '#1f2729'],
+  '--dt-fill-color2': ['#C9CDD4', 'rgba(255, 255, 255, 0.24)'],
+  '--dt-fill-color3': ['#ffffff', '#293133'],
+  '--dt-fill-color4': ['#F2F3F5', 'rgba(255, 255, 255, 0.08)'],
+  '--dt-fill-color5': ['#E5E6EB', 'rgba(255, 255, 255, 0.16)'],
+  '--dt-fill-color6': ['#4E5969', '#C5C5C5'],
+  '--dt-success-color1': ['#7CDB1D', '#27C346'],
+  '--dt-success-color2': ['#96E24A', '#23AE3F'],
+  '--dt-success-color3': ['#71C71A', '#67D57D'],
+  '--dt-success-color4': ['#C3EE97', 'rgba(39, 195, 70, 0.5)'],
+  '--dt-success-color5': ['#D6F4B9', 'rgba(39, 195, 70, 0.4)'],
+  '--dt-success-color6': ['#F2FBE8', 'rgba(39, 195, 70, 0.2)'],
+  '--dt-warning-color1': ['#ED6A0C', '#FF9626'],
+  '--dt-warning-color2': ['#F1883D', '#FF890A'],
+  '--dt-warning-color3': ['#D8600B', '#FFB567'],
+  '--dt-warning-color4': ['#F7BA8F', 'rgba(255, 150, 38, 0.5)'],
+  '--dt-warning-color5': ['#F9D1B4', 'rgba(255, 150, 38, 0.4)'],
+  '--dt-warning-color6': ['#FDF0E7', 'rgba(255, 150, 38, 0.2)'],
+  '--dt-error-color1': ['#f24a3f', '#F76965'],
+  '--dt-error-color2': ['#f56e65', '#F6504C'],
+  '--dt-error-color3': ['#dc4339', '#F99693'],
+  '--dt-error-color4': ['#f9aca7', 'rgba(247, 105, 101, 0.5)'],
+  '--dt-error-color5': ['#fbc7c3', 'rgba(247, 105, 101, 0.4)'],
+  '--dt-error-color6': ['#feedec', 'rgba(247, 105, 101, 0.2)'],
+  '--dt-white-color1': ['rgba(255, 255, 255, 1)', 'rgba(255, 255, 255, 1)'],
+  '--dt-white-color2': ['rgba(255, 255, 255, 1)', 'rgba(255, 255, 255, 1)'],
+  '--dt-cyan-color6': ['#14C9C9', '#14C9C9'],
+  '--dt-special-color1': ['#ffffff', '#ffffff'],
+  '--dt-special-color2': ['#ffffff', '#ffffff'],
+  '--dt-special-color3': ['#000000', '#000000'],
+  '--dt-special-color4': ['#1f232b', '#2e3739'],
+  '--dt-special-color5': ['#3a4143', '#3a4143'],
+  '--dt-shadow-color1':['rgba(0, 0, 0, 0.10)','rgba(0, 0, 0, 0.25)'],
+};
+
+export { myTheme };

+ 6 - 0
src/utils/theme1/ThemeWrapper.jsx

@@ -0,0 +1,6 @@
+import { useTheme } from '@/utils/theme/index.js';
+
+export default (props) => {
+  const [] = useTheme();
+  return props.children;
+};

+ 37 - 0
src/utils/theme1/applyTheme.js

@@ -0,0 +1,37 @@
+export function addDarkTheme (){
+  let styleLink = document.getElementById('theme-style');
+  let hrefSrc = './antd.dark-2.css';
+  if (styleLink) {
+      styleLink.href = hrefSrc;
+  } else {
+      styleLink = document.createElement('link');
+      styleLink.type = 'text/css';
+      styleLink.rel = 'stylesheet';
+      styleLink.id = 'theme-style';
+      styleLink.href = hrefSrc;
+      document.body.append(styleLink);
+  }
+};
+
+export function addGreenTheme (){
+  let styleLink = document.getElementById('theme-style');
+  let hrefSrc = './antd.green.css';
+  if (styleLink) {
+      styleLink.href = hrefSrc;
+  } else {
+      styleLink = document.createElement('link');
+      styleLink.type = 'text/css';
+      styleLink.rel = 'stylesheet';
+      styleLink.id = 'theme-style';
+      styleLink.href = hrefSrc;
+      document.body.append(styleLink);
+  }
+};
+
+
+export function rmDarkTheme (){
+  let styleLink = document.getElementById('theme-style');
+  if (styleLink) {
+      styleLink.href = "";
+  }
+};

+ 53 - 0
src/utils/theme1/blue/index.js

@@ -0,0 +1,53 @@
+// light绿,dark蓝
+const myTheme = {
+    '--dt-primary-color': ['#3dcd58', '#3c7eff'],
+    '--dt-primary-color-hover': ['#64d779', 'lightsalmon'],
+    '--dt-primary-color-click': ['#38bb50', 'lightsalmon'],
+    '--dt-primary-color-disabled': ['#a6e8b2', 'lightsalmon'],
+    '--dt-primary-color-label': [
+      '#ecfaee',
+      'linear-gradient(0deg, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), var(--dt-primary-color)',
+    ],
+  '--dt-text-color1': ['#323233', 'rgba(255, 255, 255, 0.85)'],
+  '--dt-text-color2': ['#4D596A', 'rgba(255, 255, 255, 0.6)'],
+  '--dt-text-color3': ['#848e9b', 'rgba(255, 255, 255, 0.3)'],
+  '--dt-text-color4': ['#c9ced6', 'lightsalmon'],
+  '--dt-text-color5': ['#FFFFFF', 'rgba(255, 255, 255, 0.85)'],
+  '--dt-line-color1': ['#c9cdd4', 'lightsalmon'],
+  '--dt-line-color2': ['#e5e6ea', 'rgba(255, 255, 255, 0.08)'],
+  '--dt-line-color3': ['#f2f3f5', '#3a4133'],
+  '--dt-fill-color1': ['#f7f8fa', '#1f2729'],
+  '--dt-fill-color2': ['var(--dt-line-color3)', 'var(--dt-line-color3)'],
+  '--dt-fill-color3': ['#ffffff', '#293133'],
+  '--dt-success-color1': ['#2da641', 'lightsalmon'],
+  '--dt-success-color2': ['#53c759', 'lightsalmon'],
+  '--dt-success-color3': ['#209426', 'lightsalmon'],
+  '--dt-success-color4': ['#d4f1d6', 'lightsalmon'],
+  '--dt-success-color5': ['#eaf8ea', 'lightsalmon'],
+  '--dt-warning-color1': ['#ed6a0c', 'lightsalmon'],
+  '--dt-warning-color2': ['#f1883d', 'lightsalmon'],
+  '--dt-warning-color3': ['#cb773b', 'lightsalmon'],
+  '--dt-warning-color4': ['#fbe1ce', 'lightsalmon'],
+  '--dt-warning-color5': ['#fdf0e7', 'lightsalmon'],
+  '--dt-error-color1': ['#d40000', 'lightsalmon'],
+  '--dt-error-color2': ['#dd3333', 'lightsalmon'],
+  '--dt-error-color3': ['#aa0000', 'lightsalmon'],
+  '--dt-error-color4': ['#f6cccc', 'lightsalmon'],
+  '--dt-error-color5': ['#fbe6e6', 'lightsalmon'],
+
+  '--dt-bg': ['var(--dt-fill-color1)', 'var(--dt-fill-color1)'],
+  '--dt-appBg': ['var(--dt-fill-color3)', 'var(--dt-fill-color3)'],
+  '--dt-fontColor1': ['#1f232b', 'var(--dt-text-color2)'],
+  '--dt-fontColor2': ['var(--dt-text-color2)', 'var(--dt-text-color2)'],
+  '--dt-buttonColor1': ['var(--dt-primary-color)', 'var(--dt-primary-color)'],
+  '--dt-buttonTextColor1': ['var(--dt-text-color5)', 'var(--dt-text-color5)'],
+  '--dt-divideLine1': ['var(--dt-line-color3)', 'var(--dt-line-color3)'],
+  '--dt-list-selected-bg': [
+    'var(--dt-primary-color-label)',
+    'var(--dt-primary-color-label)',
+  ],
+  '--dt-placeholderColor1': ['var(--dt-text-color3)', 'var(--dt-text-color3)'],
+  '--dt-inputBorderColor1': ['var(--dt-line-color2)', 'var(--dt-line-color2)'],
+};
+
+export { myTheme };

+ 69 - 0
src/utils/theme1/index.js

@@ -0,0 +1,69 @@
+import { useEffect, useMemo, useState } from 'react'
+import { useSearchParams } from 'react-router-dom'
+import { changeTheme, useVariable } from '@/utils/theme.js'
+
+const allowedThemeKeys = ['light', 'dark']
+
+const useLocalStorageThemeKey = () => {
+  const [key, setKey] = useState(() => localStorage.getItem('--dt-theme') ?? null)
+  useEffect(() => {
+    const cb = (e) => {
+      const theme = localStorage.getItem('--dt-theme')
+      setKey(theme ?? null)
+    }
+    window.addEventListener('storage', cb)
+    return () => {
+      window.removeEventListener('storage', cb)
+    }
+  }, [])
+  return [key]
+}
+
+const useThemeKey = () => {
+  const [lsKey] = useLocalStorageThemeKey()
+  const [searchParams] = useSearchParams()
+  const theme = useMemo(() => {
+    // if (process.env.REACT_APP_THEME_CHANGEABLE == 'false') {
+    //   return process.env.REACT_APP_THEME
+    // } else {
+    let key = (
+      searchParams.get('theme') ??
+      lsKey ??
+      // process.env.REACT_APP_THEME ??
+      'light'
+    )
+    if (allowedThemeKeys.indexOf(key) === -1) {
+      key = allowedThemeKeys[0]
+    }
+    return key
+    // }
+  }, [searchParams, lsKey])
+  return [theme]
+}
+
+const useTheme = () => {
+  const [theme] = useThemeKey()
+  useEffect(() => {
+    // console.log(theme);
+    changeTheme(theme)
+    // const loTheme = localStorage.getItem('--dt-theme')
+    // if (typeof loTheme !== 'string' || loTheme === '' || allowedThemeKeys.indexOf(loTheme) !== -1) {
+    //   localStorage.setItem('--dt-theme', theme)
+    // }
+  }, [theme])
+  return [theme]
+}
+
+const useColors = colors => {
+  const [theme] = useThemeKey()
+  const colorsStr = useMemo(() => {
+    return JSON.stringify(colors ?? [])
+  }, [colors])
+  const pColors = useMemo(() => {
+    return JSON.parse(colorsStr)
+  }, [colorsStr])
+  const [retColors] = useVariable(theme, pColors)
+  return [retColors]
+}
+
+export { useTheme, useColors, useThemeKey }

+ 41 - 0
src/utils/themes/index.js

@@ -0,0 +1,41 @@
+import { useMemo } from 'react'
+
+const myTheme = {
+  '--sys-color-1': ['#FFFFFF'],
+  '--sys-color-2': ['linear-gradient(180deg, #233251 0%, #0B1C3E 100%)'],
+  '--sys-color-3': ['#F7F8FA'],
+  '--sys-color-4': ['#1F232B'],
+  '--sys-color-5': ['rgba(0,0,0,0.05)'],
+  '--sys-color-6': ['#EAF1FE'],
+  '--sys-color-7': ['#E5E6EA'],
+  '--sys-color-8': ['#848E9B'],
+  '--sys-color-9': ['#2A6FF6'],
+  '--sys-color-10': ['rgba(0,0,0,0.1)'],
+  '--sys-color-11': ['#F2F3F5'],
+  '--text-color-1': ['rgba(31, 35, 43, 1)'],
+  '--text-color-2': ['rgba(77, 89, 106, 1)'],
+  '--text-color-3': ['rgba(132, 142, 155, 1)']
+}
+const changeTheme = theme => {
+  const nextTheme = theme
+  Object.keys(nextTheme).forEach(key => {
+    document.documentElement.style.setProperty(key, nextTheme[key][0])
+  })
+}
+
+changeTheme(myTheme)
+
+const fetchThemeColorKeyValue = () => {
+  return Object.keys(myTheme ?? {}).map(key => ({
+    [key]: myTheme?.[key]?.[0] ?? ''
+  }))
+}
+
+const useThemeColorKeyValue = () => {
+  const kVals = useMemo(() => {
+    return fetchThemeColorKeyValue()
+  }, [])
+  return [kVals]
+}
+
+export { useThemeColorKeyValue, fetchThemeColorKeyValue }

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott