sun-chaochao 3 лет назад
Родитель
Сommit
e7f4cf37f3

BIN
public/images/northbound/aliyun.png


BIN
public/images/northbound/aliyun1.jpg


BIN
public/images/northbound/aliyun2.png


BIN
public/images/northbound/图片44.png


+ 0 - 0
public/images/network/doeros.jpg


+ 39 - 0
src/components/ProTableCard/CardItems/aliyun.tsx

@@ -0,0 +1,39 @@
+import React from 'react';
+import { TableCard } from '@/components';
+import '@/style/common.less';
+import '../index.less';
+
+export interface AliyunCardProps extends AliCloudType {
+  detail?: React.ReactNode;
+  actions?: React.ReactNode[];
+  avatarSize?: number;
+}
+
+const defaultImage = require('/public/images/northbound/aliyun.png');
+
+export default (props: AliyunCardProps) => {
+  return (
+    <TableCard showStatus={false} actions={props.actions} showMask={false}>
+      <div className={'pro-table-card-item'}>
+        <div className={'card-item-avatar'}>
+          <img width={88} height={88} src={defaultImage} alt={''} />
+        </div>
+        <div className={'card-item-body'}>
+          <div className={'card-item-header'}>
+            <span className={'card-item-header-name ellipsis'}>{props.name}</span>
+          </div>
+          <div className={'card-item-content'}>
+            <div>
+              <label>网桥产品</label>
+              <div className={'ellipsis'}>{'网桥产品' || '--'}</div>
+            </div>
+            <div>
+              <label>说明</label>
+              <div className={'ellipsis'}>{'说明' || '--'}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </TableCard>
+  );
+};

+ 35 - 0
src/pages/Northbound/AliCloud/Detail/index.less

@@ -0,0 +1,35 @@
+.doc {
+  height: 750px;
+  padding: 24px;
+  overflow-y: auto;
+  color: rgba(#000, 0.8);
+  font-size: 14px;
+  background-color: #fafafa;
+
+  .url {
+    padding: 8px 16px;
+    color: #2f54eb;
+    background-color: rgba(#a7bdf7, 0.2);
+  }
+
+  h1 {
+    margin: 16px 0;
+    color: rgba(#000, 0.85);
+    font-weight: bold;
+    font-size: 14px;
+
+    &:first-child {
+      margin-top: 0;
+    }
+  }
+
+  h2 {
+    margin: 6px 0;
+    color: rgba(0, 0, 0, 0.8);
+    font-size: 14px;
+  }
+
+  .image {
+    margin: 16px 0;
+  }
+}

+ 259 - 0
src/pages/Northbound/AliCloud/Detail/index.tsx

@@ -0,0 +1,259 @@
+import { TitleComponent } from '@/components';
+import { PageContainer } from '@ant-design/pro-layout';
+import {
+  ArrayItems,
+  Form,
+  FormButtonGroup,
+  FormGrid,
+  FormItem,
+  Input,
+  Select,
+  Submit,
+  ArrayCollapse,
+} from '@formily/antd';
+import { createForm } from '@formily/core';
+import { createSchemaField } from '@formily/react';
+import { Card, Col, Row, Image } from 'antd';
+import { useEffect } from 'react';
+import { useParams } from 'umi';
+import './index.less';
+
+const Detail = () => {
+  const params = useParams<{ id: string }>();
+
+  const form = createForm({
+    validateFirst: true,
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      FormGrid,
+      Input,
+      Select,
+      ArrayItems,
+      ArrayCollapse,
+    },
+  });
+
+  const schema = {
+    type: 'object',
+    properties: {
+      name: {
+        type: 'string',
+        title: '名称',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          placeholder: '请输入名称',
+        },
+        'x-validator': [
+          {
+            max: 64,
+            message: '最多可输入64个字符',
+          },
+        ],
+      },
+      address: {
+        type: 'string',
+        title: '服务地址',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Select',
+        'x-component-props': {
+          placeholder: '请选择服务地址',
+        },
+        'x-decorator-props': {
+          tooltip: '阿里云内部给每台机器设置的唯一编号',
+        },
+      },
+      accessKey: {
+        type: 'string',
+        title: 'accessKey',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          placeholder: '请输入accessKey',
+        },
+        'x-validator': [
+          {
+            max: 64,
+            message: '最多可输入64个字符',
+          },
+        ],
+        'x-decorator-props': {
+          tooltip: '用于程序通知方式调用云服务API的用户标识',
+        },
+      },
+      accessSecret: {
+        type: 'string',
+        title: 'accessSecret',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          placeholder: '请输入accessSecret',
+        },
+        'x-validator': [
+          {
+            max: 64,
+            message: '最多可输入64个字符',
+          },
+        ],
+        'x-decorator-props': {
+          tooltip: '用于程序通知方式调用云服务费API的秘钥标识',
+        },
+      },
+      network: {
+        type: 'string',
+        title: '网桥产品',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Select',
+        'x-component-props': {
+          placeholder: '请选择网桥产品',
+        },
+        'x-decorator-props': {
+          tooltip: '物联网平台对应的阿里云产品',
+        },
+      },
+      array: {
+        type: 'array',
+        'x-component': 'ArrayCollapse',
+        title: '产品映射',
+        items: {
+          type: 'object',
+          'x-component': 'ArrayCollapse.CollapsePanel',
+          'x-component-props': {
+            header: '产品映射',
+          },
+          properties: {
+            grid: {
+              type: 'void',
+              'x-component': 'FormGrid',
+              'x-component-props': {
+                minColumns: [24],
+                maxColumns: [24],
+              },
+              properties: {
+                product1: {
+                  type: 'string',
+                  'x-decorator': 'FormItem',
+                  title: '阿里云产品',
+                  required: true,
+                  'x-component': 'Select',
+                  'x-component-props': {
+                    placeholder: '请选择阿里云产品',
+                  },
+                  'x-decorator-props': {
+                    gridSpan: 12,
+                    tooltip: '阿里云物联网平台产品标识',
+                  },
+                },
+                product2: {
+                  type: 'string',
+                  title: '平台产品',
+                  required: true,
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Select',
+                  'x-decorator-props': {
+                    gridSpan: 12,
+                  },
+                  'x-component-props': {
+                    placeholder: '请选择平台产品',
+                  },
+                },
+              },
+            },
+            remove: {
+              type: 'void',
+              'x-component': 'ArrayCollapse.Remove',
+            },
+          },
+        },
+        properties: {
+          addition: {
+            type: 'void',
+            title: '添加',
+            'x-component': 'ArrayCollapse.Addition',
+          },
+        },
+      },
+      description: {
+        title: '说明',
+        'x-component': 'Input.TextArea',
+        'x-decorator': 'FormItem',
+        'x-component-props': {
+          rows: 3,
+          showCount: true,
+          maxLength: 200,
+          placeholder: '请输入说明',
+        },
+      },
+    },
+  };
+
+  useEffect(() => {}, [params.id]);
+
+  return (
+    <PageContainer>
+      <Card>
+        <Row gutter={24}>
+          <Col span={14}>
+            <TitleComponent data={'基本信息'} />
+            <Form form={form} layout="vertical" onAutoSubmit={console.log}>
+              <SchemaField schema={schema} />
+              <FormButtonGroup.FormItem>
+                <Submit>保存</Submit>
+              </FormButtonGroup.FormItem>
+            </Form>
+          </Col>
+          <Col span={10}>
+            <div className="doc">
+              <div className="url">
+                阿里云物联网平台:
+                <a
+                  style={{ wordBreak: 'break-all' }}
+                  href="https://help.aliyun.com/document_detail/87368.html"
+                >
+                  https://help.aliyun.com/document_detail/87368.html
+                </a>
+              </div>
+              <h1>1. 概述</h1>
+              <div>
+                在特定场景下,设备无法直接接入阿里云物联网平台时,您可先将设备接入物联网云平台,再使用阿里云“云云对接SDK”,快速构建桥接服务,搭建物联网平台与阿里云物联网平台的双向数据通道。
+              </div>
+              <div className={'image'}>
+                <Image width="100%" src={require('/public/images/northbound/aliyun2.png')} />
+              </div>
+              <h1>2.配置说明</h1>
+              <div>
+                <h2> 1、服务地址</h2>
+                <div>
+                  阿里云内部给每台机器设置的唯一编号。请根据购买的阿里云服务器地址进行选择。
+                </div>
+                <h2> 2、AccesskeyID/Secret</h2>
+                <div>
+                  用于程序通知方式调用云服务费API的用户标识和秘钥获取路径:“阿里云管理控制台”--“用户头像”--“”--“AccessKey管理”--“查看”
+                </div>
+                <div className={'image'}>
+                  <Image width="100%" src={require('/public/images/northbound/aliyun1.jpg')} />
+                </div>
+                <h2> 3. 网桥产品</h2>
+                <div>
+                  物联网平台对于阿里云物联网平台,是一个网关设备,需要映射到阿里云物联网平台的具体产品
+                </div>
+                <h2> 4. 产品映射</h2>
+                <div>将阿里云物联网平台中的产品实例与物联网平台的产品实例进行关联</div>
+              </div>
+            </div>
+          </Col>
+        </Row>
+      </Card>
+    </PageContainer>
+  );
+};
+
+export default Detail;

+ 212 - 1
src/pages/Northbound/AliCloud/index.tsx

@@ -1,5 +1,216 @@
 import { PageContainer } from '@ant-design/pro-layout';
+import SearchComponent from '@/components/SearchComponent';
+import { useRef, useState } from 'react';
+import { history } from 'umi';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { PermissionButton, ProTableCard } from '@/components';
+import {
+  DeleteOutlined,
+  EditOutlined,
+  ExclamationCircleFilled,
+  PlusOutlined,
+} from '@ant-design/icons';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
+import AliyunCard from '@/components/ProTableCard/CardItems/aliyun';
+import Service from './service';
+
+export const service = new Service('device-instance');
 
 export default () => {
-  return <PageContainer>AliCloud</PageContainer>;
+  const actionRef = useRef<ActionType>();
+  const intl = useIntl();
+  const [searchParams, setSearchParams] = useState<any>({});
+
+  const { permission } = PermissionButton.usePermission('Northbound/AliCloud');
+
+  const Tools = (record: any, type: 'card' | 'table') => {
+    return [
+      <PermissionButton
+        key={'update'}
+        type={'link'}
+        style={{ padding: 0 }}
+        isPermission={permission.update}
+        tooltip={
+          type === 'table'
+            ? {
+                title: intl.formatMessage({
+                  id: 'pages.data.option.edit',
+                  defaultMessage: '编辑',
+                }),
+              }
+            : undefined
+        }
+        onClick={() => {}}
+      >
+        <EditOutlined />
+        {type !== 'table' &&
+          intl.formatMessage({
+            id: 'pages.data.option.edit',
+            defaultMessage: '编辑',
+          })}
+      </PermissionButton>,
+      <PermissionButton
+        key={'delete'}
+        type={'link'}
+        style={{ padding: 0 }}
+        isPermission={permission.delete}
+        disabled={record.state.value === 'started'}
+        popConfirm={{
+          title: '确认删除?',
+          disabled: record.state.value === 'started',
+          onConfirm: () => {},
+        }}
+        tooltip={{
+          title:
+            record.state.value === 'started' ? <span>请先禁用,再删除</span> : <span>删除</span>,
+        }}
+      >
+        <DeleteOutlined />
+      </PermissionButton>,
+    ];
+  };
+
+  const columns: ProColumns<AliCloudType>[] = [
+    {
+      title: '名称',
+      dataIndex: 'name',
+    },
+    {
+      title: '网桥产品',
+      dataIndex: 'name1',
+    },
+    {
+      title: '说明',
+      dataIndex: 'description',
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.data.option',
+        defaultMessage: '操作',
+      }),
+      valueType: 'option',
+      align: 'center',
+      width: 200,
+      render: (text, record) => Tools(record, 'table'),
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <SearchComponent<AliCloudType>
+        field={columns}
+        target="device-instance"
+        onSearch={(data) => {
+          actionRef.current?.reset?.();
+          setSearchParams(data);
+        }}
+      />
+      <div style={{ backgroundColor: 'white', width: '100%', height: 60, padding: 20 }}>
+        <div
+          style={{
+            padding: 10,
+            width: '100%',
+            color: 'rgba(0, 0, 0, 0.55)',
+            backgroundColor: '#f6f6f6',
+          }}
+        >
+          <ExclamationCircleFilled style={{ marginRight: 10 }} />
+          将平台产品与设备数据通过API的方式同步到阿里云物联网平台
+        </div>
+      </div>
+      <ProTableCard<AliCloudType>
+        rowKey="id"
+        search={false}
+        columns={columns}
+        actionRef={actionRef}
+        params={searchParams}
+        options={{ fullScreen: true }}
+        request={(params) =>
+          service.query({
+            ...params,
+            sorts: [
+              {
+                name: 'createTime',
+                order: 'desc',
+              },
+            ],
+          })
+        }
+        pagination={{ pageSize: 10 }}
+        headerTitle={[
+          <PermissionButton
+            onClick={() => {
+              const url = `${getMenuPathByParams(MENUS_CODE['Northbound/AliCloud/Detail'])}`;
+              console.log(url);
+              history.push(url);
+            }}
+            style={{ marginRight: 12 }}
+            isPermission={permission.add}
+            key="button"
+            icon={<PlusOutlined />}
+            type="primary"
+          >
+            {intl.formatMessage({
+              id: 'pages.data.option.add',
+              defaultMessage: '新增',
+            })}
+          </PermissionButton>,
+        ]}
+        cardRender={(record) => (
+          <AliyunCard
+            {...record}
+            actions={[
+              <PermissionButton
+                type={'link'}
+                onClick={() => {}}
+                key={'edit'}
+                isPermission={permission.update}
+              >
+                <EditOutlined />
+                {intl.formatMessage({
+                  id: 'pages.data.option.edit',
+                  defaultMessage: '编辑',
+                })}
+              </PermissionButton>,
+              <PermissionButton
+                key="delete"
+                isPermission={permission.delete}
+                type={'link'}
+                style={{ padding: 0 }}
+                // tooltip={
+                //   record.state.value !== 'notActive'
+                //     ? { title: intl.formatMessage({ id: 'pages.device.instance.deleteTip' }) }
+                //     : undefined
+                // }
+                // disabled={record.state.value !== 'notActive'}
+                // popConfirm={{
+                //   title: intl.formatMessage({
+                //     id: 'pages.data.option.remove.tips',
+                //   }),
+                //   disabled: record.state.value !== 'notActive',
+                //   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' }));
+                //     }
+                //   },
+                // }}
+              >
+                <DeleteOutlined />
+              </PermissionButton>,
+            ]}
+          />
+        )}
+      />
+    </PageContainer>
+  );
 };

+ 16 - 0
src/pages/Northbound/AliCloud/service.ts

@@ -0,0 +1,16 @@
+import BaseService from '@/utils/BaseService';
+import { request } from 'umi';
+import SystemConst from '@/utils/const';
+
+class Service extends BaseService<AliCloudType> {
+  public detail = (id: string) => request(`${this.uri}/${id}/detail`, { method: 'GET' });
+
+  // 查询产品列表
+  public getProductList = (params?: any) =>
+    request(`/${SystemConst.API_BASE}/device/product/_query/no-paging?paging=false`, {
+      method: 'GET',
+      params,
+    });
+}
+
+export default Service;

+ 4 - 0
src/pages/Northbound/AliCloud/typings.d.ts

@@ -0,0 +1,4 @@
+type AliCloudType = {
+  id: string;
+  name: string;
+};

+ 70 - 0
src/pages/device/Instance/Detail/MetadataLog/Property/AMap.tsx

@@ -0,0 +1,70 @@
+import { AMap, PathSimplifier } from '@/components';
+import { Button, Space } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+
+interface Props {
+  value: any;
+  name: string;
+}
+
+export default (props: Props) => {
+  const [speed] = useState(1000000);
+  const PathNavigatorRef = useRef<PathNavigator | null>(null);
+  const [dataSource, setDataSource] = useState<any>({});
+
+  useEffect(() => {
+    const list: any[] = [];
+    (props?.value?.data || []).forEach((item: any) => {
+      list.push([item.value.lon, item.value.lat]);
+    });
+    setDataSource({
+      name: props?.name || '',
+      path: [...list],
+    });
+  }, [props.value]);
+  return (
+    <div style={{ position: 'relative' }}>
+      <div style={{ position: 'absolute', right: 0, top: 5, zIndex: 999 }}>
+        <Space>
+          <Button
+            type="primary"
+            onClick={() => {
+              if (PathNavigatorRef.current) {
+                PathNavigatorRef.current.start();
+              }
+            }}
+          >
+            开始动画
+          </Button>
+          <Button
+            type="primary"
+            onClick={() => {
+              if (PathNavigatorRef.current) {
+                PathNavigatorRef.current.stop();
+              }
+            }}
+          >
+            停止动画
+          </Button>
+        </Space>
+      </div>
+      <AMap
+        AMapUI
+        style={{
+          height: 500,
+          width: '100%',
+        }}
+      >
+        <PathSimplifier pathData={[dataSource]}>
+          <PathSimplifier.PathNavigator
+            speed={speed}
+            isAuto={false}
+            onCreate={(nav) => {
+              PathNavigatorRef.current = nav;
+            }}
+          />
+        </PathSimplifier>
+      </AMap>
+    </div>
+  );
+};

+ 14 - 2
src/pages/device/Instance/Detail/MetadataLog/Property/Detail.tsx

@@ -1,5 +1,5 @@
 import { Modal, Input } from 'antd';
-// import ReactMarkdown from "react-markdown";
+import ReactJson from 'react-json-view';
 
 interface Props {
   close: () => void;
@@ -15,7 +15,18 @@ const Detail = (props: Props) => {
       return (
         <div>
           <div>自定义属性</div>
-          {JSON.stringify(value)}
+          <div>
+            {
+              // @ts-ignore
+              <ReactJson
+                displayObjectSize={false}
+                displayDataTypes={false}
+                style={{ marginTop: 10 }}
+                name={false}
+                src={value}
+              />
+            }
+          </div>
         </div>
       );
     } else {
@@ -32,6 +43,7 @@ const Detail = (props: Props) => {
     <Modal
       title="详情"
       visible
+      destroyOnClose={true}
       onOk={() => {
         props.close();
       }}

+ 76 - 6
src/pages/device/Instance/Detail/MetadataLog/Property/index.tsx

@@ -1,6 +1,16 @@
-import { service } from '@/pages/device/Instance';
+import { InstanceModel, service } from '@/pages/device/Instance';
 import { useParams } from 'umi';
-import { DatePicker, Modal, Radio, Select, Space, Table, Tabs } from 'antd';
+import {
+  DatePicker,
+  Modal,
+  Popconfirm,
+  Radio,
+  Select,
+  Space,
+  Table,
+  Tabs,
+  Tooltip as ATooltip,
+} from 'antd';
 import type { PropertyMetadata } from '@/pages/device/Product/typings';
 import encodeQuery from '@/utils/encodeQuery';
 import { useEffect, useState } from 'react';
@@ -9,6 +19,7 @@ import { Axis, Chart, Geom, Legend, Tooltip, Slider } from 'bizcharts';
 import FileComponent from '../../Running/Property/FileComponent';
 import { DownloadOutlined, SearchOutlined } from '@ant-design/icons';
 import Detail from './Detail';
+import AMap from './AMap';
 interface Props {
   visible: boolean;
   close: () => void;
@@ -33,6 +44,8 @@ const PropertyLog = (props: Props) => {
   const [detailVisible, setDetailVisible] = useState<boolean>(false);
   const [current, setCurrent] = useState<any>('');
 
+  const [geoList, setGeoList] = useState<any[]>([]);
+
   const columns = [
     {
       title: '时间',
@@ -62,13 +75,49 @@ const PropertyLog = (props: Props) => {
               }}
             />
           ) : (
-            <DownloadOutlined />
+            <ATooltip title="下载">
+              <Popconfirm
+                title="确认修改"
+                onConfirm={() => {
+                  const type = (record?.value || '').split('.').pop();
+                  const downloadUrl = record.value;
+                  const downNode = document.createElement('a');
+                  downNode.href = downloadUrl;
+                  downNode.download = `${InstanceModel.detail.name}-${data.name}${moment(
+                    new Date().getTime(),
+                  ).format('YYYY-MM-DD-HH-mm-ss')}.${type}`;
+                  downNode.style.display = 'none';
+                  document.body.appendChild(downNode);
+                  downNode.click();
+                  document.body.removeChild(downNode);
+                }}
+              >
+                <DownloadOutlined />
+              </Popconfirm>
+            </ATooltip>
           )}
         </a>
       ),
     },
   ];
 
+  const geoColumns = [
+    {
+      title: '时间',
+      dataIndex: 'timestamp',
+      key: 'timestamp',
+      render: (text: any) => <span>{text ? moment(text).format('YYYY-MM-DD HH:mm:ss') : ''}</span>,
+    },
+    {
+      title: '位置',
+      dataIndex: 'value',
+      key: 'value',
+      render: (text: any, record: any) => (
+        <FileComponent type="table" value={{ formatValue: record.value }} data={data} />
+      ),
+    },
+  ];
+
   const tabList = [
     {
       tab: '列表',
@@ -140,7 +189,6 @@ const PropertyLog = (props: Props) => {
   };
 
   useEffect(() => {
-    console.log(data);
     if (visible) {
       handleSearch(
         {
@@ -179,7 +227,7 @@ const PropertyLog = (props: Props) => {
               );
             }}
             dataSource={dataSource?.data || []}
-            columns={columns}
+            columns={data?.valueType?.type === 'geoPoint' ? geoColumns : columns}
             pagination={{
               pageSize: dataSource?.pageSize || 10,
               showSizeChanger: true,
@@ -317,7 +365,8 @@ const PropertyLog = (props: Props) => {
       visible={visible}
       onCancel={() => close()}
       onOk={() => close()}
-      width="45vw"
+      destroyOnClose={true}
+      width="50vw"
     >
       <div style={{ marginBottom: '20px' }}>
         <Space>
@@ -430,6 +479,22 @@ const PropertyLog = (props: Props) => {
               });
             }
           }
+          if (key === 'geo') {
+            service
+              .getPropertyData(
+                params.id,
+                encodeQuery({
+                  paging: false,
+                  terms: { property: data.id, timestamp$BTW: start && end ? [start, end] : [] },
+                  sorts: { timestamp: 'desc' },
+                }),
+              )
+              .then((resp) => {
+                if (resp.status === 200) {
+                  setGeoList(resp.result);
+                }
+              });
+          }
         }}
       >
         {tabList.map((item) => (
@@ -437,6 +502,11 @@ const PropertyLog = (props: Props) => {
             {renderComponent(item.key)}
           </Tabs.TabPane>
         ))}
+        {data?.valueType?.type === 'geoPoint' && (
+          <Tabs.TabPane tab="轨迹" key="geo">
+            <AMap value={geoList} name={data.name} />
+          </Tabs.TabPane>
+        )}
       </Tabs>
       {detailVisible && (
         <Detail

+ 1 - 1
src/pages/device/Instance/Detail/Running/Property/FileComponent/index.tsx

@@ -53,7 +53,7 @@ const FileComponent = (props: Props) => {
           <img src={imgMap.get(flag) || imgMap.get('other')} />
         </div>
       );
-    } else if (data?.valueType?.type === 'object') {
+    } else if (data?.valueType?.type === 'object' || data?.valueType?.type === 'geoPoint') {
       return (
         <div className={props.type === 'card' ? styles.other : {}}>
           {JSON.stringify(value?.formatValue)}

+ 17 - 1
src/pages/device/Instance/Detail/Tags/Edit.tsx

@@ -4,6 +4,7 @@ import { InstanceModel, service } from '@/pages/device/Instance';
 import { ArrayTable, FormItem, Input } from '@formily/antd';
 import { message, Modal } from 'antd';
 import { useIntl } from 'umi';
+import GeoComponent from './location/GeoComponent';
 
 interface Props {
   close: () => void;
@@ -25,6 +26,7 @@ const Edit = (props: Props) => {
       FormItem,
       Input,
       ArrayTable,
+      GeoComponent,
     },
   });
 
@@ -51,7 +53,6 @@ const Edit = (props: Props) => {
                   type: 'string',
                   'x-decorator': 'FormItem',
                   'x-component': 'Input',
-                  // 'x-disabled': true
                 },
               },
             },
@@ -84,10 +85,25 @@ const Edit = (props: Props) => {
                 }),
               },
               properties: {
+                type: {
+                  type: 'string',
+                  name: '类型',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Input',
+                  'x-hidden': true,
+                },
                 value: {
                   type: 'string',
                   'x-decorator': 'FormItem',
                   'x-component': 'Input',
+                  'x-reactions': {
+                    dependencies: ['.type'],
+                    fulfill: {
+                      state: {
+                        componentType: '{{$deps[0]==="geoPoint"?"GeoComponent":"Input"}}',
+                      },
+                    },
+                  },
                 },
               },
             },

+ 42 - 0
src/pages/device/Instance/Detail/Tags/location/AMap.tsx

@@ -0,0 +1,42 @@
+import { AMap } from '@/components';
+import { Input, Modal } from 'antd';
+import { useEffect } from 'react';
+
+interface Props {
+  value: any;
+  close: () => void;
+  ok: (data: any) => void;
+}
+
+export default (props: Props) => {
+  useEffect(() => {}, [props.value]);
+  return (
+    <Modal
+      visible
+      title="地理位置"
+      width={'55vw'}
+      onCancel={() => props.close()}
+      onOk={() => {
+        props.ok('');
+      }}
+    >
+      <div style={{ position: 'relative' }}>
+        <div style={{ position: 'absolute', right: 5, top: 5, zIndex: 999 }}>
+          <Input />
+        </div>
+        <AMap
+          AMapUI
+          style={{
+            height: 500,
+            width: '100%',
+          }}
+          events={{
+            click: (value: any) => {
+              console.log(value);
+            },
+          }}
+        />
+      </div>
+    </Modal>
+  );
+};

+ 40 - 0
src/pages/device/Instance/Detail/Tags/location/GeoComponent.tsx

@@ -0,0 +1,40 @@
+import { EnvironmentOutlined } from '@ant-design/icons';
+import { Input } from 'antd';
+import { useState } from 'react';
+import AMap from './AMap';
+
+interface Props {
+  value: string;
+  onChange: (value: string) => void;
+}
+
+const GeoComponent = (props: Props) => {
+  const [visible, setVisible] = useState<boolean>(false);
+  return (
+    <div>
+      <Input
+        readOnly
+        addonAfter={
+          <EnvironmentOutlined
+            onClick={() => {
+              setVisible(true);
+            }}
+          />
+        }
+        value={props.value}
+      />
+      {visible && (
+        <AMap
+          value={props.value}
+          close={() => {
+            setVisible(false);
+          }}
+          ok={(param) => {
+            console.log(param);
+          }}
+        />
+      )}
+    </div>
+  );
+};
+export default GeoComponent;

+ 2 - 0
src/utils/menu/router.ts

@@ -111,6 +111,7 @@ export enum MENUS_CODE {
   'link/Type/Detail' = 'link/Type/Detail',
   'Northbound/DuerOS' = 'Northbound/DuerOS',
   'Northbound/AliCloud' = 'Northbound/AliCloud',
+  'Northbound/AliCloud/Detail' = 'Northbound/AliCloud/Detail',
 }
 
 export type MENUS_CODE_TYPE = keyof typeof MENUS_CODE | string;
@@ -150,4 +151,5 @@ export const getDetailNameByCode = {
   'link/AccessConfig/Detail': '配置详情',
   'media/Stream/Detail': '流媒体详情',
   'rule-engine/Alarm/Log/Detail': '告警日志',
+  'Northbound/AliCloud/Detail': '阿里云详情',
 };