Prechádzať zdrojové kódy

feat: 设备接入配置

sun-chaochao 3 rokov pred
rodič
commit
a65fc23639

+ 71 - 0
src/pages/link/AccessConfig/Detail/Access/index.less

@@ -0,0 +1,71 @@
+.box {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin: 20px 30px;
+  .steps {
+    width: 100%;
+  }
+
+  .content {
+    width: 100%;
+    margin: 20px 0;
+  }
+
+  .action {
+    width: 100%;
+  }
+}
+
+.title {
+  font-weight: 600;
+}
+
+.desc {
+  width: 100%;
+  margin-top: 10px;
+  overflow: hidden;
+  color: rgba(0, 0, 0, 0.55);
+  font-weight: 400;
+  font-size: 13px;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+
+.cardContent {
+  display: flex;
+  margin-top: 10px;
+  color: rgba(0, 0, 0, 0.55);
+  .item {
+    width: 100%;
+    margin: 5px 0;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+  }
+}
+
+.view {
+  display: flex;
+  justify-content: space-between;
+  .info,
+  .config {
+    width: 48%;
+    .title {
+      width: 100%;
+      margin-bottom: 10px;
+      font-weight: 600;
+    }
+    .title::before {
+      margin-right: 10px;
+      background-color: #2810ff;
+      content: '|';
+    }
+  }
+}
+
+.search {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}

+ 448 - 0
src/pages/link/AccessConfig/Detail/Access/index.tsx

@@ -0,0 +1,448 @@
+import {
+  Alert,
+  Badge,
+  Button,
+  Card,
+  Col,
+  Descriptions,
+  Empty,
+  Form,
+  Input,
+  message,
+  Row,
+  Steps,
+} from 'antd';
+import { useEffect, useState } from 'react';
+import styles from './index.less';
+import { service } from '@/pages/link/AccessConfig';
+import encodeQuery from '@/utils/encodeQuery';
+import { useHistory } from 'umi';
+import { getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
+
+interface Props {
+  change: () => void;
+  data: any;
+}
+const Access = (props: Props) => {
+  const [form] = Form.useForm();
+
+  const history = useHistory();
+
+  const [current, setCurrent] = useState<number>(0);
+  const [networkList, setNetworkList] = useState<any[]>([]);
+  const [procotolList, setProcotolList] = useState<any[]>([]);
+  const [procotolCurrent, setProcotolCurrent] = useState<string>('');
+  const [networkCurrent, setNetworkCurrent] = useState<string>('');
+  // const [config, setConfig] = useState<any>();
+
+  const MetworkTypeMapping = new Map();
+  MetworkTypeMapping.set('websocket-server', 'WEB_SOCKET_SERVER');
+  MetworkTypeMapping.set('http-server-gateway', 'HTTP_SERVER');
+  MetworkTypeMapping.set('udp-device-gateway', 'UDP');
+  MetworkTypeMapping.set('coap-server-gateway', 'COAP_SERVER');
+  MetworkTypeMapping.set('mqtt-client-gateway', 'MQTT_CLIENT');
+  MetworkTypeMapping.set('mqtt-server-gateway', 'MQTT_SERVER');
+  MetworkTypeMapping.set('tcp-server-gateway', 'TCP_SERVER');
+
+  const ProcotoleMapping = new Map();
+  ProcotoleMapping.set('websocket-server', 'WebSocket');
+  ProcotoleMapping.set('http-server-gateway', 'HTTP');
+  ProcotoleMapping.set('udp-device-gateway', 'UDP');
+  ProcotoleMapping.set('coap-server-gateway', 'COAP');
+  ProcotoleMapping.set('mqtt-client-gateway', 'MQTT');
+  ProcotoleMapping.set('mqtt-server-gateway', 'MQTT');
+  ProcotoleMapping.set('tcp-server-gateway', 'TCP');
+
+  const queryNetworkList = (params?: any) => {
+    service.getNetworkList(MetworkTypeMapping.get(props.data?.id), params).then((resp) => {
+      if (resp.status === 200) {
+        setNetworkList(resp.result);
+      }
+    });
+  };
+
+  const queryProcotolList = (params?: any) => {
+    service.getProtocolList(ProcotoleMapping.get(props.data?.id), params).then((resp) => {
+      if (resp.status === 200) {
+        setProcotolList(resp.result);
+      }
+    });
+  };
+
+  useEffect(() => {
+    if (props.data) {
+      queryNetworkList();
+      setCurrent(0);
+    }
+  }, [props.data]);
+
+  const next = () => {
+    if (current === 0) {
+      if (!networkCurrent) {
+        message.error('请选择网络组件!');
+      } else {
+        queryProcotolList();
+        setCurrent(current + 1);
+      }
+    }
+    if (current === 1) {
+      if (!procotolCurrent) {
+        message.error('请选择消息协议!');
+      } else {
+        service
+          .getConfigView(procotolCurrent, ProcotoleMapping.get(props.data?.id))
+          .then((resp) => {
+            if (resp.status === 200) {
+              // setConfig(resp.result)
+            }
+          });
+        setCurrent(current + 1);
+      }
+    }
+  };
+
+  const prev = () => {
+    setCurrent(current - 1);
+  };
+
+  const steps = [
+    {
+      title: '网络组件',
+    },
+    {
+      title: '消息协议',
+    },
+    {
+      title: '完成',
+    },
+  ];
+
+  // const columns = [
+  //     {
+  //         title: '姓名',
+  //         dataIndex: 'name',
+  //         key: 'name',
+  //     },
+  //     {
+  //         title: '年龄',
+  //         dataIndex: 'age',
+  //         key: 'age',
+  //     },
+  //     {
+  //         title: '住址',
+  //         dataIndex: 'address',
+  //         key: 'address',
+  //     },
+  //     {
+  //         title: '姓名',
+  //         dataIndex: 'name',
+  //         key: 'name',
+  //     },
+  //     {
+  //         title: '年龄',
+  //         dataIndex: 'age',
+  //         key: 'age',
+  //     },
+  //     {
+  //         title: '住址',
+  //         dataIndex: 'address',
+  //         key: 'address',
+  //     },
+  // ];
+
+  // const dataSource = [
+  //     {
+  //         key: '1',
+  //         name: '胡彦斌',
+  //         age: 32,
+  //         address: '西湖区湖底公园1号',
+  //     },
+  //     {
+  //         key: '2',
+  //         name: '胡彦祖',
+  //         age: 42,
+  //         address: '西湖区湖底公园1号',
+  //     },
+  // ];
+
+  const renderSteps = (cur: number) => {
+    switch (cur) {
+      case 0:
+        return (
+          <div>
+            <Alert message="选择与设备通信的网络组件" type="warning" showIcon />
+            <div className={styles.search}>
+              <Input.Search
+                placeholder="请输入名称"
+                onSearch={(value: string) => {
+                  queryNetworkList(
+                    encodeQuery({
+                      terms: {
+                        name$LIKE: `%${value}%`,
+                      },
+                    }),
+                  );
+                }}
+                style={{ width: 500, margin: '20px 0' }}
+              />
+              <Button
+                type="primary"
+                onClick={() => {
+                  history.push(`${getMenuPathByCode(MENUS_CODE['link/Type/Save'])}`);
+                }}
+              >
+                新增
+              </Button>
+            </div>
+            {networkList.length > 0 ? (
+              <Row gutter={[16, 16]}>
+                {networkList.map((item) => (
+                  <Col key={item.name} span={8}>
+                    <Card
+                      style={{
+                        width: '100%',
+                        border: networkCurrent === item.id ? '1px solid #1d39c4' : '',
+                      }}
+                      hoverable
+                      onClick={() => {
+                        setNetworkCurrent(item.id);
+                      }}
+                    >
+                      <div className={styles.title}>{item.name}</div>
+                      <div className={styles.cardContent}>
+                        <div style={{ width: '40%' }}>
+                          <div className={styles.item}>
+                            {MetworkTypeMapping.get(props.data?.id)}
+                          </div>
+                          <div className={styles.item}>共享配置</div>
+                        </div>
+                        <div style={{ width: '60%' }}>
+                          {item.addresses.slice(0, 2).map((i: any) => (
+                            <div className={styles.item} key={i.address}>
+                              公网: {i.address}
+                            </div>
+                          ))}
+                        </div>
+                      </div>
+                    </Card>
+                  </Col>
+                ))}
+              </Row>
+            ) : (
+              <Empty
+                image="https://gw.alipayobjects.com/zos/antfincdn/ZHrcdLPrvN/empty.svg"
+                imageStyle={{
+                  height: 60,
+                }}
+                description={
+                  <span>
+                    暂无数据{' '}
+                    <a
+                      onClick={() => {
+                        history.push(`${getMenuPathByCode(MENUS_CODE['link/Type/Save'])}`);
+                      }}
+                    >
+                      创建接入方式
+                    </a>
+                  </span>
+                }
+              />
+            )}
+          </div>
+        );
+      case 1:
+        return (
+          <div>
+            <Alert
+              message="使用选择的消息协议,对网络组件通信数据进行编解码、认证等操作"
+              type="warning"
+              showIcon
+            />
+            <div className={styles.search}>
+              <Input.Search
+                placeholder="请输入名称"
+                onSearch={(value: string) => {
+                  queryProcotolList(
+                    encodeQuery({
+                      terms: {
+                        name$LIKE: `%${value}%`,
+                      },
+                    }),
+                  );
+                }}
+                style={{ width: 500, margin: '20px 0' }}
+              />
+              <Button
+                type="primary"
+                onClick={() => {
+                  history.push(`${getMenuPathByCode(MENUS_CODE['link/Protocol'])}`);
+                }}
+              >
+                新增
+              </Button>
+            </div>
+            {procotolList.length > 0 ? (
+              <Row gutter={[16, 16]}>
+                {procotolList.map((item) => (
+                  <Col key={item.name} span={8}>
+                    <Card
+                      style={{
+                        width: '100%',
+                        border: procotolCurrent === item.id ? '1px solid #1d39c4' : '',
+                      }}
+                      hoverable
+                      onClick={() => {
+                        setProcotolCurrent(item.id);
+                      }}
+                    >
+                      <div className={styles.title}>{item.name}</div>
+                      <div className={styles.desc}>这里是协议包中的协议说明</div>
+                    </Card>
+                  </Col>
+                ))}
+              </Row>
+            ) : (
+              <Empty
+                image="https://gw.alipayobjects.com/zos/antfincdn/ZHrcdLPrvN/empty.svg"
+                imageStyle={{
+                  height: 60,
+                }}
+                description={
+                  <span>
+                    暂无数据
+                    <a
+                      onClick={() => {
+                        history.push(`${getMenuPathByCode(MENUS_CODE['link/Protocol'])}`);
+                      }}
+                    >
+                      去新增
+                    </a>
+                  </span>
+                }
+              />
+            )}
+          </div>
+        );
+      case 2:
+        return (
+          <div className={styles.view}>
+            <div className={styles.info}>
+              <div className={styles.title}>基本信息</div>
+              <Form name="basic" layout="vertical" form={form}>
+                <Form.Item
+                  label="名称"
+                  name="name"
+                  rules={[{ required: true, message: '请输入名称' }]}
+                >
+                  <Input />
+                </Form.Item>
+                <Form.Item name="description" label="说明">
+                  <Input.TextArea showCount maxLength={200} />
+                </Form.Item>
+              </Form>
+            </div>
+            <div className={styles.config}>
+              <div className={styles.title}>配置概览</div>
+              <Descriptions column={1}>
+                <Descriptions.Item label="接入方式">{props.data?.name || ''}</Descriptions.Item>
+                <Descriptions.Item>{props.data?.description || ''}</Descriptions.Item>
+                <Descriptions.Item label="消息协议">
+                  {procotolList.find((i) => i.id === procotolCurrent)?.name || ''}
+                </Descriptions.Item>
+                <Descriptions.Item>
+                  {procotolList.find((i) => i.id === procotolCurrent)?.description ||
+                    '----缺少描述呀----'}
+                </Descriptions.Item>
+                <Descriptions.Item label="网络组件">
+                  {(networkList.find((i) => i.id === networkCurrent)?.addresses || []).map(
+                    (item: any) => (
+                      <Badge
+                        key={item.address}
+                        color={item.health === -1 ? 'red' : 'green'}
+                        text={item.address}
+                        style={{ marginLeft: '20px' }}
+                      />
+                    ),
+                  )}
+                </Descriptions.Item>
+              </Descriptions>
+              {/* <div>
+                            <div>路由信息</div>
+                            <Table dataSource={dataSource} columns={columns} pagination={false} />
+                        </div> */}
+            </div>
+          </div>
+        );
+      default:
+        return null;
+    }
+  };
+
+  return (
+    <Card>
+      <Button
+        type="link"
+        onClick={() => {
+          props.change();
+        }}
+      >
+        返回
+      </Button>
+      <div className={styles.box}>
+        <div className={styles.steps}>
+          <Steps size="small" current={current}>
+            {steps.map((item) => (
+              <Steps.Step key={item.title} title={item.title} />
+            ))}
+          </Steps>
+        </div>
+        <div className={styles.content}>{renderSteps(current)}</div>
+        <div className={styles.action}>
+          {current < steps.length - 1 && (
+            <Button type="primary" onClick={() => next()}>
+              下一步
+            </Button>
+          )}
+          {current === steps.length - 1 && (
+            <Button
+              type="primary"
+              onClick={async () => {
+                try {
+                  const values = await form.validateFields();
+                  const params = {
+                    name: values.name,
+                    description: values.description,
+                    provider: props.data.id,
+                    protocol: procotolCurrent,
+                    transport: ProcotoleMapping.get(props.data.id),
+                    channel: 'network', // 网络组件
+                    channelId: networkCurrent,
+                  };
+                  service.save(params).then((resp: any) => {
+                    if (resp.status === 200) {
+                      message.success('操作成功!');
+                      setCurrent(0);
+                      setNetworkCurrent('');
+                      setProcotolCurrent('');
+                    }
+                  });
+                } catch (errorInfo) {
+                  console.error('Failed:', errorInfo);
+                }
+              }}
+            >
+              保存
+            </Button>
+          )}
+          {current > 0 && (
+            <Button style={{ margin: '0 8px' }} onClick={() => prev()}>
+              上一步
+            </Button>
+          )}
+        </div>
+      </div>
+    </Card>
+  );
+};
+
+export default Access;

+ 27 - 0
src/pages/link/AccessConfig/Detail/Provider/index.less

@@ -0,0 +1,27 @@
+.images {
+  width: 64px;
+  height: 64px;
+  color: white;
+  font-size: 12px;
+  line-height: 64px;
+  text-align: center;
+  background: linear-gradient(
+    128.453709216706deg,
+    rgba(255, 255, 255, 1) 4%,
+    rgba(113, 187, 255, 1) 43%,
+    rgba(24, 144, 255, 1) 100%
+  );
+  border: 1px solid rgba(242, 242, 242, 1);
+  border-radius: 50%;
+}
+
+.desc {
+  width: 100%;
+  margin-top: 10px;
+  overflow: hidden;
+  color: rgba(0, 0, 0, 0.55);
+  font-weight: 400;
+  font-size: 13px;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}

+ 74 - 0
src/pages/link/AccessConfig/Detail/Provider/index.tsx

@@ -0,0 +1,74 @@
+import { Button, Card, Col, Empty, Row } from 'antd';
+import { service } from '@/pages/link/AccessConfig';
+import { useEffect, useState } from 'react';
+import styles from './index.less';
+
+interface Props {
+  change: (id: string) => void;
+}
+
+const Provider = (props: Props) => {
+  const [dataSource, setDataSource] = useState<any[]>([]);
+
+  const handleSearch = () => {
+    service.getProviders().then((resp) => {
+      if (resp.status === 200) {
+        setDataSource(resp.result);
+      }
+    });
+  };
+
+  useEffect(() => {
+    handleSearch();
+  }, []);
+
+  return (
+    <Card style={{ padding: '20px' }}>
+      {dataSource.length > 0 ? (
+        <Row gutter={[16, 16]}>
+          {dataSource.map((item) => (
+            <Col key={item.name} span={12}>
+              <Card style={{ width: '100%' }} hoverable>
+                <div
+                  style={{
+                    width: '100%',
+                    display: 'flex',
+                    alignItems: 'center',
+                    justifyContent: 'space-between',
+                  }}
+                >
+                  <div
+                    style={{
+                      display: 'flex',
+                      width: 'calc(100% - 70px)',
+                    }}
+                  >
+                    <div className={styles.images}>{item.name}</div>
+                    <div style={{ margin: '10px', width: 'calc(100% - 84px)' }}>
+                      <div style={{ fontWeight: 600 }}>{item.name}</div>
+                      <div className={styles.desc}>{item.description}</div>
+                    </div>
+                  </div>
+                  <div style={{ width: '70px' }}>
+                    <Button
+                      type="primary"
+                      onClick={() => {
+                        props.change(item);
+                      }}
+                    >
+                      接入
+                    </Button>
+                  </div>
+                </div>
+              </Card>
+            </Col>
+          ))}
+        </Row>
+      ) : (
+        <Empty />
+      )}
+    </Card>
+  );
+};
+
+export default Provider;

+ 31 - 0
src/pages/link/AccessConfig/Detail/index.tsx

@@ -0,0 +1,31 @@
+import { PageContainer } from '@ant-design/pro-layout';
+import { useState } from 'react';
+import Access from './Access';
+import Provider from './Provider';
+
+const Detail = () => {
+  const [visible, setVisible] = useState<boolean>(true);
+  const [id, setId] = useState<any>({});
+
+  return (
+    <PageContainer>
+      {visible ? (
+        <Provider
+          change={(data: string) => {
+            setId(data);
+            setVisible(false);
+          }}
+        />
+      ) : (
+        <Access
+          data={id}
+          change={() => {
+            setVisible(true);
+          }}
+        />
+      )}
+    </PageContainer>
+  );
+};
+
+export default Detail;

+ 15 - 3
src/pages/link/AccessConfig/index.less

@@ -1,7 +1,9 @@
 .content {
   display: flex;
   width: 90%;
+  height: 100px;
   margin: 0 78px;
+  overflow: hidden;
   .server {
     width: calc(50% - 78px);
     :global {
@@ -11,15 +13,25 @@
     }
   }
   .procotol {
+    display: -webkit-box;
     width: calc(50% - 78px);
+    overflow: hidden;
+    text-overflow: ellipsis;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 4;
   }
 }
 
 .desc {
+  width: 50%;
   margin-top: 10px;
+  overflow: hidden;
   color: rgba(0, 0, 0, 0.55);
   font-weight: 400;
   font-size: 13px;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  background-color: antiquewhite;
 }
 
 .title {
@@ -36,11 +48,11 @@
 }
 
 .images {
-  width: 136px;
-  height: 108px;
+  width: 64px;
+  height: 64px;
   color: white;
   font-size: 18px;
-  line-height: 108px;
+  line-height: 64px;
   text-align: center;
   background: linear-gradient(
     128.453709216706deg,

+ 130 - 63
src/pages/link/AccessConfig/index.tsx

@@ -1,65 +1,102 @@
 import SearchComponent from '@/components/SearchComponent';
+import { getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
 import { CheckCircleOutlined, DeleteOutlined, EditOutlined, StopOutlined } from '@ant-design/icons';
 import { PageContainer } from '@ant-design/pro-layout';
 import ProList from '@jetlinks/pro-list';
-import { Badge, Card } from 'antd';
+import type { ProColumns } from '@jetlinks/pro-table';
+import { Badge, Button, Card, message, Popconfirm } from 'antd';
+import { useState } from 'react';
+import { useHistory } from 'umi';
 import styles from './index.less';
+import Service from './service';
+
+export const service = new Service('gateway/device');
 
 const AccessConfig = () => {
-  const dataSource = [
-    {
-      name: 'MQTT-官方协议接入',
-      avatar: 'MQTT',
-      state: 0,
-      describe: ' 我是一条测试的描述我是一条测试的描述我是一条测试的描述我是一条测试',
-    },
-    {
-      name: 'Modbus-TCP',
-      avatar: 'Modbus-TCP',
-      state: 0,
-      describe: ' 我是一条测试的描述我是一条测试的描述我是一条测试',
-    },
-    {
-      name: 'Modbus-TCP',
-      avatar: 'Modbus',
-      state: 1,
-      describe: ' 我是一条测试的描述我是一条测试的描述',
-    },
+  const history = useHistory();
+  const [param, setParam] = useState({});
+  // const actionRef = useRef<ActionType>();
+
+  const columns: ProColumns<any>[] = [
     {
-      name: 'MQTT-官方协议接入',
-      avatar: 'MQTT',
-      state: 0,
-      describe: ' 我是一条测试的描述',
+      title: '名称',
+      dataIndex: 'name',
     },
+    // {
+    //   title: '状态',
+    //   dataIndex: 'state',
+    //   align: 'center',
+    //   valueType: 'select',
+    //   valueEnum: {
+    //     // 1: {
+    //     //   text: intl.formatMessage({
+    //     //     id: 'pages.searchTable.titleStatus.normal',
+    //     //     defaultMessage: '正常',
+    //     //   }),
+    //     //   status: 1,
+    //     // },
+    //     // 0: {
+    //     //   text: intl.formatMessage({
+    //     //     id: 'pages.searchTable.titleStatus.disable',
+    //     //     defaultMessage: '禁用',
+    //     //   }),
+    //     //   status: 0,
+    //     // },
+    //   },
+    //   render: (text, record) => (
+    //     <Badge status={record.status === 1 ? 'success' : 'error'} text={text} />
+    //   ),
+    // },
   ];
 
   return (
     <PageContainer>
       <Card>
-        <SearchComponent field={[]} pattern={'simple'} onSearch={() => {}} />
+        <SearchComponent
+          field={columns}
+          pattern={'simple'}
+          onSearch={(data: any) => {
+            setParam(data);
+            // actionRef.current?.reset?.();
+          }}
+          onReset={() => {
+            setParam({});
+            // actionRef.current?.reset?.();
+          }}
+        />
+        <div style={{ width: '100%', display: 'flex', justifyContent: 'flex-end' }}>
+          <Button
+            type="primary"
+            onClick={() => {
+              history.push(`${getMenuPathByCode(MENUS_CODE['link/AccessConfig/Detail'])}`);
+            }}
+          >
+            新增
+          </Button>
+        </div>
         <ProList<any>
           pagination={{
             defaultPageSize: 8,
             showSizeChanger: false,
           }}
           showActions="always"
-          rowKey="name"
-          dataSource={dataSource}
+          rowKey="id"
+          // actionRef={actionRef}
+          request={async (data) =>
+            service.queryList({ ...param, ...data, sorts: [{ name: 'createTime', order: 'desc' }] })
+          }
           grid={{ gutter: 16, column: 2 }}
           showExtra="always"
           metas={{
             title: {
               dataIndex: 'name',
               render: (text, row) => (
-                <div style={{ fontSize: 16 }}>
+                <div style={{ fontSize: 16, width: '70%' }}>
                   <div>
                     {text}
-                    {/* <a style={{ marginLeft: '10px' }}>
-                      <EditOutlined />
-                    </a> */}
                     <Badge
-                      color={row.state !== 1 ? 'red' : 'green'}
-                      text={row.state !== 1 ? '禁用' : '正常'}
+                      color={row.state.value === 'disabled' ? 'red' : 'green'}
+                      text={row.state.text}
                       style={{ marginLeft: '20px' }}
                     />
                   </div>
@@ -68,7 +105,7 @@ const AccessConfig = () => {
               ),
             },
             avatar: {
-              render: (text, reocrd) => <div className={styles.images}>{reocrd.avatar}</div>,
+              render: (text, reocrd) => <div className={styles.images}>{reocrd.name}</div>,
             },
             subTitle: {
               render: () => <div></div>,
@@ -77,51 +114,81 @@ const AccessConfig = () => {
               render: (text, row) => (
                 <div className={styles.content}>
                   <div className={styles.server}>
-                    <div className={styles.title}>MQTT服务</div>
+                    <div className={styles.title}>{row?.channelInfo?.name}</div>
                     <p>
-                      <div>
-                        <Badge color={'green'} text={'mqtt://192.1.1:8080'} />
-                      </div>
-                      <div>
-                        <Badge color={'green'} text={'mqtt://192.1.1:8080'} />
-                      </div>
-                      <div>
-                        <Badge color={'red'} text={'mqtt://192.1.1:8080'} />
-                      </div>
+                      {row.channelInfo?.addresses.map((item: any) => (
+                        <div key={item.address}>
+                          <Badge color={'green'} text={item.address} />
+                        </div>
+                      ))}
                     </p>
                   </div>
                   <div className={styles.procotol}>
-                    <div className={styles.title}>官方协议1.0</div>
-                    <p style={{ color: 'rgba(0, 0, 0, .55)' }}>
-                      {row.describe}
-                      {row.describe}
-                    </p>
+                    <div className={styles.title}>{row?.protocolDetail?.name}</div>
+                    <p style={{ color: 'rgba(0, 0, 0, .55)' }}>{row.description}</p>
                   </div>
                 </div>
               ),
             },
             actions: {
               render: (text, row) => [
-                <a key="edit">
+                <a
+                  key="edit"
+                  onClick={() => {
+                    history.push(
+                      `${getMenuPathByCode(MENUS_CODE['link/AccessConfig/Detail'])}?id=${row.id}`,
+                    );
+                  }}
+                >
                   <EditOutlined />
                   编辑
                 </a>,
                 <a key="warning">
-                  {row.state === 1 ? (
-                    <span>
-                      <StopOutlined />
-                      禁用
-                    </span>
-                  ) : (
-                    <span>
-                      <CheckCircleOutlined />
-                      启用
-                    </span>
-                  )}
+                  <Popconfirm
+                    title={`确认${row.state.value !== 'disabled' ? '禁用' : '启用'}`}
+                    onConfirm={() => {
+                      if (row.state.value !== 'disabled') {
+                        service.shutDown(row.id).then((resp) => {
+                          if (resp.status === 200) {
+                            message.success('操作成功!');
+                          }
+                        });
+                      } else {
+                        service.startUp(row.id).then((resp) => {
+                          if (resp.status === 200) {
+                            message.success('操作成功!');
+                          }
+                        });
+                      }
+                    }}
+                  >
+                    {row.state.value !== 'disabled' ? (
+                      <span>
+                        <StopOutlined />
+                        禁用
+                      </span>
+                    ) : (
+                      <span>
+                        <CheckCircleOutlined />
+                        启用
+                      </span>
+                    )}
+                  </Popconfirm>
                 </a>,
                 <a key="remove">
-                  <DeleteOutlined />
-                  删除
+                  <Popconfirm
+                    title={'确认删除?'}
+                    onConfirm={() => {
+                      service.remove(row.id).then((resp: any) => {
+                        if (resp.status === 200) {
+                          message.success('操作成功!');
+                        }
+                      });
+                    }}
+                  >
+                    <DeleteOutlined />
+                    删除
+                  </Popconfirm>
                 </a>,
               ],
             },

+ 40 - 0
src/pages/link/AccessConfig/service.ts

@@ -0,0 +1,40 @@
+import { request } from 'umi';
+import BaseService from '@/utils/BaseService';
+import SystemConst from '@/utils/const';
+import type { AccessItem } from './typings';
+
+class Service extends BaseService<AccessItem> {
+  public queryList = (data: any) =>
+    request(`/${SystemConst.API_BASE}/gateway/device/detail/_query`, {
+      method: 'POST',
+      data,
+    });
+  public startUp = (id: string) =>
+    request(`/${SystemConst.API_BASE}/gateway/device/${id}/_startup`, {
+      method: 'POST',
+    });
+  public shutDown = (id: string) =>
+    request(`/${SystemConst.API_BASE}/gateway/device/${id}/__shutdown`, {
+      method: 'POST',
+    });
+  public getProviders = () =>
+    request(`/${SystemConst.API_BASE}/gateway/device/providers`, {
+      method: 'GET',
+    });
+  public getNetworkList = (networkType: string, params?: any) =>
+    request(`/${SystemConst.API_BASE}/network/config/${networkType}/_alive`, {
+      method: 'GET',
+      params,
+    });
+  public getProtocolList = (transport: string, params?: any) =>
+    request(`/${SystemConst.API_BASE}/protocol/supports/${transport}`, {
+      method: 'GET',
+      params,
+    });
+  public getConfigView = (id: string, transport: string) =>
+    request(`/${SystemConst.API_BASE}/protocol/${id}/transport/${transport}`, {
+      method: 'GET',
+    });
+}
+
+export default Service;

+ 19 - 0
src/pages/link/AccessConfig/typings.d.ts

@@ -0,0 +1,19 @@
+import type { BaseItem } from '@/utils/typings';
+
+type AccessItem = {
+  id: string | undefined;
+  name: string;
+  description: string;
+  provider: string;
+  protocol: string;
+  transport: string;
+  channel: string;
+  channelId: string;
+  state: {
+    text: string;
+    value: string;
+  };
+  channelInfo: Record<string, any>;
+  protocolDetail: Record<string, any>;
+  transportDetail: Record<string, any>;
+} & BaseItem;