소스 검색

feat(设备管理): 修改设备管理列表为card模式

xieyonghong 3 년 전
부모
커밋
649c56432e

+ 38 - 0
src/components/BadgeStatus/index.tsx

@@ -0,0 +1,38 @@
+import { Badge } from 'antd';
+
+/**
+ * 状态色
+ */
+export enum StatusColorEnum {
+  'success' = 'success',
+  'error' = 'error',
+  'processing' = 'processing',
+  'warning' = 'warning',
+  'default' = 'default',
+}
+
+export type StatusColorType = keyof typeof StatusColorEnum;
+
+export interface BadgeStatusProps {
+  text: string;
+  status: string | number;
+  /**
+   * 自定义status值颜色
+   * @example {
+   *   1: 'success',
+   *   0: 'error'
+   * }
+   */
+  statusNames?: Record<string | number, StatusColorType>;
+}
+
+export default (props: BadgeStatusProps) => {
+  const handleStatusColor = (): StatusColorType | undefined => {
+    if ('statusNames' in props) {
+      return props.statusNames![props.status];
+    }
+    return StatusColorEnum['default'];
+  };
+
+  return <Badge status={handleStatusColor()} text={props.text} />;
+};

+ 37 - 16
src/components/ProTableCard/CardItems/device.tsx

@@ -1,24 +1,45 @@
-import { Card } from 'antd';
-import { EditOutlined, EllipsisOutlined, SettingOutlined } from '@ant-design/icons';
+import { Card, Avatar } from 'antd';
+import React from 'react';
+import type { DeviceInstance } from '@/pages/device/Instance/typings';
+import { BadgeStatus } from '@/components';
+import { StatusColorEnum } from '@/components/BadgeStatus';
+import '@/style/common.less';
 
-export interface DeviceCardProps {
-  id: string;
-  name: string;
-  photoUrl?: string;
+export interface DeviceCardProps extends DeviceInstance {
+  actions?: React.ReactNode[];
+  avatarSize?: number;
 }
 
 export default (props: DeviceCardProps) => {
   return (
-    <Card
-      style={{ width: 280 }}
-      cover={null}
-      actions={[
-        <SettingOutlined key="setting" />,
-        <EditOutlined key="edit" />,
-        <EllipsisOutlined key="ellipsis" />,
-      ]}
-    >
-      <div>{props.name}</div>
+    <Card style={{ width: 340 }} 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>
   );
 };

+ 39 - 6
src/components/ProTableCard/index.less

@@ -18,13 +18,46 @@
   }
 
   .pro-table-card-items {
-    display: flex;
-    flex-wrap: wrap;
-    padding-bottom: 32px;
+    display: grid;
+    grid-gap: 26px;
+    grid-template-columns: repeat(auto-fit, 340px);
+    //display: flex;
+    //flex-wrap: wrap;
+    padding-bottom: 38px;
 
-    > div {
-      margin-right: 14px;
-      margin-bottom: 14px;
+    .pro-table-card-item {
+      display: flex;
+
+      .card-item-avatar {
+        margin-right: 16px;
+      }
+
+      .card-item-body {
+        display: flex;
+        flex-direction: column;
+        flex-grow: 1;
+        width: 0;
+
+        .card-item-header {
+          display: flex;
+          width: 100%;
+          margin-bottom: 12px;
+
+          .card-item-header-name {
+            flex: 1;
+            font-weight: bold;
+            font-size: 16px;
+          }
+        }
+
+        .card-item-content {
+          display: flex;
+
+          > span {
+            flex: 1;
+          }
+        }
+      }
     }
   }
 

+ 12 - 7
src/components/ProTableCard/index.tsx

@@ -3,7 +3,7 @@ import type { ProTableProps } from '@jetlinks/pro-table';
 import type { ParamsType } from '@ant-design/pro-provider';
 import React, { useState } from 'react';
 import { isFunction } from 'lodash';
-import { Space, Pagination } from 'antd';
+import { Space, Pagination, Empty } from 'antd';
 import { AppstoreOutlined, BarsOutlined } from '@ant-design/icons';
 import classNames from 'classnames';
 import './index.less';
@@ -13,6 +13,8 @@ enum ModelEnum {
   CARD = 'CARD',
 }
 
+const Default_Size = 5;
+
 type ModelType = keyof typeof ModelEnum;
 
 interface ProTableCardProps<T> {
@@ -31,7 +33,7 @@ const ProTableCard = <
   const [total, setTotal] = useState<number | undefined>(0);
   const [current, setCurrent] = useState(1); // 当前页
   const [pageIndex, setPageIndex] = useState(0);
-  const [pageSize, setPageSize] = useState(10); // 每页条数
+  const [pageSize, setPageSize] = useState(Default_Size * 2); // 每页条数
 
   /**
    * 处理 Card
@@ -40,11 +42,11 @@ const ProTableCard = <
   const handleCard = (dataSource: readonly T[] | undefined): JSX.Element => {
     return (
       <div className={'pro-table-card-items'}>
-        {dataSource
-          ? dataSource.map((item) =>
-              cardRender && isFunction(cardRender) ? cardRender(item) : null,
-            )
-          : null}
+        {dataSource ? (
+          dataSource.map((item) => (cardRender && isFunction(cardRender) ? cardRender(item) : null))
+        ) : (
+          <Empty />
+        )}
       </div>
     );
   };
@@ -61,6 +63,7 @@ const ProTableCard = <
             pageSize,
           } as any
         }
+        options={model === ModelEnum.CARD ? false : props.options}
         request={async (param, sort, filter) => {
           if (request) {
             const resp = await request(param, sort, filter);
@@ -86,6 +89,7 @@ const ProTableCard = <
           },
           pageSize: pageSize,
           current: current,
+          pageSizeOptions: [Default_Size * 2, Default_Size * 4, 50, 100],
         }}
         toolBarRender={(action, row) => {
           const oldBar = toolBarRender ? toolBarRender(action, row) : [];
@@ -139,6 +143,7 @@ const ProTableCard = <
             setPageIndex(page - 1);
             setPageSize(size);
           }}
+          pageSizeOptions={[Default_Size * 2, Default_Size * 4, 50, 100]}
           pageSize={pageSize}
           showTotal={(num) => {
             const minSize = pageIndex * pageSize + 1;

+ 1 - 0
src/components/index.ts

@@ -1,3 +1,4 @@
 export { default as RadioCard } from './RadioCard';
 export { default as UploadImage } from './Upload/Image';
 export { default as ProTableCard } from './ProTableCard';
+export { default as BadgeStatus } from './BadgeStatus';

+ 76 - 80
src/pages/device/Instance/index.tsx

@@ -65,13 +65,79 @@ const Instance = () => {
   const [bindKeys, setBindKeys] = useState<any[]>([]);
   const intl = useIntl();
 
+  const tools = (record: DeviceInstance) => [
+    <Link
+      onClick={() => {
+        InstanceModel.current = record;
+      }}
+      to={`/device/instance/detail/${record.id}`}
+      key="link"
+    >
+      <Tooltip
+        title={intl.formatMessage({
+          id: 'pages.data.option.detail',
+          defaultMessage: '查看',
+        })}
+        key={'detail'}
+      >
+        <EyeOutlined />
+      </Tooltip>
+    </Link>,
+    <a href={record.id} target="_blank" rel="noopener noreferrer" key="view">
+      <Popconfirm
+        title={intl.formatMessage({
+          id: 'pages.data.option.disabled.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();
+        }}
+      >
+        <Tooltip
+          title={intl.formatMessage({
+            id: `pages.data.option.${record.state.value !== 'notActive' ? 'disabled' : 'enabled'}`,
+            defaultMessage: record.state.value !== 'notActive' ? '禁用' : '启用',
+          })}
+        >
+          {record.state.value !== 'notActive' ? <StopOutlined /> : <CheckCircleOutlined />}
+        </Tooltip>
+      </Popconfirm>
+    </a>,
+
+    <a key={'delete'}>
+      <Popconfirm
+        title="确认删除"
+        onConfirm={async () => {
+          await service.remove(record.id);
+          message.success(
+            intl.formatMessage({
+              id: 'pages.data.option.success',
+              defaultMessage: '操作成功!',
+            }),
+          );
+          actionRef.current?.reload();
+        }}
+      >
+        <Tooltip title={'删除'}>
+          <DeleteOutlined />
+        </Tooltip>
+      </Popconfirm>
+    </a>,
+  ];
+
   const columns: ProColumns<DeviceInstance>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
       title: 'ID',
       dataIndex: 'id',
     },
@@ -152,78 +218,7 @@ const Instance = () => {
       valueType: 'option',
       align: 'center',
       width: 200,
-      render: (text, record) => [
-        <Link
-          onClick={() => {
-            InstanceModel.current = record;
-          }}
-          to={`/device/instance/detail/${record.id}`}
-          key="link"
-        >
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.detail',
-              defaultMessage: '查看',
-            })}
-            key={'detail'}
-          >
-            <EyeOutlined />
-          </Tooltip>
-        </Link>,
-        <a href={record.id} target="_blank" rel="noopener noreferrer" key="view">
-          <Popconfirm
-            title={intl.formatMessage({
-              id: 'pages.data.option.disabled.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();
-            }}
-          >
-            <Tooltip
-              title={intl.formatMessage({
-                id: `pages.data.option.${
-                  record.state.value !== 'notActive' ? 'disabled' : 'enabled'
-                }`,
-                defaultMessage: record.state.value !== 'notActive' ? '禁用' : '启用',
-              })}
-            >
-              {record.state.value !== 'notActive' ? <StopOutlined /> : <CheckCircleOutlined />}
-            </Tooltip>
-          </Popconfirm>
-        </a>,
-
-        <a key={'delete'}>
-          <Popconfirm
-            title="确认删除"
-            onConfirm={async () => {
-              await service.remove(record.id);
-              message.success(
-                intl.formatMessage({
-                  id: 'pages.data.option.success',
-                  defaultMessage: '操作成功!',
-                }),
-              );
-              actionRef.current?.reload();
-            }}
-          >
-            <Tooltip title={'删除'}>
-              <DeleteOutlined />
-            </Tooltip>
-          </Popconfirm>
-        </a>,
-      ],
+      render: (text, record) => tools(record),
     },
   ];
 
@@ -369,12 +364,13 @@ const Instance = () => {
             setBindKeys(selectedRows.map((item) => item.id));
           },
         }}
-        toolBarRender={() => [
+        headerTitle={[
           <Button
             onClick={() => {
               setVisible(true);
               setCurrent({});
             }}
+            style={{ marginRight: 12 }}
             key="button"
             icon={<PlusOutlined />}
             type="primary"
@@ -388,11 +384,11 @@ const Instance = () => {
             <Button>批量操作</Button>
           </Dropdown>,
         ]}
-        cardRender={(item) => <DeviceCard {...item} />}
+        cardRender={(record) => <DeviceCard {...record} actions={tools(record)} />}
       />
       <Save
         data={current}
-        model={!current ? 'add' : 'edit'}
+        model={!Object.keys(current).length ? 'add' : 'edit'}
         close={() => {
           setVisible(false);
         }}

+ 1 - 0
src/pages/device/Instance/typings.d.ts

@@ -37,6 +37,7 @@ export type DeviceInstance = {
   onlineTime: string | number;
   offlineTime: string | number;
   tags: any;
+  photoUrl: string;
 };
 
 type Unit = {

+ 5 - 0
src/style/common.less

@@ -0,0 +1,5 @@
+.ellipsis {
+  overflow: hidden;
+  white-space: nowrap; //文本不会换行
+  text-overflow: ellipsis; //文本溢出显示省略号
+}