Browse Source

feat: oauth授权页

wzyyy 3 năm trước cách đây
mục cha
commit
96412e7a2f

+ 11 - 0
config/routes.ts

@@ -53,6 +53,17 @@
       },
     ],
   },
+  {
+    path: '/oauth',
+    layout: false,
+    routes: [
+      {
+        name: '授权页',
+        path: '/oauth',
+        component: './oauth',
+      },
+    ],
+  },
   // {
   //   path: '/analysis',
   //   name: 'analysis',

+ 6 - 3
src/app.tsx

@@ -31,6 +31,7 @@ const isDev = process.env.NODE_ENV === 'development';
 const loginPath = '/user/login';
 const bindPath = '/account/center/bind';
 const licensePath = '/init-license';
+const oauthPath = '/oauth';
 let extraRoutes: any[] = [];
 // const { permission: userPermission } = usePermissions('system/License');
 
@@ -78,7 +79,8 @@ export async function getInitialState(): Promise<{
   if (
     history.location.pathname !== loginPath &&
     history.location.pathname !== bindPath &&
-    history.location.pathname !== licensePath
+    history.location.pathname !== licensePath &&
+    history.location.pathname !== oauthPath
   ) {
     const currentUser = await fetchUserInfo();
     const settings = await getSettings();
@@ -335,7 +337,8 @@ export const layout: RunTimeLayoutConfig = ({ initialState }) => {
         !initialState?.currentUser &&
         location.pathname !== loginPath &&
         location.pathname !== bindPath &&
-        location.pathname !== licensePath
+        location.pathname !== licensePath &&
+        location.pathname !== oauthPath
       ) {
         history.push(loginPath);
       }
@@ -417,7 +420,7 @@ export function patchRoutes(routes: any) {
 }
 
 export function render(oldRender: any) {
-  if (![loginPath, bindPath, licensePath].includes(history.location.pathname)) {
+  if (![loginPath, bindPath, licensePath, oauthPath].includes(history.location.pathname)) {
     //过滤非集成的菜单
     const params = [
       {

+ 3 - 1
src/pages/iframe/index.tsx

@@ -11,13 +11,15 @@ const Iframe = () => {
     const res = await service.detail(appId);
     let menuUrl: any = url;
     if (res.status === 200) {
+      console.log(res.result);
       if (res.result.page.routeType === 'hash') {
-        menuUrl = `/#/${url}?layout=false`;
+        menuUrl = `/${url}`;
       }
       if (res.result.provider === 'internal-standalone') {
         //{baseUrl}/api/application/sso/{appId}/login?redirect={menuUrl}
         const urlStandalone = `${res.result.page.baseUrl}/api/application/sso/${appId}/login?redirect=${menuUrl}?layout=false`;
         setIframeUrl(urlStandalone);
+        console.log(urlStandalone);
       } else {
         const urlOther = `${res.result.page.baseUrl}/${menuUrl}`;
         setIframeUrl(urlOther);

+ 78 - 0
src/pages/oauth/index.less

@@ -0,0 +1,78 @@
+.oauth {
+  .oauth-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: 60px;
+    font-size: 26px;
+    background-color: #fff;
+
+    .oauth-header-left {
+      margin-left: 10%;
+    }
+
+    .oauth-header-right {
+      display: flex;
+      width: 200px;
+      margin-right: 10%;
+      font-size: 14px;
+
+      .oauth-header-right-text {
+        color: rgb(0 0 0 / 70%);
+      }
+
+      // .oauth-header-right-connect {
+      //     padding: 0 10px;
+      // }
+    }
+  }
+
+  .oauth-content {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: space-evenly;
+    width: 370px;
+    height: 380px;
+    margin: 0 auto;
+    margin-top: 5%;
+    background: #fff;
+    box-shadow: 0 5px 5px #d4d4d4;
+
+    .oauth-content-header {
+      width: 60px;
+      height: 60px;
+
+      img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+    .oauth-content-content {
+      height: 150px;
+      .oauth-content-content-text {
+        margin: 15px 15px;
+        font-size: 16px;
+        line-height: 22px;
+      }
+
+      ul {
+        color: #00000085;
+        list-style: inherit;
+        li {
+          padding-top: 10px;
+        }
+      }
+    }
+
+    .oauth-content-button {
+      display: flex;
+      justify-content: space-around;
+      width: 200px;
+    }
+    .oauth-content-login {
+      max-width: 300px;
+    }
+  }
+}

+ 282 - 0
src/pages/oauth/index.tsx

@@ -0,0 +1,282 @@
+import { Button, message } from 'antd';
+import { useEffect, useMemo, useRef, useState } from 'react';
+import { useIntl } from 'umi';
+import './index.less';
+import { createForm } from '@formily/core';
+import { createSchemaField } from '@formily/react';
+import { Form, FormItem, Input, Password, Submit } from '@formily/antd';
+import { catchError, filter, mergeMap } from 'rxjs/operators';
+import Service from '@/pages/user/Login/service';
+import * as ICONS from '@ant-design/icons';
+import React from 'react';
+
+const Oauth = () => {
+  const intl = useIntl();
+  const logo = require('/public/logo.svg');
+  const bindPage = require('/public/images/bind/bindPage.png');
+  const headerImg = require('/public/logo.png');
+
+  const [isLogin, setIsLogin] = useState<boolean>(true);
+  const [captcha, setCaptcha] = useState<{ key?: string; base64?: string }>({});
+  const [userName, setUerName] = useState<string>('');
+  const [appName, setAppName] = useState<string>('');
+  const [params, setParams] = useState<any>({});
+  const [internal, setLinternal] = useState<any>();
+
+  const loginRef = useRef<Partial<LoginParam>>({});
+  const loginForm = useMemo(
+    () =>
+      createForm({
+        validateFirst: true,
+        initialValues: loginRef.current,
+      }),
+    [captcha],
+  );
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Password,
+      // Checkbox,
+    },
+    scope: {
+      icon(name: any) {
+        return React.createElement(ICONS[name]);
+      },
+    },
+  });
+  const getCode = () => {
+    delete loginForm.values?.verifyCode;
+    loginRef.current = loginForm.values;
+    // setLoading(false);
+    Service.captchaConfig()
+      .pipe(
+        filter((r) => {
+          if (!r.enabled) {
+            // setLoading(false);
+          }
+          return r.enabled;
+        }),
+        mergeMap(Service.getCaptcha),
+        catchError(() => message.error('系统开小差,请稍后重试')),
+      )
+      .subscribe((res) => {
+        setCaptcha(res);
+        // setLoading(false);
+      });
+  };
+  const goOAuth2 = async () => {
+    const res = await Service.getOAuth2(params);
+    if (res.status === 200) {
+      window.location.href = res.result;
+    } else {
+      getCode();
+    }
+  };
+  const doLogin = async (data: LoginParam) => {
+    // setIsLogin(true)
+    const res = await Service.login2({
+      expires: loginRef.current.expires,
+      verifyKey: captcha.key,
+      ...data,
+    });
+    if (res.status === 200) {
+      const token = res.result.token;
+      localStorage.setItem('X-Access-Token', token);
+      goOAuth2();
+    } else {
+      getCode();
+    }
+  };
+  const schema = {
+    type: 'object',
+    properties: {
+      username: {
+        type: 'string',
+        'x-decorator': 'FormItem',
+        'x-validator': { required: true, message: '请输入用户名!' },
+        'x-component': 'Input',
+        'x-component-props': {
+          placeholder: intl.formatMessage({
+            id: 'pages.login.username.placeholder',
+            defaultMessage: '用户名',
+          }),
+          prefix: "{{icon('UserOutlined')}}",
+        },
+      },
+      password: {
+        type: 'string',
+        'x-validator': { required: true, message: '请输入密码!' },
+        'x-decorator': 'FormItem',
+        'x-component': 'Password',
+        'x-component-props': {
+          prefix: "{{icon('LockOutlined')}}",
+          placeholder: intl.formatMessage({
+            id: 'pages.login.password.placeholder',
+            defaultMessage: '密码',
+          }),
+        },
+      },
+      verifyCode: {
+        type: 'string',
+        'x-visible': !!captcha.key,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          addonAfter: <img src={captcha.base64} alt="验证码" onClick={getCode} />,
+          placeholder: intl.formatMessage({
+            id: 'pages.login.captcha.placeholder',
+            defaultMessage: '请输入验证码',
+          }),
+        },
+      },
+    },
+  };
+
+  //未登录状态
+  const loginPage = () => {
+    return (
+      <>
+        <div className="oauth-content-header">
+          <img src={headerImg} />
+        </div>
+        <div className="oauth-content-login">
+          <Form form={loginForm} layout="horizontal" size="large" onAutoSubmit={doLogin}>
+            <SchemaField schema={schema} />
+            <Submit block size="large">
+              {intl.formatMessage({
+                id: 'pages.login.submit',
+                defaultMessage: '登录',
+              })}
+            </Submit>
+          </Form>
+        </div>
+      </>
+    );
+  };
+
+  const initApplication = async () => {
+    const res: any = await Service.initApplication(params.client_id);
+    if (res.status === 200) {
+      setAppName(res.result?.name);
+    }
+  };
+
+  const getLoginUser = async () => {
+    const res = await Service.queryCurrent();
+    if (res && res.status === 200) {
+      setUerName(res.result.user.name);
+      setIsLogin(true);
+      initApplication();
+      if (internal === 'true') {
+        goOAuth2();
+      }
+    } else if (res.status === 401) {
+      setIsLogin(false);
+      getCode();
+      initApplication();
+    } else {
+      setIsLogin(false);
+    }
+  };
+
+  const getQueryVariable = (variable: any) => {
+    const query = window.location.search.substring(1);
+    const vars = query.split('&');
+    for (let i = 0; i < vars.length; i++) {
+      const pair = vars[i].split('=');
+      if (pair[0] === variable) {
+        return pair[1];
+      }
+    }
+    return '';
+  };
+
+  useEffect(() => {
+    document.title = 'OAuth授权-jetlinks';
+    getCode();
+    getLoginUser();
+  }, []);
+
+  useEffect(() => {
+    const items = {
+      code: getQueryVariable('code'),
+      client_id: getQueryVariable('client_id'),
+      state: getQueryVariable('state'),
+      redirect_uri: decodeURIComponent(getQueryVariable('redirect_uri')),
+      response_type: getQueryVariable('response_type'),
+      scope: getQueryVariable('scope'),
+    };
+    const item = getQueryVariable('internal');
+    setLinternal(item);
+    setParams(items);
+    console.log(items);
+  }, [window.location]);
+
+  return (
+    <div
+      className="oauth"
+      style={{
+        width: '100%',
+        height: '100vh',
+        background: `url(${bindPage}) no-repeat`,
+        backgroundSize: '100% 100%',
+      }}
+    >
+      <div className="oauth-header">
+        <div className="oauth-header-left">
+          <img src={logo} />
+        </div>
+        <div className="oauth-header-right">
+          <a style={{ color: 'rgb(0 0 0 / 70%)' }}>{userName || '-'}</a>
+          {/* <div className="oauth-header-right-connect">|</div>
+                    <a
+                        style={{ color: 'rgb(0 0 0 / 70%)' }}
+                        onClick={(() => {
+                            setIsLogin(false)
+                        })}
+                    >切换账号</a> */}
+        </div>
+      </div>
+      <div className="oauth-content">
+        {isLogin ? (
+          <>
+            <div className="oauth-content-header">
+              <img src={headerImg} />
+            </div>
+            <div className="oauth-content-content">
+              <div className="oauth-content-content-text">
+                {`您正在授权登录,${appName || '-'}将获得以下权限:`}
+              </div>
+              <ul>
+                <li>关联jetlinks账号</li>
+                <li>获取您的个人信息 </li>
+              </ul>
+            </div>
+            <div className="oauth-content-button">
+              <Button
+                type="primary"
+                onClick={() => {
+                  goOAuth2();
+                }}
+              >
+                同意授权
+              </Button>
+              <Button
+                onClick={() => {
+                  localStorage.removeItem('X-Access-Token');
+                  setIsLogin(false);
+                }}
+              >
+                切换账号
+              </Button>
+            </div>
+          </>
+        ) : (
+          loginPage()
+        )}
+      </div>
+    </div>
+  );
+};
+export default Oauth;

+ 5 - 1
src/pages/system/DataSource/Save/index.tsx

@@ -360,7 +360,11 @@ const Save = (props: Props) => {
         props.close();
       }}
       onOk={() => {
-        handleSave();
+        if (props.data.id && props.data.typeId === 'rdb') {
+          handleSave();
+        } else {
+          onlyMessage('该类型数据库不可以编辑', 'warning');
+        }
       }}
     >
       <Form form={form} layout="vertical">

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

@@ -66,6 +66,16 @@ const Service = {
       method: 'GET',
     }),
   initPage: () => request(`/${SystemConst.API_BASE}/user/settings/init`, { method: 'GET' }),
+
+  getOAuth2: (params?: any) =>
+    request(`/${SystemConst.API_BASE}/oauth2/authorize`, {
+      method: 'GET',
+      params,
+    }),
+  initApplication: (clientId: any) =>
+    request(`/${SystemConst.API_BASE}/application/${clientId}/info`, {
+      method: 'GET',
+    }),
 };
 
 export default Service;