Commit 1fc4aa00 authored by 崔佳豪's avatar 崔佳豪

feat: 云平台企业微信登录

parent 49febe70
Pipeline #48402 passed with stages
in 2 minutes 38 seconds
...@@ -52,6 +52,7 @@ export const API = { ...@@ -52,6 +52,7 @@ export const API = {
'/CityInterface/rest/services/CountyProduct.svc/AccountManage/GetHomePagePartInfo', '/CityInterface/rest/services/CountyProduct.svc/AccountManage/GetHomePagePartInfo',
SAVE_Page_PART_INFO: SAVE_Page_PART_INFO:
'/CityInterface/rest/services/CountyProduct.svc/AccountManage/SaveHomePage', '/CityInterface/rest/services/CountyProduct.svc/AccountManage/SaveHomePage',
GET_TOKEN_QYWX: '/CityInterface/rest/services/portal.svc/OMManage/WeChatScanQRCode',
}; };
const services = { const services = {
...@@ -102,7 +103,7 @@ const services = { ...@@ -102,7 +103,7 @@ const services = {
type: constants.REQUEST_HTTP, type: constants.REQUEST_HTTP,
}, },
getInfo: { getInfo: {
url: API.GET_INFO_QYWX, url: () => window.location.origin.replace(/^(http|https):\/\//, '') === 'mis.panda-water.cn' ? API.GET_INFO_QYWX : API.GET_TOKEN_QYWX,
method: constants.REQUEST_METHOD_GET, method: constants.REQUEST_METHOD_GET,
type: constants.REQUEST_HTTP, type: constants.REQUEST_HTTP,
}, },
......
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
...@@ -11,14 +11,19 @@ export const LOGIN_DISPLAY = { ...@@ -11,14 +11,19 @@ export const LOGIN_DISPLAY = {
Account: 'Account', Account: 'Account',
WeChart: 'WeChart', WeChart: 'WeChart',
Mobile: 'Mobile', Mobile: 'Mobile',
WeCom: 'WeCom', // 企业微信登录
}; };
export const LOGIN_WAY = { export const LOGIN_WAY = {
Account: 'pdw', Account: 'pdw',
WeChart: 'iotWechat', WeChart: 'iotWechat',
Mobile: 'iotPhone', Mobile: 'iotPhone',
WeCom: 'qywx', // 企业微信登录
}; };
export const WECHART_APPID = 'wx8bfa8b02cb95010b'; export const WECHART_APPID = 'wx8bfa8b02cb95010b';
export const WECOM_APPID = 'wxec56ca668e7f9155'; // 企业微信APPID
export const AGENTID_CLOUD = '1000102'; // 云平台 agentid
export const AGENTID_CRM = '1000083'; // CRM agentid
export const WX_REDIRECT_URI = 'https://panda-water.com/civbase/user/login'; export const WX_REDIRECT_URI = 'https://panda-water.com/civbase/user/login';
......
...@@ -126,7 +126,7 @@ const LoginItem = props => { ...@@ -126,7 +126,7 @@ const LoginItem = props => {
delete options.rules; delete options.rules;
return ( return (
<FormItem shouldUpdate> <FormItem shouldUpdate style={{marginBottom: '14px'}}>
{({ getFieldValue, validateFields }) => ( {({ getFieldValue, validateFields }) => (
<> <>
<FormItem name={name} {...options} rules={rules}> <FormItem name={name} {...options} rules={rules}>
......
...@@ -7,12 +7,13 @@ import InfoLogin from './template/infoLogin'; ...@@ -7,12 +7,13 @@ import InfoLogin from './template/infoLogin';
import Yulin from './template/yulin'; import Yulin from './template/yulin';
// import { useParams } from '@wisdom-utils/runtime'; // import { useParams } from '@wisdom-utils/runtime';
import EnergyQuota from './template/energy_quota/index'; import EnergyQuota from './template/energy_quota/index';
import CloudLogin from './template/cloud';
import { initGlobalConfig } from '../../../initConfig'; import { initGlobalConfig } from '../../../initConfig';
const LoginTemplate = { const LoginTemplate = {
'新春 - 智联.html': NewYear, '新春 - 智联.html': NewYear,
'Dark - IOTMultiLogin.html': BaseLogin, 'Dark - IOTMultiLogin.html': BaseLogin,
'Dark.html': BaseLogin, 'Dark.html': BaseLogin,
'DarkCloud.html': BaseLogin, 'DarkCloud.html': window.location.origin.replace(/^(http|https):\/\//, '') === 'panda-water.cn' ? CloudLogin : BaseLogin,
'信息化.html': InfoLogin, '信息化.html': InfoLogin,
'项目 - 榆林.html': Yulin, '项目 - 榆林.html': Yulin,
'能源-定额平台.html': EnergyQuota, '能源-定额平台.html': EnergyQuota,
......
...@@ -2,9 +2,10 @@ import React from 'react'; ...@@ -2,9 +2,10 @@ import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Popover } from 'antd'; import { Popover } from 'antd';
import QRCode from 'qrcode.react'; import QRCode from 'qrcode.react';
import styles from '../style.less'; import baseStyles from '../style.less';
const useRenderQcode = props => { const useRenderQcode = props => {
const styles = props.styles || baseStyles;
if (!props.qrcode) { if (!props.qrcode) {
return null; return null;
} }
......
...@@ -80,6 +80,7 @@ class Login { ...@@ -80,6 +80,7 @@ class Login {
} else if (ddCode && loginMode === 'dingding') { } else if (ddCode && loginMode === 'dingding') {
self.ddLoginIn(ddCode); self.ddLoginIn(ddCode);
} else if (ddCode && loginMode === 'qywx') { } else if (ddCode && loginMode === 'qywx') {
// 云平台和信息化都有企业微信,得区分(接口定义里面做了判断)
self.qywxLoginIn(ddCode); self.qywxLoginIn(ddCode);
} else if (!!loginName && !!password) { } else if (!!loginName && !!password) {
self.otherLoginIn(loginName, password); self.otherLoginIn(loginName, password);
...@@ -582,8 +583,7 @@ class Login { ...@@ -582,8 +583,7 @@ class Login {
self.getUserInfoAndConfig(); self.getUserInfoAndConfig();
} else { } else {
self.handleLoginError(); self.handleLoginError();
// msgUtils.fault("登录失败,请检查用户名或密码"); message.error("登录失败,用户信息错误");
// msgUtils.fault("登录失败," + error.message);
self.hasTry = true; // 已经输错过密码 self.hasTry = true; // 已经输错过密码
} }
}); });
......
import React, { useEffect, useState } from 'react';
import { encode } from 'js-base64';
import Cookies from 'js-cookie';
import { LOGIN_WAY } from '@/constants';
import { WECOM_APPID, AGENTID_CLOUD } from '@/constants';
import styles from './index.less';
const useWeCom = props => {
useEffect(() => {
window.WwLogin({
id: props.container || 'wxlogin_container',
appid: props.appid || WECOM_APPID,
agentid: props.agentid || AGENTID_CLOUD,
redirect_uri: props.redirect_uri || encodeURIComponent(window.location.href),
href: props.href || 'https://panda-water.cn/web4/styles/wx.css',
self_redirect: false,
state: props.state || 'STATE',
});
}, []);
};
const useIOTQRCode = () => {
const [rstate, setRstate] = useState(() =>
encode(`panda_${Math.round(Math.random() * 10000 + Date.now()).toString(16)}`),
);
useEffect(() => {
Cookies.set('redirect_state', rstate, {
expires: 5 * 60 * 1000,
path: '/',
});
}, []);
const redirectURI = function() {
let redirectUri = window.location.href;
if(redirectUri.indexOf('loginWay') > -1) return redirectUri;
if(redirectUri.indexOf('?') > -1) redirectUri += `&loginWay=${LOGIN_WAY.WeCom}`;
else redirectUri += `?loginWay=${LOGIN_WAY.WeCom}`;
return redirectUri;
}
const props = Object.assign(
{},
{
container: 'wxlogin_container',
appid: WECOM_APPID,
agentid: AGENTID_CLOUD,
redirect_uri: encodeURIComponent(window.location.href), // window.location.href
href: 'https://panda-water.cn/web4/styles/wecom.css',
state: rstate,
},
);
useWeCom(props);
return (
<div id="wxlogin_container" className={styles.wxlogin_container}></div>
);
};
export default useIOTQRCode;
import Cookies from 'js-cookie';
import React from 'react';
import { LOGIN_DISPLAY, LOGIN_WAY } from '@/constants';
import Account from '../../js/useAccount';
import Phone from '../../js/usePhone';
import WeComLogin from './WeComLogin';
import { useIntl } from '@wisdom-utils/components';
import styles from './index.less';
const isRQcodeFunc = loginFunc => {
const rqcodeFuncs = [LOGIN_DISPLAY.WeChart, LOGIN_DISPLAY.WeCom];
return !!(loginFunc && rqcodeFuncs.indexOf(loginFunc) !== -1);
};
const useIOTComponent = props => {
const handlerType = type => {
props.setType(type);
props.updateLoginMode(LOGIN_WAY[type]);
Cookies.set('loginMode', LOGIN_WAY[type], {
expires: 5 * 60 * 1000,
path: '/',
});
};
return (
<div className={styles.wechatQRcode}>
{props.type === LOGIN_DISPLAY.Account ? (
<Account {...props} />
) : props.type === LOGIN_DISPLAY.WeCom ? (
<WeComLogin {...props} />
) : props.type === LOGIN_DISPLAY.Mobile ? (
<Phone {...props} />
) : (
<Account {...props} />
)}
<div className={styles.loginDisplay}>
{/* {isRQcodeFunc(props.type) ? null : (
<>
<a
className={styles.loginTab}
onClick={() => handlerType(LOGIN_DISPLAY.Account)}
style={{
display: props.type === LOGIN_DISPLAY.Account ? 'none' : 'block',
}}
>
{useIntl().formatMessage({ id: 'pages.login.accountLogin.tab' })}
</a>
<a
className={styles.loginTab}
onClick={() => handlerType(LOGIN_DISPLAY.Mobile)}
style={{
display: props.type === LOGIN_DISPLAY.Mobile ? 'none' : 'block',
}}
>
{useIntl().formatMessage({ id: 'pages.login.phoneLogin.tab' })}
</a>
</>
)} */}
</div>
</div>
);
};
export default useIOTComponent;
import '@wisdom-utils/utils/lib/helpers/format';
import React, { forwardRef, useEffect, useRef, useState } from 'react';
import { connect } from 'react-redux';
import { Modal } from 'antd';
import { Helmet, HelmetProvider } from 'react-helmet-async';
import classNames from 'classnames';
import { dom } from '@wisdom-utils/utils/lib/helpers';
import { useHistory, withRouter } from '@wisdom-utils/runtime';
import { actionCreators } from '@/containers/App/store';
import { LOGIN_WAY } from '@/constants';
import defaultSetting from '../../../../../../config/defaultSetting';
import LoginAction from '../../login';
import styles from './index.less';
import useRenderQcode from '../../js/useRenderQcode';
import Account from '../../js/useAccount';
import IotComponent from '../../js/useIOTComponent';
import CloudForm from './cloudForm';
import { initMicroApps } from '../../../../../micro';
import useTime from '../../js/useTime';
import { LOGIN_DISPLAY } from '../../../../../constants';
const isRQcodeFunc = loginFunc => {
const rqcodeFuncs = [LOGIN_DISPLAY.WeChart, LOGIN_DISPLAY.WeCom];
return !!(loginFunc && rqcodeFuncs.indexOf(loginFunc) !== -1);
};
const loginFuncImg = loginFunc => {
if (isRQcodeFunc(loginFunc)) {
return require('@/assets/images/login/cloud/func_rqcode.png');
}
return require('@/assets/images/login/cloud/func_pwd.png');
};
const Login = forwardRef((props, _ref) => {
const videoRef = useRef();
const loginRef = useRef();
const timeRef = useRef();
const titleRef = useRef();
const sliVerify = useRef();
const loginFormRef = useRef();
const formRef = useRef(null);
const footerRef = useRef();
const currentDate = useTime(); // 计时时间
const [status, setStatus] = useState('normal');
const [autoLogin, setAutoLogin] = useState(false); // 是否记住密码
const [submitting, setSubmitting] = useState(false); // 提交状态
const [type, setType] = useState(LOGIN_DISPLAY.Account); // 登录方式,type是LOGIN_DISPLAY的value,也是LOGIN_WAY的key,根据type决定loginModel
const [visible, setVisible] = useState(false);
const history = useHistory();
const [action, setAction] = useState(() => new LoginAction(Object.assign({}, props, { history }), setVisible, true));
const handleSubmit = values => {
/* eslint-disable */
action &&
(type === 'Account'
? action.loginHandler(values.userName, values.password, null, autoLogin, sliVerify)
: type === 'Mobile'
? action.phoneLoginFormHandler(values.mobile, values.captcha)
: null);
setSubmitting(true);
props.updateCurrentIndex && props.updateCurrentIndex(-1);
};
useEffect(() => {
// if (props.loginMode === LOGIN_WAY.WeChart) {
action &&
action.events.on('loginSuccess', event => {
setSubmitting(false);
props.updateCurrentIndex && props.updateCurrentIndex(0);
props.history.push(`/?client=${props.global.client}`);
// window.share.event.emit('triggerMicro', props.global);
initMicroApps();
});
action &&
action.events.on('loginError', event => {
setVisible(false);
setSubmitting(false);
});
action &&
action.events.on('loginVisible', status => {
setVisible(status);
});
// }
return () => {
action && action.events && action.events.removeAllListeners('loginSuccess');
action && action.events && action.events.removeAllListeners('loginError');
action && action.events && action.events.removeAllListeners('loginVisible');
};
}, [props.loginMode]);
let videoTimeout = null;
useEffect(() => {
if (videoRef && videoRef.current) {
videoRef.current.addEventListener('ended', function() {
dom.removeClass(loginRef.current, styles.caseHide);
dom.addClass(loginRef.current, styles.loginTimeShow);
videoTimeout = setTimeout(() => {
dom.removeClass(timeRef.current, styles.caseHide);
dom.addClass(timeRef.current, 'animate__fadeIn');
dom.removeClass(loginFormRef.current, styles.caseHide);
dom.addClass(loginFormRef.current, 'animate__fadeInUp');
dom.removeClass(footerRef.current, styles.caseHide);
dom.addClass(footerRef.current, 'animate__slideInUp');
dom.removeClass(titleRef.current, styles.caseHide);
dom.addClass(titleRef.current, 'animte__fadeInUp');
}, 500);
});
}
return () => {
videoTimeout && clearTimeout(videoTimeout);
videoRef.current.removeEventListener('ended', () => {});
};
}, [videoRef]);
useEffect(() => {
setSubmitting(false);
}, [visible]);
const renderAddons = useRenderQcode({ ...props.global, styles });
const renderPlatform = () => {
const template = props.global.loginTemplate;
const params = {
fromRef: formRef,
type,
setType,
status,
submitting,
autoLogin,
setAutoLogin,
action,
onSubmit: handleSubmit,
loginMode: props.loginMode,
updateLoginMode: props.updateLoginMode,
};
switch (template) {
case 'DarkCloud.html':
case 'Dark.html':
return <CloudForm {...params} />;
case 'Dark - IOTMultiLogin.html':
return <IotComponent {...params} />;
default:
return <Account {...params} />;
}
};
/* eslint-disable */
const toggleLoginFunc = e => {
e && e.stopPropagation && e.stopPropagation();
setType(isRQcodeFunc(type) ? LOGIN_DISPLAY.Account : LOGIN_DISPLAY.WeCom);
props.updateLoginMode(isRQcodeFunc(type) ? LOGIN_WAY[LOGIN_DISPLAY.Account] : LOGIN_WAY[LOGIN_DISPLAY.WeCom])
};
return (
<HelmetProvider>
<Helmet>
<title>{props.global.title || defaultSetting.title}</title>
<meta name="description" content={props.global.title || defaultSetting.title} />
</Helmet>
<div className={styles.main}>
<video
src={require('@/assets/videos/beforPage.mp4')}
className={styles.videLayer}
autoPlay="autoPlay"
muted
playsInline="playsinline"
ref={videoRef}
/>
{/* main content */}
<div className={styles.inner}>
<div className={classNames(styles.loginTime, styles.caseHide)} ref={loginRef}>
<img
role="logo"
src={
props.global &&
props.global.transformDevAssetsBaseURL &&
props.global.transformDevAssetsBaseURL(props.global.logo)
}
/>
<div className={classNames(styles.titleCase, styles.caseHide, 'animated')} ref={titleRef}>
<span className={styles.title}>{props.global.title}</span>
<span className={styles.subtitle}>{window.globalConfig.subtitle}</span>
</div>
</div>
<div className={classNames(styles.timeCase, styles.caseHide, 'animate__animated')} ref={timeRef}>
<span className={styles.time}>{currentDate.time}</span>
<span className={styles.dayofweek}>{currentDate.dayofweek}</span>
<span className={styles.date}>{currentDate.date}</span>
</div>
{/* login container */}
<div className={classNames(styles['login-block'], styles.caseHide, 'animate__animated')} ref={loginFormRef}>
<div>
<img src="https://panda-water.cn/web4/assets/images/login/dark/login.png" />
</div>
{/* right form */}
<div className={styles['login-form']}>
{/* 登录类型切换,扫码/账号密码 */}
<div className={styles.loginFunc}>
<img
title={type ? '密码登录' : '扫码登录'}
src={loginFuncImg(type)}
onClick={toggleLoginFunc}
/>
</div>
{renderPlatform()}
</div>
</div>
</div>
{/* footer */}
<div className={classNames(styles.footerCase, styles.caseHide, 'animate__animated')} ref={footerRef}>
<div className={classNames(styles.quickMark)}>{renderAddons}</div>
<span className={classNames(styles.copyright)}>
Copyright ©
<a target="_blank" href="https://panda-water.cn">
熊猫智慧水务
</a>
{new Date().getFullYear()} All Rights Reserved{' '}
<a target="_blank" id="IndexCaseNumber" href="">
ICP11036640-1
</a>
<span className="addons">
<span className="split" />
<a id="qrcode">
<span className="glyphicon glyphicon-qrcode" role="button" title="手持APP下载" />
</a>
</span>
</span>
</div>
<Modal centered visible={visible} width={340} footer={null} closable={false} bodyStyle={{ padding: '15px' }}>
<div ref={sliVerify} />
</Modal>
</div>
</HelmetProvider>
);
});
const mapStateToProps = state => ({
global: state.getIn(['global', 'globalConfig']),
loginMode: state.getIn(['global', 'loginMode']),
});
const mapDispatchToProps = dispatch => ({
updateConfig(config) {
dispatch(actionCreators.getConfig(config));
},
createContext(data) {
dispatch(actionCreators.createContext(data));
},
updateLoginMode(mode) {
dispatch(actionCreators.changeLoginMode(mode));
},
updateCurrentIndex(index) {
dispatch(actionCreators.updateCurrentIndex(index));
},
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(withRouter(Login));
@import '~antd/es/style/themes/default.less';
.videLayer {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
object-fit: fill;
}
.inner {
position: absolute;
width: 100%;
height: 84%;
@keyframes loginTimeShow {
0% {
opacity: 1;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
zoom: 2;
}
50% {
opacity: 1;
top: 28px;
left: 45px;
transform: translate(0, 0);
zoom: 1;
}
60% {
opacity: 1;
left: 45px;
top: 28px;
transform: translate(0, 0);
}
70% {
opacity: 1;
left: 45px;
top: 28px;
transform: translate(0, 0);
}
75% {
opacity: 1;
left: 45px;
top: 28px;
transform: translate(0, 0);
}
100% {
opacity: 1;
left: 45px;
top: 28px;
transform: translate(0, 0);
}
}
// logo及标题
.loginTime {
position: absolute;
top: 50%;
left: 50%;
display: flex;
align-items: center;
justify-content: center;
opacity: 1;
transform: translate(-50%, -50%);
&.newYearLoginTime {
position: absolute!important;
top: 28px!important;
left: 45px!important;
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
transform: inherit!important;
}
img[role='logo'] {
height: 40px;
vertical-align: middle;
}
&.loginTimeShow {
animation-name: loginTimeShow;
animation-duration: 1s;
animation-iteration-count: 1;
animation-fill-mode: forwards;
}
.titleCase {
display: flex;
flex-direction: column;
margin: -5px 0 0 20px;
max-width: 570px;
.title,
.subtitle {
font-family: 'Microsoft YaHei';
text-align: left;
overflow-wrap: break-word;
color: #ffffff;
}
.title {
font-size: 28px;
}
.subtitle {
font-size: 12px;
line-height: 1;
}
}
}
// 时间
.timeCase {
.time,
.dayofweek,
.date {
position: absolute;
font-family: 'MicrosoftYaHeiUI';
color: rgba(255, 255, 255, 1);
}
.time {
top: 26px;
right: 180px;
font-size: 40px;
}
.dayofweek {
top: 32px;
right: 125px;
font-size: 16px;
}
.date {
top: 57px;
right: 95px;
font-size: 14px;
}
}
// 登录方式
.loginFunc {
position: absolute;
top: 0;
right: 0;
width: 58px;
overflow: hidden;
cursor: pointer;
img {
width: 100%;
margin: 0 !important;
}
}
.loginBlockWrapper {
width: 100%;
height: 100%;
background-image: url(https://panda-water.com/web4/assets/images/login/workflow/智联新春背景.png);
background-size: 100% 100%;
background-repeat: no-repeat;
}
.login-block {
width: 800px;
position: absolute;
left: 50%;
top: 50%;
margin-left: -400px;
margin-top: -150px;
display: flex;
background: #ffffff;
&.login-newYear-block {
width: 994px!important;
height: 424px!important;
margin-left: -497px!important;
margin-top: -212px!important;
background: url(https://panda-water.com/web4/assets/images/login/workflow/%E6%99%BA%E8%81%94%E6%96%B0%E6%98%A5%E8%83%8C%E6%99%AF%E6%A1%86.png)!important;
//width: 994px;
//height: 424px;
//position: absolute;
//left: 50%;
//top: 50%;
//margin-left: -497px;
//margin-top: -212px;
//display: flex;
background-size: 100% 100%;
background-repeat: no-repeat;
img {
margin-top: 90px;
margin-left: 168px;
}
.login-form {
display: flex;
align-items: center;
margin-left: 32px;
padding: 0;
background: transparent;
margin-top: -36px;
// :global {
// .ant-btn-primary {
// background: #ff9600 !important;
// border-color: #ff9600 !important;
// }
// }
}
}
img {
margin-top: 50px;
margin-bottom: 50px;
margin-left: 30px;
}
.login-form {
padding: 50px 50px 50px;
background-color: #ffffff;
}
.loginDisplay {
.loginTab {
color: @primary-6;
}
}
.wxlogin_container{
position: absolute;
width: 300px;
height: 300px;
background: #fff;
top: 35px;
right: 24px;
}
}
.wechatQRcode {
height: 300px;
}
}
.caseHide {
display: none !important ;
}
.footerCase {
width: 100%;
height: 16%;
background: #fff;
position: absolute;
bottom: 0;
left: 0;
.quickMark {
position: absolute;
bottom: 48px;
text-align: center;
width: 100%;
font-size: 12px;
min-height: 71px;
&-single {
text-align: center;
width: 120px;
display: inline-block;
span {
&.Android {
display: block;
background-image: url(https://panda-water.cn/web4/assets/images/login/dark/Android1.png);
width: 26px;
height: 32px;
margin: auto;
margin-bottom: 6px;
}
&.Wechat {
display: block;
background-image: url(https://panda-water.cn/web4/assets/images/login/dark/Wechat1.png);
width: 34px;
height: 32px;
margin: auto;
margin-bottom: 6px;
}
&.iphone {
display: block;
background-image: url(https://panda-water.cn/web4/assets/images/login/dark/iphone1.png);
width: 28px;
height: 32px;
margin: auto;
margin-bottom: 6px;
}
}
}
.Android-single .Android-code,
.iphone-single .iphone-code {
margin: 0px 0px 10px 0px;
width: 150px;
height: 150px;
background: #fff;
padding: 5px;
display: none;
transform: translateX(-15px);
}
.icon-Container {
height: 50px;
cursor: pointer;
}
}
.copyright {
position: absolute;
z-index: 2;
text-align: center;
width: 100%;
bottom: 10px;
font-size: 13pz;
color: #000000;
.frontIcon {
margin-top: -5px;
}
a {
color: #000000;
}
.addons {
display: none;
}
.split {
border-left: 1px solid #000000;
margin: 0 8px;
}
.glyphicon-qrcode {
vertical-align: text-top;
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment