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

+ 14 - 4
src/app.tsx

@@ -18,6 +18,7 @@ import { AIcon } from '@/components';
 
 const isDev = process.env.NODE_ENV === 'development';
 const loginPath = '/user/login';
+const bindPath = '/account/center/bind';
 let extraRoutes: any[] = [];
 
 /** 获取用户信息比较慢的时候会展示一个 loading */
@@ -43,7 +44,7 @@ export async function getInitialState(): Promise<{
     return undefined;
   };
   // 如果是登录页面,不执行
-  if (history.location.pathname !== loginPath) {
+  if (history.location.pathname !== loginPath && history.location.pathname !== bindPath) {
     const currentUser = await fetchUserInfo();
     return {
       fetchUserInfo,
@@ -113,7 +114,12 @@ export async function getInitialState(): Promise<{
  * @param url
  * @param options
  */
-const filterUrl = ['/authorize/captcha/config', '/authorize/login'];
+const filterUrl = [
+  '/authorize/captcha/config',
+  '/authorize/login',
+  '/sso/bind-code/',
+  '/sso/providers',
+];
 const requestInterceptor = (url: string, options: RequestOptionsInit) => {
   // const {params} = options;
   let authHeader = {};
@@ -195,7 +201,11 @@ export const layout: RunTimeLayoutConfig = ({ initialState }) => {
     onPageChange: () => {
       const { location } = history;
       // 如果没有登录,重定向到 login
-      if (!initialState?.currentUser && location.pathname !== loginPath) {
+      if (
+        !initialState?.currentUser &&
+        location.pathname !== loginPath &&
+        location.pathname !== bindPath
+      ) {
         history.push(loginPath);
       }
     },
@@ -253,7 +263,7 @@ export function patchRoutes(routes: any) {
 }
 
 export function render(oldRender: any) {
-  if (history.location.pathname !== loginPath) {
+  if (history.location.pathname !== loginPath && history.location.pathname !== bindPath) {
     SystemConfigService.getAMapKey().then((res) => {
       if (res && res.status === 200 && res.result) {
         localStorage.setItem(SystemConst.AMAP_KEY, res.result.apiKey);

+ 30 - 0
src/components/DashBoard/baseCard.tsx

@@ -0,0 +1,30 @@
+import Header from './header';
+import type { HeaderProps } from './header';
+import Echarts from './echarts';
+import type { EchartsProps } from './echarts';
+import Style from './index.less';
+import classNames from 'classnames';
+
+interface BaseCardProps extends HeaderProps, EchartsProps {
+  height: number;
+  classNames?: string;
+}
+
+export default (props: BaseCardProps) => {
+  return (
+    <div
+      className={classNames(Style['dash-board-echarts'], props.classNames)}
+      style={{
+        height: props.height || 200,
+      }}
+    >
+      <Header
+        title={props.title}
+        onParamsChange={props.onParamsChange}
+        extraParams={props.extraParams}
+        initialValues={props.initialValues}
+      />
+      <Echarts options={props.options} />
+    </div>
+  );
+};

+ 106 - 0
src/components/DashBoard/echarts.tsx

@@ -0,0 +1,106 @@
+import { useEffect, useRef } from 'react';
+import * as echarts from 'echarts/core';
+import type { ECharts } from 'echarts';
+import {
+  TitleComponent,
+  ToolboxComponent,
+  TooltipComponent,
+  GridComponent,
+  LegendComponent,
+  MarkLineComponent,
+} from 'echarts/components';
+
+import { LineChart } from 'echarts/charts';
+import { PieChart } from 'echarts/charts';
+import { BarChart } from 'echarts/charts';
+
+import { UniversalTransition } from 'echarts/features';
+import { CanvasRenderer } from 'echarts/renderers';
+
+import Style from './index.less';
+import type { EChartsOption } from 'echarts';
+
+export interface EchartsProps {
+  options?: EChartsOption;
+}
+
+echarts.use([
+  TitleComponent,
+  ToolboxComponent,
+  TooltipComponent,
+  GridComponent,
+  LegendComponent,
+  LineChart,
+  CanvasRenderer,
+  UniversalTransition,
+  BarChart,
+  MarkLineComponent,
+  PieChart,
+]);
+
+const DefaultOptions = {
+  xAxis: {
+    type: 'category',
+    data: [0],
+  },
+  yAxis: {
+    type: 'value',
+  },
+  series: [
+    {
+      data: [],
+      type: 'line',
+    },
+  ],
+};
+
+export default (props: EchartsProps) => {
+  const chartsRef = useRef<any>(null);
+
+  const initEcharts = (dom: HTMLDivElement) => {
+    chartsRef.current = echarts.init(dom);
+    if (props.options) {
+      chartsRef.current.setOption(props.options);
+    } else {
+      chartsRef.current.setOption(DefaultOptions);
+    }
+  };
+
+  const updateSize = () => {
+    if (chartsRef.current) {
+      // 自适应屏幕变化
+      (chartsRef.current as ECharts).resize();
+    }
+  };
+
+  const updateOptions = () => {
+    if (chartsRef.current && props.options) {
+      chartsRef.current.setOption(props.options);
+    }
+  };
+
+  useEffect(() => {
+    (window as Window).addEventListener('resize', updateSize);
+
+    return () => {
+      (window as Window).removeEventListener('resize', updateSize);
+    };
+  }, []);
+
+  useEffect(() => {
+    updateOptions();
+  }, [props.options, chartsRef.current]);
+
+  return (
+    <div
+      className={Style.content}
+      ref={(ref) => {
+        if (ref) {
+          setTimeout(() => {
+            initEcharts(ref);
+          }, 100);
+        }
+      }}
+    />
+  );
+};

+ 114 - 0
src/components/DashBoard/header.tsx

@@ -0,0 +1,114 @@
+import React, { useEffect, useState } from 'react';
+import Style from './index.less';
+import { Col, DatePicker, Form, Input, Radio, Row } from 'antd';
+import moment from 'moment';
+
+export interface HeaderProps {
+  title: string;
+  /**
+   * 参数发生变化时的回调
+   * @param data
+   */
+  onParamsChange: (data: any) => void;
+  extraParams?: {
+    key: string;
+    Children: React.ReactNode;
+  };
+  initialValues?: any;
+}
+
+export default (props: HeaderProps) => {
+  const [form] = Form.useForm();
+  const [radioValue, setRadioValue] = useState<string | undefined>('today');
+
+  const change = async () => {
+    const data = await form.getFieldsValue();
+    data.time = [data.time[0].valueOf(), data.time[1].valueOf()];
+    if (props.onParamsChange) {
+      props.onParamsChange(data);
+    }
+  };
+  const setTime = (sTime: number, eTime: number) => {
+    form.setFieldsValue({ time: [moment(sTime), moment(eTime)] });
+  };
+
+  useEffect(() => {
+    setTime(moment().startOf('day').valueOf(), new Date().getTime());
+    setRadioValue('today');
+    change();
+  }, []);
+
+  return (
+    <div className={Style.header}>
+      <div className={Style.title}>{props.title}</div>
+      <div className={Style.form}>
+        <Form
+          form={form}
+          colon={false}
+          layout={'inline'}
+          preserve={false}
+          initialValues={props.initialValues}
+          onValuesChange={() => {
+            change();
+          }}
+        >
+          <Row style={{ width: '100%' }}>
+            {props.extraParams && (
+              <Col span={6}>
+                <Form.Item name={props.extraParams.key}>{props.extraParams.Children}</Form.Item>
+              </Col>
+            )}
+            <Col span={props.extraParams ? 18 : 24}>
+              <Form.Item noStyle>
+                <Input.Group compact>
+                  <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
+                    <Radio.Group
+                      defaultValue="day"
+                      buttonStyle="solid"
+                      value={radioValue}
+                      onChange={(e) => {
+                        let startTime: any = 0;
+                        const endTime = moment(new Date());
+                        const value = e.target.value;
+                        if (value === 'today') {
+                          startTime = moment().startOf('day').valueOf();
+                        } else if (value === 'week') {
+                          startTime = moment().subtract(6, 'days').valueOf();
+                        } else if (value === 'month') {
+                          startTime = moment().subtract(29, 'days').valueOf();
+                        } else {
+                          startTime = moment().subtract(365, 'days').valueOf();
+                        }
+                        setRadioValue(value);
+                        form.setFieldsValue({ time: [moment(startTime), moment(endTime)] });
+                        change();
+                      }}
+                    >
+                      <Radio.Button value="today">当天</Radio.Button>
+                      <Radio.Button value="week">近一周</Radio.Button>
+                      <Radio.Button value="month">近一月</Radio.Button>
+                      <Radio.Button value="year">近一年</Radio.Button>
+                    </Radio.Group>
+                    <Form.Item name={'time'} noStyle>
+                      {
+                        // @ts-ignore
+                        <DatePicker.RangePicker
+                          showTime
+                          onChange={(date: any) => {
+                            if (date) {
+                              setRadioValue(undefined);
+                            }
+                          }}
+                        />
+                      }
+                    </Form.Item>
+                  </div>
+                </Input.Group>
+              </Form.Item>
+            </Col>
+          </Row>
+        </Form>
+      </div>
+    </div>
+  );
+};

+ 30 - 0
src/components/DashBoard/index.less

@@ -0,0 +1,30 @@
+.dash-board-echarts {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  padding: 24px;
+  background-color: #fff;
+  border: 1px solid #f0f0f0;
+  border-radius: 2px;
+}
+.header {
+  display: flex;
+  gap: 20px;
+  padding: 8px 0;
+}
+
+.title {
+  font-weight: bold;
+  font-size: 16px;
+}
+
+.form {
+  flex: 1;
+}
+
+.content {
+  flex-grow: 1;
+  width: 100%;
+  height: 0;
+  margin: 12px;
+}

+ 6 - 0
src/components/DashBoard/index.tsx

@@ -0,0 +1,6 @@
+import BaseCard from './baseCard';
+
+export { default as DashBoardEcharts } from './echarts';
+export { default as DashBoardHeader } from './header';
+
+export default BaseCard;

+ 3 - 0
src/components/DashBoard/topCard.tsx

@@ -0,0 +1,3 @@
+export default () => {
+  return <div></div>;
+};

+ 42 - 10
src/pages/account/Center/bind/index.less

@@ -1,18 +1,16 @@
 .cards {
-  position: absolute;
-  top: 150px;
-  left: 17%;
   box-sizing: border-box;
-  width: 66%;
+  width: 850px;
+  min-height: 510px;
   background: #fff;
   border: 1px solid #e0e4e8;
   border-radius: 2px;
 
   .title {
-    margin: 50px 0;
+    margin: 30px 0;
     color: #0f1222;
     font-weight: 400;
-    font-size: 24px;
+    font-size: 20px;
     font-family: 'PingFang SC';
     font-style: normal;
     line-height: 25px;
@@ -23,13 +21,14 @@
     display: flex;
     justify-content: center;
 
+    //登录
     .infotitle {
       width: 64px;
       height: 24px;
       margin-left: 10px;
       color: rgba(0, 0, 0, 0.85);
       font-weight: 400;
-      font-size: 16px;
+      font-size: 14px;
       font-family: 'PingFang SC';
       font-style: normal;
       line-height: 24px;
@@ -39,18 +38,51 @@
       display: flex;
       flex-direction: column;
       align-items: center;
-      width: 300px;
+      width: 280px;
 
       .fonts {
         font-weight: 400;
-        font-size: 16px;
+        font-size: 14px;
         font-family: 'PingFang SC';
         font-style: normal;
-        line-height: 16px;
+        line-height: 14px;
         opacity: 0.75;
         mix-blend-mode: normal;
       }
     }
+
+    //未登录
+    .topimg {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+
+    .topfont {
+      margin-top: 30px;
+      margin-bottom: 30px;
+      font-size: 14px;
+      font-family: 'PingFang SC';
+      font-style: normal;
+      line-height: 14px;
+      opacity: 0.75;
+      mix-blend-mode: normal;
+    }
+    .form {
+      width: 250px;
+      :global {
+        // .ant-form-item{
+        //   margin-bottom: 24px;
+        // }
+        .ant-form-item-label > label {
+          font-weight: 600;
+        }
+        .ant-form-item-label
+          > label.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before {
+          content: none;
+        }
+      }
+    }
   }
 
   .btn {

+ 151 - 57
src/pages/account/Center/bind/index.tsx

@@ -1,14 +1,20 @@
-import { Button, Card, message } from 'antd';
+import { Button, Card, message, Form, Input } from 'antd';
 import { useEffect, useState } from 'react';
 import Service from '@/pages/account/Center/service';
+import api from '@/pages/user/Login/service';
 import styles from './index.less';
+import Token from '@/utils/token';
+import { useModel } from '@@/plugin-model/useModel';
 
 export const service = new Service();
 
 const Bind = () => {
+  const [form] = Form.useForm();
   const [bindUser, setBindUser] = useState<any>();
   const [user, setUser] = useState<any>();
   const [code, setCode] = useState<string>('');
+  const [isLogin, setIslogin] = useState<any>('yes');
+  const { initialState, setInitialState } = useModel('@@initialState');
 
   const bindPage = require('/public/images/bind/bindPage.png');
   const Vector = require('/public/images/bind/Vector.png');
@@ -28,7 +34,63 @@ const Bind = () => {
   };
   const getDetail = () => {
     service.getUserDetail().subscribe((res) => {
-      setUser(res.result);
+      setUser(res?.result);
+    });
+  };
+
+  //未登录页
+  const loginDiv = () => (
+    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
+      <div className={styles.topimg}>
+        <img src={logo} style={{ width: '50px', height: '50px' }} />
+        <img src={Vector} style={{ height: '15px', margin: '0 15px' }} />
+        <img src={logo} style={{ width: '50px', height: '50px' }} />
+      </div>
+      <div className={styles.topfont}>
+        你已通过{bindUser?.type === 'dingtalk' ? '钉钉' : '微信'}授权,完善以下登录信息即可以完成绑定
+      </div>
+      <div className={styles.form}>
+        <Form layout="vertical" form={form}>
+          <Form.Item
+            label="账户"
+            name="username"
+            rules={[{ required: true, message: '请输入账户' }]}
+          >
+            <Input />
+          </Form.Item>
+          <Form.Item
+            label="密码"
+            name="password"
+            rules={[{ required: true, message: '请输入密码' }]}
+          >
+            <Input.Password />
+          </Form.Item>
+        </Form>
+      </div>
+    </div>
+  );
+
+  const fetchUserInfo = async () => {
+    const userInfo = (await initialState?.fetchUserInfo?.()) as UserInfo;
+    if (userInfo) {
+      await setInitialState({
+        ...initialState,
+        currentUser: userInfo,
+      });
+    }
+  };
+  const doLogin = async (data: any) => {
+    api.login(data).subscribe({
+      next: async (userInfo) => {
+        Token.set(userInfo.token);
+        await fetchUserInfo();
+        localStorage.setItem('onLogin', 'yes');
+        message.success('登录成功');
+        setTimeout(() => window.close(), 1000);
+      },
+      error: () => {
+        message.error('登录失败,请重试!');
+      },
     });
   };
 
@@ -37,80 +99,112 @@ const Bind = () => {
     // const params = 'b584032923c78d69e6148cf0e9312723'
     setCode(params);
     bindUserInfo(params);
-    getDetail();
+    if (localStorage.getItem('onLogin') === 'yes') {
+      getDetail();
+    }
+    if (localStorage.getItem('onLogin')) {
+      setIslogin(localStorage.getItem('onLogin'));
+    }
   }, []);
+
   return (
     <>
       <div
         style={{
           width: '100%',
-          height: '100%',
+          height: '100vh',
           background: `url(${bindPage}) no-repeat`,
           backgroundSize: '100% 100%',
+          display: 'flex',
+          justifyContent: 'center',
+          alignItems: 'center',
         }}
       >
         <div className={styles.cards}>
           <div className={styles.title}>第三方账户绑定</div>
           <div className={styles.info}>
-            <Card
-              title={
-                <div style={{ display: 'flex', alignItems: 'center' }}>
-                  <div>
-                    <img src={Rectangle} />
+            {isLogin === 'no' ? (
+              <>{loginDiv()}</>
+            ) : (
+              <>
+                <Card
+                  title={
+                    <div style={{ display: 'flex', alignItems: 'center' }}>
+                      <div>
+                        <img src={Rectangle} />
+                      </div>
+                      <div className={styles.infotitle}>个人信息</div>
+                    </div>
+                  }
+                >
+                  <div className={styles.item}>
+                    <div style={{ height: 100, marginTop: 10, marginBottom: 10 }}>
+                      <img src={logo} style={{ height: 70 }} />
+                    </div>
+                    <p className={styles.fonts}>账号:{user?.username}</p>
+                    <p className={styles.fonts}>用户名:{user?.name}</p>
                   </div>
-                  <div className={styles.infotitle}>个人信息</div>
-                </div>
-              }
-            >
-              <div className={styles.item}>
-                <div style={{ height: 100, marginTop: 10, marginBottom: 10 }}>
-                  <img src={logo} style={{ width: 90, height: 90 }} />
+                </Card>
+                <div style={{ position: 'relative', top: '135px', margin: '0 20px' }}>
+                  <img src={Vector} style={{ height: '15px' }} />
                 </div>
-                <p className={styles.fonts}>账号:{user?.username}</p>
-                <p className={styles.fonts}>用户名:{user?.name}</p>
-              </div>
-            </Card>
-            <div style={{ position: 'relative', top: '135px', margin: '0 20px' }}>
-              <img src={Vector} />
-            </div>
-            <Card
-              title={
-                <div style={{ display: 'flex', alignItems: 'center' }}>
-                  <div>
-                    <img src={Rectangle} />
+                <Card
+                  title={
+                    <div style={{ display: 'flex', alignItems: 'center' }}>
+                      <div>
+                        <img src={Rectangle} />
+                      </div>
+                      <div className={styles.infotitle}>三方账户信息</div>
+                    </div>
+                  }
+                >
+                  <div className={styles.item}>
+                    <div style={{ height: 100, marginTop: 10, marginBottom: 10 }}>
+                      <img style={{ height: 70 }} src={iconMap.get(bindUser?.type)} />
+                    </div>
+                    <p className={styles.fonts}>账户:{bindUser?.providerName}</p>
+                    <p className={styles.fonts}>用户名:{bindUser?.result.others.name}</p>
                   </div>
-                  <div className={styles.infotitle}>三方账户信息</div>
-                </div>
-              }
-            >
-              <div className={styles.item}>
-                <div style={{ height: 100, marginTop: 10, marginBottom: 10 }}>
-                  <img style={{ height: 80 }} src={iconMap.get(bindUser?.type)} />
-                </div>
-                <p className={styles.fonts}>账户:{bindUser?.providerName}</p>
-                <p className={styles.fonts}>用户名:{bindUser?.result.others.name}</p>
-              </div>
-            </Card>
+                </Card>
+              </>
+            )}
           </div>
           <div className={styles.btn}>
-            <Button
-              style={{ marginTop: 30, marginBottom: 30 }}
-              type="primary"
-              onClick={() => {
-                // window.close()
-                service.bind(code).then((res) => {
-                  if (res.status === 200) {
-                    message.success('绑定成功');
-                    localStorage.setItem('onBind', 'true');
-                    setTimeout(() => window.close(), 1000);
-                  } else {
-                    message.error('绑定失败');
+            {isLogin === 'no' ? (
+              <Button
+                style={{ marginTop: 10, marginBottom: 30, width: 250 }}
+                type="primary"
+                onClick={async () => {
+                  const data = await form.validateFields();
+                  if (data) {
+                    doLogin({
+                      ...data,
+                      bindCode: code,
+                    });
                   }
-                });
-              }}
-            >
-              立即绑定
-            </Button>
+                }}
+              >
+                登录并已绑定账户
+              </Button>
+            ) : (
+              <Button
+                style={{ marginTop: 30, marginBottom: 30 }}
+                type="primary"
+                onClick={() => {
+                  service.bind(code).then((res) => {
+                    if (res.status === 200) {
+                      message.success('绑定成功');
+                      localStorage.setItem('onBind', 'true');
+                      setTimeout(() => window.close(), 1000);
+                    } else {
+                      message.error('绑定失败');
+                    }
+                  });
+                }}
+              >
+                立即绑定
+              </Button>
+            )}
           </div>
         </div>
       </div>

+ 19 - 18
src/pages/account/Center/index.tsx

@@ -22,14 +22,16 @@ import PasswordEdit from './edit/passwordEdit';
 import Service from '@/pages/account/Center/service';
 import moment from 'moment';
 import { useModel } from 'umi';
+import usePermissions from '@/hooks/permission';
+import { PermissionButton } from '@/components';
 
 export const service = new Service();
 
 const Center = () => {
   const { initialState, setInitialState } = useModel('@@initialState');
+  const { permission: userPermission } = usePermissions('system/User');
   const [data, setData] = useState<any>();
   const [imageUrl, setImageUrl] = useState<string>('');
-  // const [loading, setLoading] = useState<boolean>(false)
   const [infos, setInfos] = useState<boolean>(false);
   const [password, setPassword] = useState<boolean>(false);
   const [bindList, setBindList] = useState<any>([]);
@@ -82,12 +84,6 @@ const Center = () => {
     service.getUserDetail().subscribe((res) => {
       setData(res.result);
       setImageUrl(res.result.avatar);
-      // setInitialState({
-      //   ...initialState,
-      //   currentUser:{
-
-      //   }
-      // })
     });
   };
   const saveInfo = (parms: UserDetail) => {
@@ -198,14 +194,22 @@ const Center = () => {
           </div>
         }
         extra={
-          <a>
-            {' '}
-            <EditOutlined
+          <>
+            <PermissionButton
+              type="link"
+              key="password"
+              style={{ padding: 0 }}
+              tooltip={{
+                title: '重置密码',
+              }}
               onClick={() => {
                 setPassword(true);
               }}
-            />
-          </a>
+              isPermission={userPermission.update}
+            >
+              <EditOutlined />
+            </PermissionButton>
+          </>
         }
       >
         <div style={{ display: 'flex', alignItems: 'flex-end' }}>
@@ -280,13 +284,10 @@ const Center = () => {
                       <Button
                         type="primary"
                         onClick={() => {
-                          window.open(
-                            `/${SystemConst.API_BASE}/sso/${item.provider}/login`,
-                            '',
-                            'width=700,height=500,left=500,top=300',
-                          );
-                          // window.open(`/#/account/center/bind`,'','width=700,height=500,left=500,top=300');
+                          window.open(`/${SystemConst.API_BASE}/sso/${item.provider}/login`);
+                          // window.open(`/#/account/center/bind`);
                           localStorage.setItem('onBind', 'false');
+                          localStorage.setItem('onLogin', 'yes');
                           window.onstorage = (e) => {
                             if (e.newValue) {
                               getBindInfo();

+ 2 - 2
src/pages/device/Instance/Detail/Modbus/index.tsx

@@ -54,7 +54,7 @@ const Modbus = () => {
     },
     {
       title: '值',
-      render: (record: any) => <>{propertyValue[record?.property] || '-'}</>,
+      render: (record: any) => <>{propertyValue[record?.metadataId] || '-'}</>,
     },
     {
       title: '状态',
@@ -201,7 +201,7 @@ const Modbus = () => {
         const { value } = payload;
         propertyValue[value.property] = value.formatValue;
         setPropertyValue({ ...propertyValue });
-        // console.log(propertyValue)
+        console.log(propertyValue);
       });
   }, [data]);
 

+ 1 - 1
src/pages/link/Channel/Modbus/Access/index.tsx

@@ -57,7 +57,7 @@ const Access = () => {
     },
     {
       title: '值',
-      render: (record: any) => <>{propertyValue[record?.property] || '-'}</>,
+      render: (record: any) => <>{propertyValue[record?.metadataId] || '-'}</>,
     },
     {
       title: '状态',

+ 28 - 10
src/pages/link/Channel/Opcua/Save/index.tsx

@@ -6,7 +6,10 @@ import type { ISchema } from '@formily/json-schema';
 import { service } from '@/pages/link/Channel/Opcua';
 import { Modal } from '@/components';
 import { message } from 'antd';
-import { useEffect, useMemo, useState } from 'react';
+import { useMemo } from 'react';
+import { action } from '@formily/reactive';
+import type { Response } from '@/utils/typings';
+import type { Field } from '@formily/core';
 
 interface Props {
   data: Partial<OpaUa>;
@@ -16,8 +19,8 @@ interface Props {
 
 const Save = (props: Props) => {
   const intl = useIntl();
-  const [policies, setPolicies] = useState<any>([]);
-  const [modes, setModes] = useState<any>([]);
+  // const [policies, setPolicies] = useState<any>([]);
+  // const [modes, setModes] = useState<any>([]);
 
   const form = useMemo(
     () =>
@@ -31,6 +34,21 @@ const Save = (props: Props) => {
     [props.data.id],
   );
 
+  const useAsyncDataSource = (api: any) => (field: Field) => {
+    field.loading = true;
+    api(field).then(
+      action.bound!((resp: Response<any>) => {
+        field.dataSource = resp.result?.map((item: Record<string, unknown>) => ({
+          label: item,
+          value: item,
+        }));
+        field.loading = false;
+      }),
+    );
+  };
+
+  const getPolicies = () => service.policies();
+  const getModes = () => service.modes();
   const SchemaField = createSchemaField({
     components: {
       FormItem,
@@ -125,7 +143,7 @@ const Save = (props: Props) => {
                 message: '请选择安全策略',
               },
             ],
-            enum: policies,
+            'x-reactions': ['{{useAsyncDataSource(getPolicies)}}'],
           },
           'clientConfigs.securityMode': {
             title: '安全模式',
@@ -145,7 +163,7 @@ const Save = (props: Props) => {
               },
             ],
             required: true,
-            enum: modes,
+            'x-reactions': ['{{useAsyncDataSource(getModes)}}'],
           },
           'clientConfigs.username': {
             title: '用户名',
@@ -253,10 +271,10 @@ const Save = (props: Props) => {
     }
   };
 
-  useEffect(() => {
-    service.policies().then((res) => setPolicies(res.result));
-    service.modes().then((res) => setModes(res.result));
-  }, []);
+  // useEffect(() => {
+  //   service.policies().then((res) => setPolicies(res.result));
+  //   service.modes().then((res) => setModes(res.result));
+  // }, []);
   return (
     <Modal
       title={intl.formatMessage({
@@ -272,7 +290,7 @@ const Save = (props: Props) => {
       permission={['add', 'edit']}
     >
       <Form form={form} layout="vertical">
-        <SchemaField schema={schema} />
+        <SchemaField schema={schema} scope={{ useAsyncDataSource, getPolicies, getModes }} />
       </Form>
     </Modal>
   );

+ 34 - 0
src/pages/media/DashBoard/index.less

@@ -0,0 +1,34 @@
+.media-dash-board {
+  .top-card-items {
+    margin-bottom: 12px;
+
+    .top-card-item {
+      width: 25%;
+      padding: 6px 24px;
+      border: 1px solid #e3e3e3;
+
+      .top-card-top {
+        display: flex;
+        padding: 12px 0;
+
+        .top-card-top-left {
+          width: 80px;
+        }
+
+        .top-card-top-right {
+          .top-card-total {
+            font-weight: bold;
+            font-size: 20px;
+          }
+        }
+      }
+
+      .top-card-bottom {
+        display: flex;
+        justify-content: space-between;
+        padding: 12px 0;
+        border-top: 1px solid #e3e3e3;
+      }
+    }
+  }
+}

+ 147 - 0
src/pages/media/DashBoard/index.tsx

@@ -0,0 +1,147 @@
+import { PageContainer } from '@ant-design/pro-layout';
+import { Badge, Card, Select } from 'antd';
+import DashBoard from '@/components/DashBoard';
+import { useRequest } from 'umi';
+import React, { useEffect, useState } from 'react';
+import Service from './service';
+import './index.less';
+import encodeQuery from '@/utils/encodeQuery';
+import { EChartsOption } from 'echarts';
+
+interface TopCardProps {
+  url: string;
+  title: string;
+  total: number | string;
+  bottomRender: () => React.ReactNode;
+}
+
+const service = new Service('media/device');
+
+const TopCard = (props: TopCardProps) => {
+  return (
+    <div className={'top-card-item'}>
+      <div className={'top-card-top'}>
+        <div className={'top-card-top-left'}></div>
+        <div className={'top-card-top-right'}>
+          <div className={'top-card-title'}>{props.title}</div>
+          <div className={'top-card-total'}>{props.total}</div>
+        </div>
+      </div>
+      <div className={'top-card-bottom'}>{props.bottomRender()}</div>
+    </div>
+  );
+};
+
+export default () => {
+  const [deviceOnline, setDeviceOnline] = useState(0);
+  const [deviceOffline, setDeviceOffline] = useState(0);
+  const [options, setOptions] = useState<EChartsOption>({});
+
+  const { data: deviceTotal } = useRequest(service.deviceCount, {
+    formatResult: (res) => res.result,
+  });
+
+  /**
+   * 设备数量
+   */
+  const deviceStatus = async () => {
+    const onlineRes = await service.deviceCount(encodeQuery({ terms: { state: 'online' } }));
+    if (onlineRes.status === 200) {
+      setDeviceOnline(onlineRes.result);
+    }
+    const offlineRes = await service.deviceCount(encodeQuery({ terms: { state: 'offline' } }));
+    if (offlineRes.status === 200) {
+      setDeviceOffline(offlineRes.result);
+    }
+  };
+
+  const getEcharts = async (params: any) => {
+    // 请求数据
+    console.log(params);
+
+    setOptions({
+      xAxis: {
+        type: 'category',
+        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
+      },
+      yAxis: {
+        type: 'value',
+      },
+      series: [
+        {
+          data: [150, 230, 224, 218, 135, 147, 260],
+          type: 'line',
+        },
+      ],
+    });
+  };
+
+  useEffect(() => {
+    deviceStatus();
+  }, []);
+
+  return (
+    <PageContainer>
+      <div className={'media-dash-board'}>
+        <Card className={'top-card-items'} bodyStyle={{ display: 'flex', gap: 12 }}>
+          <TopCard
+            title={'设备数量'}
+            total={deviceTotal}
+            url={''}
+            bottomRender={() => (
+              <>
+                <Badge status="error" text="在线" />{' '}
+                <span style={{ padding: '0 4px' }}>{deviceOnline}</span>
+                <Badge status="success" text="离线" />{' '}
+                <span style={{ padding: '0 4px' }}>{deviceOffline}</span>
+              </>
+            )}
+          />
+          <TopCard
+            title={'通道数量'}
+            total={12}
+            url={''}
+            bottomRender={() => (
+              <>
+                <Badge status="error" text="在线" /> <span style={{ padding: '0 4px' }}>12</span>
+                <Badge status="success" text="离线" /> <span style={{ padding: '0 4px' }}>12</span>
+              </>
+            )}
+          />
+          <TopCard
+            title={'录像数量'}
+            total={12}
+            url={''}
+            bottomRender={() => <div>总时长: </div>}
+          />
+          <TopCard
+            title={'播放中数量'}
+            total={12}
+            url={''}
+            bottomRender={() => <div>播放人数: </div>}
+          />
+        </Card>
+        <DashBoard
+          title={'播放数量(人次)'}
+          options={options}
+          height={500}
+          initialValues={{
+            test: '2',
+          }}
+          extraParams={{
+            key: 'test',
+            Children: (
+              <Select
+                options={[
+                  { label: '1', value: '1' },
+                  { label: '2', value: '2' },
+                ]}
+              ></Select>
+            ),
+          }}
+          onParamsChange={getEcharts}
+        />
+      </div>
+    </PageContainer>
+  );
+};

+ 12 - 0
src/pages/media/DashBoard/service.ts

@@ -0,0 +1,12 @@
+import BaseService from '@/utils/BaseService';
+import { request } from 'umi';
+import type { DeviceItem } from '@/pages/media/Device/typings';
+
+class Service extends BaseService<DeviceItem> {
+  deviceCount = (data?: any) => request(`${this.uri}/_count`, { methods: 'GET', params: data });
+
+  channelCount = (data?: any) =>
+    request(`${this.uri}/channel/_count`, { methods: 'POST', params: data });
+}
+
+export default Service;

+ 2 - 2
src/pages/media/Device/Channel/Live/index.tsx

@@ -58,7 +58,7 @@ const LiveFC = (props: LiveProps) => {
               onClick={async () => {
                 if (isRecord === 0) {
                   setIsRecord(1);
-                  const resp = await service.recordStop(props.deviceId, props.channelId, {
+                  const resp = await service.recordStart(props.deviceId, props.channelId, {
                     local: false,
                   });
                   if (resp.status === 200) {
@@ -67,7 +67,7 @@ const LiveFC = (props: LiveProps) => {
                     setIsRecord(0);
                   }
                 } else if (isRecord === 2) {
-                  const resp = await service.recordStart(props.deviceId, props.channelId, {
+                  const resp = await service.recordStop(props.deviceId, props.channelId, {
                     local: false,
                   });
                   if (resp.status === 200) {

+ 0 - 1
src/pages/rule-engine/Scene/Save/index.tsx

@@ -217,7 +217,6 @@ export default () => {
           preserve={false}
           className={'scene-save'}
           onValuesChange={(changeValue, allValues) => {
-            console.log(changeValue, allValues);
             if (changeValue.trigger) {
               if (changeValue.trigger.device) {
                 if (

+ 9 - 0
src/pages/system/Basis/index.less

@@ -0,0 +1,9 @@
+.content {
+  margin-left: 30px;
+  :global {
+    .ant-upload.ant-upload-select-picture-card {
+      width: 150px;
+      height: 150px;
+    }
+  }
+}

+ 137 - 0
src/pages/system/Basis/index.tsx

@@ -0,0 +1,137 @@
+import { Card, Form, Input, Select, Upload, message } from 'antd';
+import { useModel } from '@@/plugin-model/useModel';
+import { useEffect, useState } from 'react';
+import usePermissions from '@/hooks/permission';
+import { PermissionButton } from '@/components';
+import { UploadProps } from 'antd/lib/upload';
+import Token from '@/utils/token';
+import SystemConst from '@/utils/const';
+import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
+import styles from './index.less';
+
+const Basis = () => {
+  const [form] = Form.useForm();
+  const { initialState } = useModel('@@initialState');
+  const { permission: userPermission } = usePermissions('system/Basis');
+  const [imageUrl, setImageUrl] = useState<string>('');
+  const [loading, setLoading] = useState(false);
+
+  const uploadProps: UploadProps = {
+    showUploadList: false,
+    listType: 'picture-card',
+    accept: 'image/jpeg,image/png',
+    action: `/${SystemConst.API_BASE}/file/static`,
+    headers: {
+      'X-Access-Token': Token.get(),
+    },
+    beforeUpload(file) {
+      const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
+      if (!isJpgOrPng) {
+        message.error('请上传.png.jpg格式的文件');
+      }
+      return isJpgOrPng;
+    },
+    onChange(info) {
+      if (info.file.status === 'uploading') {
+        setLoading(true);
+      }
+      if (info.file.status === 'done') {
+        setImageUrl(info.file.response.result);
+        setLoading(false);
+      }
+    },
+  };
+  const uploadButton = (
+    <div>
+      {loading ? <LoadingOutlined /> : <PlusOutlined />}
+      <div style={{ marginTop: 8 }}>Upload</div>
+    </div>
+  );
+
+  useEffect(() => {
+    console.log(initialState);
+  }, []);
+
+  return (
+    <Card>
+      <div
+        style={{
+          display: 'flex',
+          alignItems: 'flex-start',
+          justifyContent: 'flex-start',
+        }}
+      >
+        <div style={{ width: 400 }}>
+          <Form layout="vertical" form={form}>
+            <Form.Item
+              label="系统名称"
+              name="title"
+              rules={[{ required: true, message: '请输入系统名称' }]}
+            >
+              <Input />
+            </Form.Item>
+            <Form.Item
+              label="主题色"
+              name="navTheme"
+              rules={[{ required: true, message: '请选择主题色' }]}
+            >
+              <Select
+                onChange={() => {
+                  // setInitialState({
+                  //     ...initialState,
+                  //     settings:{
+                  //         navTheme:e,
+                  //         headerTheme:e,
+                  //         title:'hahahah'
+                  //     }
+                  // })
+                }}
+              >
+                <Select.Option value="light">light</Select.Option>
+                <Select.Option value="dark">dark</Select.Option>
+              </Select>
+            </Form.Item>
+            <Form.Item label="高德API Key" name="apikey" tooltip="配置后平台可调用高德地图GIS服务">
+              <Input />
+            </Form.Item>
+          </Form>
+        </div>
+        <div className={styles.content}>
+          <div style={{ marginBottom: 8 }}>系统logo</div>
+          <Upload {...uploadProps}>
+            {imageUrl ? (
+              <img src={imageUrl} alt="avatar" style={{ width: '100%' }} />
+            ) : (
+              uploadButton
+            )}
+          </Upload>
+        </div>
+      </div>
+      <div>
+        <PermissionButton
+          type="primary"
+          key="basis"
+          onClick={async () => {
+            // setPassword(true);
+            const data = await form.validateFields();
+            if (data) {
+              if (imageUrl !== '') {
+                console.log({
+                  ...data,
+                  imageUrl,
+                });
+              } else {
+                message.error('请上传图片');
+              }
+            }
+          }}
+          isPermission={userPermission.update}
+        >
+          保存
+        </PermissionButton>
+      </div>
+    </Card>
+  );
+};
+
+export default Basis;

+ 0 - 0
src/pages/system/Basis/service.ts


+ 0 - 1
src/pages/system/Department/index.tsx

@@ -368,7 +368,6 @@ export default observer(() => {
       />
       <Save<DepartmentItem>
         parentChange={(pId) => {
-          console.log(getSortIndex(treeData, pId));
           return getSortIndex(treeData, pId);
         }}
         title={

+ 2 - 1
src/pages/system/Department/save.tsx

@@ -73,7 +73,8 @@ const Save = <T extends object>(props: SaveModalProps<T>) => {
     initialValues: data || {},
     effects: () => {
       onFieldReact('sortIndex', (field) => {
-        if (props.parentChange) {
+        const value = (field as Field).value;
+        if (props.parentChange && !value) {
           const sortIndex = props.parentChange(field.query('parentId').value());
           (field as Field).value = !!sortIndex ? sortIndex : sortIndex + 1;
         }

+ 3 - 0
src/pages/system/Platforms/index.tsx

@@ -46,10 +46,12 @@ export default () => {
     {
       dataIndex: 'name',
       title: '名称',
+      ellipsis: true,
     },
     {
       dataIndex: 'username',
       title: '用户名',
+      ellipsis: true,
     },
     // {
     //   dataIndex: 'roleIdList',
@@ -98,6 +100,7 @@ export default () => {
         defaultMessage: '说明',
       }),
       hideInSearch: true,
+      ellipsis: true,
     },
     {
       title: intl.formatMessage({

+ 36 - 4
src/pages/user/Login/index.tsx

@@ -1,4 +1,4 @@
-import { Checkbox, message, Spin } from 'antd';
+import { Button, Checkbox, message, Spin } from 'antd';
 import React, { useEffect, useRef, useState } from 'react';
 import { Link } from 'umi';
 import styles from './index.less';
@@ -10,16 +10,16 @@ import { Form, FormItem, Input, Password, Submit } from '@formily/antd';
 import { catchError, filter, mergeMap } from 'rxjs/operators';
 import * as ICONS from '@ant-design/icons';
 import { useModel } from '@@/plugin-model/useModel';
-// import SystemConst from '@/utils/const';
+import SystemConst from '@/utils/const';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { SelectLang } from '@@/plugin-locale/SelectLang';
 import Footer from '@/components/Footer';
+import { DingdingOutlined, WechatOutlined } from '@ant-design/icons';
 
 const Login: React.FC = () => {
   const [captcha, setCaptcha] = useState<{ key?: string; base64?: string }>({});
-
+  const [bindings, setBindings] = useState<any>([]);
   const { initialState, setInitialState } = useModel('@@initialState');
-
   const intl = useIntl();
 
   const fetchUserInfo = async () => {
@@ -63,6 +63,13 @@ const Login: React.FC = () => {
   };
 
   useEffect(getCode, []);
+  useEffect(() => {
+    Service.bindInfo().then((res) => {
+      if (res.status === 200) {
+        setBindings(res.result);
+      }
+    });
+  }, []);
 
   const SchemaField = createSchemaField({
     components: {
@@ -188,6 +195,31 @@ const Login: React.FC = () => {
                       defaultMessage: '登录',
                     })}
                   </Submit>
+                  <div style={{ marginTop: 10 }}>
+                    <div>其他方式登录</div>
+                    <div>
+                      {bindings.map((item: any) => (
+                        <Button
+                          type="link"
+                          onClick={() => {
+                            localStorage.setItem('onLogin', 'no');
+                            window.open(`/${SystemConst.API_BASE}/sso/${item.provider}/login`);
+                            window.onstorage = (e) => {
+                              if (e.newValue) {
+                                window.location.href = '/';
+                              }
+                            };
+                          }}
+                        >
+                          {item.type === 'dingtalk' ? (
+                            <DingdingOutlined style={{ color: '#009BF5', fontSize: '20px' }} />
+                          ) : (
+                            <WechatOutlined style={{ color: '#2AAE67', fontSize: '20px' }} />
+                          )}
+                        </Button>
+                      ))}
+                    </div>
+                  </div>
                 </Form>
               </div>
             </div>

+ 5 - 0
src/pages/user/Login/service.ts

@@ -47,6 +47,11 @@ const Service = {
     request(`${SystemConst.API_BASE}/user-token/reset`, {
       method: 'GET',
     }),
+  bindInfo: (params?: any) =>
+    request(`/${SystemConst.API_BASE}/sso/providers`, {
+      method: 'GET',
+      params,
+    }),
 };
 
 export default Service;

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

@@ -55,6 +55,7 @@ export enum MENUS_CODE {
   'media/Reveal' = 'media/Reveal',
   'media/Stream' = 'media/Stream',
   'media/Stream/Detail' = 'media/Stream/Detail',
+  'media/DashBoard' = 'media/DashBoard',
   'notice/Type' = 'notice/Type',
   'notice/Config' = 'notice/Config',
   'media/SplitScreen' = 'media/SplitScreen',
@@ -90,6 +91,7 @@ export enum MENUS_CODE {
   'system/Tenant' = 'system/Tenant',
   'system/User' = 'system/User',
   'system/Relationship' = 'system/Relationship',
+  'system/Basis' = 'system/Basis',
   'user/Login' = 'user/Login',
   'visualization/Category' = 'visualization/Category',
   'visualization/Configuration' = 'visualization/Configuration',