page.tsx 9.1 KB

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