page.tsx 9.0 KB

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