index.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. import { Button, message, Radio, Spin } from 'antd';
  2. import React, { useCallback, useEffect, useState } from 'react';
  3. import { useModel, useRequest } from 'umi';
  4. import { fakeAccountLogin, getCode, getDingKey, ddlogin, phoneLogin, getNoteCode, isPreserve } from '@/services/login';
  5. import styles from './style.less';
  6. import { CopyrightOutlined, SwapRightOutlined } from '@ant-design/icons';
  7. import { useBase64 } from '@/Hook/useBase64'
  8. import Bg from './components/bg';
  9. import { history } from 'umi'
  10. import { api } from '@/services/api'
  11. import { useAjax } from '@/Hook/useAjax';
  12. /**
  13. * 退出登录,并且将当前的 url 保存
  14. */
  15. const loginOut = async () => {
  16. sessionStorage.removeItem('Admin-Token')
  17. // window.location.href = '/';
  18. };
  19. const Login: React.FC<{}> = () => {
  20. const { encrypt, decode } = useBase64()
  21. const { setInitialState, initialState } = useModel('@@initialState');
  22. const codeRes = useRequest((phone) => getCode(phone), { manual: true, formatResult: (res: any) => res })//钉钉验证码
  23. const noteCode = useRequest((phone) => getNoteCode(phone), { manual: true, formatResult: (res: any) => res })//短信验证码
  24. const getKey = useRequest(() => getDingKey(), { manual: true, formatResult: (res: any) => res })//获取服务器KEY
  25. const Ddlogin = useRequest((params: any) => ddlogin(params), { manual: true, formatResult: (res: any) => res })//用钉钉登录
  26. const phone_login = useRequest((params: any) => phoneLogin(params), { manual: true, formatResult: (res: any) => res })//用手机登录
  27. const ispreserve = useAjax(() => isPreserve())
  28. let phone = decode('phone')
  29. let code = decode('code')
  30. const [values, setValues] = useState<{ phone: string, code: string }>({ phone: phone || '', code: code || '' })
  31. const [isLogin, setIsLogin] = useState<boolean>(false)
  32. const [videoMenu, setVideoMenu] = useState<{ visible: boolean, left?: number, top?: number }>({ visible: false, left: 0, top: 0 })
  33. const [show, setShow] = useState<boolean>(false)
  34. const [tab, setTab] = useState<1 | 2>(2)
  35. const [APPID,] = useState<string>('dingwfmhucedfluenpuj')//测试 dingdsfuoevmawlyyrjx 线上dingwfmhucedfluenpuj
  36. const [REDIRECT_URI,] = useState<string>(encodeURIComponent(location.href?.split('?')[0]))
  37. const [companyList, setCompanyList] = useState<any[]>([])
  38. const [loading, setLoading] = useState<boolean>(false)
  39. const [isMobile, setIsMobile] = useState<boolean>(false)
  40. const [m, setM] = useState<any>(0)//倒计时,0可以点击
  41. const [codeType,setCodeType]=useState(1)//1钉钉验证码 2短信验证码
  42. // 获取运行环境
  43. useEffect(() => {
  44. let u = navigator.userAgent
  45. let isPhone = !!u.match(/AppleWebKit.*Mobile.*/) || u.indexOf('iPad') > -1
  46. if (isPhone) {
  47. setTab(1)
  48. setIsMobile(isPhone) // 判断是否在移动端
  49. }
  50. }, [])
  51. // 获取公司列表
  52. useEffect(() => {
  53. let data = initialState?.currentUser?.companyList
  54. if (data && Object.keys(data).length > 0) {
  55. setCompanyList(() => data as any)
  56. }
  57. }, [initialState?.currentUser?.companyList])
  58. //登录事件
  59. // const handleSubmit = async () => {
  60. // setIsLogin(true)
  61. // values.account = values.account ? values.account : account || '';
  62. // values.password = values.password ? values.password : password || '';
  63. // if (!Object.keys(values).every((key) => !!values[key])) {
  64. // message.error('请填写完整!')
  65. // setIsLogin(false)
  66. // return
  67. // } else {
  68. // let { code, ...value } = values
  69. // Object.keys(value).forEach((key) => {
  70. // encrypt({ key, value: value[key] })
  71. // })
  72. // }
  73. // console.log(values)
  74. // };
  75. //显示隐藏登录
  76. let handleShow = useCallback((props?: boolean) => {
  77. if (props && !show) {
  78. console.log('显示')
  79. setShow(true)
  80. }
  81. if (!props && show) {
  82. console.log('关闭')
  83. setShow(false)
  84. }
  85. }, [show])
  86. // 钉钉登录
  87. useEffect(() => {
  88. if (!sessionStorage.getItem('Admin-Token')) {
  89. let query = history?.location?.query
  90. if (query?.code) {
  91. Ddlogin.run({ code: query?.code, state: query?.state, stateKey: sessionStorage.getItem('DDK') }).then((msg: any) => {
  92. if (msg.code === 200) {
  93. sessionStorage.setItem('Admin-Token', msg?.data?.token)
  94. let companyInfo = msg?.data?.companyRelationInfo?.filter((item: { companyId: number }) => item.companyId !== 4 && item.companyId !== 3)
  95. setIsLogin(false)
  96. if (companyInfo?.length === 0) {
  97. sessionStorage.removeItem('Admin-Token')
  98. message.error('登录失败,请用趣程运营平台账号登录')
  99. return
  100. } else if (companyInfo?.length === 1) {
  101. setCompanyHandle(msg?.data?.companyRelationInfo[0].companyId)
  102. } else {
  103. setCompanyList(companyInfo)
  104. }
  105. // message.success('登录成功!');
  106. return;
  107. } else {
  108. setIsLogin(false)
  109. message.success('2秒后刷新页面,请重新扫码登录')
  110. setTimeout(() => {
  111. window.open(location?.origin, '_parent')
  112. }, 2000)
  113. }
  114. })
  115. } else {
  116. if (tab === 2) {
  117. let state = ''
  118. getKey.run().then((res) => {
  119. if (res?.code === 200) {
  120. let strArr = res?.data?.split('_')
  121. sessionStorage.setItem('DDK', strArr[0])
  122. state = strArr[1]
  123. }
  124. });
  125. (window as any).DDLogin({
  126. id: "login_container",
  127. goto: encodeURIComponent(`https://oapi.dingtalk.com/connect/qrconnect?appid=${APPID}&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=${REDIRECT_URI}`),
  128. style: "border:none;padding:0 0 20px 0;background-color:rgba(255,255,255,0.7);",
  129. width: "100%",
  130. height: "400"
  131. });
  132. const handleMessage = (event: { data: any; origin: any; }) => {
  133. // 获取loginTempCode
  134. const loginTempCode = event.data;
  135. // 获取消息来源
  136. const origin = event.origin;
  137. // 拼接 url
  138. const url = `https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=${APPID}&response_type=code&scope=snsapi_login&state=${encodeURIComponent(state)}&redirect_uri=${REDIRECT_URI}&loginTmpCode=${loginTempCode}`
  139. // 如果来源为https://login.dingtalk.com,则在当前窗口打开回调链接
  140. if (origin === 'https://login.dingtalk.com') {
  141. window.open(encodeURI(url), '_parent')
  142. }
  143. }
  144. // 监听iframe的消息
  145. if (typeof window.addEventListener != 'undefined') {
  146. window.addEventListener('message', handleMessage, false);
  147. } else if (typeof (window as any).attachEvent != 'undefined') {
  148. (window as any).attachEvent('onmessage', handleMessage);
  149. }
  150. }
  151. }
  152. }
  153. }, [tab, sessionStorage.getItem('Admin-Token')])
  154. // 选择公司
  155. const setCompanyHandle = (companyId: number) => {
  156. setLoading(() => true)
  157. fetch(api + `/erp/user/chooseCompany/${companyId}`, {
  158. method: 'PUT',
  159. headers: { ['Authorization']: 'Bearer ' + sessionStorage.getItem('Admin-Token') }
  160. }).then(res => res.json()).then((res: any) => {
  161. setLoading(() => false)
  162. if (res?.code === 200) {
  163. // 验证服务器是否升级
  164. fetch(api + '/erp/config/sysVersion/preserve', {
  165. headers: {
  166. 'Authorization': 'Bearer ' + res?.data?.token
  167. }
  168. }).then(res => res.json()).then(js => {
  169. if (js?.data?.configValue === 'true') {
  170. message.error('版本更新中....请关注大群公告!!!!',5,()=>{
  171. sessionStorage.removeItem('Admin-Token')
  172. window.location.href = '/';
  173. })
  174. } else {
  175. sessionStorage.setItem('Admin-Token', res?.data?.token)
  176. window.location.href = '/';
  177. // setTimeout(() => {
  178. // refresh();
  179. // }, 0);
  180. }
  181. })
  182. }
  183. }).catch(() => setLoading(() => false))
  184. }
  185. // 更换账户
  186. const logOut = () => {
  187. setInitialState({ ...initialState, currentUser: undefined });
  188. loginOut();
  189. }
  190. // 获取手机钉钉验证码
  191. const getPhoneCode = () => {
  192. let reg = new RegExp(/^(13\d|14[579]|15[^4\D]|17[^49\D]|18\d)\d{8}$/g)
  193. if (values) {
  194. setM(30)
  195. codeRes.run(values?.phone).then(res => {
  196. if (res.code === 200) {
  197. message.success('获取成功,请查看你的钉钉消息!验证码将发送到钉钉')
  198. timeOut(30)
  199. } else {
  200. timeOut(0)
  201. }
  202. })
  203. } else {
  204. message.error('请输入正确的手机号!!!')
  205. }
  206. }
  207. // 获取手机短信验证码
  208. const getPhoneNoteCode = () => {
  209. let reg = new RegExp(/^(13\d|14[579]|15[^4\D]|17[^49\D]|18\d)\d{8}$/g)
  210. if (values) {
  211. setM(30)
  212. noteCode.run(values?.phone).then(res => {
  213. if (res.code === 200) {
  214. message.success('获取成功,请查看你的短信消息!验证码将以短信的形式发送到您的手机!')
  215. timeOut(30)
  216. } else {
  217. timeOut(0)
  218. }
  219. })
  220. } else {
  221. message.error('请输入正确的手机号!!!')
  222. }
  223. }
  224. // 倒计时
  225. const timeOut = useCallback((num) => {
  226. let timer: any = null
  227. if (num > 0) {
  228. timer = setTimeout(() => {
  229. setM(num - 1)
  230. timeOut(num - 1)
  231. clearTimeout(timer)
  232. timer = null
  233. }, 1000)
  234. } else {
  235. setM(0)
  236. }
  237. }, [])
  238. // 手机登录
  239. const phoneSubmit = () => {
  240. if (values?.code) {
  241. phone_login.run(values).then(res => {
  242. try {
  243. if (res.code === 200) {
  244. setIsLogin(false)
  245. sessionStorage.setItem('Admin-Token', res?.data?.token)
  246. let companyInfo = res?.data?.companyRelationInfo?.filter((item: { companyId: number }) => item.companyId !== 4 && item.companyId !== 3)
  247. if (companyInfo?.length === 0) {
  248. sessionStorage.removeItem('Admin-Token')
  249. message.error('登录失败,请用趣程运营平台账号登录')
  250. return
  251. } else if (companyInfo?.length === 1) {
  252. setCompanyHandle(res?.data?.companyRelationInfo[0].companyId)
  253. } else {
  254. setCompanyList(companyInfo)
  255. }
  256. // message.success('登录成功!');
  257. return;
  258. } else {
  259. setIsLogin(false)
  260. // codeRes.run()
  261. }
  262. // 如果失败去设置用户错误信息
  263. } catch (error) {
  264. setIsLogin(false)
  265. message.error('登录失败,请重试!');
  266. }
  267. })
  268. }
  269. }
  270. return (
  271. <>
  272. {
  273. <>
  274. {/* {isOk?} */}
  275. <div className={`${styles.container}`} id='login'>
  276. <div className={styles.content}>
  277. {sessionStorage.getItem('Admin-Token') && companyList?.length > 0 ? <div className={`${styles.company} ${isMobile ? '' : show ? '' : styles.login_hide}`}>
  278. <h1>趣程运营管理后台</h1>
  279. <div className={styles.companyAccount}>
  280. <h3 className={styles.title}>请选择公司账户登录</h3>
  281. <div className={styles.chooseTableBlock}>
  282. <Spin spinning={loading}>
  283. {
  284. companyList?.map((item: any) => <div className={styles.acTableLine} key={item?.companyId} onClick={() => { setCompanyHandle(item.companyId) }}>
  285. <div className={styles.actname}>{item?.companyInfo?.companyName}</div>
  286. <div className={styles.right}> <div className={styles.actcha}>{item?.powerLevel === 999 ? '超级管理员' : item?.powerLevel === 100 ? '系统管理员' : item?.powerLevel === 99 ? '管理员' : '普通用户'}</div> <SwapRightOutlined className={styles.iconRight} /></div>
  287. </div>)
  288. }
  289. </Spin>
  290. </div>
  291. <div className={styles.button}>
  292. <Button type="link" onClick={logOut}>更换账号</Button>
  293. </div>
  294. </div>
  295. </div> : <div className={`${styles.main} ${isMobile ? '' : show ? '' : styles.login_hide}`}>
  296. <h1>趣程运营管理后台</h1>
  297. {<div className={styles.tabs}>
  298. <span onClick={() => { setTab(1) }} style={tab === 1 ? { color: localStorage.getItem('color') || '#24DB95' } : {}}>手机</span>
  299. <span onClick={() => { setTab(2) }} style={tab === 2 ? { color: localStorage.getItem('color') || '#24DB95' } : {}}>钉钉</span>
  300. </div>}
  301. {
  302. tab === 1 ? <>
  303. <div>
  304. <input
  305. placeholder='Phone'
  306. value={values?.phone}
  307. onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
  308. let value = e.target.value
  309. if (value === '背景') {
  310. setVideoMenu({ visible: true })
  311. }
  312. setValues({ ...values, phone: value })
  313. }}
  314. />
  315. </div>
  316. <div>
  317. <input
  318. placeholder='Code'
  319. style={{ width: '60%' }}
  320. onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
  321. let value = e.target.value
  322. setValues({ ...values, code: value })
  323. }}
  324. onKeyUp={(event: React.KeyboardEvent<HTMLInputElement>) => {
  325. if (event.key === 'Enter') {
  326. phoneSubmit()
  327. }
  328. }}
  329. />
  330. <button
  331. className={styles.btn}
  332. style={!m ? { background: localStorage.getItem('color') || '#24DB95' } : { background: '#999' }}
  333. onClick={codeType===1 ?getPhoneCode : getPhoneNoteCode}
  334. disabled={!!m}
  335. >
  336. {!m ? '获取验证码' : m + '秒再次获取'}
  337. </button>
  338. </div>
  339. <Radio.Group value={codeType} onChange={(e)=>{
  340. let v = e.target.value
  341. setCodeType(v)
  342. }}>
  343. <Radio value={1} style={{ color: '#fff' }}>钉钉验证码</Radio>
  344. <Radio value={2} style={{ color: '#fff' }}>手机验证码</Radio>
  345. </Radio.Group>
  346. <button
  347. onClick={phoneSubmit}
  348. disabled={isLogin}
  349. style={{ background: localStorage.getItem('color') || '#24DB95' }}
  350. >登录</button>
  351. </> :
  352. <div id='login_container' />
  353. }
  354. </div>}
  355. </div>
  356. {/**背景 */}
  357. {!isMobile && <Bg data={videoMenu} open={setVideoMenu} show={handleShow} isShow={show} />}
  358. <footer className={`${styles.footer} ${show ? '' : styles.footer_hide}`} onClick={() => { setVideoMenu({ visible: !videoMenu.visible }) }}>
  359. <a >
  360. 趣程
  361. </a>
  362. <span>Copyright<span><CopyrightOutlined /></span>趣程 </span>
  363. </footer>
  364. </div>
  365. </>
  366. }
  367. </>
  368. );
  369. };
  370. export default Login;