page.tsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. "use client";
  2. import {LockOutlined, UserOutlined} from "@ant-design/icons";
  3. import type {ProFormInstance} from "@ant-design/pro-components";
  4. import {LoginFormPage, ProConfigProvider, ProFormCheckbox, ProFormText,} from "@ant-design/pro-components";
  5. import {Divider, message, Spin, theme} from "antd";
  6. import {deleteCookie, getCookie, setCookie} from "cookies-next";
  7. import {useRouter} from "next/navigation";
  8. import Image from "next/image";
  9. import {useEffect, useRef, useState} from "react";
  10. import {LoginReq} from "../_modules/definies";
  11. import {decrypt, displayModeIsDark, encrypt, watchDarkModeChange,} from "../_modules/func";
  12. type Captcha = {
  13. img: string;
  14. uuid: string;
  15. };
  16. //cookies 记住的用户名 key
  17. const cookie_username_key = "mortnon_username";
  18. //cookies 记住的密码 key
  19. const cookie_password_key = "mortnon_password";
  20. //浅色背景图
  21. const backgroudLight = "/bg3.jpg";
  22. //深色前景图
  23. const backgroundDark = "/bg-dark.jpg";
  24. export default function Login() {
  25. //验证码数据
  26. const [captcha, setCaptcha] = useState({} as Captcha);
  27. //是否展示验证码框
  28. const [showCaptcha, setShowCaptcha] = useState(false);
  29. //验证码加载状态
  30. const [isLoadingImg, setIsLoadingImg] = useState(true);
  31. //获取验证码
  32. const getCaptcha = async () => {
  33. try {
  34. const response = await fetch("/api/captchaImage");
  35. if (response.ok) {
  36. const data = await response.json();
  37. setShowCaptcha(data.captchaEnabled);
  38. if (data.captchaEnabled) {
  39. const imagePrefix = "data:image/gif;base64,";
  40. const captchaData: Captcha = {
  41. img: imagePrefix + data.img,
  42. uuid: data.uuid,
  43. };
  44. setCaptcha(captchaData);
  45. setIsLoadingImg(false);
  46. }
  47. } else {
  48. }
  49. } catch (error) {
  50. } finally {
  51. }
  52. };
  53. //深色模式
  54. const [isDark, setIsDark] = useState(false);
  55. //背景图片
  56. const [background, setBackground] = useState(backgroudLight);
  57. useEffect(() => {
  58. getCaptcha();
  59. readUserNamePassword();
  60. setIsDark(displayModeIsDark());
  61. setBackground(displayModeIsDark() ? backgroundDark : backgroudLight);
  62. const unsubscribe = watchDarkModeChange((matches: boolean) => {
  63. setIsDark(matches);
  64. setBackground(matches ? backgroundDark : backgroudLight);
  65. });
  66. return () => {
  67. unsubscribe();
  68. };
  69. }, []);
  70. const router = useRouter();
  71. //提交登录
  72. const userLogin = async (values: any) => {
  73. const loginData: LoginReq = {
  74. username: values.username,
  75. password: values.password,
  76. code: values.code,
  77. uuid: captcha.uuid,
  78. };
  79. //是否记住密码
  80. const autoLogin = values.autoLogin;
  81. try {
  82. const response = await fetch("/api/login", {
  83. method: "POST",
  84. headers: {
  85. "Content-Type": "application/json",
  86. },
  87. body: JSON.stringify(loginData),
  88. credentials: "include",
  89. });
  90. //获得响应
  91. if (response.ok) {
  92. const data = await response.json();
  93. //登录成功
  94. if (data.code == 200) {
  95. message.success("登录成功");
  96. setCookie("token", data.token);
  97. //记住密码
  98. if (autoLogin) {
  99. rememberUserNamePassword(values.username, values.password);
  100. } else {
  101. removeUserNamePassword();
  102. }
  103. router.push("/");
  104. } else {
  105. message.open({
  106. type: "error",
  107. content: data.msg,
  108. });
  109. //异常,自动刷新验证码
  110. getCaptcha();
  111. }
  112. } else {
  113. const data = await response.json();
  114. message.open({
  115. type: "error",
  116. content: data.msg,
  117. });
  118. }
  119. } catch (error) {
  120. console.log("error:", error);
  121. message.open({
  122. type: "error",
  123. content: "登录发生异常,请重试",
  124. });
  125. } finally {
  126. }
  127. };
  128. //记住用户名密码到cookie
  129. const rememberUserNamePassword = (username: string, password: string) => {
  130. setCookie(cookie_username_key, encrypt(username));
  131. setCookie(cookie_password_key, encrypt(password));
  132. };
  133. //移除cookie中的用户名和密码
  134. const removeUserNamePassword = () => {
  135. deleteCookie(cookie_username_key);
  136. deleteCookie(cookie_password_key);
  137. };
  138. const loginFormRef = useRef<ProFormInstance>(null);
  139. //读取cookie中用户名密码,并填写到表单中
  140. const readUserNamePassword = () => {
  141. const username = getCookie(cookie_username_key);
  142. const password = getCookie(cookie_password_key);
  143. if (username !== undefined && password !== undefined) {
  144. if (loginFormRef) {
  145. if (typeof username === "string" && password === "string") {
  146. loginFormRef.current?.setFieldsValue({
  147. username: decrypt(username),
  148. password: decrypt(password),
  149. autoLogin: true,
  150. });
  151. }
  152. }
  153. }
  154. };
  155. const {token} = theme.useToken();
  156. return (
  157. <ProConfigProvider dark={isDark}>
  158. <div
  159. style={{
  160. backgroundColor: "white",
  161. height: "100vh",
  162. }}
  163. >
  164. <LoginFormPage
  165. formRef={loginFormRef}
  166. backgroundImageUrl={background}
  167. logo="https://static.dongfangzan.cn/img/mortnon.svg"
  168. title={(<span>MorTnon 若依后台管理</span>) as any}
  169. containerStyle={{
  170. backgroundColor: "rgba(0,0,0,0)",
  171. backdropFilter: "blur(4px)",
  172. }}
  173. subTitle={
  174. <span style={{color: "rgba(255,255,255,1)"}}>
  175. MorTnon,高质量的快速开发框架
  176. </span>
  177. }
  178. actions={
  179. <div
  180. style={{
  181. display: "flex",
  182. justifyContent: "center",
  183. alignItems: "center",
  184. }}
  185. >
  186. <p style={{color: "rgba(255,255,255,.6)"}}>
  187. ©{new Date().getFullYear()} Mortnon.
  188. </p>
  189. </div>
  190. }
  191. onFinish={userLogin}
  192. >
  193. <Divider>账号密码登录</Divider>
  194. <>
  195. <ProFormText
  196. name="username"
  197. fieldProps={{
  198. size: "large",
  199. prefix: (
  200. <UserOutlined
  201. style={{
  202. color: token.colorText,
  203. }}
  204. className={"prefixIcon"}
  205. />
  206. ),
  207. }}
  208. placeholder={"用户名"}
  209. rules={[
  210. {
  211. required: true,
  212. message: "用户名不能为空",
  213. },
  214. ]}
  215. />
  216. <ProFormText.Password
  217. name="password"
  218. fieldProps={{
  219. size: "large",
  220. prefix: (
  221. <LockOutlined
  222. style={{
  223. color: token.colorText,
  224. }}
  225. className={"prefixIcon"}
  226. />
  227. ),
  228. }}
  229. placeholder={"密码"}
  230. rules={[
  231. {
  232. required: true,
  233. message: "密码不能为空",
  234. },
  235. ]}
  236. />
  237. {showCaptcha && (
  238. <div
  239. style={{
  240. display: "flex",
  241. justifyContent: "center",
  242. flexDirection: "row",
  243. }}
  244. >
  245. <ProFormText
  246. name="code"
  247. fieldProps={{
  248. size: "large",
  249. prefix: (
  250. <UserOutlined
  251. style={{
  252. color: token.colorText,
  253. }}
  254. className={"prefixIcon"}
  255. />
  256. ),
  257. }}
  258. placeholder={"验证码"}
  259. rules={[
  260. {
  261. required: true,
  262. message: "验证码不能为空",
  263. },
  264. ]}
  265. />
  266. <div style={{margin: "0 0 0 8px"}}>
  267. <Spin spinning={isLoadingImg}>
  268. {captcha.img === undefined ? (
  269. <div style={{width: 80, height: 40}}></div>
  270. ) : (
  271. <Image
  272. src={captcha.img}
  273. width={80}
  274. height={40}
  275. alt="captcha"
  276. onClick={getCaptcha}
  277. />
  278. )}
  279. </Spin>
  280. </div>
  281. </div>
  282. )}
  283. </>
  284. <div
  285. style={{
  286. marginBlockEnd: 24,
  287. }}
  288. >
  289. <ProFormCheckbox noStyle name="autoLogin">
  290. 记住密码
  291. </ProFormCheckbox>
  292. </div>
  293. </LoginFormPage>
  294. </div>
  295. </ProConfigProvider>
  296. );
  297. }