Browse Source

对接企业微信的二维码登录和客户端登录

tuonina 5 years ago
parent
commit
85860b6464

+ 4 - 0
config/env.js

@@ -14,6 +14,10 @@ if (!NODE_ENV) {
   );
 }
 
+console.log('env=>',process.env);
+process.env.PORT=80;
+
+
 // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
 var dotenvFiles = [
   `${paths.dotenv}.${NODE_ENV}.local`,

+ 2 - 1
public/index.html

@@ -26,6 +26,7 @@
 <body>
 	<link rel="stylesheet/less" type="text/css" href="./theme.less" />
 	<script src="https://cdnjs.cloudflare.com/ajax/libs/less.js/2.7.2/less.min.js"></script>
+	<script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
 	<script>
 		const primaryColor = localStorage.getItem('@primary-color');
 		primaryColor && window.less.modifyVars({
@@ -48,4 +49,4 @@
     -->
 </body>
 
-</html>
+</html>

+ 8 - 5
src/App.js

@@ -14,6 +14,7 @@ class App extends Component {
         collapsed: false,
         title: '',
     };
+
     componentWillMount() {
         const { setAlitaState } = this.props;
         const user = JSON.parse(localStorage.getItem('user'));
@@ -27,6 +28,7 @@ class App extends Component {
             this.getClientWidth();
         };
     }
+
     componentDidMount() {
         const openNotification = () => {
             notification.open({
@@ -55,7 +57,7 @@ class App extends Component {
                         </p>
                     </div>
                 ),
-                icon: <Icon type="smile-circle" style={{ color: 'red' }} />,
+                icon: <Icon type="smile-circle" style={{ color: 'red' }}/>,
                 duration: 0,
             });
             localStorage.setItem('isFirst', JSON.stringify(true));
@@ -63,6 +65,7 @@ class App extends Component {
         const isFirst = JSON.parse(localStorage.getItem('isFirst'));
         !isFirst && openNotification();
     }
+
     getClientWidth = () => {
         // 获取当前浏览器宽度并设置responsive管理响应式
         const { setAlitaState } = this.props;
@@ -76,15 +79,15 @@ class App extends Component {
             collapsed: !this.state.collapsed,
         });
     };
+
     render() {
         const { title } = this.state;
         const { auth = { data: {} }, responsive = { data: {} } } = this.props;
-        console.log(auth);
         return (
             <DocumentTitle title={title}>
                 <Layout>
-                    {!responsive.data.isMobile && <SiderCustom collapsed={this.state.collapsed} />}
-                    <ThemePicker />
+                    {!responsive.data.isMobile && <SiderCustom collapsed={this.state.collapsed}/>}
+                    <ThemePicker/>
                     <Layout style={{ flexDirection: 'column' }}>
                         <HeaderCustom
                             toggle={this.toggle}
@@ -92,7 +95,7 @@ class App extends Component {
                             user={auth.data || {}}
                         />
                         <Content style={{ margin: '0 16px', overflow: 'initial', flex: '1 1 0' }}>
-                            <Routes auth={auth} />
+                            <Routes auth={auth} {...this.props}/>
                         </Content>
                         <Footer style={{ textAlign: 'center' }}>
                             React-Admin ©{new Date().getFullYear()} Created by 976056042@qq.com

+ 3 - 2
src/api/index.js

@@ -2,10 +2,11 @@
  * 所有API统一出口
  */
 
-import * as configAPI from './config'
+import * as configAPI from './config';
+import * as qywxAPI from './qywx';
 
 
-const apis={...configAPI};
+const apis = { ...configAPI, ...qywxAPI };
 
 
 export default apis;

+ 15 - 0
src/api/qywx/index.js

@@ -0,0 +1,15 @@
+import qywx from '../../const/qywx';
+import { request } from '../../axios';
+
+
+/**
+ * 根据临时票据查询用户信息
+ * @param params
+ * @returns {Promise<unknown>}
+ */
+export const qywxUserInfo = (params) => request({
+    url: 'http://100.100.1.35:11000/qywx/user',
+    method: 'get',
+    params: { ...params, agentId: qywx.agentId },
+}).then(resp => resp.data);
+

+ 11 - 0
src/axios/index.js

@@ -6,6 +6,17 @@ import { get, post } from './tools';
 import * as config from './config';
 import { findPLabel, labelAdd, labelEdit, pLabelDelete, searchLabel } from './property/label';
 import { queryConfig } from './property/config';
+import CONST from '../const'
+
+
+export const request = axios.create({
+    baseURL:CONST.BASE_API,
+    timeout:500000,
+    withCredentials:true,
+    maxRedirects:0,
+    transformRequest:axios.defaults.transformRequest,
+    transformResponse:axios.defaults.transformResponse
+});
 
 /*配置中心*/
 export {

+ 1 - 1
src/axios/tools.js

@@ -20,7 +20,7 @@ function checkURL(url) {
  * @param params    请求参数
  * @param headers   接口所需header配置
  */
-export const get = ({ url, headers, params, msg = '接口异常' }) =>
+export const get = ({ url, headers={}, params, msg = '接口异常' }) =>
     axios
         .get(checkURL(url), { headers, params })
         .then(res => res.data)

+ 28 - 21
src/components/pages/Login.jsx

@@ -2,10 +2,10 @@
  * Created by hao.cheng on 2017/4/16.
  */
 import React from 'react';
-import { Form, Icon, Input, Button, Checkbox } from 'antd';
+import { Button, Checkbox, Form, Icon, Input } from 'antd';
 import { PwaInstaller } from '../widget';
 import { connectAlita } from 'redux-alita';
-import QyWx from '../../const/qywx'
+import { qyWxOauthUrl } from '../../const';
 
 const FormItem = Form.Item;
 
@@ -14,6 +14,7 @@ class Login extends React.Component {
         const { setAlitaState } = this.props;
         setAlitaState({ stateName: 'auth', data: null });
     }
+
     componentDidUpdate(prevProps) { // React 16.3+弃用componentWillReceiveProps
         const { auth: nextAuth = {}, history } = this.props;
         // const { history } = this.props;
@@ -22,47 +23,52 @@ class Login extends React.Component {
             history.push('/');
         }
     }
+
     handleSubmit = (e) => {
         e.preventDefault();
         this.props.form.validateFields((err, values) => {
             if (!err) {
                 console.log('Received values of form: ', values);
                 const { setAlitaState } = this.props;
-                if (values.userName === 'admin' && values.password === 'admin') setAlitaState({ funcName: 'admin', stateName: 'auth' });
-                if (values.userName === 'guest' && values.password === 'guest') setAlitaState({ funcName: 'guest', stateName: 'auth' });
+                if (values.userName === 'admin' && values.password === 'admin') setAlitaState({
+                    funcName: 'admin',
+                    stateName: 'auth',
+                });
+                if (values.userName === 'guest' && values.password === 'guest') setAlitaState({
+                    funcName: 'guest',
+                    stateName: 'auth',
+                });
             }
         });
     };
-    gitHub = () => {
-        window.location.href = 'https://github.com/login/oauth/authorize?client_id=792cdcd244e98dcd2dee&redirect_uri=http://localhost:3006/&scope=user&state=reactAdmin';
-    };
-
-    qyWx=()=>{
-        window.location.href = QyWx.oauth
+    qyWx = () => {
+        window.location.href = qyWxOauthUrl();
     };
 
     render() {
         const { getFieldDecorator } = this.props.form;
         return (
             <div className="login">
-                <div className="login-form" >
+                <div className="login-form">
                     <div className="login-logo">
                         <span>React Admin</span>
-                        <PwaInstaller />
+                        <PwaInstaller/>
                     </div>
-                    <Form onSubmit={this.handleSubmit} style={{maxWidth: '300px'}}>
+                    <Form onSubmit={this.handleSubmit} style={{ maxWidth: '300px' }}>
                         <FormItem>
                             {getFieldDecorator('userName', {
                                 rules: [{ required: true, message: '请输入用户名!' }],
                             })(
-                                <Input prefix={<Icon type="user" style={{ fontSize: 13 }} />} placeholder="管理员输入admin, 游客输入guest" />
+                                <Input prefix={<Icon type="user" style={{ fontSize: 13 }}/>}
+                                       placeholder="管理员输入admin, 游客输入guest"/>,
                             )}
                         </FormItem>
                         <FormItem>
                             {getFieldDecorator('password', {
                                 rules: [{ required: true, message: '请输入密码!' }],
                             })(
-                                <Input prefix={<Icon type="lock" style={{ fontSize: 13 }} />} type="password" placeholder="管理员输入admin, 游客输入guest" />
+                                <Input prefix={<Icon type="lock" style={{ fontSize: 13 }}/>} type="password"
+                                       placeholder="管理员输入admin, 游客输入guest"/>,
                             )}
                         </FormItem>
                         <FormItem>
@@ -70,15 +76,16 @@ class Login extends React.Component {
                                 valuePropName: 'checked',
                                 initialValue: true,
                             })(
-                                <Checkbox>记住我</Checkbox>
+                                <Checkbox>记住我</Checkbox>,
                             )}
-                            <span className="login-form-forgot" href="" style={{float: 'right'}}>忘记密码</span>
-                            <Button type="primary" htmlType="submit" className="login-form-button" style={{width: '100%'}}>
+                            <span className="login-form-forgot" href="" style={{ float: 'right' }}>忘记密码</span>
+                            <Button type="primary" htmlType="submit" className="login-form-button"
+                                    style={{ width: '100%' }}>
                                 登录
                             </Button>
-                            <p style={{display: 'flex', justifyContent: 'space-between'}}>
-                                <span >或 现在就去注册!</span>
-                                <span onClick={this.qyWx} ><Icon type="github" />(企业微信)</span>
+                            <p style={{ display: 'flex', justifyContent: 'space-between' }}>
+                                <span>或 现在就去注册!</span>
+                                <span onClick={this.qyWx}><Icon type="github"/>(企业微信)</span>
                             </p>
                         </FormItem>
                     </Form>

+ 30 - 0
src/components/pages/qywx/QywxAuth.jsx

@@ -0,0 +1,30 @@
+/**
+ * @Classname QywxAuth
+ * @Description TODO
+ * @Date 2019/8/26 13:24
+ * @Created by Administrator
+ */
+
+import React from 'react';
+import * as PropTypes from 'prop-types';
+
+class QywxAuth extends React.Component {
+
+    static propTypes = {};
+
+    static defaultProps = {};
+
+    constructor(props) {
+        super(props);
+        this.state = {};
+    }
+
+    componentDidMount() {
+
+    }
+
+
+}
+
+
+export default QywxAuth;

+ 36 - 4
src/const/index.js

@@ -1,4 +1,4 @@
-import qywx from './qywx'
+import qywx from './qywx';
 
 const NODE_ENV = process.env.NODE_ENV;
 
@@ -10,6 +10,7 @@ console.log('当前环境: ', NODE_ENV);
  */
 const BASE_CONF = {
     BASE_API: 'http://config.tonyandmoney.cn/',
+    WX_OAUTH_STATE: 'wx_oauth_',
 };
 
 /**
@@ -25,8 +26,39 @@ if (NODE_ENV === 'development') {
 export default CONF;
 
 
+/**
+ * 构造企业微信Oauth认证授权链接
+ * @param uri 因为是前端采用hash,#xxx的方式跳转页面,所以把最终跳转的页面放在state中,原样返回
+ * @returns {string}
+ */
+export const qyWxOauthUrl = (uri) => {
+    return `${qywx.path}appid=${qywx.appId}&redirect_uri=${encodeURI(qywx.redirect_url)}
+    &response_type=code&scope=snsapi_base&state=${CONF.WX_OAUTH_STATE}${uri}#wechat_redirect`;
+};
 
-export const qyWxOauthUrl=(state)=>{
-    return ``
-}
+/**
+ * 企业微信二维码登录
+ * @param uri 登录成功后跳转的路径
+ * @returns {string}
+ */
+export const qyWxQrCodeUri = (uri) => {
+    return `https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=${qywx.appId}&agentid=${qywx.agentId}
+    &redirect_uri=${qywx.redirect_url}&state=${CONF.WX_OAUTH_STATE}${uri}`;
+};
 
+export const getStateUri = (state) => {
+    if (!!state){
+        return  state.replace(CONF.WX_OAUTH_STATE, '');
+    }else {
+        return ''
+    }
+};
+
+export function wxConfig() {
+    console.log('wx=>', window.wx);
+    window.wx.config({
+        beta: true,
+        debug: true,
+        appId: qywx.appId,
+    });
+}

+ 4 - 3
src/const/qywx.json

@@ -1,8 +1,9 @@
 {
-  "appId": "1000002",
-  "redirect_url": "http://config.tonyandmoney.cn/portal#/app/wx",
+  "appId": "ww8f998c05fc31f118",
+  "redirect_url": "http://portal.tonyandmoney.cn/",
   "response_type": "code",
   "scope": "snsapi_userinfo",
   "path": "https://open.weixin.qq.com/connect/oauth2/authorize?",
-  "end": "#wechat_redirect"
+  "end": "#wechat_redirect",
+  "agentId": "1000002"
 }

+ 1 - 1
src/routes/config.js

@@ -83,7 +83,7 @@ export default {
             key: '/subs4',
             title: '页面',
             icon: 'switcher',
-            subs: [{ key: '/login', title: '登录' }, { key: '/404', title: '404' }],
+            subs: [{ key: '/login', title: '登录' ,permission:"none"}, { key: '/404', title: '404',permission:"none" }],
         },
         {
             key: '/app/auth',

+ 133 - 63
src/routes/index.js

@@ -1,5 +1,6 @@
 /**
  * Created by 叶子 on 2017/8/13.
+ * 所有路由的入口
  */
 import React, { Component } from 'react';
 import { Redirect, Route, Switch } from 'react-router-dom';
@@ -7,81 +8,150 @@ import DocumentTitle from 'react-document-title';
 import Components from '../components';
 import AllPages from '../pages';
 import routesConfig from './config';
-import queryString from 'query-string';
+import { getStateUri, qyWxOauthUrl, qyWxQrCodeUri } from '../const';
+import { queryString } from '../utils';
+import * as PropTypes from 'prop-types';
+import { Modal, Skeleton } from 'antd';
 
 let AllComponents = { ...Components, ...AllPages };
 export default class CRouter extends Component {
+
+    static propTypes = {
+        auth: PropTypes.object,
+    };
+
+    constructor(props) {
+        super(props);
+        this.state = {
+            isLoading: false,
+            modal: {
+                title: '',
+                visible: false,
+
+            },
+        };
+        console.log('props=>', props);
+    }
+
+    /**
+     * 暂时不管权限
+     **/
     requireAuth = (permission, component) => {
-        const { auth } = this.props;
-        const { permissions } = auth.data;
-        // const { auth } = store.getState().httpData;
-        if (!permissions || !permissions.includes(permission)) return <Redirect to={'404'}/>;
+        // const { auth } = this.props;
+        // const { permissions } = auth.data;
+        // if (!permissions || !permissions.includes(permission)) return <Redirect to={'404'}/>;
         return component;
     };
-    requireLogin = (component, permission) => {
+
+
+    requireWxOauth = (component, permission, info) => {
+        let params = queryString();
+        this.requestQywxUser(params,info)
+            .then(() => undefined);
+        return null;
+    };
+
+
+    requireLogin = (component, permission, info) => {
         const { auth } = this.props;
-        const { permissions } = auth.data;
-        if (process.env.NODE_ENV === 'production' && !permissions) {
-            // 线上环境判断是否登录
-            return <Redirect to={'/login'}/>;
+        const { user } = auth.data;
+        if (!user) {
+            // return <Redirect to={'/login'}/>;
+            return this.requireWxOauth(component, permission, info);
         }
-        return permission ? this.requireAuth(permission, component) : component;
+        return (!permission && permission === 'none') ? component : this.requireAuth(permission, component);
+    };
+
+
+    async requestQywxUser(params,info) {
+        this.setState({ isLoading: true });
+        let { code, state, appid } = params;
+        let path = getStateUri(state);
+        let _this = this;
+        this.props.setAlitaState({ funcName: 'qywxUserInfo', params: { code, appid } })
+            .then(({ data: resp }) => {
+                console.log('resp=>', resp, path);
+                if (resp.code === 0) {
+                    _this.props.setAlitaState({ stateName: 'auth', data: { user: resp.data } });
+                    _this.setState({ isLoading: false });
+                } else if (resp.code === 40029) {
+                    window.location.href = qyWxOauthUrl(info.key);
+                } else {
+                    Promise.reject({ message: resp.message });
+                }
+            })
+            .catch(e => {
+                console.log('error=>', e);
+                Modal.confirm({
+                    title: '错误',
+                    content: '请求用户信息错误,请重试!',
+                    onOk() {
+                        _this.requestQywxUser(params);
+                    },
+                    onCancel() {
+                        window.location.href = '/';
+                    },
+                });
+            });
     };
 
     render() {
+        let { isLoading, modal } = this.state;
+        console.log('isLoading', isLoading, modal);
         return (
-            <Switch>
-                {Object.keys(routesConfig).map(key =>
-                    routesConfig[key].map(r => {
-                        const route = r => {
-                            let Component;
-                            let component = r.component;
-                            if (typeof component == 'string') {
-                                Component = AllComponents[r.component];
-                            } else {
-                                Component = component;
-                            }
-                            return (
-                                <Route
-                                    key={r.route || r.key}
-                                    exact
-                                    path={r.route || r.key}
-                                    render={props => {
-                                        const reg = /\?\S*/g;
-                                        // 匹配?及其以后字符串
-                                        const queryParams = window.location.hash.match(reg);
-                                        // 去除?的参数
-                                        const { params } = props.match;
-                                        Object.keys(params).forEach(key => {
-                                            params[key] =
-                                                params[key] && params[key].replace(reg, '');
-                                        });
-                                        props.match.params = { ...params };
-                                        const merge = {
-                                            ...props,
-                                            query: queryParams
-                                                ? queryString.parse(queryParams[0])
-                                                : {},
-                                        };
-                                        // 重新包装组件
-                                        const wrappedComponent = (
-                                            <DocumentTitle title={r.title}>
-                                                <Component {...merge} />
-                                            </DocumentTitle>
-                                        );
-                                        return r.login
-                                            ? wrappedComponent
-                                            : this.requireLogin(wrappedComponent, r.auth);
-                                    }}
-                                />
-                            );
-                        };
-                        return r.component ? route(r) : r.subs.map(r => route(r));
-                    }),
-                )}
-
-                <Route render={() => <Redirect to="/404"/>}/>
-            </Switch>
+            <Skeleton loading={isLoading}>
+                <Switch>
+                    {Object.keys(routesConfig).map(key =>
+                        routesConfig[key].map(r => {
+                            const route = r => {
+                                let Component;
+                                let component = r.component;
+                                if (typeof component == 'string') {
+                                    Component = AllComponents[r.component];
+                                } else {
+                                    Component = component;
+                                }
+                                return (
+                                    <Route
+                                        key={r.route || r.key}
+                                        exact
+                                        path={r.route || r.key}
+                                        render={props => {
+                                            const reg = /\?\S*/g;
+                                            // 匹配?及其以后字符串
+                                            const queryParams = window.location.hash.match(reg);
+                                            // 去除?的参数
+                                            const { params } = props.match;
+                                            Object.keys(params).forEach(key => {
+                                                params[key] =
+                                                    params[key] && params[key].replace(reg, '');
+                                            });
+                                            props.match.params = { ...params };
+                                            const merge = {
+                                                ...props,
+                                                query: queryParams
+                                                    ? queryString.parse(queryParams[0])
+                                                    : {},
+                                            };
+                                            // 重新包装组件
+                                            const wrappedComponent = (
+                                                <DocumentTitle title={r.title}>
+                                                    <Component {...merge} />
+                                                </DocumentTitle>
+                                            );
+                                            return r.login
+                                                ? wrappedComponent
+                                                : this.requireLogin(wrappedComponent, r.auth, r);
+                                        }}
+                                    />
+                                );
+                            };
+                            return r.component ? route(r) : r.subs.map(r => route(r));
+                        }),
+                    )}
+                    <Route render={() => <Redirect to="/404"/>}/>
+                </Switch>
+            </Skeleton>
         );
     }
 }