1175160654 4 лет назад
Родитель
Сommit
fdc77001f5

+ 5 - 0
config/config.ts

@@ -49,6 +49,11 @@ export default defineConfig({
   manifest: {
     basePath: '/',
   },
+  lessLoader: {
+    modifyVars: {
+      'root-entry-name': 'default',
+    },
+  },
   // Fast Refresh 热更新
   fastRefresh: {},
   openAPI: [

+ 3 - 3
package.json

@@ -56,10 +56,10 @@
   "dependencies": {
     "@ant-design/charts": "^1.2.13",
     "@ant-design/icons": "^4.5.0",
-    "@ant-design/pro-card": "^1.14.5",
+    "@ant-design/pro-card": "^1.16.2",
     "@ant-design/pro-descriptions": "^1.6.8",
     "@ant-design/pro-form": "^1.18.3",
-    "@ant-design/pro-layout": "^6.15.3",
+    "@ant-design/pro-layout": "^6.27.2",
     "@dabeng/react-orgchart": "^1.0.0",
     "@formily/antd": "2.0.0-rc.17",
     "@formily/core": "2.0.0-rc.17",
@@ -72,7 +72,7 @@
     "@jetlinks/pro-table": "^2.43.7",
     "@umijs/route-utils": "^1.0.36",
     "ahooks": "^2.10.9",
-    "antd": "^4.14.0",
+    "antd": "^4.17.0-alpha.9",
     "classnames": "^2.2.6",
     "dexie": "^3.0.3",
     "isomorphic-form-data": "^2.0.0",

+ 1 - 1
src/locales/en-US/pages.ts

@@ -179,7 +179,7 @@ export default {
   'pages.device.productDetail.metadata.true': 'true',
   'pages.device.productDetail.metadata.false': 'false',
   'pages.device.productDetail.metadata.dataType': 'Data Type',
-  'pages.device.productDetail.metadata.getData': 'Get Data',
+  'pages.device.productDetail.metadata.saveData': 'Save Data',
   'pages.device.productDetail.metadata.accuracy': 'Accuracy',
   'pages.device.productDetail.metadata.boolean': 'Boolean',
   'pages.device.productDetail.metadata.timeFormat': 'Time Format',

+ 1 - 1
src/locales/zh-CN/pages.ts

@@ -181,7 +181,7 @@ export default {
   'pages.device.productDetail.metadata.true': '是',
   'pages.device.productDetail.metadata.false': '否',
   'pages.device.productDetail.metadata.dataType': '数据类型',
-  'pages.device.productDetail.metadata.getData': '获取数据',
+  'pages.device.productDetail.metadata.saveData': '保存数据',
   'pages.device.productDetail.metadata.accuracy': '精度',
   'pages.device.productDetail.metadata.boolean': '布尔值',
   'pages.device.productDetail.metadata.timeFormat': '时间格式',

+ 32 - 8
src/pages/device/Product/Detail/Metadata/Base/Edit/index.tsx

@@ -1,4 +1,4 @@
-import { Button, Drawer } from 'antd';
+import { Button, Drawer, message } from 'antd';
 import { createSchemaField } from '@formily/react';
 import MetadataModel from '@/pages/device/Product/Detail/Metadata/Base/model';
 import type { Field, IFieldState } from '@formily/core';
@@ -26,7 +26,7 @@ import {
 import { useCallback, useEffect, useState } from 'react';
 import { productModel, service } from '@/pages/device/Product';
 import { Store } from 'jetlinks-store';
-import type { UnitType } from '@/pages/device/Product/typings';
+import type { MetadataItem, MetadataType, UnitType } from '@/pages/device/Product/typings';
 
 import JsonParam from '@/components/Metadata/JsonParam';
 import ArrayParam from '@/components/Metadata/ArrayParam';
@@ -35,6 +35,7 @@ import BooleanEnum from '@/components/Metadata/BooleanParam';
 import ConfigParam from '@/components/Metadata/ConfigParam';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { lastValueFrom } from 'rxjs';
+import type { DeviceMetadata } from '@/pages/device/Product/typings';
 
 const Edit = () => {
   const intl = useIntl();
@@ -102,6 +103,7 @@ const Edit = () => {
         required: true,
         'x-decorator': 'FormItem',
         'x-component': 'Input',
+        'x-disabled': MetadataModel.action === 'edit',
       },
       name: {
         title: intl.formatMessage({
@@ -570,6 +572,26 @@ const Edit = () => {
     });
   }, [getUnits]);
 
+  const saveMetadata = async (type: MetadataType, params: MetadataItem) => {
+    const product = productModel.current;
+    if (!product) return;
+    const metadata = JSON.parse(product.metadata) as DeviceMetadata;
+    const config = metadata[type] as MetadataItem[];
+    const index = config.findIndex((item) => item.id === params.id);
+    // todo 考虑优化
+    if (index > -1) {
+      config[index] = params;
+    } else {
+      config.push(params);
+    }
+    product.metadata = JSON.stringify(metadata);
+    const result = await service.saveProduct(product);
+    if (result.status === 200) {
+      message.success('操作成功!');
+    } else {
+      message.error('操作失败!');
+    }
+  };
   return (
     <Drawer
       width="25vw"
@@ -587,17 +609,19 @@ const Edit = () => {
       }}
       destroyOnClose
       zIndex={1000}
-      footer={
+      placement={'right'}
+      extra={
         <Button
+          type="primary"
           onClick={async () => {
-            // const data = await form.submit() as MetadataItem;
-            // const {type} = MetadataModel;
-            // saveMetadata(type, data);
+            const data = (await form.submit()) as MetadataItem;
+            const { type } = MetadataModel;
+            await saveMetadata(type, data);
           }}
         >
           {intl.formatMessage({
-            id: 'pages.device.productDetail.metadata.getData',
-            defaultMessage: '获取数据',
+            id: 'pages.device.productDetail.metadata.saveData',
+            defaultMessage: '保存数据',
           })}
         </Button>
       }

+ 6 - 0
src/pages/device/Product/service.ts

@@ -59,6 +59,12 @@ class Service extends BaseService<ProductItem> {
     request(`/${SystemConst.API_BASE}/protocol/units`, {
       method: 'GET',
     });
+
+  public saveProduct = (data: Record<string, unknown>) =>
+    request(`/${SystemConst.API_BASE}/device-product`, {
+      method: 'PATCH',
+      data,
+    });
 }
 
 export default Service;

+ 114 - 114
src/pages/user/Login/index.less

@@ -6,11 +6,121 @@
   height: 100vh;
   overflow: auto;
   background: @layout-body-background;
-}
 
-.left {
-  width: 27%;
-  background: #fff;
+  .left {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    width: 27%;
+    background: #fff;
+
+    :global(.ant-layout-footer) {
+      background: #fff;
+    }
+
+    .lang {
+      width: 100%;
+      height: 40px;
+      line-height: 44px;
+      text-align: right;
+
+      :global(.ant-dropdown-trigger) {
+        margin-right: 24px;
+      }
+    }
+
+    .content {
+      display: flex;
+      flex-direction: row-reverse;
+      justify-content: center;
+
+      .top {
+        width: 100%;
+        text-align: center;
+
+        .header {
+          height: 44px;
+          line-height: 44px;
+
+          a {
+            text-decoration: none;
+          }
+
+          .logo {
+            height: 44px;
+            margin-right: 16px;
+            vertical-align: top;
+          }
+
+          .title {
+            position: relative;
+            top: 2px;
+            color: @heading-color;
+            font-weight: 600;
+            font-size: 33px;
+            font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
+          }
+        }
+
+        .desc {
+          margin-top: 12px;
+          margin-bottom: 40px;
+          color: @text-color-secondary;
+          font-size: @font-size-base;
+        }
+
+        .main {
+          width: 328px;
+          margin: 60px auto 0;
+          @media screen and (max-width: @screen-sm) {
+            width: 95%;
+            max-width: 328px;
+          }
+
+          :global {
+            .@{ant-prefix}-tabs-nav-list {
+              margin: auto;
+              font-size: 16px;
+            }
+          }
+
+          .icon {
+            margin-left: 16px;
+            color: rgba(0, 0, 0, 0.2);
+            font-size: 24px;
+            vertical-align: middle;
+            cursor: pointer;
+            transition: color 0.3s;
+
+            &:hover {
+              color: @primary-color;
+            }
+          }
+
+          .other {
+            margin-top: 24px;
+            line-height: 22px;
+            text-align: left;
+
+            .register {
+              float: right;
+            }
+          }
+
+          .prefixIcon {
+            color: @primary-color;
+            font-size: @font-size-base;
+          }
+
+          .remember {
+            display: flex;
+            flex-direction: row;
+            margin-bottom: 30px;
+          }
+        }
+      }
+    }
+  }
 }
 
 .right {
@@ -53,30 +163,6 @@
   }
 }
 
-.lang {
-  width: 100%;
-  height: 40px;
-  line-height: 44px;
-  text-align: right;
-
-  :global(.ant-dropdown-trigger) {
-    margin-right: 24px;
-  }
-}
-
-.content {
-  display: flex;
-  flex-direction: row-reverse;
-  justify-content: center;
-  padding: 40px 0;
-}
-
-.remember {
-  display: flex;
-  flex-direction: row;
-  margin-bottom: 30px;
-}
-
 @media (min-width: @screen-md-min) {
   .container {
     //background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
@@ -89,89 +175,3 @@
     padding: 32px 0 24px;
   }
 }
-
-.top {
-  width: 100%;
-  margin-top: 100px;
-  text-align: center;
-
-  .footer {
-    position: absolute;
-    bottom: 0;
-  }
-}
-
-.header {
-  height: 44px;
-  line-height: 44px;
-
-  a {
-    text-decoration: none;
-  }
-}
-
-.logo {
-  height: 44px;
-  margin-right: 16px;
-  vertical-align: top;
-}
-
-.title {
-  position: relative;
-  top: 2px;
-  color: @heading-color;
-  font-weight: 600;
-  font-size: 33px;
-  font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
-}
-
-.desc {
-  margin-top: 12px;
-  margin-bottom: 40px;
-  color: @text-color-secondary;
-  font-size: @font-size-base;
-}
-
-.main {
-  width: 328px;
-  margin: 60px auto 0;
-  @media screen and (max-width: @screen-sm) {
-    width: 95%;
-    max-width: 328px;
-  }
-
-  :global {
-    .@{ant-prefix}-tabs-nav-list {
-      margin: auto;
-      font-size: 16px;
-    }
-  }
-
-  .icon {
-    margin-left: 16px;
-    color: rgba(0, 0, 0, 0.2);
-    font-size: 24px;
-    vertical-align: middle;
-    cursor: pointer;
-    transition: color 0.3s;
-
-    &:hover {
-      color: @primary-color;
-    }
-  }
-
-  .other {
-    margin-top: 24px;
-    line-height: 22px;
-    text-align: left;
-
-    .register {
-      float: right;
-    }
-  }
-
-  .prefixIcon {
-    color: @primary-color;
-    font-size: @font-size-base;
-  }
-}

+ 19 - 15
src/pages/user/Login/index.tsx

@@ -13,18 +13,7 @@ import { useModel } from '@@/plugin-model/useModel';
 import SystemConst from '@/utils/const';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { SelectLang } from '@@/plugin-locale/SelectLang';
-
-/** 此方法会跳转到 redirect 参数所在的位置 */
-const goto = () => {
-  if (!history) return;
-  setTimeout(() => {
-    const { query } = history.location;
-    const { redirect } = query as {
-      redirect: string;
-    };
-    history.push(redirect || '/');
-  }, 10);
-};
+import Footer from '@/components/Footer';
 
 const Login: React.FC = () => {
   const [captcha, setCaptcha] = useState<{ key?: string; base64?: string }>({});
@@ -49,6 +38,21 @@ const Login: React.FC = () => {
     initialValues: loginRef.current,
   });
 
+  const [loading, setLoading] = useState<boolean>(false);
+
+  /** 此方法会跳转到 redirect 参数所在的位置 */
+  const goto = () => {
+    if (!history) return;
+    setTimeout(() => {
+      const { query } = history.location;
+      const { redirect } = query as {
+        redirect: string;
+      };
+      history.push(redirect || '/');
+      setLoading(false);
+    }, 10);
+  };
+
   const getCode = () => {
     delete loginForm.values?.verifyCode;
     loginRef.current = loginForm.values;
@@ -121,7 +125,6 @@ const Login: React.FC = () => {
     },
   };
 
-  const [loading, setLoading] = useState<boolean>(false);
   const doLogin = async (data: LoginParam) => {
     setLoading(true);
     Service.login({ expires: loginRef.current.expires, verifyKey: captcha.key, ...data }).subscribe(
@@ -130,7 +133,6 @@ const Login: React.FC = () => {
           Token.set(userInfo.token);
           await fetchUserInfo();
           goto();
-          setLoading(false);
         },
         error: () => {
           message.error(
@@ -179,7 +181,6 @@ const Login: React.FC = () => {
                         loginRef.current.expires = e.target.checked ? -1 : 3600000;
                       }}
                     >
-                      {' '}
                       记住密码
                     </Checkbox>
                   </div>
@@ -193,6 +194,9 @@ const Login: React.FC = () => {
               </div>
             </div>
           </div>
+          <div>
+            <Footer />
+          </div>
         </div>
         <div className={styles.right}>
           <div className={styles.systemName}>{SystemConst.SYSTEM_NAME}</div>