Prechádzať zdrojové kódy

feat(merge): merge xyh

Next xyh
Lind 3 rokov pred
rodič
commit
b54d90ba7a

BIN
public/images/device-type-3-big.png


BIN
public/images/rectangle.png


+ 1 - 1
src/app.tsx

@@ -238,7 +238,7 @@ export function patchRoutes(routes: any) {
 export function render(oldRender: any) {
   if (history.location.pathname !== loginPath) {
     MenuService.queryOwnThree({ paging: false }).then((res) => {
-      if (res.status === 200) {
+      if (res && res.status === 200) {
         extraRoutes = handleRoutes(res.result);
         saveMenusCache(extraRoutes);
       }

+ 3 - 1
src/components/BadgeStatus/index.tsx

@@ -14,6 +14,8 @@ export enum StatusColorEnum {
 
 export type StatusColorType = keyof typeof StatusColorEnum;
 
+export type StatusType = string | number;
+
 export interface BadgeStatusProps {
   text: string | React.ReactNode;
   status: string | number;
@@ -32,7 +34,7 @@ export default (props: BadgeStatusProps) => {
     if ('statusNames' in props) {
       return props.statusNames![props.status];
     }
-    return StatusColorEnum['default'];
+    return StatusColorEnum.default;
   };
 
   return <Badge status={handleStatusColor()} text={props.text} />;

+ 55 - 20
src/components/ProTableCard/CardItems/device.tsx

@@ -1,45 +1,80 @@
-import { Avatar, Card } from 'antd';
 import React from 'react';
 import type { DeviceInstance } from '@/pages/device/Instance/typings';
-import { BadgeStatus } from '@/components';
 import { StatusColorEnum } from '@/components/BadgeStatus';
+import { TableCard } from '@/components';
+import '@/style/common.less';
 import '../index.less';
 
 export interface DeviceCardProps extends DeviceInstance {
+  detail?: React.ReactNode;
   actions?: React.ReactNode[];
   avatarSize?: number;
 }
 
+const defaultImage = require('/public/images/device-type-3-big.png');
+
 export default (props: DeviceCardProps) => {
   return (
-    <Card style={{ width: '100%' }} cover={null} actions={props.actions}>
+    <TableCard
+      detail={props.detail}
+      actions={props.actions}
+      status={props.state.value}
+      statusText={props.state.text}
+      statusNames={{
+        online: StatusColorEnum.processing,
+        offline: StatusColorEnum.error,
+        notActive: StatusColorEnum.warning,
+      }}
+    >
       <div className={'pro-table-card-item'}>
         <div className={'card-item-avatar'}>
-          <Avatar size={props.avatarSize || 64} src={props.photoUrl} />
+          <img width={88} height={88} src={props.photoUrl || defaultImage} alt={''} />
         </div>
         <div className={'card-item-body'}>
           <div className={'card-item-header'}>
             <span className={'card-item-header-name ellipsis'}>{props.name}</span>
-            <BadgeStatus
-              status={props.state.value}
-              text={props.state.text}
-              statusNames={{
-                online: StatusColorEnum.success,
-                offline: StatusColorEnum.error,
-                notActive: StatusColorEnum.processing,
-              }}
-            />
-          </div>
-          <div className={'card-item-content'}>
-            <label>设备类型:</label>
-            <span className={'ellipsis'}>{props.deviceType ? props.deviceType.text : '--'}</span>
           </div>
           <div className={'card-item-content'}>
-            <label>产品名称:</label>
-            <span className={'ellipsis'}>{props.productName || '--'}</span>
+            <div>
+              <label>设备类型</label>
+              <div className={'ellipsis'}>{props.deviceType ? props.deviceType.text : '--'}</div>
+            </div>
+            <div>
+              <label>产品名称</label>
+              <div className={'ellipsis'}>{props.productName || '--'}</div>
+            </div>
           </div>
         </div>
       </div>
-    </Card>
+    </TableCard>
+    // <Card style={{ width: '100%' }} cover={null} actions={props.actions}>
+    //   <div className={'pro-table-card-item'}>
+    //     <div className={'card-item-avatar'}>
+    //       <Avatar size={props.avatarSize || 64} src={props.photoUrl} />
+    //     </div>
+    //     <div className={'card-item-body'}>
+    //       <div className={'card-item-header'}>
+    //         <span className={'card-item-header-name ellipsis'}>{props.name}</span>
+    //         <BadgeStatus
+    //           status={props.state.value}
+    //           text={props.state.text}
+    //           statusNames={{
+    //             online: StatusColorEnum.success,
+    //             offline: StatusColorEnum.error,
+    //             notActive: StatusColorEnum.processing,
+    //           }}
+    //         />
+    //       </div>
+    //       <div className={'card-item-content'}>
+    //         <label>设备类型:</label>
+    //         <span className={'ellipsis'}>{props.deviceType ? props.deviceType.text : '--'}</span>
+    //       </div>
+    //       <div className={'card-item-content'}>
+    //         <label>产品名称:</label>
+    //         <span className={'ellipsis'}>{props.productName || '--'}</span>
+    //       </div>
+    //     </div>
+    //   </div>
+    // </Card>
   );
 };

+ 55 - 22
src/components/ProTableCard/TableCard.tsx

@@ -1,39 +1,74 @@
 import React, { useState } from 'react';
 import classNames from 'classnames';
-
-/**
- * 状态色
- */
-export enum StatusColorEnum {
-  'success' = 'success',
-  'error' = 'error',
-  'processing' = 'processing',
-  'warning' = 'warning',
-  'default' = 'default',
-}
-
-export type StatusColorType = keyof typeof StatusColorEnum;
+import { BadgeStatus } from '@/components';
+import { StatusColorEnum, StatusColorType } from '@/components/BadgeStatus';
+import './index.less';
 
 export interface TableCardProps {
+  className?: string;
+  showStatus?: boolean;
+  showTool?: boolean;
+  showMask?: boolean;
+  detail?: React.ReactNode;
   status?: string | number;
+  statusText?: React.ReactNode;
   statusNames?: Record<string | number, StatusColorType>;
   children?: React.ReactNode;
+  actions?: React.ReactNode[];
+}
+
+function getAction(actions: React.ReactNode[]) {
+  return actions.map((item: any) => {
+    return (
+      <div
+        className={classNames('card-button', {
+          delete: item.key === 'delete',
+          disabled: item.disabled,
+        })}
+      >
+        {item}
+      </div>
+    );
+  });
 }
 
 export default (props: TableCardProps) => {
   const [maskShow, setMaskShow] = useState(false);
 
-  const maskClassName = classNames('card-mask', { show: maskShow });
-
   const handleStatusColor = (): StatusColorType | undefined => {
     if ('statusNames' in props && props.status) {
       return props.statusNames![props.status];
     }
-    return StatusColorEnum['default'];
+    return StatusColorEnum.default;
   };
 
+  const statusNode =
+    props.showStatus === false ? null : (
+      <div className={classNames('card-state', handleStatusColor())}>
+        <div className={'card-state-content'}>
+          <BadgeStatus
+            status={props.status !== undefined ? props.status : ''}
+            text={props.statusText}
+            statusNames={props.statusNames}
+          />
+        </div>
+      </div>
+    );
+
+  const maskClassName = classNames('card-mask', { show: maskShow });
+
+  const maskNode =
+    props.showMask === false ? null : <div className={maskClassName}>{props.detail}</div>;
+
+  const toolNode =
+    props.showTool === false ? null : (
+      <div className={'card-tools'}>
+        {props.actions && props.actions.length ? getAction(props.actions) : null}
+      </div>
+    );
+
   return (
-    <div className={'iot-card'}>
+    <div className={classNames('iot-card', { hover: maskShow }, props.className)}>
       <div className={'card-warp'}>
         <div
           className={'card-content'}
@@ -45,13 +80,11 @@ export default (props: TableCardProps) => {
           }}
         >
           {props.children}
-          <div className={classNames('card-state', handleStatusColor())}>
-            <div className={'card-state-content'}></div>
-          </div>
+          {statusNode}
+          {maskNode}
         </div>
-        <div className={maskClassName}></div>
       </div>
-      <div className={'card-tools'}></div>
+      {toolNode}
     </div>
   );
 };

+ 131 - 14
src/components/ProTableCard/index.less

@@ -1,4 +1,8 @@
-@import '../../../node_modules/antd/lib/style/themes/variable';
+@import '~antd/es/style/themes/default.less';
+
+@border-color: #e6e6e6;
+@card-content-padding-top: 30px;
+@card-content-padding-left: 30px;
 
 .pro-table-card {
   position: relative;
@@ -21,8 +25,6 @@
     display: grid;
     grid-gap: 26px;
     grid-template-columns: repeat(4, 1fr);
-    //display: flex;
-    //flex-wrap: wrap;
     padding-bottom: 38px;
 
     .pro-table-card-item {
@@ -53,8 +55,21 @@
         .card-item-content {
           display: flex;
 
-          > span {
-            flex: 1;
+          > div:last-child {
+            flex-grow: 1;
+            width: 0;
+            margin-left: 12px;
+          }
+
+          label {
+            color: rgba(#000, 0.75);
+            font-size: 12px;
+          }
+
+          .ellipsis {
+            width: 100%;
+            font-weight: bold;
+            font-size: 14px;
           }
         }
       }
@@ -72,10 +87,13 @@
   }
 }
 
-@border-color: #e6e6e6;
-
 .iot-card {
   width: 100%;
+  background-color: #fff;
+
+  &.hover {
+    box-shadow: 0 0 24px rgba(#000, 0.1);
+  }
 
   .card-warp {
     position: relative;
@@ -83,17 +101,29 @@
 
     .card-content {
       position: relative;
-      padding: 30px 12px 12px 30px;
+      padding: @card-content-padding-top 12px 16px @card-content-padding-left;
       overflow: hidden;
 
-      .card-state {
+      &::before {
         position: absolute;
         top: 0;
-        right: 0;
+        left: @card-content-padding-left + 10px;
+        display: block;
+        width: 120px;
+        height: 4px;
+        background-image: url('/images/rectangle.png');
+        background-repeat: no-repeat;
+        content: ' ';
+      }
+
+      .card-state {
+        position: absolute;
+        top: @card-content-padding-top;
+        right: -12px;
         display: flex;
         justify-content: center;
         width: 100px;
-        padding: 8px 0;
+        padding: 2px 0;
         background-color: @info-color-deprecated-bg;
         transform: skewX(45deg);
 
@@ -119,17 +149,104 @@
       position: absolute;
       top: 0;
       left: 0;
-      display: none;
+      z-index: 2;
+      display: flex;
       align-items: center;
       justify-content: center;
       width: 100%;
       height: 100%;
       color: #fff;
-      background-color: rgba(#000, 0.5);
+      background-color: rgba(#000, 0);
       cursor: pointer;
+      transition: all 0.3s;
 
       &.show {
-        display: flex;
+        background-color: rgba(#000, 0.5);
+      }
+    }
+  }
+
+  .card-tools {
+    display: flex;
+    margin-top: 8px;
+
+    .card-button {
+      display: flex;
+      flex-grow: 1;
+
+      > span,
+      & button {
+        width: 100%;
+        border-radius: 0;
+      }
+
+      button {
+        background: #f6f6f6;
+        border: 1px solid #e6e6e6;
+
+        &:hover {
+          background-color: @primary-color-hover;
+          border-color: @primary-color-hover;
+          span {
+            color: #fff !important;
+          }
+        }
+
+        &:active {
+          background-color: @primary-color-active;
+          border-color: @primary-color-active;
+          span {
+            color: #fff !important;
+          }
+        }
+      }
+
+      &:not(:last-child) {
+        margin-right: 8px;
+      }
+
+      &.delete {
+        flex-basis: 60px;
+        flex-grow: 0;
+
+        button {
+          background: @error-color-deprecated-bg;
+          border: 1px solid @error-color-outline;
+          span {
+            color: @error-color !important;
+          }
+
+          &:hover {
+            background-color: @error-color-hover;
+            span {
+              color: #fff !important;
+            }
+          }
+
+          &:active {
+            background-color: @error-color-active;
+            span {
+              color: #fff !important;
+            }
+          }
+        }
+      }
+
+      button[disabled] {
+        background: @disabled-bg;
+        border-color: @disabled-color;
+
+        span {
+          color: @disabled-color !important;
+        }
+
+        &:hover {
+          background-color: @disabled-active-bg;
+        }
+
+        &:active {
+          background-color: @disabled-active-bg;
+        }
       }
     }
   }

+ 1 - 0
src/components/index.ts

@@ -1,6 +1,7 @@
 export { default as RadioCard } from './RadioCard';
 export { default as UploadImage } from './Upload/Image';
 export { default as ProTableCard } from './ProTableCard';
+export { default as TableCard } from './ProTableCard/TableCard';
 export { default as BadgeStatus } from './BadgeStatus';
 export { default as Player } from './Player';
 export { default as ScreenPlayer } from './Player/ScreenPlayer';

+ 94 - 1
src/pages/device/Instance/index.tsx

@@ -14,6 +14,7 @@ import {
   PlusOutlined,
   StopOutlined,
   SyncOutlined,
+  EditOutlined,
 } from '@ant-design/icons';
 import { model } from '@formily/reactive';
 import Service from '@/pages/device/Instance/service';
@@ -405,7 +406,99 @@ const Instance = () => {
             <Button>批量操作</Button>
           </Dropdown>,
         ]}
-        cardRender={(record) => <DeviceCard {...record} actions={tools(record)} />}
+        cardRender={(record) => (
+          <DeviceCard
+            {...record}
+            detail={
+              <div
+                style={{ padding: 8, fontSize: 24 }}
+                onClick={() => {
+                  InstanceModel.current = record;
+                  const url = getMenuPathByParams(MENUS_CODE['device/Instance/Detail'], record.id);
+                  history.push(url);
+                }}
+              >
+                <EyeOutlined />
+              </div>
+            }
+            actions={[
+              <Button
+                type={'link'}
+                onClick={() => {
+                  setCurrent(record);
+                  setVisible(true);
+                }}
+                key={'edit'}
+              >
+                <EditOutlined />
+                {intl.formatMessage({
+                  id: 'pages.data.option.edit',
+                  defaultMessage: '编辑',
+                })}
+              </Button>,
+              <Popconfirm
+                key={'state'}
+                title={intl.formatMessage({
+                  id: `pages.data.option.${
+                    record.state.value !== 'notActive' ? 'disabled' : 'enabled'
+                  }.tips`,
+                  defaultMessage: '确认禁用?',
+                })}
+                onConfirm={async () => {
+                  if (record.state.value !== 'notActive') {
+                    await service.undeployDevice(record.id);
+                  } else {
+                    await service.deployDevice(record.id);
+                  }
+                  message.success(
+                    intl.formatMessage({
+                      id: 'pages.data.option.success',
+                      defaultMessage: '操作成功!',
+                    }),
+                  );
+                  actionRef.current?.reload();
+                }}
+              >
+                <Button type={'link'} style={{ padding: 0 }}>
+                  {record.state.value !== 'notActive' ? <StopOutlined /> : <CheckCircleOutlined />}
+                  {intl.formatMessage({
+                    id: `pages.data.option.${
+                      record.state.value !== 'notActive' ? 'disabled' : 'enabled'
+                    }`,
+                    defaultMessage: record.state.value !== 'notActive' ? '禁用' : '启用',
+                  })}
+                </Button>
+              </Popconfirm>,
+              <Popconfirm
+                title={intl.formatMessage({
+                  id:
+                    record.state.value === 'notActive'
+                      ? 'pages.data.option.remove.tips'
+                      : 'pages.device.instance.deleteTip',
+                })}
+                key={'delete'}
+                onConfirm={async () => {
+                  if (record.state.value === 'notActive') {
+                    await service.remove(record.id);
+                    message.success(
+                      intl.formatMessage({
+                        id: 'pages.data.option.success',
+                        defaultMessage: '操作成功!',
+                      }),
+                    );
+                    actionRef.current?.reload();
+                  } else {
+                    message.error(intl.formatMessage({ id: 'pages.device.instance.deleteTip' }));
+                  }
+                }}
+              >
+                <Button type={'link'} style={{ padding: 0 }}>
+                  <DeleteOutlined />
+                </Button>
+              </Popconfirm>,
+            ]}
+          />
+        )}
       />
       <Save
         data={current}