重写复现方法
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
package com.mini.capi.biz.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author gaoxq
|
||||
* @since 2025-08-28
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/biz/apiUser")
|
||||
public class ApiUserController {
|
||||
|
||||
}
|
||||
86
src/main/java/com/mini/capi/biz/domain/ApiUser.java
Normal file
86
src/main/java/com/mini/capi/biz/domain/ApiUser.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package com.mini.capi.biz.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* @author gaoxq
|
||||
* @since 2025-08-28
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("biz_api_user")
|
||||
public class ApiUser implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableField("create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
@TableId(value = "user_id", type = IdType.AUTO)
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 登录名称
|
||||
*/
|
||||
@TableField("api_user")
|
||||
private String apiUser;
|
||||
|
||||
/**
|
||||
* 登录密码
|
||||
*/
|
||||
@TableField("api_pswd")
|
||||
private String apiPswd;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
@TableField("uname")
|
||||
private String uname;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
@TableField("ustatus")
|
||||
private String ustatus;
|
||||
|
||||
@TableField("update_time")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 租户id
|
||||
*/
|
||||
@TableField("f_tenant_id")
|
||||
private String fTenantId;
|
||||
|
||||
/**
|
||||
* 流程id
|
||||
*/
|
||||
@TableField("f_flow_id")
|
||||
private String fFlowId;
|
||||
|
||||
/**
|
||||
* 流程任务主键
|
||||
*/
|
||||
@TableField("f_flow_task_id")
|
||||
private String fFlowTaskId;
|
||||
|
||||
/**
|
||||
* 流程任务状态
|
||||
*/
|
||||
@TableField("f_flow_state")
|
||||
private Integer fFlowState;
|
||||
}
|
||||
16
src/main/java/com/mini/capi/biz/mapper/ApiUserMapper.java
Normal file
16
src/main/java/com/mini/capi/biz/mapper/ApiUserMapper.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.mini.capi.biz.mapper;
|
||||
|
||||
import com.mini.capi.biz.domain.ApiUser;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author gaoxq
|
||||
* @since 2025-08-28
|
||||
*/
|
||||
public interface ApiUserMapper extends BaseMapper<ApiUser> {
|
||||
|
||||
}
|
||||
16
src/main/java/com/mini/capi/biz/service/ApiUserService.java
Normal file
16
src/main/java/com/mini/capi/biz/service/ApiUserService.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.mini.capi.biz.service;
|
||||
|
||||
import com.mini.capi.biz.domain.ApiUser;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author gaoxq
|
||||
* @since 2025-08-28
|
||||
*/
|
||||
public interface ApiUserService extends IService<ApiUser> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.mini.capi.biz.service.impl;
|
||||
|
||||
import com.mini.capi.biz.domain.ApiUser;
|
||||
import com.mini.capi.biz.mapper.ApiUserMapper;
|
||||
import com.mini.capi.biz.service.ApiUserService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author gaoxq
|
||||
* @since 2025-08-28
|
||||
*/
|
||||
@Service
|
||||
public class ApiUserServiceImpl extends ServiceImpl<ApiUserMapper, ApiUser> implements ApiUserService {
|
||||
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package com.mini.capi.config;
|
||||
|
||||
import com.mini.capi.utils.vToken;
|
||||
import com.mini.capi.biz.domain.ApiUser;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
|
||||
@Component
|
||||
public class AuthInterceptor implements HandlerInterceptor {
|
||||
@@ -13,9 +14,9 @@ public class AuthInterceptor implements HandlerInterceptor {
|
||||
public boolean preHandle(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Object handler) throws Exception {
|
||||
|
||||
String token = request.getHeader("Authorization");
|
||||
if (token == null || !vToken.isValidToken(token)) {
|
||||
HttpSession session = request.getSession();
|
||||
ApiUser apiUser = (ApiUser) session.getAttribute("Authorization");
|
||||
if (apiUser == null) {
|
||||
response.sendRedirect(request.getContextPath() + "/login");
|
||||
return false;
|
||||
}
|
||||
|
||||
52
src/main/java/com/mini/capi/model/auth/Result.java
Normal file
52
src/main/java/com/mini/capi/model/auth/Result.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package com.mini.capi.model.auth;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class Result implements Serializable {
|
||||
|
||||
// 状态码:200表示成功,其他表示错误
|
||||
private int code;
|
||||
// 响应信息
|
||||
private String msg;
|
||||
// 响应数据(可选)
|
||||
private Object data;
|
||||
|
||||
// 私有构造方法,防止直接创建实例
|
||||
private Result() {}
|
||||
|
||||
// 成功响应
|
||||
public static Result success(String msg) {
|
||||
Result result = new Result();
|
||||
result.code = 200;
|
||||
result.msg = msg;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 带数据的成功响应
|
||||
public static Result success(String msg, Object data) {
|
||||
Result result = new Result();
|
||||
result.code = 200;
|
||||
result.msg = msg;
|
||||
result.data = data;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 错误响应
|
||||
public static Result error(String msg) {
|
||||
Result result = new Result();
|
||||
result.code = 500; // 500表示服务器错误,也可以根据实际情况使用其他错误码
|
||||
result.msg = msg;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 带自定义错误码的错误响应
|
||||
public static Result error(int code, String msg) {
|
||||
Result result = new Result();
|
||||
result.code = code;
|
||||
result.msg = msg;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ public class demo {
|
||||
.pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "/src/main/resources/mapper"));
|
||||
})
|
||||
.strategyConfig(builder -> {
|
||||
builder.addInclude("biz_sync_task_log")
|
||||
builder.addInclude("biz_api_user")
|
||||
.addTablePrefix("biz_")
|
||||
.entityBuilder()
|
||||
.enableLombok()
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.mini.capi.sys.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.mini.capi.biz.domain.ApiUser;
|
||||
import com.mini.capi.biz.service.ApiUserService;
|
||||
import com.mini.capi.model.auth.Result;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.Data;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/Sys/login")
|
||||
public class LoginController {
|
||||
|
||||
|
||||
@Resource
|
||||
private ApiUserService userService;
|
||||
|
||||
|
||||
@Data
|
||||
public static class LoginRequest implements Serializable {
|
||||
private String username;
|
||||
private String password;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 密码校验(生产环境需替换为加密比对)
|
||||
*/
|
||||
private boolean verifyPassword(String rawPassword, String encodedPassword) {
|
||||
return Objects.equals(rawPassword, encodedPassword);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
@PostMapping("/userLogin")
|
||||
public Result login(@RequestBody LoginRequest user, HttpSession session) {
|
||||
try {
|
||||
QueryWrapper<ApiUser> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("api_user", user.getUsername())
|
||||
.eq("ustatus", 1);
|
||||
ApiUser apiUser = userService.getOne(queryWrapper);
|
||||
if (apiUser == null) {
|
||||
return Result.error("账户不存在");
|
||||
}
|
||||
if (!verifyPassword(user.getPassword(), apiUser.getApiPswd())) {
|
||||
// 可记录登录失败日志,用于后续风控
|
||||
return Result.error("账户或密码错误");
|
||||
}
|
||||
session.setAttribute("Authorization", apiUser);
|
||||
return Result.success("登录成功");
|
||||
} catch (Exception e) {
|
||||
return Result.error("登录失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,4 +10,13 @@ public class LoginPageController {
|
||||
public String loginPage() {
|
||||
return "forward:/index.html";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 首页
|
||||
*/
|
||||
@GetMapping("/welcome")
|
||||
public String welcomePage() {
|
||||
return "forward:/views/demo.html";
|
||||
}
|
||||
}
|
||||
|
||||
25
src/main/resources/mapper/ApiUserMapper.xml
Normal file
25
src/main/resources/mapper/ApiUserMapper.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.mini.capi.biz.mapper.ApiUserMapper">
|
||||
|
||||
<!-- 通用查询映射结果 -->
|
||||
<resultMap id="BaseResultMap" type="com.mini.capi.biz.domain.ApiUser">
|
||||
<id column="user_id" property="userId" />
|
||||
<result column="create_time" property="createTime" />
|
||||
<result column="api_user" property="apiUser" />
|
||||
<result column="api_pswd" property="apiPswd" />
|
||||
<result column="uname" property="uname" />
|
||||
<result column="ustatus" property="ustatus" />
|
||||
<result column="update_time" property="updateTime" />
|
||||
<result column="f_tenant_id" property="fTenantId" />
|
||||
<result column="f_flow_id" property="fFlowId" />
|
||||
<result column="f_flow_task_id" property="fFlowTaskId" />
|
||||
<result column="f_flow_state" property="fFlowState" />
|
||||
</resultMap>
|
||||
|
||||
<!-- 通用查询结果列 -->
|
||||
<sql id="Base_Column_List">
|
||||
create_time, user_id, api_user, api_pswd, uname, ustatus, update_time, f_tenant_id, f_flow_id, f_flow_task_id, f_flow_state
|
||||
</sql>
|
||||
|
||||
</mapper>
|
||||
324
src/main/resources/static/assets/css/login-style.css
Normal file
324
src/main/resources/static/assets/css/login-style.css
Normal file
@@ -0,0 +1,324 @@
|
||||
/* 全局样式重置:增加box-sizing强制继承,避免尺寸计算偏差 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
|
||||
/* 样式变量:统一管理,减少硬编码 */
|
||||
:root {
|
||||
--primary-color: #3498db;
|
||||
--primary-dark: #2980b9;
|
||||
--primary-light: #e3f2fd;
|
||||
--bg-gradient-1: #2c3e50;
|
||||
--bg-gradient-2: #3498db;
|
||||
--white: #fff;
|
||||
--gray-light: #f8f9fa;
|
||||
--gray-border: #ddd;
|
||||
--gray-hover: #f1f1f1;
|
||||
--text-gray: #555;
|
||||
--text-light-gray: #888;
|
||||
--danger-color: #e74c3c;
|
||||
--shadow-normal: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||
--shadow-modal: 0 10px 25px rgba(0, 0, 0, 0.2);
|
||||
--shadow-input: 0 0 0 3px rgba(52, 152, 219, 0.1);
|
||||
--transition-base: all 0.25s ease;
|
||||
--login-box-width: 420px; /* 固定登录框宽度,避免变形 */
|
||||
--input-height: 48px; /* 固定输入框高度,统一视觉 */
|
||||
}
|
||||
|
||||
/* 页面主体:固定布局,确保登录框不被挤压 */
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, var(--bg-gradient-1), var(--bg-gradient-2));
|
||||
padding: 0 80px;
|
||||
overflow: hidden; /* 禁止页面滚动 */
|
||||
}
|
||||
|
||||
/* -------------------------- 左侧品牌区域:固定尺寸和位置 -------------------------- */
|
||||
.brand-side {
|
||||
width: 55%;
|
||||
max-width: 700px;
|
||||
color: var(--white);
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
margin-right: 60px;
|
||||
}
|
||||
|
||||
.brand-title-group {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.brand-side h1 {
|
||||
font-size: 3.2rem;
|
||||
margin-bottom: 15px;
|
||||
text-shadow: 0 3px 12px rgba(0, 0, 0, 0.25);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.brand-divider {
|
||||
width: 80px;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, transparent, var(--white), transparent);
|
||||
margin: 0 auto 20px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.brand-side p {
|
||||
font-size: 1.15rem;
|
||||
opacity: 0.95;
|
||||
line-height: 1.7;
|
||||
text-shadow: 0 1px 5px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.brand-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 18px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.brand-icon i {
|
||||
font-size: 2.8rem;
|
||||
opacity: 0.95;
|
||||
transition: var(--transition-base);
|
||||
}
|
||||
|
||||
.brand-icon i:hover {
|
||||
transform: translateY(-6px) scale(1.05);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* -------------------------- 右侧登录容器:固定尺寸,彻底解决变形 -------------------------- */
|
||||
.login-container {
|
||||
width: var(--login-box-width); /* 固定宽度,不随内容拉伸 */
|
||||
background: var(--white);
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--shadow-normal);
|
||||
overflow: hidden;
|
||||
transition: var(--transition-base);
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.login-container:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
padding: 25px;
|
||||
background: var(--gray-light);
|
||||
border-bottom: 1px solid var(--gray-border);
|
||||
}
|
||||
|
||||
.login-header h2 {
|
||||
font-size: 1.7rem;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* 表单区域:固定内边距,避免内容溢出 */
|
||||
.login-form {
|
||||
padding: 35px 30px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 表单组:固定间距,不随内容变化 */
|
||||
.form-group {
|
||||
margin-bottom: 28px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.required-mark {
|
||||
color: var(--danger-color);
|
||||
margin-left: 4px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 9px;
|
||||
color: var(--text-gray);
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* -------------------------- 输入框组:固定高度+定位,彻底解决变形 -------------------------- */
|
||||
.input-group {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: var(--input-height); /* 固定容器高度,与输入框一致 */
|
||||
}
|
||||
|
||||
/* 输入框:固定高度+内边距,不拉伸 */
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
height: 100%; /* 继承容器高度,避免自行拉伸 */
|
||||
padding: 0 18px 0 45px; /* 左侧留足图标空间,右侧留足按钮空间 */
|
||||
border: 1px solid var(--gray-border);
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: var(--transition-base);
|
||||
background-color: var(--white);
|
||||
resize: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
/* 输入框聚焦:仅改变边框和阴影,不改变尺寸 */
|
||||
.form-group input:focus {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: var(--shadow-input);
|
||||
background-color: var(--primary-light);
|
||||
}
|
||||
|
||||
.form-group input:not(:placeholder-shown) {
|
||||
border-color: #b3d9f2;
|
||||
}
|
||||
|
||||
/* 输入框图标:固定左侧定位,不随输入框变化 */
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
left: 16px; /* 固定左侧距离,不偏移 */
|
||||
color: var(--text-light-gray);
|
||||
font-size: 1.1rem;
|
||||
z-index: 1;
|
||||
width: 20px; /* 固定图标宽度,避免抖动 */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-group input:not(:placeholder-shown) + .input-icon {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 密码切换按钮:固定最右侧,不偏移 */
|
||||
.toggle-pwd {
|
||||
position: absolute;
|
||||
right: 12px; /* 固定右侧距离 */
|
||||
top: 50%;
|
||||
transform: translateY(-50%); /* 垂直居中 */
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-light-gray);
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
transition: var(--transition-base);
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.toggle-pwd:hover {
|
||||
background-color: var(--gray-hover);
|
||||
color: var(--text-gray);
|
||||
}
|
||||
|
||||
/* 登录按钮:固定尺寸,不拉伸 */
|
||||
.btn-login {
|
||||
width: 100%;
|
||||
height: 52px; /* 固定按钮高度 */
|
||||
background: var(--primary-color);
|
||||
color: var(--white);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: var(--transition-base);
|
||||
letter-spacing: 0.5px;
|
||||
box-shadow: 0 3px 8px rgba(52, 152, 219, 0.2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn-login:hover {
|
||||
background: var(--primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 12px rgba(41, 128, 185, 0.25);
|
||||
}
|
||||
|
||||
.btn-login:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 5px rgba(41, 128, 185, 0.2);
|
||||
}
|
||||
|
||||
/* -------------------------- 弹窗:固定层级,避免被遮挡 -------------------------- */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999; /* 最高层级,不被任何元素遮挡 */
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: var(--white);
|
||||
padding: 25px;
|
||||
border-radius: 10px;
|
||||
width: 320px;
|
||||
text-align: center;
|
||||
box-shadow: var(--shadow-modal);
|
||||
transform: translateY(-20px);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.modal.show .modal-content {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.modal-content .icon {
|
||||
font-size: 2.5rem;
|
||||
color: var(--danger-color);
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.modal-content p {
|
||||
margin-bottom: 20px;
|
||||
color: var(--text-gray);
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.modal-content button {
|
||||
padding: 10px 25px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: var(--primary-color);
|
||||
color: var(--white);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-base);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.modal-content button:hover {
|
||||
background: var(--primary-dark);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.modal-content button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
175
src/main/resources/static/assets/js/login-script.js
Normal file
175
src/main/resources/static/assets/js/login-script.js
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* 登录逻辑处理:阻止表单默认提交,校验用户名/密码,调用登录接口
|
||||
* @param {Event} e - 表单提交事件
|
||||
* @returns {boolean} - 返回false阻止默认提交
|
||||
*/
|
||||
function handleLogin(e) {
|
||||
// 1. 阻止表单默认提交行为(避免页面刷新)
|
||||
e.preventDefault();
|
||||
|
||||
// 2. 获取并清理用户名、密码(去除前后空格)
|
||||
const username = document.getElementById('username').value.trim();
|
||||
const password = document.getElementById('password').value.trim();
|
||||
|
||||
// 3. 前端简单校验(空值判断)
|
||||
if (!username) {
|
||||
showModal('请输入用户名');
|
||||
return false;
|
||||
}
|
||||
if (!password) {
|
||||
showModal('请输入密码');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 调用后端登录接口(POST请求)
|
||||
fetch('Sys/login/userLogin', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json' // 声明请求体为JSON格式
|
||||
},
|
||||
body: JSON.stringify({username, password}) // 转换为JSON字符串
|
||||
})
|
||||
.then(response => {
|
||||
// 4.1 校验HTTP响应状态(非200-299范围视为错误)
|
||||
if (!response.ok) {
|
||||
throw new Error(`请求失败,状态码:${response.status}`);
|
||||
}
|
||||
// 4.2 解析响应体(假设后端返回JSON格式)
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
// 5. 处理后端返回结果(假设接口返回 { code: 200, msg: '成功' } 结构)
|
||||
if (data.code === 200) {
|
||||
// 登录成功:跳转到demo页面(可根据实际需求修改跳转路径)
|
||||
window.location.href = 'welcome';
|
||||
} else {
|
||||
// 业务错误:显示后端返回的错误信息,无信息时显示默认提示
|
||||
showModal(data.msg || '登录失败,请检查账号密码是否正确');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// 6. 捕获请求异常(网络错误、接口500等)
|
||||
showModal(`登录异常:${error.message}`);
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示错误弹窗
|
||||
* @param {string} msg - 弹窗提示内容
|
||||
*/
|
||||
function showModal(msg) {
|
||||
const errorMsgElem = document.getElementById('errorMsg');
|
||||
const modalElem = document.getElementById('errorModal');
|
||||
|
||||
// 设置弹窗文本内容
|
||||
errorMsgElem.innerText = msg;
|
||||
// 显示弹窗(通过flex布局实现居中)
|
||||
modalElem.style.display = 'flex';
|
||||
// 延迟添加show类(触发过渡动画,避免动画不生效)
|
||||
setTimeout(() => {
|
||||
modalElem.classList.add('show');
|
||||
}, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭错误弹窗
|
||||
*/
|
||||
function closeModal() {
|
||||
const modalElem = document.getElementById('errorModal');
|
||||
|
||||
// 移除show类(触发淡出动画)
|
||||
modalElem.classList.remove('show');
|
||||
// 动画结束后隐藏弹窗(300ms与CSS过渡时间保持一致)
|
||||
setTimeout(() => {
|
||||
modalElem.style.display = 'none';
|
||||
}, 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空指定输入框内容
|
||||
* @param {string} inputId - 输入框的ID属性值
|
||||
*/
|
||||
function clearInput(inputId) {
|
||||
const inputElem = document.getElementById(inputId);
|
||||
if (inputElem) {
|
||||
// 清空输入框内容
|
||||
inputElem.value = '';
|
||||
// 触发input事件(更新清空按钮的显示状态)
|
||||
inputElem.dispatchEvent(new Event('input'));
|
||||
// 聚焦到当前输入框(提升用户体验)
|
||||
inputElem.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换密码输入框的可见性(明文/密文切换)
|
||||
*/
|
||||
function togglePasswordVisibility() {
|
||||
const passwordInput = document.getElementById('password');
|
||||
const toggleBtn = passwordInput.parentElement.querySelector('.toggle-pwd i');
|
||||
|
||||
if (passwordInput.type === 'password') {
|
||||
// 切换为明文显示
|
||||
passwordInput.type = 'text';
|
||||
// 更换图标为"眼睛"(表示当前是明文)
|
||||
toggleBtn.classList.remove('bi-eye-slash');
|
||||
toggleBtn.classList.add('bi-eye');
|
||||
} else {
|
||||
// 切换为密文显示
|
||||
passwordInput.type = 'password';
|
||||
// 更换图标为"眼睛斜杠"(表示当前是密文)
|
||||
toggleBtn.classList.remove('bi-eye');
|
||||
toggleBtn.classList.add('bi-eye-slash');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入框内容变化时的动态反馈:控制清空按钮的显示/隐藏
|
||||
* @param {HTMLInputElement} inputElem - 输入框元素
|
||||
*/
|
||||
function handleInputChange(inputElem) {
|
||||
const clearBtn = inputElem.parentElement.querySelector('.clear-btn');
|
||||
if (clearBtn) {
|
||||
// 输入框有内容时显示清空按钮,无内容时隐藏
|
||||
clearBtn.hidden = !inputElem.value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后:绑定全局交互事件
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 1. 获取用户名和密码输入框元素
|
||||
const usernameInput = document.getElementById('username');
|
||||
const passwordInput = document.getElementById('password');
|
||||
|
||||
// 2. 绑定输入框内容变化事件(控制清空按钮显示)
|
||||
if (usernameInput) {
|
||||
usernameInput.addEventListener('input', () => handleInputChange(usernameInput));
|
||||
}
|
||||
if (passwordInput) {
|
||||
passwordInput.addEventListener('input', () => handleInputChange(passwordInput));
|
||||
}
|
||||
|
||||
// 3. 点击弹窗外部关闭弹窗
|
||||
const modalElem = document.getElementById('errorModal');
|
||||
modalElem.addEventListener('click', (e) => {
|
||||
// 仅当点击弹窗背景(而非内容区)时关闭
|
||||
if (e.target === modalElem) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// 4. 按ESC键关闭弹窗
|
||||
document.addEventListener('keydown', (e) => {
|
||||
// 仅当弹窗显示时生效
|
||||
if (e.key === 'Escape' && modalElem.style.display === 'flex') {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// 5. 页面初始化时,默认聚焦到用户名输入框(提升用户体验)
|
||||
if (usernameInput) {
|
||||
usernameInput.focus();
|
||||
}
|
||||
});
|
||||
@@ -1,10 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="./assets/css/login-style.css">
|
||||
<title>cApi登录</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 左侧品牌区域 -->
|
||||
<div class="brand-side">
|
||||
<div class="brand-icon">
|
||||
<i class="bi bi-shield-lock" style="color: #ecf0f1;"></i>
|
||||
<i class="bi bi-key" style="color: #2ecc71;"></i>
|
||||
<i class="bi bi-server" style="color: #1abc9c;"></i>
|
||||
</div>
|
||||
<div class="brand-title-group">
|
||||
<h1>cApi管理系统</h1>
|
||||
<div class="brand-divider"></div>
|
||||
<p>安全、高效的一站式管理解决方案,为您的业务保驾护航</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧登录容器:固定宽度,避免变形 -->
|
||||
<div class="login-container">
|
||||
<div class="login-header">
|
||||
<h2>系统登录</h2>
|
||||
</div>
|
||||
<form class="login-form" onsubmit="return handleLogin(event)">
|
||||
<!-- 用户名输入组:结构扁平化,避免嵌套冲突 -->
|
||||
<div class="form-group">
|
||||
<label for="username">
|
||||
账户
|
||||
<span class="required-mark">*</span>
|
||||
</label>
|
||||
<!-- 输入框容器:仅包裹输入框+功能按钮,固定定位规则 -->
|
||||
<div class="input-group">
|
||||
<input type="text" id="username" placeholder="请输入用户名" required>
|
||||
<i class="bi bi-person input-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 密码输入组:同用户名结构,保持一致性 -->
|
||||
<div class="form-group">
|
||||
<label for="password">
|
||||
密码
|
||||
<span class="required-mark">*</span>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="password" id="password" placeholder="请输入密码" required>
|
||||
<i class="bi bi-lock input-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-login">登录</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 错误提示弹窗:固定层级,避免被遮挡 -->
|
||||
<div class="modal" id="errorModal">
|
||||
<div class="modal-content">
|
||||
<i class="bi bi-exclamation-circle icon"></i>
|
||||
<p id="errorMsg"></p>
|
||||
<button onclick="closeModal()">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="./assets/js/login-script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
10
src/main/resources/static/views/demo.html
Normal file
10
src/main/resources/static/views/demo.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user