index.tsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. import * as React from 'react';
  2. // import { observer } from 'mobx-react';
  3. import { Form, Input, Checkbox, Button, Row, Col, Select, message } from 'antd';
  4. import { UserOutlined, LockOutlined, SafetyOutlined } from '@ant-design/icons';
  5. // import Copyright from './components/Copyright';
  6. // import backgroundSrc from '@/assets/login/background.jpg';
  7. import { regex } from '../../utils';
  8. // import store from './store';
  9. import './style.scss';
  10. // import logoSrc from '@/assets/public/logo.png';
  11. import logoSrc from "../../icons/logo.png";
  12. import api from "@/app/api/api";
  13. import { isIOS, useMobileScreen, getContrastColor } from "../../utils";
  14. import { useAppConfig, useChatStore, useGlobalStore } from "../../store";
  15. import {replaceUrl} from '@/app/utils/index'
  16. const FormItem = Form.Item;
  17. const Login: React.FC = () => {
  18. const chatStore = useChatStore();
  19. const globalStore = useGlobalStore();
  20. const isMobileScreen = useMobileScreen();
  21. const [form] = Form.useForm();
  22. const [isTab, setIsTab] = React.useState(true);
  23. const [captchaData, setCaptchaData] = React.useState({
  24. img: '',
  25. uuid: ''
  26. });
  27. const apis: any = {
  28. // 验证码
  29. captchaImag: async () => {
  30. console.log('验证码图片加载')
  31. try {
  32. const res: any = await api.get('/auth/code');
  33. if (res?.code === 200) {
  34. setCaptchaData(res?.data || {});
  35. } else {
  36. message.error(res?.msg || '获取验证码失败');
  37. }
  38. } catch (error: any) {
  39. console.log('error', error)
  40. }
  41. },
  42. }
  43. const init = () => {
  44. apis.captchaImag();
  45. }
  46. const fetchUserInfoApi = async (token:string) => {
  47. const res:any =await api.get(`/system/user/getInfo`);
  48. if (res?.code === 200) {
  49. const info = res.data;
  50. localStorage.setItem('userInfo', JSON.stringify({
  51. nickName: info?.user?.nickName,
  52. userId: info?.user?.userId,
  53. token: token
  54. }));
  55. chatStore.clearSessions()
  56. globalStore.setSelectedAppId('');
  57. const originUrl = window.location.origin;
  58. window.open(`${originUrl}/#/welcome`, '_self');
  59. } else {
  60. message.error(res?.msg || '获取用户信息失败');
  61. }
  62. }
  63. // 点击登录
  64. const onClickLogin = async (data: any, remember: any) => {
  65. try {
  66. const res:any = await api.post('/auth/login', data, {
  67. headers: {
  68. isEncrypt: true
  69. }
  70. });
  71. if (res?.code === 200) {
  72. const info = res.data;
  73. localStorage.setItem('token', info.access_token);
  74. if( remember) {
  75. localStorage.setItem('accountPassword', JSON.stringify({
  76. account: data.username,
  77. password: data.password
  78. }));
  79. } else {
  80. localStorage.removeItem('accountPassword');
  81. }
  82. fetchUserInfoApi(info.access_token);
  83. } else {
  84. console.log('1111',res);
  85. message.error(res?.msg || '获取验证码失败');
  86. }
  87. } catch (error: any) {
  88. init();
  89. }
  90. }
  91. React.useEffect(() => {
  92. window.location.replace(`${replaceUrl}?redirectUrl=${window.location.href}`);
  93. // init();
  94. // return () => reset();
  95. }, []);
  96. // 校验密码
  97. const validatorPassword = (rule: any, value: string) => {
  98. if (value) {
  99. const passwordRegex = new RegExp(regex.password);
  100. if (passwordRegex.test(value)) {
  101. return Promise.resolve();
  102. } else {
  103. return Promise.reject('密码格式不正确');
  104. }
  105. } else {
  106. return Promise.reject('密码不能为空');
  107. }
  108. };
  109. // 集团统一登录
  110. const group = () => {
  111. return <div id="group-login-form" className="space-y-6">
  112. <div className="text-center">
  113. <div className="w-20 h-20 bg-blue-50 rounded-full flex items-center justify-center mx-auto mb-6">
  114. <i className="fa fa-building text-3xl text-primary"></i>
  115. </div>
  116. <h4 className="text-xl font-medium text-neutral-700 mb-3">集团统一身份认证</h4>
  117. <p className="text-neutral-400 max-w-md mx-auto text-base">
  118. 点击下方按钮,将<span className="font-bold">跳转至集团统一登录平台</span>进行身份验证
  119. </p>
  120. </div>
  121. <div className="p-5 bg-neutral-50 rounded-lg border border-neutral-200">
  122. <div className="flex items-start">
  123. <div className="flex-shrink-0 mt-0.5">
  124. <i className="fa fa-info-circle text-primary text-xl"></i>
  125. </div>
  126. <div className="ml-4">
  127. <p className="text-base text-neutral-500">
  128. 集团统一登录将使用您的集团账号密码进行验证,无需重复注册
  129. </p>
  130. </div>
  131. </div>
  132. </div>
  133. <Button color="primary" size='large' className="w-full py-4 bg-white border-2 border-primary text-primary font-medium rounded-lg hover:bg-primary/5" variant="outlined">
  134. 前往集团统一登录平台
  135. </Button>
  136. </div>
  137. }
  138. return (
  139. // Mobile (H5) optimized layout
  140. isMobileScreen ? (
  141. <div className="mobile-login p-4 bg-white min-h-screen flex flex-col justify-center">
  142. <div className="mobile-login-header text-center mb-6">
  143. <img src={logoSrc.src} alt="Logo" className="w-20 h-20 mx-auto mb-2" />
  144. <h1 className="text-2xl font-bold">盈科</h1>
  145. <p className="text-sm text-neutral-500">安全登录,高效办公</p>
  146. </div>
  147. <div className="mobile-login-tabs mb-4 flex items-center justify-center gap-2">
  148. <Button type={isTab ? 'primary' : 'default'} size="middle" onClick={() => setIsTab(true)}>
  149. 本地登录
  150. </Button>
  151. <Button type={!isTab ? 'primary' : 'default'} size="middle" onClick={() => setIsTab(false)}>
  152. 集团统一登录
  153. </Button>
  154. </div>
  155. <div className="mobile-login-form bg-white">
  156. {isTab ? (
  157. <Form form={form} layout="vertical">
  158. <FormItem name="account" label="账号" rules={[{ required: true, message: '账号不能为空' }]}>
  159. <Input size='large' placeholder='请输入账号' prefix={<UserOutlined />} />
  160. </FormItem>
  161. <FormItem name="password" label="密码" rules={[{ required: true, validator: validatorPassword }]}>
  162. <Input.Password size='large' placeholder='请输入密码' className='password-input' prefix={<LockOutlined />} />
  163. </FormItem>
  164. <FormItem name='code' label='验证码' rules={[{ required: true, message: '验证码不能为空' }]}>
  165. <Row gutter={8}>
  166. <Col span={14}>
  167. <Input size='large' className='w-full yzm_input' placeholder='请输入验证码' />
  168. </Col>
  169. <Col span={10}>
  170. <div onClick={() => apis.captchaImag()} style={{ cursor: 'pointer' }}>
  171. <img src={`data:image/png;base64,${captchaData.img}`} alt="验证码" style={{ width: '100%', height: 38, borderRadius: 4 }} />
  172. </div>
  173. </Col>
  174. </Row>
  175. </FormItem>
  176. <FormItem name='remember' valuePropName='checked'>
  177. <Checkbox>记住密码</Checkbox>
  178. </FormItem>
  179. <FormItem>
  180. <Button type='primary' size='large' block onClick={() => {
  181. form.validateFields().then(async (values) => {
  182. const data = {
  183. username: values.account,
  184. password: values.password,
  185. code: values.code,
  186. uuid: captchaData.uuid,
  187. clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e',
  188. grantType: 'password'
  189. }
  190. onClickLogin(data, values.remember);
  191. }).catch((error) => {
  192. console.error(error);
  193. });
  194. }}>登录</Button>
  195. </FormItem>
  196. </Form>
  197. ) : (
  198. <div>
  199. {group()}
  200. </div>
  201. )}
  202. </div>
  203. </div>
  204. ) : (
  205. <div className='login'>
  206. {/* <img src={backgroundSrc} /> */}
  207. <div className="w-[55%] bg-gradient-primary text-white p-1 md:p-16 flex flex-col justify-center relative overflow-hidden">
  208. <div className="relative z-10 max-w-xl mx-auto md:mx-0">
  209. <div className="flex items-end mb-12">
  210. <div className="flex items-center justify-center mr-4">
  211. <img src={logoSrc.src} alt="Logo" className="w-36 h-36" />
  212. </div>
  213. <h1 className="text-5xl font-bold">盈科</h1>
  214. </div>
  215. <h2 className="text-[clamp(2.5rem,5vw,3.5rem)] font-bold mb-6 leading-tight">
  216. 安全登录,<br />高效办公
  217. </h2>
  218. <p className="text-white/80 text-lg mb-10 max-w-lg">
  219. 提供多种安全登录方式,保障您的信息安全,提升工作效率
  220. </p>
  221. <div className="grid grid-cols-2 gap-6 mt-10">
  222. <div className="flex items-start">
  223. <div className="w-10 h-10 rounded-full bg-white/20 flex items-center justify-center mr-4 flex-shrink-0">
  224. <i className="fa fa-lock text-white"></i>
  225. </div>
  226. <div>
  227. <h3 className="text-xl font-semibold mb-2">安全可靠</h3>
  228. <p className="text-white/70">多重加密保障,保护您的账号安全</p>
  229. </div>
  230. </div>
  231. <div className="flex items-start">
  232. <div className="w-10 h-10 rounded-full bg-white/20 flex items-center justify-center mr-4 flex-shrink-0">
  233. <i className="fa fa-bolt text-white"></i>
  234. </div>
  235. <div>
  236. <h3 className="text-xl font-semibold mb-2">高效便捷</h3>
  237. <p className="text-white/70">简化登录流程,提升工作效率</p>
  238. </div>
  239. </div>
  240. </div>
  241. </div>
  242. </div>
  243. <div className='login-right'>
  244. <div className='login-right-content h-[700px]'>
  245. <div className="mb-2">
  246. <h3 className="text-[clamp(1.5rem,3vw,2.25rem)] font-bold text-neutral-700 mb-3 mt-0">账户登录</h3>
  247. <p className="text-neutral-400 text-lg">请选择登录方式并完成验证</p>
  248. </div>
  249. <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-10">
  250. <Button type={isTab ? 'primary' : 'default'} size='large' onClick={() => {
  251. setIsTab(true);
  252. }} >
  253. <i className="fa fa-user-circle text-2xl"></i>
  254. <span>本地登录</span>
  255. </Button>
  256. <Button type={!isTab ? 'primary' : 'default'} size='large' onClick={() => {
  257. setIsTab(false);
  258. }} >
  259. <i className="fa fa-building text-2xl"></i>
  260. <span>集团统一登录</span>
  261. </Button>
  262. </div>
  263. {isTab && <Form form={form}>
  264. <FormItem
  265. name='account'
  266. label='账号'
  267. colon={false}
  268. rules={[{ required: true, message: '账号不能为空', whitespace: true }]}
  269. labelCol={{ span: 24 }} // 标签占满整行
  270. wrapperCol={{ span: 24 }} // 输入框占满整行
  271. >
  272. <Input size='large' placeholder='请输入账号' prefix={<UserOutlined />} />
  273. </FormItem>
  274. <FormItem
  275. name='password'
  276. label='密码'
  277. colon={false}
  278. rules={[{ required: true, validator: validatorPassword }]}
  279. labelCol={{ span: 24 }} // 标签占满整行
  280. wrapperCol={{ span: 24 }} // 输入框占满整行
  281. >
  282. <Input.Password size='large' placeholder='请输入密码' prefix={<LockOutlined />} className='password-input' />
  283. </FormItem>
  284. <FormItem
  285. name='code'
  286. label='验证码'
  287. colon={false}
  288. rules={[{ required: true, message: '验证码' }]}
  289. labelCol={{ span: 24 }} // 标签占满整行
  290. wrapperCol={{ span: 24 }} // 输入框占满整行
  291. >
  292. <Row gutter={8} >
  293. {/* 输入框占大部分宽度 */}
  294. <Col span={17}>
  295. <Input size='large' placeholder='请输入验证码' prefix={<SafetyOutlined />} />
  296. </Col>
  297. {/* 验证码图片固定宽度 */}
  298. <Col span={7}>
  299. <div>
  300. <img
  301. src={`data:image/png;base64,${captchaData.img}`}
  302. alt="验证码"
  303. style={{
  304. width: '100%',
  305. height: '38px',
  306. borderRadius: '2px',
  307. cursor: 'pointer' // 提示可点击刷新
  308. }}
  309. onClick={() => { apis.captchaImag() }}
  310. />
  311. </div>
  312. </Col>
  313. </Row>
  314. </FormItem>
  315. <FormItem
  316. name='remember'
  317. valuePropName='checked'
  318. >
  319. <Checkbox>
  320. 记住密码
  321. </Checkbox>
  322. </FormItem>
  323. <FormItem>
  324. <Button
  325. type='primary'
  326. size='large'
  327. block={true}
  328. // loading={buttonLoading}
  329. onClick={() => {
  330. form.validateFields().then(async (values) => {
  331. const data = {
  332. username: values.account,
  333. password: values.password,
  334. // tenantId: values.tenantId,
  335. code: values.code,
  336. uuid: captchaData.uuid,
  337. clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e',
  338. grantType: 'password'
  339. }
  340. onClickLogin(data, values.remember);
  341. }).catch((error) => {
  342. console.error(error);
  343. });
  344. }}
  345. >
  346. 登录
  347. </Button>
  348. </FormItem>
  349. </Form>}
  350. {!isTab && group()}
  351. </div>
  352. {/* <Copyright /> */}
  353. </div>
  354. </div>
  355. ));
  356. };
  357. // export default observer(Login);
  358. export default Login;