page.tsx 8.9 KB

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