首页
关于这个博客
Search
1
Java 实现Google 账号单点登录(OAuth 2.0)全流程解析
822 阅读
2
Spring AI 无法获取大模型深度思考内容?解决方案来了
360 阅读
3
EasyExcel 实战:导出带图片的 Excel 完整方案
169 阅读
4
微信小程序实现页面返回前确认弹窗:兼容左上角返回与右滑返回
155 阅读
5
服务器遭遇 XMRig 挖矿程序入侵排查与清理全记录
153 阅读
Java 核心
框架与中间件
数据库技术
开发工具与效率
问题排查与踩坑记录
程序员成长与思考
前端
登录
Search
标签搜索
java虚拟机
JVM
保姆级教程
Java
Spring AI
SpringBoot
Spring
WebFlux
Nginx
Spring Retry
EasyExcel
流式输出
WebSocket
JustAuth
sso
google
单点登录
源码解析
Tool
图片导出
Luca Ju
累计撰写
39
篇文章
累计收到
1
条评论
首页
栏目
Java 核心
框架与中间件
数据库技术
开发工具与效率
问题排查与踩坑记录
程序员成长与思考
前端
页面
关于这个博客
搜索到
39
篇与
的结果
2025-11-17
Java 高效替换 Word 文档内容(模板填充方案)
使用java操作Excel大家应该非常熟悉,但是操作word可能会稍微少一点。本文将提供一套基于 Apache POI 的完整解决方案,支持普通段落+表格单元格的占位符替换,逻辑简洁、可直接复用。一、核心思路模板设计:在 Word 文档中,用 {{占位符名}} 标记需要替换的内容(如 {{name}}、{{date}});技术选型:使用 Apache POI(poi-ooxml)解析 .docx 文档,遍历段落和表格,替换占位符;核心优势:无需依赖第三方付费组件,支持复杂文档结构,兼容主流 Word 版本。二、实现步骤1. 环境准备(添加 Maven 依赖)核心依赖 poi-ooxml 用于处理 Office Open XML 格式(.docx),兼容 5.2.0 及以上版本(推荐使用最新稳定版):<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.0</version> <!-- 你可以使用最新版本 --> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>5.2.0</version> </dependency>2. 编写工具类(完整可运行代码)工具类封装了「读取模板、替换占位符、保存文件」全流程,支持普通段落和表格单元格的替换,还保留了原文本格式(字体、大小、颜色):package com.water.ocrimagerecognize.util; import org.apache.poi.xwpf.usermodel.*; import java.io.*; import java.util.HashMap; import java.util.List; import java.util.Map; public class WordTemplateExporter { public static void main(String[] args) { // 输出文件路径 String outputPath = "output.docx"; try { Map<String,String> contractData = new HashMap<>(); // 占位符替换 contractData.put("date", "2025"); contractData.put("s15", "FF"); contractData.put("s16", "增效降本"); contractData.put("name", "张三"); contractData.put("luca", "lucaju"); contractData.put("s1", "game"); // 加载资源文件夹resources/tem文件夹下的合同模板 //不在resources获取模板文件需要修改此处,通过这个方法只能获取resources中的资源文件 InputStream templateInputStream = new FileInputStream("/Users/lucaju/Documents/文档/水务/word/template.docx"); XWPFDocument document = new XWPFDocument(templateInputStream); // 遍历文档中的段落 for (XWPFParagraph paragraph : document.getParagraphs()) { if (paragraph == null || paragraph.getRuns().size() == 0){ continue; } //替换占位符 change(paragraph,contractData); } // 遍历文档中的表格 for (XWPFTable table : document.getTables()) { for (XWPFTableRow row : table.getRows()) { for (XWPFTableCell cell : row.getTableCells()) { // 遍历单元格中的段落 for (XWPFParagraph cellParagraph : cell.getParagraphs()) { //替换占位符 change(cellParagraph,contractData); } } } } // 遍历文档中的图片 for (XWPFPictureData picture : document.getAllPictures()) { // 图片不会被修改,直接跳过 //需要修改图片在此处 System.out.println("图片: " + picture.getFileName()); } //该路径为相对路径,默认创建保存位置为项目文件所在盘符的根目录下 // 保存填充后的合同(将毫秒数添加到文件名防止命名冲突) String filePath = "contract_" + System.currentTimeMillis() + ".docx"; //检查目录是否存在,不存在则创建 File directory = new File(filePath); // 使用 FileOutputStream 保存填充后的合同 try (FileOutputStream out = new FileOutputStream(filePath)) { document.write(out); // 将内容写入文件 } System.out.println("输出完成"); } catch (Exception e) { e.printStackTrace(); } } //替换占位符方法 public static void change(XWPFParagraph paragraph, Map<String, String> contractData){ for (XWPFRun run : paragraph.getRuns()) { // 获取当前 run 的文本 String runText = run.getText(0); if (runText == null){ continue; } StringBuilder newText = new StringBuilder(runText); // 替换占位符 for (Map.Entry<String, String> entry : contractData.entrySet()) { //此处的占位符“{{”和“}}”可以任意更改,合同模板文件随着替换即可。 String placeholder = "{{" + entry.getKey() + "}}"; int startIndex = newText.indexOf(placeholder); while (startIndex != -1) { newText.replace(startIndex, startIndex + placeholder.length(), entry.getValue()); startIndex = newText.indexOf(placeholder, startIndex + entry.getValue().length()); } } // 更新替换后的文本 run.setText(newText.toString(), 0); } } /** * 替换 Word 文件中的占位符(支持普通段落 + 表格单元格) * * @param document Word 文档对象 * @param placeholder 占位符,例如 {{name}} * @param replacement 替换后的值,例如 "张三" */ private static void replacePlaceholder(XWPFDocument document, String placeholder, String replacement) { // 1. 替换普通段落中的占位符(原有逻辑保留) for (XWPFParagraph paragraph : document.getParagraphs()) { replaceRunInParagraph(paragraph, placeholder, replacement); } // 2. 替换表格中的占位符(新增核心逻辑) for (XWPFTable table : document.getTables()) { // 遍历所有表格 for (XWPFTableRow row : table.getRows()) { // 遍历表格的所有行 for (XWPFTableCell cell : row.getTableCells()) { // 遍历行的所有单元格 for (XWPFParagraph paragraph : cell.getParagraphs()) { // 遍历单元格内的所有段落 replaceRunInParagraph(paragraph, placeholder, replacement); // 替换段落中的占位符 } } } } } /** * 替换单个段落中所有 Run 里的占位符(抽取通用逻辑,避免重复代码) */ private static void replaceRunInParagraph(XWPFParagraph para, String placeholder, String replacement) { List<XWPFRun> runs = para.getRuns(); int runCount = runs.size(); // 情况1:段落只有1个 Run(直接替换) if (runCount == 1) { XWPFRun run = runs.get(0); String text = run.getText(0); if (text != null && text.contains(placeholder)) { run.setText(text.replace(placeholder, replacement), 0); } return; } // 情况2:段落有多个 Run(合并文本后替换) StringBuilder mergedText = new StringBuilder(); // 第一步:合并所有 Run 的文本 for (XWPFRun run : runs) { String text = run.getText(0); if (text != null) { mergedText.append(text); } } // 检查合并后的文本是否包含占位符 String finalText = mergedText.toString(); if (!finalText.contains(placeholder)) { return; // 不包含则无需处理 } // 第二步:替换占位符 finalText = finalText.replace(placeholder, replacement); // 第三步:清空原有所有 Run(兼容低版本 POI 的写法) // 注意:要倒序删除,避免索引错乱 for (int i = runCount - 1; i >= 0; i--) { para.removeRun(i); } // 第四步:创建新的 Run,写入替换后的文本 XWPFRun newRun = para.createRun(); newRun.setText(finalText, 0); // (可选)复制原有文本的格式(如字体、大小、颜色) if (runCount > 0) { XWPFRun originalFirstRun = runs.get(0); // 取第一个 Run 的格式 newRun.setFontFamily(originalFirstRun.getFontFamily()); newRun.setFontSize(originalFirstRun.getFontSize()); newRun.setColor(originalFirstRun.getColor()); } } }3. 模板编辑规范占位符格式:统一使用 {{占位符名}}(如 {{name}}、{{date}}),可自定义分隔符(需同步修改代码中 placeholder 的拼接逻辑);关键注意点:占位符必须作为一个整体输入(直接复制粘贴 {{name}},或一次性输入完成);避免输入一半保存、再续输的操作(会导致占位符被拆分成多个 Run,替换失败);模板文件需保存为 .docx 格式(不支持 .doc 旧格式,如需兼容可先转成 .docx)。三、使用示例编辑模板 template.docx,内容如下:姓名:{{name}} 日期:{{date}} 项目:{{s1}} 目标:{{s16}}运行 main 方法,传入替换数据;生成的文件中,占位符会被自动替换为对应值:姓名:张三 日期:2025年11月 项目:game 目标:增效降本这套方案轻量化、无额外依赖,适合合同生成、报表导出、通知书批量制作等场景,可直接集成到 Spring Boot、SSM 等主流 Java 项目中。
2025年11月17日
90 阅读
0 评论
0 点赞
2025-10-16
Spring Boot 整合 Milvus 向量数据库:CRUD封装
本文围绕 Spring Boot 集成 Milvus 向量数据库展开,详细介绍了二次封装的完整方案:首先通过配置类实现 Milvus 连接池与连接参数管理,再以@CollectionName和@PrimaryKey注解建立实体与 Milvus Collection 的映射,搭配驼峰转下划线命名策略解决字段名适配问题;核心封装泛型MilvusBaseService
类,实现新增、主键查询、分页查询等通用 CRUD 方法,并针对 Milvus 无原生更新的特性,采用 “先删除后插入” 策略实现更新功能;同时提供 Lambda 查询构造器简化条件查询,辅以工具类处理主键解析等通用逻辑。文中还给出业务层 Service、Controller 的使用示例与完整配置文件,附项目 GitHub 地址,帮助开发者快速落地 Milvus 相关业务。
2025年10月16日
44 阅读
0 评论
2 点赞
2025-09-30
Java 实战:基于 WebSocket 获取大模型流式输出并转为 Flux
本文详细讲解 Java 如何基于 ReactorNettyWebSocketClient 实现 WebSocket 流式输出获取,附带完整代码演示将 WebSocket 消息转为 Flux 响应式流的全流程,包含连接建立、异常处理、会话管理等关键逻辑,助力开发者快速解决大模型流式对接需求,适配前端 HTTP 流式交互场景。
2025年09月30日
76 阅读
0 评论
1 点赞
2025-09-19
Java 实现Google 账号单点登录(OAuth 2.0)全流程解析
在当今的互联网应用中,单点登录(SSO)已成为提升用户体验的重要功能 —— 用户无需重复注册账号,通过常用的第三方平台(如 Google、GitHub)即可快速登录。本文将详细介绍如何基于 OAuth 2.0 协议,使用 Java 结合 JustAuth 框架实现 Google 账号单点登录,涵盖从 Google 平台配置到前后端代码落地的完整流程。一、前置知识:Google 账号登录核心原理Google 账号登录基于 OAuth 2.0 授权协议,核心流程可概括为 3 步:应用引导用户跳转至 Google 授权页面,用户完成身份验证并同意授权;Google 授权服务器向应用的回调地址返回授权码;应用通过授权码向 Google 申请访问令牌,进而获取用户基本信息,最终完成登录逻辑。本文将使用 JustAuth 框架(轻量级 OAuth 工具包)简化开发,避免重复编写 OAuth 协议相关的底层代码。二、前期准备:Google Cloud 平台服务配置要实现 Google 登录,首先需要在 Google Cloud 平台创建应用并配置授权信息,这是整个流程的基础。1. 登录 Google Cloud 平台访问 Google Cloud 控制台,使用 Google 账号登录。2. 创建 / 选择项目若已有项目:直接在顶部项目列表中选择目标项目;若新建项目:点击顶部「项目」→「新建项目」,输入项目名称(如「Java-Google-SSO-Demo」),完成创建后等待项目初始化(约 1-2 分钟)。3. 配置「OAuth 同意屏幕」新创建的项目必须先配置「同意屏幕」,否则无法创建客户端凭证。在左侧菜单找到「API 和服务」→「OAuth 同意屏幕」;选择「外部」(适用于面向公众的应用,内部应用仅适用于企业域内用户),点击「创建」;填写「应用信息」:应用名称:将显示在 Google 登录授权页面(如「我的 Java 应用」);支持邮箱:用于接收 Google 相关通知;后续步骤(范围、测试用户、摘要)保持默认配置,点击「提交」即可(测试阶段无需审核,直接可用)。4. 创建 Client 客户端(关键!)客户端凭证(Client ID、Client Secret)是应用与 Google 授权服务器通信的身份标识,需重点配置。在左侧菜单找到「API 和服务」→「凭据」→「创建凭据」→「OAuth 客户端 ID」;应用类型选择「Web 应用」,填写名称(如「Java-Web-SSO」);配置回调地址(Redirect URI):这是 Google 授权成功后跳转的地址,必须与后端代码中配置的一致(如 http://localhost:8080/google-login-callback);本地测试可添加 http://localhost:8080/*,线上环境需填写真实域名;点击「创建」,记录生成的 Client ID 和 Client Secret(后续代码会用到,切勿泄露)。三、前端实现:Google 登录按钮与跳转前端只需提供一个登录按钮,点击后跳转至后端接口,由后端引导至 Google 授权页面(避免前端直接与 Google 交互,保证安全性)。本文以 Vue 为例,其他框架(React、Angular)逻辑一致:<template> <!-- 页面中的 Google 登录按钮 --> <button class="google-login-btn" @click="handleGoogleLogin" > <i class="icon-google"></i> 用 Google 账号登录 </button> </template> <script> export default { name: 'LoginPage', methods: { /** * 处理 Google 登录点击事件 * 核心:跳转至后端接口,由后端重定向到 Google 授权页 */ handleGoogleLogin() { // 后端接口地址(需与后端服务端口一致,如本地 8080 端口) window.location.href = "http://localhost:8080/google-login"; } } }; </script> <style scoped> .google-login-btn { padding: 8px 16px; background: #4285F4; color: white; border: none; border-radius: 4px; cursor: pointer; } </style>注意:此处需使用 window.location.href 进行页面级跳转,而非异步请求(axios/fetch)—— 因为 Google 授权页面需要用户手动交互(输入账号密码、同意授权)。四、后端实现:基于 JustAuth 框架的核心逻辑后端使用 Spring Boot + JustAuth 框架,主要实现两个接口:google-login:引导用户跳转至 Google 授权页;google-login-callback:接收 Google 回调,处理授权逻辑并完成登录。1. 第一步:添加 JustAuth 依赖在 pom.xml 中引入 JustAuth 最新版本(版本可从 JustAuth GitHub 发布页 获取):<!-- JustAuth:轻量级 OAuth 工具包,简化第三方登录开发 --> <dependency> <groupId>me.zhyd.oauth</groupId> <artifactId>JustAuth</artifactId> <version>1.16.5</version> <!-- 替换为最新版本 --> </dependency> <!-- 若使用 Spring Boot,需确保已引入 web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>2. 第二步:实现「引导授权」接口(/google-login)该接口的作用是:接收前端登录请求,生成 Google 授权地址,并将页面重定向至该地址。import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.request.AuthGoogleRequest; import me.zhyd.oauth.request.AuthRequest; import me.zhyd.oauth.utils.AuthStateUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; @RestController @AllArgsConstructor public class AuthController { /** * Google 登录引导:重定向至 Google 授权页 * @param response 用于重定向的响应对象 * @throws IOException 重定向异常 */ @GetMapping("/google-login") public void googleLogin(HttpServletResponse response) throws IOException { // 1. 创建 Google 授权请求对象 AuthRequest authRequest = getAuthRequest(); // 2. 生成授权地址(包含随机 State,防止 CSRF 攻击) String googleAuthUrl = authRequest.authorize(AuthStateUtils.createState()); // 3. 重定向至 Google 授权页 response.sendRedirect(googleAuthUrl); } /** * 封装 Google 授权配置(抽离为工具方法,便于复用) * @return AuthRequest 授权请求对象 */ private AuthRequest getAuthRequest() { return new AuthGoogleRequest(AuthConfig.builder() // 1. 填入 Google Cloud 配置的 Client ID .clientId("你的 Client ID") // 2. 填入 Google Cloud 配置的 Client Secret .clientSecret("你的 Client Secret") // 3. 填入 Google Cloud 配置的回调地址(必须一致!) .redirectUri("http://localhost:8080/google-login-callback") // 4. (可选)国内环境配置代理(Google 服务需科学上网) .httpConfig(HttpConfig.builder() .timeout(15000) // 超时时间:15秒 .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7897))) // 本地代理地址 .build()) .build()); } }关键说明:AuthStateUtils.createState():生成随机 State 参数,用于防止跨站请求伪造(CSRF)攻击,Google 会在回调时带回该参数,JustAuth 会自动校验;代理配置:国内环境访问 Google 服务需配置代理(如 Clash、V2Ray),需将 host 和 port 替换为本地代理的实际参数;Client ID/Secret/Redirect URI:必须与 Google Cloud 配置完全一致,否则会报「无效客户端」或「回调地址不匹配」错误。3. 第三步:实现「回调处理」接口(/google-login-callback)用户在 Google 授权页完成操作后,Google 会将页面重定向至我们配置的回调地址,并携带 授权码(code)和 State 参数。该接口需完成以下逻辑:接收 Google 回调参数;通过 JustAuth 解析授权码,获取用户信息;结合业务逻辑完成登录(如创建用户、生成自定义 Token);重定向至前端首页(携带自定义 Token)。import me.zhyd.oauth.model.AuthResponse; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @RestController public class AuthController { // 从配置文件读取前端首页地址(如:http://localhost:8081/home?token=) @Value("${login.redirect.page}") private String loginRedirectPage; // 注入业务层服务(处理用户登录逻辑) private final AuthService authService; public AuthController(AuthService authService) { this.authService = authService; } /** * Google 登录回调:处理授权逻辑,完成登录 * @param callback Google 回调参数(包含 code、state 等) * @param response 用于重定向至前端首页 * @throws IOException 重定向异常 */ @GetMapping("/google-login-callback") public void googleLoginCallback(AuthCallback callback, HttpServletResponse response) throws IOException { // 1. 创建授权请求对象(复用之前的配置) AuthRequest authRequest = getAuthRequest(); // 2. 解析回调参数,获取 Google 用户信息(JustAuth 自动处理 code 换 token、拉取用户信息) AuthResponse authResponse = authRequest.login(callback); // 3. 业务层处理:结合 Google 用户信息完成登录(核心逻辑) // 自定义 VO:包含系统生成的 Token、用户信息等 AuthLoginRespVO loginResult = authService.doGoogleLogin(authResponse); // 4. 重定向至前端首页,并携带系统 Token(前端通过 Token 验证登录状态) response.sendRedirect(loginRedirectPage + loginResult.getAccessToken()); } // (省略 getAuthRequest() 方法,与上文一致) }4. 补充:业务层登录逻辑(AuthService)doGoogleLogin 方法是业务核心,需根据项目需求实现(如「用户首次登录自动创建账号」「已存在用户直接关联登录」)。以下是一个简化示例:import me.zhyd.oauth.model.AuthResponse; import me.zhyd.oauth.model.AuthUser; import org.springframework.stereotype.Service; @Service public class AuthService { /** * Google 登录业务逻辑 * @param authResponse JustAuth 返回的 Google 用户信息 * @return AuthLoginRespVO 系统登录结果(含 Token) */ public AuthLoginRespVO doGoogleLogin(AuthResponse authResponse) { // 1. 解析 Google 用户信息(AuthUser 包含用户名、邮箱、头像等) AuthUser googleUser = (AuthUser) authResponse.getData(); String googleOpenId = googleUser.getUuid(); // Google 用户唯一标识(OpenID) String googleEmail = googleUser.getEmail(); // Google 邮箱(需用户授权才能获取) // 2. 数据库查询:判断用户是否已关联 Google 账号 User existingUser = userMapper.selectByGoogleOpenId(googleOpenId); User loginUser; if (existingUser != null) { // 3. 已关联用户:直接更新登录时间等信息 existingUser.setLastLoginTime(new Date()); userMapper.updateById(existingUser); loginUser = existingUser; } else { // 4. 未关联用户:创建新用户(或引导绑定已有账号) loginUser = new User(); loginUser.setGoogleOpenId(googleOpenId); loginUser.setUsername(googleUser.getNickname()); loginUser.setEmail(googleEmail); loginUser.setAvatar(googleUser.getAvatar()); loginUser.setCreateTime(new Date()); userMapper.insert(loginUser); } // 5. 生成系统自定义 Token(如 JWT Token) String systemToken = jwtUtils.generateToken(loginUser.getId()); // 6. 返回登录结果(Token + 用户基本信息) return new AuthLoginRespVO() .setAccessToken(systemToken) .setUsername(loginUser.getUsername()) .setAvatar(loginUser.getAvatar()); } }五、整体流程回顾用户点击前端「Google 登录」按钮,跳转至后端 /google-login 接口;后端生成 Google 授权地址,重定向至 Google 授权页;用户在 Google 页输入账号密码,同意授权后,Google 重定向至后端 /google-login-callback 接口,并携带 code;后端通过 code 向 Google 申请 Token,拉取用户信息;后端结合用户信息完成登录逻辑,生成系统 Token;后端重定向至前端首页,携带 Token;前端解析 Token,验证登录状态,跳转至首页。六、常见问题与注意事项回调地址不匹配:检查 Google Cloud 配置的 Redirect URI 与后端 redirectUri 是否完全一致(包括协议、域名、端口);国内环境无法访问 Google:需配置代理(如上文的 httpConfig),确保后端服务能正常调用 Google 授权接口;用户信息无法获取:需在 Google Cloud「OAuth 同意屏幕」中添加「邮箱」「个人资料」等权限范围;State 验证失败:JustAuth 会自动校验 State,若报错需检查是否在授权过程中修改了 State 参数;Client Secret 泄露风险:Client Secret 需存储在后端配置文件(如 application.yml),切勿暴露在前端代码中。七、总结本文基于 OAuth 2.0 协议和 JustAuth 框架,实现了 Google 账号单点登录的完整流程,核心在于「Google 平台配置」「授权引导」「回调处理」三个环节。JustAuth 框架极大简化了 OAuth 协议的底层实现,让开发者可以专注于业务逻辑(如用户关联、Token 生成)。实际项目中,还可根据需求扩展功能,如「账号绑定」「登录日志记录」「Token 过期刷新」等。希望本文能为你的第三方登录开发提供参考!
2025年09月19日
822 阅读
0 评论
2 点赞
2025-09-09
微信小程序实现页面返回前确认弹窗:兼容左上角返回与右滑返回
在小程序开发中,经常会遇到这样的场景:用户在填写表单、编辑数据等页面操作时,若误触左上角返回按钮或右滑返回,可能导致未保存的数据丢失。本文将详细介绍如何基于微信小程序原生组件 page-container,实现页面离开前的确认弹窗功能,确保用户操作安全。一、功能核心:为什么选择 page-container?实现返回确认的关键在于拦截页面的返回行为,而微信小程序原生组件 page-container 提供了 bindbeforeleave 事件,能完美监听以下两种返回场景:左上角原生返回按钮点击右滑手势返回(包括安卓物理返回键)相比自定义导航栏 + 拦截路由的方案,page-container 无需修改页面路由结构,且能原生兼容所有返回方式,稳定性更高。官方文档参考:page-container | 微信开放文档二、实现步骤:三步完成返回确认1. 页面结构:嵌套 page-container 与条件渲染核心思路是用 wx:if 控制页面整体显示 / 隐藏,内部嵌套 page-container 组件拦截返回事件,具体结构如下:<!-- 最外层用 wx:if 控制页面是否显示 --> <view wx:if="{{isShow}}"> <!-- page-container 核心组件:拦截返回行为 --> <page-container show="{{isShow}}" <!-- 控制组件显示,与页面显示状态同步 --> overlay="{{false}}" <!-- 关闭遮罩层(根据需求选择,true则显示半透明遮罩) --> custom-style="height:100vh;overflow:scroll" <!-- 让组件占满屏幕并支持滚动 --> bindbeforeleave="onBeforeLeave" <!-- 返回前触发的事件 --> > <!-- 你的业务页面内容 --> <view class="page-content"> <view class="form-item"> <label>用户名:</label> <input placeholder="请输入用户名" value="{{username}}" bindinput="handleInput" /> </view> <view class="form-item"> <label>备注:</label> <textarea placeholder="请输入备注" value="{{remark}}" bindinput="handleTextarea" /> </view> <!-- 其他业务组件... --> </view> </page-container> </view>关键属性说明:属性名作用取值建议show控制 page-container 显示 / 隐藏与外层 wx:if 的 isShow 同步,确保状态一致overlay是否显示背景遮罩表单页建议设为 false(避免遮罩遮挡表单),弹窗类页面可设为 truecustom-style自定义组件样式必须设置 height:100vh(占满屏幕),加 overflow:scroll 确保页面内容可滚动bindbeforeleave返回前触发的事件核心事件,用于弹出确认弹窗2. JS 逻辑:处理返回确认与页面状态通过 onBeforeLeave 事件拦截返回行为,弹出确认弹窗,根据用户选择决定是否真正返回;同时用 isShow 控制页面显示状态,避免弹窗消失后页面残留。Page({ /** * 页面初始数据 */ data: { isShow: true, // 控制页面显示/隐藏 username: '', // 示例:表单数据 remark: '' // 示例:表单数据 }, /** * 页面返回前触发:核心逻辑 */ onBeforeLeave() { const that = this; // 先隐藏页面(避免弹窗显示时页面仍可操作) that.setData({ isShow: false }); // 弹出确认弹窗 wx.showModal({ title: '确认退出吗?', content: '当前数据未保存,退出后将丢失', cancelText: '取消', // 自定义按钮文本(可选) confirmText: '确认退出', success: (res) => { if (res.confirm) { // 用户确认退出:返回上一页 wx.navigateBack({ delta: 1, // 返回层级(1=上一页) fail: (err) => { // 异常处理:若返回失败,强制关闭当前页面 wx.redirectTo({ url: '/pages/index/index' }); } }); } else if (res.cancel) { // 用户取消退出:重新显示页面 that.setData({ isShow: true }); } }, fail: (err) => { // 弹窗调用失败时,恢复页面显示 that.setData({ isShow: true }); console.error('弹窗调用失败:', err); } }); }, // 示例:表单输入事件(根据业务需求添加) handleInput(e) { this.setData({ username: e.detail.value }); }, handleTextarea(e) { this.setData({ remark: e.detail.value }); } });逻辑关键点:先隐藏页面:调用 setData({ isShow: false }) 后,页面会暂时消失,避免用户在弹窗显示时继续操作页面;用户选择分支:确认退出:调用 wx.navigateBack() 返回上一页,同时添加 fail 回调处理异常;取消退出:重新设置 isShow: true,恢复页面显示;异常兜底:在 showModal 的 fail 回调中恢复页面显示,避免因弹窗调用失败导致页面永久隐藏。3. 样式优化:确保页面正常显示为避免 page-container 嵌套导致的样式异常,需添加基础样式确保页面占满屏幕且布局正常:/* 页面外层容器:确保无默认边距 */ page { margin: 0; padding: 0; background-color: #f5f5f5; /* 与页面内容背景协调 */ } /* 业务页面内容样式:根据需求调整 */ .page-content { padding: 20rpx; } .form-item { display: flex; flex-direction: column; margin-bottom: 30rpx; } .form-item label { font-size: 28rpx; color: #333; margin-bottom: 10rpx; } .form-item input, .form-item textarea { padding: 20rpx; border: 1rpx solid #eee; border-radius: 8rpx; font-size: 26rpx; }三、效果演示当用户触发返回行为(左上角按钮 / 右滑)时,会弹出如下确认弹窗:点击「取消」:弹窗关闭,页面重新显示,用户可继续操作;点击「确认退出」:弹窗关闭,页面返回上一页,未保存数据提示生效。四、总结本文基于微信小程序原生组件 page-container,通过 “条件渲染 + 事件拦截 + 状态控制” 三步,实现了兼容所有返回方式的确认弹窗功能。核心优势在于:原生兼容:无需自定义导航栏,直接支持左上角返回和右滑返回;状态安全:通过 isShow 控制页面显示,避免弹窗交互时的页面异常;易于扩展:可根据业务需求修改弹窗文案、添加数据保存逻辑(如点击 “确认” 前自动保存草稿)。如果需要进一步优化,可考虑添加 “自动保存草稿” 功能,或根据页面是否有修改过的内容动态判断是否需要弹出确认弹窗(避免无操作时冗余弹窗)。
2025年09月09日
155 阅读
0 评论
1 点赞
1
2
3
...
8