From 2948a25d9f97c6cd6558c69e3855e652e8422254 Mon Sep 17 00:00:00 2001 From: gaoxq <376340421@qq.com> Date: Thu, 28 Aug 2025 18:09:20 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E5=86=99=E5=A4=8D=E7=8E=B0=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../biz/controller/ApiUserController.java | 18 + .../com/mini/capi/biz/domain/ApiUser.java | 86 +++++ .../mini/capi/biz/mapper/ApiUserMapper.java | 16 + .../mini/capi/biz/service/ApiUserService.java | 16 + .../biz/service/impl/ApiUserServiceImpl.java | 20 ++ .../com/mini/capi/config/AuthInterceptor.java | 9 +- .../java/com/mini/capi/model/auth/Result.java | 52 +++ src/main/java/com/mini/capi/mybatis/demo.java | 2 +- .../capi/sys/controller/LoginController.java | 66 ++++ .../sys/controller/LoginPageController.java | 9 + src/main/resources/mapper/ApiUserMapper.xml | 25 ++ .../static/assets/css/login-style.css | 324 ++++++++++++++++++ .../static/assets/js/login-script.js | 175 ++++++++++ src/main/resources/static/index.html | 67 +++- src/main/resources/static/views/demo.html | 10 + 15 files changed, 887 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/mini/capi/biz/controller/ApiUserController.java create mode 100644 src/main/java/com/mini/capi/biz/domain/ApiUser.java create mode 100644 src/main/java/com/mini/capi/biz/mapper/ApiUserMapper.java create mode 100644 src/main/java/com/mini/capi/biz/service/ApiUserService.java create mode 100644 src/main/java/com/mini/capi/biz/service/impl/ApiUserServiceImpl.java create mode 100644 src/main/java/com/mini/capi/model/auth/Result.java create mode 100644 src/main/java/com/mini/capi/sys/controller/LoginController.java create mode 100644 src/main/resources/mapper/ApiUserMapper.xml create mode 100644 src/main/resources/static/assets/css/login-style.css create mode 100644 src/main/resources/static/assets/js/login-script.js create mode 100644 src/main/resources/static/views/demo.html diff --git a/src/main/java/com/mini/capi/biz/controller/ApiUserController.java b/src/main/java/com/mini/capi/biz/controller/ApiUserController.java new file mode 100644 index 0000000..118ddc2 --- /dev/null +++ b/src/main/java/com/mini/capi/biz/controller/ApiUserController.java @@ -0,0 +1,18 @@ +package com.mini.capi.biz.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * 前端控制器 + *

+ * + * @author gaoxq + * @since 2025-08-28 + */ +@RestController +@RequestMapping("/biz/apiUser") +public class ApiUserController { + +} diff --git a/src/main/java/com/mini/capi/biz/domain/ApiUser.java b/src/main/java/com/mini/capi/biz/domain/ApiUser.java new file mode 100644 index 0000000..f2f661d --- /dev/null +++ b/src/main/java/com/mini/capi/biz/domain/ApiUser.java @@ -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; + +/** + *

+ * + *

+ * + * @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; +} diff --git a/src/main/java/com/mini/capi/biz/mapper/ApiUserMapper.java b/src/main/java/com/mini/capi/biz/mapper/ApiUserMapper.java new file mode 100644 index 0000000..2ddece9 --- /dev/null +++ b/src/main/java/com/mini/capi/biz/mapper/ApiUserMapper.java @@ -0,0 +1,16 @@ +package com.mini.capi.biz.mapper; + +import com.mini.capi.biz.domain.ApiUser; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * Mapper 接口 + *

+ * + * @author gaoxq + * @since 2025-08-28 + */ +public interface ApiUserMapper extends BaseMapper { + +} diff --git a/src/main/java/com/mini/capi/biz/service/ApiUserService.java b/src/main/java/com/mini/capi/biz/service/ApiUserService.java new file mode 100644 index 0000000..36dbda1 --- /dev/null +++ b/src/main/java/com/mini/capi/biz/service/ApiUserService.java @@ -0,0 +1,16 @@ +package com.mini.capi.biz.service; + +import com.mini.capi.biz.domain.ApiUser; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 服务类 + *

+ * + * @author gaoxq + * @since 2025-08-28 + */ +public interface ApiUserService extends IService { + +} diff --git a/src/main/java/com/mini/capi/biz/service/impl/ApiUserServiceImpl.java b/src/main/java/com/mini/capi/biz/service/impl/ApiUserServiceImpl.java new file mode 100644 index 0000000..9ee1a51 --- /dev/null +++ b/src/main/java/com/mini/capi/biz/service/impl/ApiUserServiceImpl.java @@ -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; + +/** + *

+ * 服务实现类 + *

+ * + * @author gaoxq + * @since 2025-08-28 + */ +@Service +public class ApiUserServiceImpl extends ServiceImpl implements ApiUserService { + +} diff --git a/src/main/java/com/mini/capi/config/AuthInterceptor.java b/src/main/java/com/mini/capi/config/AuthInterceptor.java index 865e807..355904c 100644 --- a/src/main/java/com/mini/capi/config/AuthInterceptor.java +++ b/src/main/java/com/mini/capi/config/AuthInterceptor.java @@ -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; } diff --git a/src/main/java/com/mini/capi/model/auth/Result.java b/src/main/java/com/mini/capi/model/auth/Result.java new file mode 100644 index 0000000..85bc79d --- /dev/null +++ b/src/main/java/com/mini/capi/model/auth/Result.java @@ -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; + } +} diff --git a/src/main/java/com/mini/capi/mybatis/demo.java b/src/main/java/com/mini/capi/mybatis/demo.java index 455c6db..5bf931d 100644 --- a/src/main/java/com/mini/capi/mybatis/demo.java +++ b/src/main/java/com/mini/capi/mybatis/demo.java @@ -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() diff --git a/src/main/java/com/mini/capi/sys/controller/LoginController.java b/src/main/java/com/mini/capi/sys/controller/LoginController.java new file mode 100644 index 0000000..33c6ba4 --- /dev/null +++ b/src/main/java/com/mini/capi/sys/controller/LoginController.java @@ -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 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("登录失败,请稍后重试"); + } + } +} diff --git a/src/main/java/com/mini/capi/sys/controller/LoginPageController.java b/src/main/java/com/mini/capi/sys/controller/LoginPageController.java index 06b984a..661f02f 100644 --- a/src/main/java/com/mini/capi/sys/controller/LoginPageController.java +++ b/src/main/java/com/mini/capi/sys/controller/LoginPageController.java @@ -10,4 +10,13 @@ public class LoginPageController { public String loginPage() { return "forward:/index.html"; } + + + /** + * 首页 + */ + @GetMapping("/welcome") + public String welcomePage() { + return "forward:/views/demo.html"; + } } diff --git a/src/main/resources/mapper/ApiUserMapper.xml b/src/main/resources/mapper/ApiUserMapper.xml new file mode 100644 index 0000000..16bd74c --- /dev/null +++ b/src/main/resources/mapper/ApiUserMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + 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 + + + diff --git a/src/main/resources/static/assets/css/login-style.css b/src/main/resources/static/assets/css/login-style.css new file mode 100644 index 0000000..1cff237 --- /dev/null +++ b/src/main/resources/static/assets/css/login-style.css @@ -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); +} diff --git a/src/main/resources/static/assets/js/login-script.js b/src/main/resources/static/assets/js/login-script.js new file mode 100644 index 0000000..cd15122 --- /dev/null +++ b/src/main/resources/static/assets/js/login-script.js @@ -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(); + } +}); diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 566549b..1068291 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -1,10 +1,71 @@ - + - Title + + + + cApi登录 + +
+
+ + + +
+
+

cApi管理系统

+
+

安全、高效的一站式管理解决方案,为您的业务保驾护航

+
+
+ + + + + + + - \ No newline at end of file + diff --git a/src/main/resources/static/views/demo.html b/src/main/resources/static/views/demo.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/src/main/resources/static/views/demo.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file