hear 3 лет назад
Родитель
Сommit
c2d3dab09a

+ 2 - 2
config/proxy.ts

@@ -17,8 +17,8 @@ export default {
       // 测试环境
       // target: 'http://120.77.179.54:8844/',
       // ws: 'ws://120.77.179.54:8844/',
-      // target: 'http://192.168.32.65:8850/',
-      // ws: 'ws://192.168.32.65:8850/',
+      // target: 'http://192.168.32.65:8844/',
+      // ws: 'ws://192.168.32.65:8844/',
       //v2环境
       // ws: 'ws://47.109.52.230:8844',
       // target: 'http://47.109.52.230:8844',

+ 10 - 1
src/components/RadioCard/index.tsx

@@ -10,6 +10,7 @@ interface RadioCardItem {
   label: string;
   value: string;
   imgUrl?: string;
+  imgSize?: number[];
 }
 
 export interface RadioCardProps {
@@ -80,7 +81,15 @@ export default (props: RadioCardProps) => {
               }
             }}
           >
-            {item.imgUrl && <img width={32} height={32} src={item.imgUrl} alt={''} />}
+            {item.imgUrl && (
+              <img
+                width={32}
+                height={32}
+                src={item.imgUrl}
+                alt={''}
+                style={{ width: item.imgSize?.[0], height: item.imgSize?.[1] }}
+              />
+            )}
             <span>{item.label}</span>
             <div className={'checked-icon'}>
               <div>

+ 60 - 0
src/pages/home/components/CardStatics.tsx

@@ -0,0 +1,60 @@
+import Title from '@/pages/home/components/Title';
+import React from 'react';
+import './index.less';
+
+type StatisticsItem = {
+  name: string;
+  value: number | string;
+  children: React.ReactNode | string;
+  permission?: any;
+  node?: any;
+};
+
+interface StatisticsProps {
+  extra?: React.ReactNode | string;
+  style?: any;
+  height?: any;
+  data: StatisticsItem[];
+  title: string;
+}
+
+const defaultImage = require('/public/images/home/top-1.png');
+
+const CardStatistics = (props: StatisticsProps) => {
+  return (
+    <div className={'home-statistics'} style={{ height: props.height }}>
+      <Title title={props.title} extra={props.extra} />
+      <div className={'home-statistics-body'} style={props.style}>
+        {props.data.map((item) => (
+          <div className={'home-guide-item'} key={item.name}>
+            <div className={'item-english'}>{item.name}</div>
+            {item.node ? (
+              <div style={{ display: 'flex', marginTop: 15, width: '60%' }}>
+                {item.node.map((i: any) => (
+                  <div style={{ marginRight: 7 }}>
+                    <div style={{ fontSize: '14px', fontWeight: 'bold' }}>{i.value}</div>
+                    <div className={`state ${i.className}`}>{i.name}</div>
+                  </div>
+                ))}
+              </div>
+            ) : (
+              <div className={'item-title'}>{item.permission ? item.permission : item.value}</div>
+            )}
+
+            {typeof item.children === 'string' ? (
+              <div className={`item-index`}>
+                <img src={item.children || defaultImage} />
+              </div>
+            ) : (
+              <div className={'item-index-echarts'} style={{ height: 75, width: 110 }}>
+                {item.children}
+              </div>
+            )}
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+};
+
+export default CardStatistics;

+ 23 - 0
src/pages/home/components/index.less

@@ -26,6 +26,29 @@
   background: linear-gradient(135.62deg, #f6f7fd 22.27%, rgba(255, 255, 255, 0.86) 91.82%);
   border-radius: 2px;
   box-shadow: 0 4px 18px #efefef;
+  .state {
+    position: relative;
+    padding-left: 8px;
+    &::before {
+      position: absolute;
+      top: 7px;
+      left: 0;
+      display: inline-block;
+      width: 6px;
+      height: 6px;
+      margin-right: 2px;
+      content: '';
+    }
+    &.normal::before {
+      background: #85a5ff;
+    }
+    &.notActive::before {
+      background: #f29b55;
+    }
+    &.stopped::before {
+      background: #c4c4c4;
+    }
+  }
 
   &.pointer {
     cursor: pointer;

+ 217 - 3
src/pages/iot-card/Home/index.tsx

@@ -1,4 +1,218 @@
-const Home = () => {
-  return <>首页</>;
+import { PageContainer } from '@ant-design/pro-layout';
+import { Col, message, Row } from 'antd';
+import { PermissionButton } from '@/components';
+import { getMenuPathByCode } from '@/utils/menu';
+import useHistory from '@/hooks/route/useHistory';
+import { useEffect, useRef, useState } from 'react';
+import { Body, Guide } from '@/pages/home/components';
+import CardStatistics from '@/pages/home/components/CardStatics';
+import Echarts from '@/components/DashBoard/echarts';
+import { EChartsOption } from 'echarts';
+import moment from 'moment';
+import Service from './service';
+
+export const service = new Service('');
+
+export default () => {
+  const dashBoardUrl = getMenuPathByCode('iot-card/Dashboard');
+  const platformUrl = getMenuPathByCode('iot-card/Platform/Detail');
+  const recordUrl = getMenuPathByCode('iot-card/Record');
+  const cardUrl = getMenuPathByCode('iot-card/CardManagement');
+
+  const [options, setOptions] = useState<EChartsOption>({});
+  const [cardOptions, setCardOptions] = useState<EChartsOption>({});
+  const currentSource = useRef(0);
+  const pieChartData = useRef([
+    {
+      key: 'using',
+      name: '正常',
+      value: 0,
+      className: 'normal',
+    },
+    {
+      key: 'toBeActivated',
+      name: '未激活',
+      value: 0,
+      className: 'notActive',
+    },
+    {
+      key: 'deactivate',
+      name: '停用',
+      value: 0,
+      className: 'stopped',
+    },
+  ]);
+
+  const { permission: paltformPermission } = PermissionButton.usePermission('iot-card/Platform');
+
+  const history = useHistory();
+
+  //昨日流量
+  const getTodayFlow = async () => {
+    const beginTime = moment().subtract(1, 'days').startOf('day').valueOf();
+    const endTime = moment().subtract(1, 'days').endOf('day').valueOf();
+    const res = await service.queryFlow(beginTime, endTime, { orderBy: 'date' });
+    if (res.status === 200) {
+      res.result.map((item: any) => {
+        currentSource.current += parseFloat(item.value.toFixed(2));
+      });
+    }
+  };
+
+  //15天流量
+  const get15DaysTrafficConsumption = async () => {
+    const beginTime = moment().subtract(15, 'days').startOf('day').valueOf();
+    const endTime = moment().subtract(1, 'days').endOf('day').valueOf();
+    const resp = await service.queryFlow(beginTime, endTime, { orderBy: 'date' });
+    if (resp.status === 200) {
+      setOptions({
+        tooltip: {},
+        xAxis: {
+          show: false,
+          data: resp.result.map((item: any) => item.date).reverse(),
+        },
+        yAxis: {
+          show: false,
+        },
+        series: [
+          {
+            name: '流量消耗',
+            type: 'bar',
+            color: '#FACD89',
+            // barWidth: '5%', // 设单柱状置宽度
+            showBackground: true, //设置柱状的背景虚拟
+            data: resp.result.map((m: any) => parseFloat(m.value.toFixed(2))).reverse(),
+          },
+        ],
+      });
+    }
+  };
+  //获取物联卡状态数据
+  const getStateCard = async () => {
+    Promise.all(
+      pieChartData.current.map((item) => {
+        const params = {
+          terms: [
+            {
+              terms: [
+                {
+                  column: 'cardStateType',
+                  termType: 'eq',
+                  value: item.key,
+                },
+              ],
+            },
+          ],
+        };
+        return service.list(params);
+      }),
+    ).then((res) => {
+      res.forEach((item, index) => {
+        if (item && item.status === 200) {
+          pieChartData.current[index].value = item.result.total;
+        }
+      });
+      setCardOptions({
+        tooltip: {
+          trigger: 'item',
+          formatter: '{b}: {c} ({d}%)',
+        },
+        color: ['#85a5ff', '#f29b55', '#c4c4c4'],
+        series: [
+          {
+            name: '',
+            type: 'pie',
+            avoidLabelOverlap: true, //是否启用防止标签重叠策略
+            radius: ['50%', '90%'],
+            center: ['50%', '50%'],
+            itemStyle: {
+              borderColor: 'rgba(255,255,255,1)',
+              borderWidth: 2,
+            },
+            label: {
+              show: false,
+            },
+            data: pieChartData.current,
+          },
+        ],
+      });
+    });
+  };
+
+  const guideList = [
+    {
+      key: 'EQUIPMENT',
+      name: '平台对接',
+      english: 'STEP1',
+      auth: !!paltformPermission.update,
+      url: platformUrl,
+    },
+    {
+      key: 'SCREEN',
+      name: '物联卡管理',
+      english: 'STEP2',
+      auth: !!cardUrl,
+      url: cardUrl,
+      param: { save: true },
+    },
+    {
+      key: 'CASCADE',
+      name: '操作记录',
+      english: 'STEP3',
+      auth: !!recordUrl,
+      url: recordUrl,
+    },
+  ];
+
+  useEffect(() => {
+    getTodayFlow();
+    get15DaysTrafficConsumption();
+    getStateCard();
+  }, []);
+
+  return (
+    <PageContainer>
+      <Row gutter={24}>
+        <Col span={14}>
+          <Guide title={'物联卡引导'} data={guideList} />
+        </Col>
+        <Col span={10}>
+          <CardStatistics
+            title={'基础统计'}
+            data={[
+              {
+                name: '昨日流量统计',
+                value: `${currentSource.current}M`,
+                children: <Echarts options={options} />,
+              },
+              {
+                name: '物联卡',
+                value: 0,
+                node: pieChartData.current,
+                children: <Echarts options={cardOptions} />,
+              },
+            ]}
+            extra={
+              <div style={{ fontSize: 14, fontWeight: 400 }}>
+                <a
+                  onClick={() => {
+                    if (!!dashBoardUrl) {
+                      history.push(`${dashBoardUrl}`);
+                    } else {
+                      message.warning('暂无权限,请联系管理员');
+                    }
+                  }}
+                >
+                  详情
+                </a>
+              </div>
+            }
+          />
+        </Col>
+        <Col span={24}>
+          <Body title={'平台架构图'} english={'PLATFORM ARCHITECTURE DIAGRAM'} />
+        </Col>
+      </Row>
+    </PageContainer>
+  );
 };
-export default Home;

+ 21 - 0
src/pages/iot-card/Home/service.ts

@@ -0,0 +1,21 @@
+import { request } from 'umi';
+import SystemConst from '@/utils/const';
+import BaseService from '@/utils/BaseService';
+
+class Service extends BaseService<any> {
+  queryFlow = (beginTime: any, endTime: any, data: any) =>
+    request(`${SystemConst.API_BASE}/network/flow/_query/${beginTime}/${endTime}`, {
+      method: 'POST',
+      data,
+    });
+  queryState = (status: any) =>
+    request(`${SystemConst.API_BASE}/network/card/${status}/state/_count`, {
+      method: 'GET',
+    });
+  list = (data: any) =>
+    request(`${SystemConst.API_BASE}/network/card/_query`, {
+      method: 'POST',
+      data,
+    });
+}
+export default Service;

+ 415 - 0
src/pages/iot-card/Platform/Detail/index.tsx

@@ -0,0 +1,415 @@
+import { RadioCard, TitleComponent } from '@/components';
+import { PageContainer } from '@ant-design/pro-layout';
+import { Form, FormButtonGroup, FormGrid, FormItem, Input } from '@formily/antd';
+import { createForm, Field, onFieldReact, onFormInit } from '@formily/core';
+import { createSchemaField, observer } from '@formily/react';
+import { Button, Card, Col, Row } from 'antd';
+import { useEffect, useMemo, useState } from 'react';
+import { useParams } from 'umi';
+import { onlyMessage, useAsyncDataSource } from '@/utils/util';
+import { service } from '../index';
+import { useModel } from '@@/plugin-model/useModel';
+import Doc from '../doc';
+
+const Detail = observer(() => {
+  const params = useParams<{ id: string }>();
+  const { initialState } = useModel('@@initialState');
+  const [docType, setDocType] = useState('');
+
+  const form = useMemo(
+    () =>
+      createForm({
+        validateFirst: true,
+        effects() {
+          onFormInit(async (form1) => {
+            if (params.id === ':id') return;
+            const resp = await service.detail(params.id);
+            if (resp.status === 200) {
+              form1.setValues(resp.result);
+            }
+          });
+          onFieldReact('operatorName', (field) => {
+            const value = (field as Field).value;
+            setDocType(value);
+            // console.log(value)
+          });
+        },
+      }),
+    [],
+  );
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      FormGrid,
+      Input,
+      RadioCard,
+    },
+  });
+
+  const schema: any = {
+    type: 'object',
+    properties: {
+      operatorName: {
+        title: '平台类型',
+        'x-component': 'RadioCard',
+        'x-decorator': 'FormItem',
+        'x-decorator-props': {
+          gridSpan: 1,
+        },
+        default: params.id === ':id' ? 'onelink' : undefined,
+        'x-component-props': {
+          model: 'singular',
+          itemStyle: {
+            display: 'flex',
+            flexDirection: 'column',
+            justifyContent: 'space-around',
+            minWidth: '130px',
+          },
+          options: [
+            {
+              label: '移动OneLink',
+              value: 'onelink',
+              imgUrl: require('/public/images/iot-card/onelink.png'),
+              imgSize: [78, 20],
+            },
+            {
+              label: '电信Ctwing',
+              value: 'ctwing',
+              imgUrl: require('/public/images/iot-card/ctwingcmp.png'),
+              imgSize: [52, 25],
+            },
+            {
+              label: '联通Unicom',
+              value: 'unicom',
+              imgUrl: require('/public/images/iot-card/unicom.png'),
+              imgSize: [56, 41],
+            },
+          ],
+        },
+        'x-validator': [
+          {
+            required: true,
+            message: '请选择类型',
+          },
+        ],
+      },
+      name: {
+        type: 'string',
+        title: '名称',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          placeholder: '请输入名称',
+        },
+        'x-validator': [
+          {
+            max: 64,
+            message: '最多可输入64个字符',
+          },
+          {
+            required: true,
+            message: '请输入名称',
+          },
+        ],
+      },
+      onelink: {
+        type: 'void',
+        'x-reactions': {
+          dependencies: ['.operatorName'],
+          fulfill: {
+            state: {
+              visible: '{{$deps[0] ==="onelink"}}',
+            },
+          },
+        },
+        properties: {
+          config: {
+            type: 'object',
+            properties: {
+              appId: {
+                type: 'string',
+                title: 'App ID',
+                required: true,
+                'x-decorator': 'FormItem',
+                'x-component': 'Input',
+                'x-component-props': {
+                  placeholder: '请输入App ID',
+                },
+                'x-validator': [
+                  {
+                    max: 64,
+                    message: '最多可输入64个字符',
+                  },
+                  {
+                    required: true,
+                    message: '请输入App ID',
+                  },
+                ],
+              },
+              passWord: {
+                type: 'string',
+                title: 'Password',
+                required: true,
+                'x-decorator': 'FormItem',
+                'x-component': 'Input',
+                'x-component-props': {
+                  placeholder: '请输入Password',
+                },
+                'x-validator': [
+                  {
+                    max: 64,
+                    message: '最多可输入64个字符',
+                  },
+                  {
+                    required: true,
+                    message: '请输入App ID',
+                  },
+                ],
+              },
+              apiAddr: {
+                type: 'string',
+                title: '接口地址',
+                required: true,
+                'x-decorator': 'FormItem',
+                'x-component': 'Input',
+                'x-component-props': {
+                  placeholder: '请输入接口地址',
+                },
+                'x-validator': [
+                  {
+                    max: 64,
+                    message: '最多可输入64个字符',
+                  },
+                  {
+                    required: true,
+                    message: '请输入接口地址',
+                  },
+                ],
+              },
+            },
+          },
+        },
+      },
+      ctwing: {
+        type: 'void',
+        'x-reactions': {
+          dependencies: ['.operatorName'],
+          fulfill: {
+            state: {
+              visible: '{{$deps[0] ==="ctwing"}}',
+            },
+          },
+        },
+
+        properties: {
+          config: {
+            type: 'object',
+            properties: {
+              userId: {
+                type: 'string',
+                title: '用户id',
+                required: true,
+                'x-decorator': 'FormItem',
+                'x-component': 'Input',
+                'x-component-props': {
+                  placeholder: '请输入用户id',
+                },
+                'x-validator': [
+                  {
+                    max: 64,
+                    message: '最多可输入64个字符',
+                  },
+                  {
+                    required: true,
+                    message: '请输入用户id',
+                  },
+                ],
+              },
+              passWord: {
+                type: 'string',
+                title: 'Password',
+                required: true,
+                'x-decorator': 'FormItem',
+                'x-component': 'Input',
+                'x-component-props': {
+                  placeholder: '请输入Password',
+                },
+                'x-validator': [
+                  {
+                    required: true,
+                    message: '请输入Password',
+                  },
+                ],
+              },
+              secretKey: {
+                type: 'string',
+                title: 'secretKey',
+                required: true,
+                'x-decorator': 'FormItem',
+                'x-component': 'Input',
+                'x-component-props': {
+                  placeholder: '请输入secretKey',
+                },
+                'x-validator': [
+                  {
+                    max: 64,
+                    message: '最多可输入64个字符',
+                  },
+                  {
+                    required: true,
+                    message: '请输入secretKey',
+                  },
+                ],
+              },
+            },
+          },
+        },
+      },
+      unicom: {
+        type: 'void',
+        'x-reactions': {
+          dependencies: ['.operatorName'],
+          fulfill: {
+            state: {
+              visible: '{{$deps[0] ==="unicom"}}',
+            },
+          },
+        },
+
+        properties: {
+          config: {
+            type: 'object',
+            properties: {
+              appId: {
+                type: 'string',
+                title: 'App ID',
+                required: true,
+                'x-decorator': 'FormItem',
+                'x-component': 'Input',
+                'x-component-props': {
+                  placeholder: '请输入App ID',
+                },
+                'x-validator': [
+                  {
+                    max: 64,
+                    message: '最多可输入64个字符',
+                  },
+                  {
+                    required: true,
+                    message: '请输入App ID',
+                  },
+                ],
+              },
+              appSecret: {
+                type: 'string',
+                title: 'App Secret',
+                required: true,
+                'x-decorator': 'FormItem',
+                'x-component': 'Input',
+                'x-component-props': {
+                  placeholder: '请输入App Secret',
+                },
+                'x-validator': [
+                  {
+                    required: true,
+                    message: '请输入App Secret',
+                  },
+                ],
+              },
+              openId: {
+                type: 'string',
+                title: '创建者ID',
+                required: true,
+                'x-decorator': 'FormItem',
+                'x-component': 'Input',
+                'x-component-props': {
+                  placeholder: '请输入创建者ID',
+                },
+                'x-validator': [
+                  {
+                    max: 64,
+                    message: '最多可输入64个字符',
+                  },
+                  {
+                    required: true,
+                    message: '请输入创建者ID',
+                  },
+                ],
+              },
+            },
+          },
+        },
+      },
+      explain: {
+        title: '说明',
+        'x-component': 'Input.TextArea',
+        'x-decorator': 'FormItem',
+        'x-component-props': {
+          rows: 3,
+          showCount: true,
+          maxLength: 200,
+          placeholder: '请输入说明',
+        },
+        'x-validator': [
+          {
+            max: 200,
+            message: '最多可输入200个字符',
+          },
+        ],
+      },
+    },
+  };
+
+  const handleSave = async () => {
+    const data: any = await form.submit();
+    const res: any = params.id === ':id' ? await service.save(data) : await service.update(data);
+    if (res.status === 200) {
+      onlyMessage('保存成功');
+    }
+    console.log(data);
+  };
+
+  useEffect(() => {
+    setTimeout(() => {
+      if (initialState?.settings?.title) {
+        document.title = `物联卡 - ${initialState?.settings?.title}`;
+      } else {
+        document.title = '物联卡';
+      }
+    }, 0);
+  }, []);
+
+  return (
+    <PageContainer>
+      <Card>
+        <Row gutter={24}>
+          <Col span={14}>
+            <TitleComponent data={'详情'} />
+            <Form form={form} layout="vertical">
+              <SchemaField
+                schema={schema}
+                scope={{
+                  useAsyncDataSource,
+                }}
+              />
+              <FormButtonGroup.Sticky>
+                <FormButtonGroup.FormItem>
+                  <Button type="primary" onClick={() => handleSave()}>
+                    保存
+                  </Button>
+                </FormButtonGroup.FormItem>
+              </FormButtonGroup.Sticky>
+            </Form>
+          </Col>
+          <Col span={10}>
+            <Doc type={docType} />
+          </Col>
+        </Row>
+      </Card>
+    </PageContainer>
+  );
+});
+
+export default Detail;

+ 35 - 0
src/pages/iot-card/Platform/doc/index.less

@@ -0,0 +1,35 @@
+.doc {
+  height: 1000px;
+  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;
+  }
+}

+ 161 - 0
src/pages/iot-card/Platform/doc/index.tsx

@@ -0,0 +1,161 @@
+import './index.less';
+import { Image } from 'antd';
+interface Props {
+  type: 'onelink' | 'ctwing' | 'unicom' | any;
+}
+
+const Doc = (props: Props) => {
+  const { type } = props;
+
+  return (
+    <>
+      {type === 'onelink' && (
+        <div className="doc">
+          <div className="url">
+            中国移动物联卡能力开放平台:
+            <a
+              style={{ wordBreak: 'break-all' }}
+              href="https://api.iot.10086.cn/api/index.html#/login"
+              target={'_blank'}
+              rel="noreferrer"
+            >
+              https://api.iot.10086.cn/api/index.html#/login
+            </a>
+          </div>
+          <h1>1.概述</h1>
+          <p>平台对接通过API的方式与三方系统进行数据对接,为物联卡的管理提供数据交互支持。</p>
+          <h1>2.配置说明</h1>
+          <h2>1、APP ID</h2>
+          <p>
+            第三方应用唯一标识,中国移动物联网全网管理员在 OneLink
+            能力开放平台上分配并展示给集团客户。
+            <br />
+            获取路径:“中移物联卡能力开放平台”--“个人中心”--“客户信息”--“接入信息”
+          </p>
+          <div className={'image'}>
+            <Image width="100%" src={require('/public/images/iot-card/onelink-appid.png')} />
+          </div>
+          <h2>2、Password</h2>
+          <p>
+            API 接入秘钥,由中国移动物联网提供,集团客户从“OneLink 能力开放平台”获取。
+            <br />
+            获取路径:“中移物联卡能力开放平台”--“个人中心”--“客户信息”--“接入信息”
+          </p>
+          <div className={'image'}>
+            <Image width="100%" src={require('/public/images/iot-card/onelink-pass.png')} />
+          </div>
+          <h2>3、接口地址</h2>
+          <p>
+            https://api.iot.10086.cn/v5/ec/get/token
+            <br />
+            token后缀请根据实际情况填写
+            <br />
+            示例:https://api.iot.10086.cn/v5/authService?appid=xxx&password=xxx&transid=xxx
+          </p>
+        </div>
+      )}
+
+      {type === 'ctwing' && (
+        <div className="doc">
+          <div className="url">
+            5G连接管理平台:
+            <a
+              style={{ wordBreak: 'break-all' }}
+              href="https://cmp.ctwing.cn:4821/login"
+              target={'_blank'}
+              rel="noreferrer"
+            >
+              https://cmp.ctwing.cn:4821/login
+            </a>
+          </div>
+          <div>
+            <h1>1.概述</h1>
+            <p>平台对接通过API的方式与三方系统进行数据对接,为物联卡的管理提供数据交互支持。</p>
+            <h1>2.配置说明</h1>
+            <h2>1、用户 id</h2>
+            <p>
+              5G连接管理平台用户的唯一标识,用于身份识别。
+              <br />
+              获取路径:“5G连接管理平台”--“能力开放”--“API网关账号管理”
+            </p>
+            <div className={'image'}>
+              <Image width="100%" src={require('/public/images/iot-card/ctwing-id.png')} />
+            </div>
+
+            <h2>2、密码</h2>
+            <p>
+              用户id经加密之后的密码。
+              <br />
+              获取路径:“5G连接管理平台”--“能力开放”--“API网关账号管理”
+            </p>
+            <div className={'image'}>
+              <Image width="100%" src={require('/public/images/iot-card/ctwing-pass.png')} />
+            </div>
+
+            <h2>3、secretKey</h2>
+            <p>
+              APP secret唯一秘钥。
+              <br />
+              获取路径:“5G连接管理平台”--“能力开放”--“API网关账号管理”
+            </p>
+            <div className={'image'}>
+              <Image width="100%" src={require('/public/images/iot-card/ctwing-secret.png')} />
+            </div>
+          </div>
+        </div>
+      )}
+      {type === 'unicom' && (
+        <div className="doc">
+          <div className="url">
+            雁飞智连CMP平台:
+            <a
+              style={{ wordBreak: 'break-all' }}
+              href="  https://cmp.10646.cn/webframe/login"
+              target={'_blank'}
+              rel="noreferrer"
+            >
+              https://cmp.10646.cn/webframe/login
+            </a>
+          </div>
+
+          <div>
+            <h1>1.概述</h1>
+            <p>平台对接通过API的方式与三方系统进行数据对接,为物联卡的管理提供数据交互支持。</p>
+            <h1>2.配置说明</h1>
+            <h2>1、APP ID</h2>
+            <p>
+              第三方应用唯一标识。
+              <br />
+              获取路径:“雁飞智连CMP平台”--“我的应用”--“应用列表”
+            </p>
+            <div className={'image'}>
+              <Image width="100%" src={require('/public/images/iot-card/unicom-id.png')} />
+            </div>
+
+            <h2>2、App Secret</h2>
+            <p>
+              API 接入秘钥。
+              <br />
+              获取路径:“雁飞智连CMP平台”--“我的应用”--“应用列表”
+            </p>
+            <div className={'image'}>
+              <Image width="100%" src={require('/public/images/iot-card/unicom-secret.png')} />
+            </div>
+
+            <h2>3、创建者ID</h2>
+            <p>
+              接口参数中的 OpenId。
+              <br />
+              获取路径:“雁飞智连CMP平台”--“我的应用”--“应用列表”
+              <br />
+            </p>
+            <div className={'image'}>
+              <Image width="100%" src={require('/public/images/iot-card/unicom-openid.png')} />
+            </div>
+          </div>
+        </div>
+      )}
+    </>
+  );
+};
+export default Doc;

+ 209 - 1
src/pages/iot-card/Platform/index.tsx

@@ -1,4 +1,212 @@
+import { PermissionButton } from '@/components';
+import SearchComponent from '@/components/SearchComponent';
+import useDomFullHeight from '@/hooks/document/useDomFullHeight';
+import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
+import { onlyMessage } from '@/utils/util';
+import {
+  DeleteOutlined,
+  EditOutlined,
+  PlayCircleOutlined,
+  PlusOutlined,
+  StopOutlined,
+} from '@ant-design/icons';
+import { PageContainer } from '@ant-design/pro-layout';
+import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { Badge } from 'antd';
+import { useRef, useState } from 'react';
+import Service from './service';
+import { useHistory } from '@/hooks';
+
+export const service = new Service('network/card/platform');
+
 const Platform = () => {
-  return <>平台对接</>;
+  const { minHeight } = useDomFullHeight(`.record`, 24);
+  const actionRef = useRef<ActionType>();
+  const [param, setParam] = useState({});
+  const history = useHistory();
+
+  const statusUpdate = async (data: any) => {
+    const res = await service.update(data);
+    if (res.status === 200) {
+      onlyMessage('操作成功');
+      actionRef.current?.reload();
+    }
+  };
+
+  const columns: ProColumns<any>[] = [
+    {
+      title: '名称',
+      dataIndex: 'name',
+      ellipsis: true,
+    },
+    {
+      title: '平台类型',
+      dataIndex: 'operatorName',
+      ellipsis: true,
+      valueType: 'select',
+      valueEnum: {
+        onelink: {
+          text: '移动OneLink',
+          status: 'onelink',
+        },
+        ctwing: {
+          text: '电信Ctwing',
+          status: 'ctwing',
+        },
+        unicom: {
+          text: '联通Unicom',
+          status: 'unicom',
+        },
+      },
+    },
+    {
+      title: '状态',
+      dataIndex: 'state',
+      ellipsis: true,
+      valueType: 'select',
+      valueEnum: {
+        enabled: {
+          text: '启用',
+          status: 'enabled',
+        },
+        disabled: {
+          text: '禁用',
+          status: 'disabled',
+        },
+      },
+      render: (_, record: any) => (
+        <Badge
+          status={record.state?.value === 'disabled' ? 'error' : 'success'}
+          text={record.state?.text}
+        />
+      ),
+    },
+    {
+      title: '说明',
+      dataIndex: 'explain',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '操作',
+      valueType: 'option',
+      fixed: 'right',
+      render: (text, record) => [
+        <PermissionButton
+          isPermission={true}
+          key="edit"
+          onClick={() => {
+            const url = `${getMenuPathByParams(MENUS_CODE['iot-card/Platform/Detail'], record.id)}`;
+            history.push(url);
+          }}
+          type={'link'}
+          style={{ padding: 0 }}
+          tooltip={{
+            title: '编辑',
+          }}
+        >
+          <EditOutlined />
+        </PermissionButton>,
+        <PermissionButton
+          isPermission={true}
+          key="action"
+          type={'link'}
+          style={{ padding: 0 }}
+          tooltip={{
+            title: record.state.value === 'enabled' ? '禁用' : '启用',
+          }}
+          popConfirm={{
+            title: `确认${record.state.value === 'enabled' ? '禁用' : '启用'}`,
+            onConfirm: () => {
+              if (record.state.value === 'enabled') {
+                statusUpdate({
+                  id: record.id,
+                  config: { ...record.config },
+                  state: 'disabled',
+                  operatorName: record.operatorName,
+                });
+              } else {
+                statusUpdate({
+                  id: record.id,
+                  config: { ...record.config },
+                  state: 'enabled',
+                  operatorName: record.operatorName,
+                });
+              }
+            },
+          }}
+        >
+          {record.state === 'enabled' ? <StopOutlined /> : <PlayCircleOutlined />}
+        </PermissionButton>,
+        <PermissionButton
+          isPermission={true}
+          tooltip={{
+            title: record.state.value !== 'enabled' ? '删除' : '请先禁用再删除',
+          }}
+          style={{ padding: 0 }}
+          disabled={record.state.value === 'enabled'}
+          popConfirm={{
+            title: '确认删除',
+            disabled: record.state.value === 'enabled',
+            onConfirm: async () => {
+              const res: any = await service.remove(record.id);
+              if (res.status === 200) {
+                onlyMessage('操作成功');
+                actionRef.current?.reload();
+              }
+            },
+          }}
+          key="delete"
+          type="link"
+        >
+          <DeleteOutlined />
+        </PermissionButton>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <SearchComponent
+        field={columns}
+        target="record"
+        onSearch={(data) => {
+          // 重置分页数据
+          actionRef.current?.reset?.();
+          setParam(data);
+        }}
+      />
+      <ProTable
+        actionRef={actionRef}
+        params={param}
+        columns={columns}
+        search={false}
+        rowKey="id"
+        tableClassName={'record'}
+        columnEmptyText={''}
+        tableStyle={{ minHeight }}
+        headerTitle={
+          <>
+            <PermissionButton
+              onClick={() => {
+                const url = `${getMenuPathByParams(MENUS_CODE['iot-card/Platform/Detail'])}`;
+                history.push(url);
+              }}
+              style={{ marginRight: 12 }}
+              isPermission={true}
+              key="button"
+              icon={<PlusOutlined />}
+              type="primary"
+            >
+              新增
+            </PermissionButton>
+          </>
+        }
+        request={async (params) =>
+          service.getList({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+        }
+      />
+    </PageContainer>
+  );
 };
 export default Platform;

+ 12 - 0
src/pages/iot-card/Platform/service.ts

@@ -0,0 +1,12 @@
+import { request } from 'umi';
+import SystemConst from '@/utils/const';
+import BaseService from '@/utils/BaseService';
+
+class Service extends BaseService<any> {
+  getList = (data: any) =>
+    request(`${SystemConst.API_BASE}/network/card/platform/_query`, {
+      method: 'POST',
+      data,
+    });
+}
+export default Service;

+ 35 - 0
src/pages/iot-card/Recharge/detail.tsx

@@ -0,0 +1,35 @@
+import { Modal, Descriptions } from 'antd';
+import moment from 'moment';
+
+interface Props {
+  data: any;
+  close: any;
+}
+
+const Detail = (props: Props) => {
+  const { data } = props;
+  return (
+    <Modal
+      title={'详情'}
+      maskClosable={false}
+      visible
+      onCancel={props.close}
+      onOk={props.close}
+      width="35vw"
+    >
+      <Descriptions bordered column={2}>
+        <Descriptions.Item label="充值金额">{data.chargeMoney}</Descriptions.Item>
+        <Descriptions.Item label="账户id">{data?.rechargeId}</Descriptions.Item>
+        <Descriptions.Item label="平台对接">{data.configName}</Descriptions.Item>
+        <Descriptions.Item label="订单号">{data.orderNumber}</Descriptions.Item>
+        <Descriptions.Item label="支付方式">{data.paymentType}</Descriptions.Item>
+        <Descriptions.Item label="支付URL">{data.url ? data.url : ''}</Descriptions.Item>
+        <Descriptions.Item label="订单时间">
+          {data.createTime ? moment(data.createTime).format('YYYY-MM-DD HH:mm:ss') : '-'}
+        </Descriptions.Item>
+      </Descriptions>
+    </Modal>
+  );
+};
+
+export default Detail;

+ 163 - 1
src/pages/iot-card/Recharge/index.tsx

@@ -1,4 +1,166 @@
+import { PermissionButton } from '@/components';
+import SearchComponent from '@/components/SearchComponent';
+import useDomFullHeight from '@/hooks/document/useDomFullHeight';
+import { ExclamationCircleOutlined, EyeOutlined } from '@ant-design/icons';
+import { PageContainer } from '@ant-design/pro-layout';
+import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { Tooltip } from 'antd';
+import moment from 'moment';
+import { useRef, useState } from 'react';
+import Service from '../CardManagement/service';
+import Detail from './detail';
+import TopUp from './topUp';
+
+export const service = new Service('network/card');
+
 const Recharge = () => {
-  return <>充值管理</>;
+  const { minHeight } = useDomFullHeight(`.record`, 24);
+  const actionRef = useRef<ActionType>();
+  const [param, setParam] = useState({});
+  const [visible, setVisible] = useState<boolean>(false);
+  const [detail, setDetail] = useState<boolean>(false);
+  const [current, setCurrent] = useState<any>({});
+
+  const columns: ProColumns<any>[] = [
+    {
+      title: '充值金额',
+      dataIndex: 'chargeMoney',
+      ellipsis: true,
+    },
+    {
+      title: '支付方式',
+      dataIndex: 'paymentType',
+      ellipsis: true,
+      valueType: 'select',
+      valueEnum: {
+        ALIPAY_WAP: {
+          text: '支付宝手机网站支付',
+          status: 'ALIPAY_WAP',
+        },
+        ALIPAY_WEB: {
+          text: '支付宝网页及时到账支付',
+          status: 'ALIPAY_WEB',
+        },
+        WEIXIN_JSAPI: {
+          text: '微信公众号支付',
+          status: 'WEIXIN_JSAPI',
+        },
+        WEIXIN_NATIVE: {
+          text: '微信扫码支付',
+          status: 'WEIXIN_NATIVE',
+        },
+      },
+    },
+    {
+      title: '订单号',
+      dataIndex: 'orderNumber',
+      ellipsis: true,
+    },
+    {
+      title: '支付URL',
+      dataIndex: 'url',
+      ellipsis: true,
+      hideInSearch: true,
+    },
+    {
+      title: '订单时间',
+      dataIndex: 'createTime',
+      ellipsis: true,
+      valueType: 'dateTime',
+      render: (_: any, record) => {
+        return record.createTime ? moment(record.createTime).format('YYYY-MM-DD HH:mm:ss') : '';
+      },
+    },
+    {
+      title: '操作',
+      key: 'action',
+      fixed: 'right',
+      align: 'center',
+      width: 200,
+      hideInSearch: true,
+      render: (_, record) => [
+        <a
+          key="editable"
+          onClick={() => {
+            setDetail(true);
+            setCurrent(record);
+          }}
+        >
+          <Tooltip title="查看">
+            <EyeOutlined />
+          </Tooltip>
+        </a>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <SearchComponent
+        field={columns}
+        target="record"
+        onSearch={(data) => {
+          // 重置分页数据
+          actionRef.current?.reset?.();
+          setParam(data);
+        }}
+      />
+      <ProTable
+        actionRef={actionRef}
+        params={param}
+        columns={columns}
+        search={false}
+        rowKey="id"
+        tableClassName={'record'}
+        columnEmptyText={''}
+        tableStyle={{ minHeight }}
+        headerTitle={
+          <>
+            <PermissionButton
+              onClick={() => {
+                setVisible(true);
+              }}
+              isPermission={true}
+              key="button"
+              type="primary"
+            >
+              充值
+            </PermissionButton>
+            <div
+              style={{
+                paddingLeft: 24,
+                background: '#fff',
+                fontSize: 14,
+              }}
+            >
+              <span style={{ marginRight: 8, fontSize: 16 }}>
+                <ExclamationCircleOutlined />
+              </span>
+              本平台仅提供充值入口,具体充值结果需以运营商的充值结果为准
+            </div>
+          </>
+        }
+        request={async (params) =>
+          service.queryRechargeList({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+        }
+      />
+      {visible && (
+        <TopUp
+          close={() => {
+            setVisible(false);
+            actionRef.current?.reload();
+          }}
+        />
+      )}
+      {detail && (
+        <Detail
+          data={current}
+          close={() => {
+            setDetail(false);
+          }}
+        />
+      )}
+    </PageContainer>
+  );
 };
 export default Recharge;

+ 215 - 0
src/pages/iot-card/Recharge/topUp.tsx

@@ -0,0 +1,215 @@
+import { createForm, Field } from '@formily/core';
+import { createSchemaField } from '@formily/react';
+import { Form, FormGrid, FormItem, Input, Select, NumberPicker } from '@formily/antd';
+import type { ISchema } from '@formily/json-schema';
+import { Modal } from '@/components';
+import { onlyMessage } from '@/utils/util';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
+import { action } from '@formily/reactive';
+import { service } from './index';
+import { PaymentMethod } from '../data';
+
+interface Props {
+  close: () => void;
+}
+
+const TopUp = (props: Props) => {
+  const form = createForm({});
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Select,
+      FormGrid,
+      NumberPicker,
+    },
+  });
+  const useAsyncDataSource = (ser: (arg0: any) => Promise<any>) => (field: Field) => {
+    field.loading = true;
+    ser(field).then(
+      action.bound?.((data) => {
+        field.dataSource = (data.result || []).map((item: any) => ({
+          label: item.name,
+          value: item.id,
+        }));
+        field.loading = false;
+      }),
+    );
+  };
+
+  const queryProvidersList = () =>
+    service.queryPlatformNoPage({
+      paging: false,
+      terms: [
+        {
+          terms: [
+            {
+              column: 'operatorName',
+              termType: 'eq',
+              value: 'onelink',
+            },
+            {
+              column: 'state',
+              termType: 'eq',
+              value: 'enabled',
+              type: 'and',
+            },
+          ],
+        },
+      ],
+    });
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      layout: {
+        type: 'void',
+        'x-decorator': 'FormGrid',
+        'x-decorator-props': {
+          maxColumns: 2,
+          minColumns: 2,
+          columnGap: 24,
+        },
+        properties: {
+          configId: {
+            title: '平台对接',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Select',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入平台对接',
+            },
+            'x-reactions': '{{useAsyncDataSource(queryProvidersList)}}',
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请选择平台对接',
+              },
+            ],
+          },
+          rechargeId: {
+            title: '账户id',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入账户id',
+            },
+
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入账户id',
+              },
+            ],
+          },
+          chargeMoney: {
+            title: '充值金额',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'NumberPicker',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入充值金额',
+            },
+
+            'x-validator': [
+              {
+                min: 1,
+                message: '请输入1~500之间的数字',
+              },
+              {
+                max: 500,
+                message: '请输入1~500之间的数字',
+              },
+              {
+                required: true,
+                message: '请输入充值金额',
+              },
+            ],
+          },
+          paymentType: {
+            title: '支付方式',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Select',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请选择支付方式',
+            },
+            name: 'name',
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择支付方式',
+              },
+            ],
+            enum: PaymentMethod,
+          },
+        },
+      },
+    },
+  };
+
+  const save = async () => {
+    const value = await form.submit<any>();
+    const res: any = await service.recharge(value);
+    if (res.status === 200) {
+      if (res.result === '失败') {
+        onlyMessage('缴费失败', 'error');
+        props.close();
+      } else {
+        window.open(res.result);
+        props.close();
+      }
+    }
+  };
+
+  return (
+    <Modal
+      title={'充值'}
+      maskClosable={false}
+      visible
+      onCancel={props.close}
+      onOk={save}
+      width="35vw"
+    >
+      <div
+        style={{
+          padding: 5,
+          background: '#f6f6f6',
+          fontSize: 14,
+          color: '#00000091',
+          marginBottom: 10,
+        }}
+      >
+        <span style={{ fontSize: 16, marginRight: 5 }}>
+          <ExclamationCircleOutlined />
+        </span>
+        暂只支持移动OneLink平台
+      </div>
+      <Form form={form} layout="vertical">
+        <SchemaField schema={schema} scope={{ useAsyncDataSource, queryProvidersList }} />
+      </Form>
+    </Modal>
+  );
+};
+export default TopUp;

+ 63 - 1
src/pages/iot-card/Record/index.tsx

@@ -1,4 +1,66 @@
+import SearchComponent from '@/components/SearchComponent';
+import useDomFullHeight from '@/hooks/document/useDomFullHeight';
+import { PageContainer } from '@ant-design/pro-layout';
+import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
+import moment from 'moment';
+import { useRef, useState } from 'react';
+import Service from './service';
+
+export const service = new Service('');
+
 const Record = () => {
-  return <>操作记录</>;
+  const { minHeight } = useDomFullHeight(`.record`, 24);
+  const actionRef = useRef<ActionType>();
+  const [param, setParam] = useState({});
+
+  const columns: ProColumns<any>[] = [
+    {
+      dataIndex: 'cardId',
+      title: '卡号',
+    },
+    {
+      dataIndex: 'type',
+      title: '操作类型',
+    },
+    {
+      dataIndex: 'time',
+      title: '操作时间',
+      valueType: 'dateTime',
+      render: (_: any, record) => {
+        return record.time ? moment(record.time).format('YYYY-MM-DD HH:mm:ss') : '';
+      },
+    },
+    {
+      dataIndex: 'operator',
+      title: '操作人',
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <SearchComponent
+        field={columns}
+        target="record"
+        onSearch={(data) => {
+          // 重置分页数据
+          actionRef.current?.reset?.();
+          setParam(data);
+        }}
+      />
+      <ProTable
+        actionRef={actionRef}
+        params={param}
+        columns={columns}
+        search={false}
+        rowKey="id"
+        tableClassName={'record'}
+        columnEmptyText={''}
+        tableStyle={{ minHeight }}
+        request={async (params) =>
+          service.getList({ ...params, sorts: [{ name: 'time', order: 'desc' }] })
+        }
+      />
+    </PageContainer>
+  );
 };
 export default Record;

+ 12 - 0
src/pages/iot-card/Record/service.ts

@@ -0,0 +1,12 @@
+import { request } from 'umi';
+import SystemConst from '@/utils/const';
+import BaseService from '@/utils/BaseService';
+
+class Service extends BaseService<any> {
+  getList = (data: any) =>
+    request(`${SystemConst.API_BASE}/network/card/stateOperate/_log`, {
+      method: 'POST',
+      data,
+    });
+}
+export default Service;

+ 0 - 12
src/pages/notice/Config/Detail/index.tsx

@@ -319,12 +319,6 @@ const Detail = observer(() => {
                 'x-component-props': {
                   placeholder: '请输入webhook',
                 },
-                'x-validator': [
-                  {
-                    max: 64,
-                    message: '最多可输入64个字符',
-                  },
-                ],
                 'x-reactions': {
                   dependencies: ['provider'],
                   fulfill: {
@@ -530,12 +524,6 @@ const Detail = observer(() => {
                 },
                 'x-component': 'Input',
                 'x-decorator': 'FormItem',
-                'x-validator': [
-                  {
-                    max: 64,
-                    message: '最多可输入64个字符',
-                  },
-                ],
               },
               headers: {
                 title: '请求头',

+ 4 - 2
src/pages/oauth/index.tsx

@@ -202,8 +202,10 @@ const Oauth = () => {
         const item = getQueryVariable('internal');
         if (items.redirect_uri) {
           const orgin = items.redirect_uri.split('/').slice(0, 3);
-          const url = `${orgin.join('/')}/%23/${items.redirect_uri?.split('redirect=')[1]}`;
-          redirectUrl = `${items.redirect_uri?.split('redirect=')[0]}redirect=${url}`;
+          // const url = `${orgin.join('/')}/%23/${items.redirect_uri?.split('redirect=')[1]}`;
+          // redirectUrl = `${items.redirect_uri?.split('redirect=')[0]}redirect=${url}`;
+          const url = `${orgin.join('/')}/%23/${items.redirect_uri?.split('redirect_uri=')[1]}`;
+          redirectUrl = `${items.redirect_uri?.split('redirect_uri=')[0]}?redirect=${url}`;
         }
         getLoginUser({
           ...items,

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

@@ -139,6 +139,7 @@ export enum MENUS_CODE {
   'system/License' = 'system/License',
   'iot-card/Home' = 'iot-card/Home',
   'iot-card/Platform' = 'iot-card/Platform',
+  'iot-card/Platform/Detail' = 'iot-card/Platform/Detail',
   'iot-card/Recharge' = 'iot-card/Recharge',
   'iot-card/Dashboard' = 'iot-card/Dashboard',
   'iot-card/CardManagement' = 'iot-card/CardManagement',
@@ -193,6 +194,7 @@ export const getDetailNameByCode = {
   'rule-engine/Alarm/Log/Detail': '告警日志',
   'Northbound/AliCloud/Detail': '阿里云详情',
   'link/Certificate/Detail': '证书详情',
+  'iot-card/Platform/Detail': '平台对接详情',
 };
 
 // 开源版路由