文章一览
发布于 2024-08-06
[开源项目分享]一款前后端分离的最美博客 star 2.3k
566 热度
0 条评论
开源项目
一、项目介绍 大家好,POETIZE已经3年了。 从2021年6月16日开始搭建项目,到2022年8月21日第一个版本发布。期间曾因为换工作而暂停几个月,也因老头环而鸽了几个月。 项目经历了大大小小的更新,每次更新都是在美观度上的精雕细琢,也修复了很多问题,添加了很多功能。 项目提供部分源码供大家参考学习,只需要在Gitee拉取项目,按照Readme启动说明就能直接启动,但需要一定的编程能力。完整版就需要您赞助本项目了。 感谢对POETIZE的支持。 二、开源协议 使用Apache-2.0开源许可协议。 三、技术栈 这是一个 SpringBoot + Vue2 + Vue3 的产物,支持移动端自适应,配有完备的前台和后台管理功能。 前端技术:Vue2(博客系统),Vue3(IM 聊天室系统),Element UI(Vue2),Element-Plus UI(Vue3),Naive UI(Vue3) 后端技术:Java,SpringBoot,MySQL,Mybatis-Plus,t-io,qiniu-java-sdk,spring-boot-starter-mail 网站分两个模块: 博客系统:具有文章,表白墙,图片墙,收藏夹,乐曲,视频播放,留言,友链,时间线,后台管理等功能。 聊天室系统:具有朋友圈(时间线),好友,群等功能。 本网站采用前后端分离进行实现,两个前端项目通过Nginx代理,后端使用Java。 部署网站需要安装Nginx、Java、MySQL,然后打包前后端项目并部署。 文件服务可以使用七牛云,也可以使用服务器。默认使用服务器。 Vue3(IM 聊天室系统)是非必须的。如果部署,则需要依赖博客,然后从博客的“联系我”进入,因为登录模块在博客。 四、项目预览 五、项目地址 博客前端(开源版):gitee.com/littledoke... 聊天室前端(开源版):gitee.com/littledoke... 后端(开源版):gitee.com/littledoke... 博客前端、聊天室前端、后端汇总版(上述三个仓库放在一个仓库里,代码无差别)(开源版):gitee.com/littledoke... 七牛云登录/注册地址(文件服务/CDN):https://s.qiniu.com/Mz6Z32
发布于 2024-08-06
基于html2canvas、jspdf 实现网页打印效果
560 热度
0 条评论
JavaScript
Vue2项目中实现将页面中指定DOM导出为PDF格式。自定义隐藏指定DOM中不想打印出来的内容。 首先是需要用到的依赖:html2canvas、jspdf 首先将导出功能封装成一个按钮组件(当然如果使用频率不高就没必要,反而更方便隐藏不想打印的内容) <template><!-- 导出分析报告 --> <el-button :loading="exporting" :disabled="disabled" @click="exportPic">导出分析报告</el-button> </template> <script> import html2canvas from 'html2canvas'; import JsPDF from 'jspdf' export default { name: 'exportPicButton', props: { operationClass: String, disabled: Boolean }, data() { return { exporting: false } }, methods: { /** 导出分析报告 */ exportPic() { if (!this.operationClass) return; // 防御避免导出时重复点击,导完后才允许点击 if (this.exporting) return; // 显示导出中 this.exporting = true; this.$emit('before-export'); // 截图 this.$nextTick(() => { setTimeout(() => { const dom = document.querySelector(.${this.operationClass}); html2canvas(dom, { height: dom.scrollHeight, windowHeight: dom.scrollHeight, // 解决导出卡顿问题 ignoreElements: e => { if ( e.contains(dom) || dom.contains(e) || e.tagName === 'STYLE' || e.tagName === 'LINK' || e.tagName === 'HEAD' || e.getAttribute('data-html2canvas') != null // header里面的样式不能筛掉 ) { return false; } return true; } }).then(canvas => { let contentWidth = canvas.width; let contentHeight = canvas.height; let pageHeight = contentWidth / 592.28 * 841.89; let leftHeight = contentHeight; let position = 0; let imgHeight = 592.28 / contentWidth * contentHeight; let pageData = canvas.toDataURL('image/jpeg', 1.0); let PDF = new JsPDF('', 'pt', 'a4'); // 边距 const margin = 20; // 调整图片宽度和高度以适应页面并保持比例 let imgWidth = 595.28 - 2 * margin; imgHeight = imgWidth / 595.28 * imgHeight; if (leftHeight < pageHeight) { PDF.addImage(pageData, 'JPEG', margin, margin, imgWidth, imgHeight); } else { while (leftHeight > 0) { PDF.addImage(pageData, 'JPEG', margin, margin + position, imgWidth, imgHeight); leftHeight -= pageHeight; position -= 841.89; if (leftHeight > 0) { PDF.addPage(); } } } // 当前时间 const now = new Date(); const nowStr = ${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}; PDF.save(分析报告${nowStr}.pdf) // 关闭导出中 this.exporting = false; }).catch((err) => { // 显示按钮 this.exporting = false; // todo 处理异常 console.log(err); }).finally(() => { this.$emit('after-export'); }); }, 500) }); } } } </script> 组件只有一个按钮内容,props中接收的operationClass(需要打印DOM的class名称)、disabled(打印/导出按钮是否可用),emit事件有before-export(导出前要做的事情,如影响导出必须为同步)、after-export(导出后要做的事情) 使用方式: <template> <div class="main" v-loading="pageLoading"> <div class="page-title"> <export-pic-button v-show="showHidden" size="small" :disabled="!canExportAnalysisReport" operation-class="content" @before-export="() =>{ showHidden = false; pageLoading = true; }" @after-export="() => { showHidden = true; pageLoading = false; }" </export-pic-button> </div> <div class="content"> 要打印的内容 <div class="hidden" v-show="showHidden">打印时不想展示的内容</div> </div> </div> </template> <script> import ExportPicButton from "@/pages/index/views/zkgl/analyse/components/ExportPicButton.vue"; export default { name: 'export-pic', components: {ExportPicButton}, data() { return { // 是否可导出 canExportAnalysisReport: true, // 按钮是否显示 showHidden: true, // 页面加载中 pageLoading: false, }; }, }; </script> 导出PDF为分析报告2024-08-06.pdf
发布于 2024-02-28
Springboot中使用Redis还在只会存字符串,快来看看各种数据类型的使用技巧!
964 热度
1 条评论
Java
在现代的Web应用程序中,缓存是一项至关重要的技术,以提高性能和减少数据库负载。而 Redis 作为一种高性能的内存数据库,被广泛应用于缓存、会话管理、消息队列等场景。本篇博客将介绍如何在 Spring Boot 中使用 Redis,并详细介绍 Redis 支持的数据类型及其使用方法。 1. 引入 Redis 依赖 首先,在 Spring Boot 项目中引入 Redis 的依赖: <dependency><groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 2. 配置 Redis 连接信息 在 application.properties 或 application.yml 文件中配置 Redis 的连接信息: spring.redis.host=localhost spring.redis.port=6379 spring.redis.password= 3. Redis 数据类型及使用方法 3.1. 字符串(String) 字符串是最简单的 Redis 数据类型,可以存储文本、数字等数据。在 Spring Boot 中,可以使用 StringRedisTemplate 来操作字符串数据: @Resource private StringRedisTemplate stringRedisTemplate; // 设置字符串值 stringRedisTemplate.opsForValue().set("key", "value"); // 获取字符串值 String value = stringRedisTemplate.opsForValue().get("key"); 3.2. 哈希(Hash) 哈希数据类型适合存储对象的属性信息,可以用于存储用户信息、配置信息等。在 Spring Boot 中,可以使用 HashOperations 来操作哈希数据: @Resource private RedisTemplate<String, Object> redisTemplate; HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash(); // 设置哈希字段值 hashOps.put("user:id:123", "name", "Alice"); hashOps.put("user:id:123", "age", "25"); // 获取哈希字段值 String name = (String) hashOps.get("user:id:123", "name"); 3.3. 列表(List) 使用 Redis 的列表数据类型可以实现队列、栈等功能。在 Spring Boot 中,可以通过 RedisTemplate 的 opsForList() 方法来操作列表数据: @Resource private RedisTemplate<String, String> redisTemplate; // 向列表头部插入值 redisTemplate.opsForList().leftPush("myList", "value1"); redisTemplate.opsForList().leftPush("myList", "value2"); // 向列表尾部插入值 redisTemplate.opsForList().rightPush("myList", "value3"); redisTemplate.opsForList().rightPush("myList", "value4"); // 获取列表范围内的值 List<String> values = redisTemplate.opsForList().range("myList", 0, -1); 3.4. 集合(Set) 使用 Redis 的集合数据类型可以实现存储唯一值的需求。在 Spring Boot 中,可以通过 RedisTemplate 的 opsForSet() 方法来操作集合数据: @Resource private RedisTemplate<String, String> redisTemplate; // 向集合添加成员 redisTemplate.opsForSet().add("mySet", "member1"); redisTemplate.opsForSet().add("mySet", "member2"); // 获取集合所有成员 Set<String> members = redisTemplate.opsForSet().members("mySet"); 3.5. 有序集合(Sorted Set) 使用 Redis 的有序集合数据类型可以按照分数(score)排序存储成员。在 Spring Boot 中,可以通过 RedisTemplate 的 opsForZSet() 方法来操作有序集合数据: @Resource private RedisTemplate<String, String> redisTemplate; // 向有序集合添加成员及分数 redisTemplate.opsForZSet().add("mySortedSet", "member1", 10.0); redisTemplate.opsForZSet().add("mySortedSet", "member2", 20.0); // 获取有序集合指定范围的成员 Set<ZSetOperations.TypedTuple<String>> membersWithScores = redisTemplate.opsForZSet().rangeWithScores("mySortedSet", 0, -1); 4. 在选择 Redis 数据类型时,需要根据具体的业务场景和需求来决定使用哪种数据类型。 4. 1. 列表(List) 适用场景: 队列(FIFO):适合实现任务队列、消息队列等。 栈(LIFO):适合实现撤销操作、浏览历史等。 优势: 可以保持元素的插入顺序。 支持从列表两端进行插入和删除操作。 4. 2. 集合(Set) 适用场景: 存储唯一值:适合存储用户标签、点赞数等。 集合运算:如并集、交集、差集等操作。 优势: 元素不重复,可确保数据的唯一性。 支持集合运算,方便对多个集合进行操作。 4. 3. 有序集合(Sorted Set) 适用场景: 排行榜:适合按分数排序存储并展示排名。 时间轴:适合按时间顺序存储事件或消息。 优势: 按照分数(score)排序,方便获取排名信息。 可以实现范围查询,获取指定范围内的成员。 如何选择: 如果需要按顺序存储数据,并且需要支持队列或栈的操作,可以选择列表。 如果需要存储唯一值,并且需要进行集合运算,可以选择集合。 如果需要按分数排序存储数据,并且需要根据排名或范围查询,可以选择有序集合。
发布于 2024-02-26
2023,2022,2021年面试题合集
1374 热度
0 条评论
面试整理
2023年算法知识合集 LeetCode题解 如何高效使用 LeetCode 大家都是如何刷 LeetCode 的? 用动画的形式呈现解LeetCode题目的思路 代码随想录 LABULADONG 的算法网站 宫水三叶的刷题日记 LeetCode题目分类与面试问题整理 汇总各大互联网公司容易考察的高频leetcode题 多种编程语言实现 LeetCode、剑指 Offer、程序员面试金典 《代码随想录》LeetCode 刷题攻略:视频难点剖析,50余张思维导图,支持多语言版本 前端算法进阶指南 和小浩学算法 2023年综合题合集 编程面试大学 前端面试题汇总 2023-05-大厂面试最爱问的Event Loop 2023-持续更新大厂前端面试题 2023.03.28-两万字三月前端面经(含回答) 2023.03.28-更新前端面试问题总结 2023-02-27-2023前端面试题总结 2023-02-27-高级前端必会面试题(边面边更) 2023-02-27-如何才能做好准备好前端面试 2023-02-27-阿里前端二面经典手写面试题汇总 2023-02-23-美团前端常见面试题整理 2023-02-一道面试题带你了解事件流和Event Loop webpack面试题及答案 2023-前端面试必备个人总结(持续更新中) 2023-前端必会手写面试题整理1 2023-前端面试题 2023-前端面试系列-- webpack & Git篇 2023-前端面试系列--网络篇 2023-前端面试系列-- JS 篇 2023-大厂面试题第一部分 2023-高频前端面试题合集之网络篇 2023-前端二面react面试题集锦 2023年小程序面试题合集 2023-07-15-uni-app开发经验总结 2023-07-13-uni-app面试题汇总 2023-03-18-(备忘录)微信小程序面试题 2023-03-12-小程序面试题总结 2023-03-2-uniapp和小程序面试题 2022-09-03-小程序面试题 2022-08-01-小程序面试题 2021-08-06-微信小程序面试总结 尚硅谷微信小程序面试题 Vue 2023-04-29-vue3面试题八股集合 2023-03-13--滴滴前端高频vue面试题(边面边更) 2023-03-01-前端常见vue面试题(必备) 2023-02-28-2023 前端 vue 面试题及答案 2023-02-28-2023前端vue面试题及答案 2023-02-27-前端一面常见vue面试题汇总 2023-02-23-每日一题之Vue的异步更新实现原理是怎样的 2023-02-21-社招前端一面必会vue面试题 2023-02-21-高级前端二面必会vue面试题合集 2023-02-19-美团前端一面高频vue面试题整理 2023-02-19-社招前端经典vue面试题(附答案) 2023-02-14-前端二面经典vue面试题总结 2023-02-14-前端一面必会vue面试题总结 2023-02-13-高级前端二面vue面试题(持续更新中) 2023-02-13-百度前端常考vue面试题(附答案) 2023-02-07-滴滴前端一面必会vue面试题 2023-02-07-面试官:vue2和vue3的区别有哪些 2023-02-06-前端一面高频vue面试题(边面边更) vue高频面试知识点汇总【2023新春版】 2023-前端面试系列-- Vue 篇 2023前端之VUE面试题汇总 React 2023-06-30-前端面试系列之 React 必备知识点 2023-06-04-2023 6月份面试题 React篇 2023-05-23-前端面试:React高频面试题 2023-02-21-社招前端常考react面试题总结 2023-01-React面试题 2023-react面试题大合集 2023-视频-年最新珠峰React全家桶【基础-进阶-项目-源码-淘系-面试题】 2023-一文带你梳理React面试题(2023年版本) 2022年react最全面试题 2021-高频前端面试题汇总之React篇(上) 2021-高频前端面试题汇总之React篇(下) 2019年17道高频React面试题及详解 Go 面试题 2023-04-04-Go必看的进阶面试题详解 2022-10-Go常见面试题【由浅入深】2022版 2022-08-GO必知必会面试题汇总 用Go语言刷力扣专栏 Golang 面试题搜集 2022年 2022-09-13-[面经] 5年前端 - 历时1个月收获7个offer 2022-05-01-2022必会的前端手写面试题 【视频】2022web前端面试题大汇总/web前端面试题详解(持续更新) 宏任务和微任务的理解 2022-最新WEB前端面试题大汇总/全栈面试题集合/Vue面试题/React面试题 2022-高频前端面试题合集之JavaScript篇(上) 2022-高频前端面试题合集之JavaScript篇(中) 2022-高频前端面试题合集之JavaScript篇(下) 2022-TypeScript最新高频面试题指南 2022-最新?前端高频面试题总结(一) 2022-高频前端面试题——CSS篇 2022-年我的面试万字总结(浏览器网络篇) 2022-年我的面试万字总结(CSS篇) 2022-年我的面试万字总结(HTML篇) 2022-年我的面试万字总结(代码篇) 2022-年我的面试万字总结(JS篇上) Vue 2022-09-05-Vue3.0面试题汇总 2022-关于Vue的一些高频面试题总结 2022-年我的面试万字总结(Vue上) 2022-年我的面试万字总结(Vue下) Node NodeJs(前端面试题整合) Node.js面试题,侧重后端应用与对Node核心的理解 Nest.js面试题 2021年 2022-04-高频前端面试题汇总之手写代码篇 原生JS灵魂之问, 请问你能接得住几个?(上) 原生JS灵魂之问(中),检验自己是否真的熟悉JavaScript 原生JS灵魂之问(下), 冲刺🚀进阶最后一公里 浏览器灵魂之问,请问你能接得住几个? FE-Interview 2021-高频前端面试题汇总之JavaScript篇(上) 2021-高频前端面试题汇总之JavaScript篇(下) 2021-高频前端面试题汇总之CSS篇 2021-高频前端面试题汇总之Vue篇(上) 2021-高频前端面试题汇总之Vue篇(下) 2021-高频前端面试题汇总之React篇(上) 2021-高频前端面试题汇总之React篇(下) 更早 「网络安全」面试常见的 web 网络安全知识整理 前端 100 问 必须掌握的面试题-50个Angular面试 转载于 麋鹿一直在路上——2023,2022,2021年面试题合集
发布于 2024-02-26
60个开源项目,提升自己、释放双手
1884 热度
0 条评论
开源项目
60个开源项目 1、Taskover 个人任务管理工具 源码地址:github.com/kesin/tas... Taskover 是基于Rails+VueJs的一款开源个人任务管理工具,通过简单易用的任务归纳分类方式,用来帮助高效地追踪管理各项任务,最大化的提升工作效率。 2、app-version APP 版本管理系统 源码地址:github.com/xtTech/ap... 3、PearProject 轻量级的在线项目/任务协作系统 源码地址:github.com/a54552239... 轻量级的在线项目/任务协作系统,远程办公协作 4、pyteam 任务管理系统 源码地址:github.com/atjiu/pyt... 仿 teambition 使用 koa 开发的一个无刷新的任务管理系统,当然功能没有人家teambition多了,不过我觉得用的最多的也就这些功能了 5、Jpom Java 项目在线管理 源码地址:github.com/dromara/J... 6、MasterLab 基于敏捷开发的项目管理工具 源码地址:github.com/gopeak/ma... MasterLab是一款简单高效、基于敏捷开发的项目管理工具,以事项驱动和敏捷开发最佳实践作为设计思想,同时参考了Jira和Gitlab优秀特性发展而来,适用于互联网团队进行高效协作和敏捷开发,交付极致卓越的产品 7、Naftis Istio 管理面板 源码地址:github.com/jukylin/i... istio-ui用于管理istio配置,目的是减轻运维的配置工作。主要实现:注入,istio配置和模板(还在开发中)等功能 8、Application Manager 程序管理器:gitee.com/mirrors/Ap... 9、task-flow 自定义后台任务流:gitee.com/qzd1989/ta... 10、LibMan 第三方客户端库管理工具:gitee.com/mirrors/Li... 11、KooTeam 在线协作与文档管理系统:gitee.com/sinbo/koot... 12、PageNow 大数据可视化开发平台:gitee.com/jman325_ad... 13、Flowable 流程表单设计:gitee.com/lwj/flowab... 14、考试答题:gitee.com/beautiful-... 15、在线考试:gitee.com/davz/yf-ex... 16、在线培训考试系统:gitee.com/wzhouzhou/... 17、内容管理系统:www.oschina.net/p/ma... 18、文件系统:gitee.com/qiwen-clou... 19、UI框架:gitee.com/TSpecific/... 20、layui框架:gitee.com/pear-admin... 21、一个后台拥有两种前端框架:gitee.com/smallc/Spr... 22、书籍:github.com/justjavac... 23、博客:gitee.com/fehey/grid... 24、智能大屏:gitee.com/beautiful-... 25、大屏:gitee.com/daoke0818/... 26、74套大屏:gitee.com/52itstyle/... 27、智能大屏2:github.com/ggymm/dat... 28、数据可视化报表:gitee.com/jeecg/Jimu... 29、报表设计器:gitee.com/doc_wei01/... 30、飞机大战:www.oschina.net/p/pl... 31、python后台管理:gitee.com/liqianglog... 32、框架: gitee.com/ssssssss-t... 33、微服务框架:gitee.com/geek_qi/cl... 34、分布式框架:gitee.com/shuzheng/z... 35、ruoyi-pro:gitee.com/zhijiantia... 36、快速开发:gitee.com/JeeHuangBi... 37、权限框架:gitee.com/52itstyle/... 38、智慧消防:www.oschina.net/p/tm... 39、自动化测试:github.com/seagull19... 40、大屏:gitee.com/MTrun/big-... 41、拖拽:gitee.com/lowcode-ch... 42、可视化:gitee.com/datagear/d... 43、移动端:gitee.com/was666/as-... 44、redis管理:gitee.com/MaxBill/Re... 45、Api:gitee.com/freakchick... 46、报表:gitee.com/happy3344y... 47、在线表格:gitee.com/mengshukej... 48、在线文档:gitee.com/zmister/Mr... 49、在线文档2:github.com/fantastic... 50、知识库:gitee.com/kyxxjs/km_... 51、微社区:gitee.com/rocboss/pa... 52、在线文档预览:gitee.com/kekingcn/f... 53、低代码平台:gitee.com/godoforang... 54、低代码平台2:gitee.com/MTrun/go-v... 55、问卷调查:gitee.com/wkeyuan 56、问卷:gitee.com/TDuckApp/t... 57、问卷表单:gitee.com/wkeyuan/DW... 58、问卷2:gitee.com/surveyking... 59、低代码开发平台:github.com/zhangdais... 60、数据可视化服务平台解决方案 ——Davinci:github.com/edp963/da...
发布于 2023-12-07
Element UI 级联选择器 el-cascader 实现懒加载和搜索功能
2110 热度
0 条评论
Vue
当代的 Web 应用程序通常需要提供高度灵活和易用的用户界面,以满足用户对数据选择和搜索的需求。在前端开发中,级联选择器(Cascader)是一种常见的组件,可以帮助用户从多层级的数据中进行选择。 Element UI 是一个流行的 Vue.js 组件库,提供了丰富的 UI 组件,其中包括了 <el-cascader> 组件。该组件可以实现级联选择功能,但默认情况下不支持懒加载和搜索的结合。在本篇博客中,我们将重点介绍如何结合懒加载和搜索来增强 <el-cascader> 的功能。 先看效果 基础功能:级联、懒加载!、搜索 后端代码 Controller.java /** 获取地区信息列表 @param pxRegion 地区信息 @return 地区信息列表 / @GetMapping(value = "/getRegionList") public AjaxResult getRegionList(PxRegion pxRegion) { return AjaxResult.success(pxRegionService.getRegionList(pxRegion)); } 地区管理Service接口 /* 地区管理Service接口 @author 裴浩宇 @date 2023-12-06 / public interface IPxRegionService { /* 查询地区管理列表 @param pxRegion 地区管理 @return 地区管理集合 / List<PxRegion> getRegionList(PxRegion pxRegion); } 地区管理Service业务层处理 /* 地区管理Service业务层处理 @author 裴浩宇 @date 2023-12-06 */ @Service public class PxRegionServiceImpl implements IPxRegionService { @Resource private PxRegionMapper pxRegionMapper; /** 查询地区管理列表 @param pxRegion 地区管理 @return 地区管理 / @Override public List<PxRegion> getRegionList(PxRegion pxRegion) { List<PxRegion> regionList = pxRegionMapper.getRegionList(pxRegion); // 如果名称不为空,说明是模糊查询需要处理数据 if (StringUtils.isNotEmpty(pxRegion.getName())) { // 遍历所有地区 for (PxRegion region : regionList) { if (region.getLb() == 1) { // 市的话默认带出该市的区 // 参数 PxRegion param = new PxRegion(); param.setLb(2L); param.setSsdqdm(region.getId()); // 获取该市下的区 List<PxRegion> children = pxRegionMapper.getRegionList(param); region.setChildren(children); } else if (region.getLb() == 2) { region.setChildren(null); // 区的话 // 参数 PxRegion param = new PxRegion(); // 找到区的所属市 param.setId(region.getSsdqdm()); List<PxRegion> city = pxRegionMapper.getRegionList(param); city.get(0).setChildren(Collections.singletonList(region)); // 将市加入 regionList.add(city.get(0)); regionList.remove(region); } } } return regionList; } } 地区管理Mapper接口 /* 地区管理Mapper接口 @author 裴浩宇 @date 2023-12-06 */ public interface PxRegionMapper { /** 查询地区管理列表 @param pxRegion 地区管理 @return 地区管理集合 */ public List<PxRegion> getRegionList(PxRegion pxRegion); } 地区管理Mapper.xml <?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.pnkx.mapper.PxRegionMapper"> <resultMap type="PxRegion" id="PxRegionResult"> <result property="id" column="id"/> <result property="name" column="name"/> <result property="py" column="py"/> <result property="ssdqdm" column="ssdqdm"/> <result property="lb" column="lb"/> <result property="yb" column="yb"/> <result property="jlzt" column="jlzt"/> </resultMap> <sql id="selectPxRegionVo"> select id, name, py, ssdqdm, lb, yb, jlzt from yy_dq_ybdmk </sql> <select id="getRegionList" parameterType="PxRegion" resultMap="PxRegionResult"> <include refid="selectPxRegionVo"/> <where> <if test="id != null "> and id = #{id} </if> <if test="name != null and name != ''"> and name like concat('%', #{name}, '%') </if> <if test="py != null and py != ''"> and py = #{py} </if> <if test="ssdqdm != null "> and ssdqdm = #{ssdqdm} </if> <if test="lb != null "> and lb = #{lb} </if> <if test="yb != null "> and yb = #{yb} </if> <if test="jlzt != null "> and jlzt = #{jlzt} </if> </where> </select> </mapper> 前端代码 Region组件 <template> <div class="cascader"> <el-cascader v-model="selectedValues" :options="options" @change="handleChange" :before-filter="handleFilter" :props="props" separator=" - " filterable clearable placeholder="请选择省市区" /> </div> </template> <script> import {getRegionList} from "@/api/px/blog/region"; export default { name: "Region", props: { address: { type: Array, default: "" } }, watch: { address: { async handler() { // 获取省列表 await this.getRegionList(0); // 获取市列表 if (this.address[0]) await this.getRegionList(1, this.address[0]); // 获取区列表 if (this.address[1]) await this.getRegionList(2, this.address[1]); // 赋值 this.selectedValues = [ Number(this.address[0]), Number(this.address[1]), Number(this.address[2]), ]; }, immediate: true, deep: true }, }, data() { const _this = this; return { // 选中的省市区值 selectedValues: [], // 数据源,省市区的JSON数据 options: [], // 映射关系 props: { // value对应属性 value: "id", // label对应属性 label: "name", // 开启懒加载 lazy: true, lazyLoad: function (node, resolve) { const {level} = node; getRegionList({ lb: level, ssdqdm: node.value }).then(res => { resolve(res.data.filter(item => { if (level === 0) { // 判断是否已存在省 return !_this.options?.find(province => province.id === item.id) } else if (level === 1) { // 判断是否已存在市 return !_this.options.find(province => province.id === item.ssdqdm)?.children.find(city => city.id === item.id) } else if (level === 2) { // 找到省 const theProvince = _this.options.find(province => province.children.find(city => city.id === item.ssdqdm)); // 判断是否已存在区 return !theProvince?.children.find(city => city.id === item.ssdqdm)?.children.find(area => area.id === item.id) } }).map(item => { return { ...item, children: [], leaf: level >= 2 } })); }) } } }; }, methods: { /** 搜索 @param searchValue / async handleFilter(searchValue) { if (searchValue) { await this.getRegionList(undefined, undefined, searchValue); this.selectedValues = Number(searchValue); } }, /* 选择省市区 @param value / handleChange(value) { this.$emit("update:address", value); }, /* 获取地区列表 @param level @param parentCode @param name */ async getRegionList(level, parentCode, name) { await getRegionList({ lb: level, ssdqdm: parentCode, name }).then(res => { res.data.forEach(item => { if (item.lb === 0) { // 省 this.options.find(province => province.id === item.id) || this.options.push(item); } else if (item.lb === 1) { // 市 this.options.find(province => province.id === item.ssdqdm)?.children?.find(city => city.id === item.id) || this.options.find(province => province.id === item.ssdqdm).children.push({ ...item, children: item.children.map(area => { return { ...area, leaf: true } }) }); } else if (item.lb === 2) { // 区 // 找到省 const theProvince = this.options.find(province => province.children.find(city => city.id === item.ssdqdm)); // 找到市放入区 theProvince.children.find(city => city.id === item.ssdqdm)?.children?.find(area => area.id === item.id) || theProvince.children.find(city => city.id === item.ssdqdm)?.children.push({ ...item, leaf: true }); } }) }) } } } </script> 使用 <template> <region :address.sync="address"/> </template> <script> import Region from "@/components/Region/index.vue"; export default { components: { Region }, data() { return { // 地址 address: [140000, 140400, 140406] } }, watch: { address(val) { this.$message.success('选中: ' + val.join('-')); } } } </script> 在上述代码中,接口大致有三个参数:level(层级)、parentCode(父级编号)、name(名称用于模糊查询) 前端组件在初始时回显选中的地区,改变时触发emit事件使父组件修改,并且支持懒加载和搜索。 源码下载 点击下载源码
发布于 2023-12-01
Java 23种设计模式——组合模式(Composite Pattern)
1936 热度
0 条评论
Java
什么是组合模式? 组合模式是一种结构型设计模式,它允许我们将对象组织成树状结构,并以统一的方式处理这些对象。组合模式通过将对象分为两个主要角色来实现这一目标:叶节点和容器节点。叶节点表示树结构中的最底层对象,而容器节点则表示由叶节点或其他容器节点组成的对象。 组合模式的结构 组合模式的结构包含以下几个核心组件: Component(组件):定义叶节点和容器节点的共同接口,可以在该接口上定义默认行为和访问子节点的方法。 Leaf(叶节点):表示树结构中的最底层对象,没有子节点。 Composite(容器节点):由叶节点或其他容器节点组成的对象,可以包含子节点,并实现在Component接口中定义的方法。 下面是一个简化的类图,展示了组合模式的结构: +------------------+| Component | +------------------+ | +operation() | | +add(Component) | | +remove(Component) | | +getChild(int) | +------------------+ / / / / / +--------------+ +--------------+ | Leaf | | Composite | +--------------+ +--------------+ | +operation() | | +operation() | +--------------+ +--------------+ 组合模式的应用场景 组合模式适用于以下情况: 当你希望以统一的方式处理对象集合时,可以使用组合模式。这种方式使得客户端可以将单个对象和组合对象一视同仁。 当你有一个对象树,且对象之间具有层次关系时,组合模式可以帮助你更好地管理和操作这些对象。 当你希望添加或移除树中的对象时,组合模式能够提供一种一致的方式来执行这些操作。 示例代码 shapes shapes/Shape.java: 通用形状接口 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; public interface Shape { int getX(); int getY(); int getWidth(); int getHeight(); void move(int x, int y); boolean isInsideBounds(int x, int y); void select(); void unSelect(); boolean isSelected(); void paint(Graphics graphics); } shapes/BaseShape.java: 提供基本功能的抽象形状 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; abstract class BaseShape implements Shape { public int x; public int y; public Color color; private boolean selected = false; BaseShape(int x, int y, Color color) { this.x = x; this.y = y; this.color = color; } @Override public int getX() { return x; } @Override public int getY() { return y; } @Override public int getWidth() { return 0; } @Override public int getHeight() { return 0; } @Override public void move(int x, int y) { this.x += x; this.y += y; } @Override public boolean isInsideBounds(int x, int y) { return x > getX() && x < (getX() + getWidth()) && y > getY() && y < (getY() + getHeight()); } @Override public void select() { selected = true; } @Override public void unSelect() { selected = false; } @Override public boolean isSelected() { return selected; } void enableSelectionStyle(Graphics graphics) { graphics.setColor(Color.LIGHT_GRAY); Graphics2D g2 = (Graphics2D) graphics; float[] dash1 = {2.0f}; g2.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 2.0f, dash1, 0.0f)); } void disableSelectionStyle(Graphics graphics) { graphics.setColor(color); Graphics2D g2 = (Graphics2D) graphics; g2.setStroke(new BasicStroke()); } @Override public void paint(Graphics graphics) { if (isSelected()) { enableSelectionStyle(graphics); } else { disableSelectionStyle(graphics); } // ... } } shapes/Dot.java: 点 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; public class Dot extends BaseShape { private final int DOT_SIZE = 3; public Dot(int x, int y, Color color) { super(x, y, color); } @Override public int getWidth() { return DOT_SIZE; } @Override public int getHeight() { return DOT_SIZE; } @Override public void paint(Graphics graphics) { super.paint(graphics); graphics.fillRect(x - 1, y - 1, getWidth(), getHeight()); } } shapes/Circle.java: 圆形 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; public class Circle extends BaseShape { public int radius; public Circle(int x, int y, int radius, Color color) { super(x, y, color); this.radius = radius; } @Override public int getWidth() { return radius * 2; } @Override public int getHeight() { return radius * 2; } public void paint(Graphics graphics) { super.paint(graphics); graphics.drawOval(x, y, getWidth() - 1, getHeight() - 1); } } shapes/Rectangle.java: 三角形 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; public class Rectangle extends BaseShape { public int width; public int height; public Rectangle(int x, int y, int width, int height, Color color) { super(x, y, color); this.width = width; this.height = height; } @Override public int getWidth() { return width; } @Override public int getHeight() { return height; } @Override public void paint(Graphics graphics) { super.paint(graphics); graphics.drawRect(x, y, getWidth() - 1, getHeight() - 1); } } shapes/CompoundShape.java: 由其他形状对象组成的复合形状 package com.pnkx.designpatterns.composite.shapes; import java.awt.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class CompoundShape extends BaseShape { protected List<Shape> children = new ArrayList<>(); public CompoundShape(Shape... components) { super(0, 0, Color.BLACK); add(components); } public void add(Shape component) { children.add(component); } public void add(Shape... components) { children.addAll(Arrays.asList(components)); } public void remove(Shape child) { children.remove(child); } public void remove(Shape... components) { children.removeAll(Arrays.asList(components)); } public void clear() { children.clear(); } @Override public int getX() { if (children.size() == 0) { return 0; } int x = children.get(0).getX(); for (Shape child : children) { if (child.getX() < x) { x = child.getX(); } } return x; } @Override public int getY() { if (children.size() == 0) { return 0; } int y = children.get(0).getY(); for (Shape child : children) { if (child.getY() < y) { y = child.getY(); } } return y; } @Override public int getWidth() { int maxWidth = 0; int x = getX(); for (Shape child : children) { int childsRelativeX = child.getX() - x; int childWidth = childsRelativeX + child.getWidth(); if (childWidth > maxWidth) { maxWidth = childWidth; } } return maxWidth; } @Override public int getHeight() { int maxHeight = 0; int y = getY(); for (Shape child : children) { int childsRelativeY = child.getY() - y; int childHeight = childsRelativeY + child.getHeight(); if (childHeight > maxHeight) { maxHeight = childHeight; } } return maxHeight; } @Override public void move(int x, int y) { for (Shape child : children) { child.move(x, y); } } @Override public boolean isInsideBounds(int x, int y) { for (Shape child : children) { if (child.isInsideBounds(x, y)) { return true; } } return false; } @Override public void unSelect() { super.unSelect(); for (Shape child : children) { child.unSelect(); } } public boolean selectChildAt(int x, int y) { for (Shape child : children) { if (child.isInsideBounds(x, y)) { child.select(); return true; } } return false; } @Override public void paint(Graphics graphics) { if (isSelected()) { enableSelectionStyle(graphics); graphics.drawRect(getX() - 1, getY() - 1, getWidth() + 1, getHeight() + 1); disableSelectionStyle(graphics); } for (Shape child : children) { child.paint(graphics); } } } editor editor/ImageEditor.java: 形状编辑器 package com.pnkx.designpatterns.composite.editor; import com.pnkx.designpatterns.composite.shapes.CompoundShape; import com.pnkx.designpatterns.composite.shapes.Shape; import javax.swing.; import javax.swing.border.Border; import java.awt.; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; public class ImageEditor { private EditorCanvas canvas; private CompoundShape allShapes = new CompoundShape(); public ImageEditor() { canvas = new EditorCanvas(); } public void loadShapes(Shape... shapes) { allShapes.clear(); allShapes.add(shapes); canvas.refresh(); } private class EditorCanvas extends Canvas { JFrame frame; private static final int PADDING = 10; EditorCanvas() { createFrame(); refresh(); addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { allShapes.unSelect(); allShapes.selectChildAt(e.getX(), e.getY()); e.getComponent().repaint(); } }); } void createFrame() { frame = new JFrame(); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.setLocationRelativeTo(null); JPanel contentPanel = new JPanel(); Border padding = BorderFactory.createEmptyBorder(PADDING, PADDING, PADDING, PADDING); contentPanel.setBorder(padding); frame.setContentPane(contentPanel); frame.add(this); frame.setVisible(true); frame.getContentPane().setBackground(Color.LIGHT_GRAY); } public int getWidth() { return allShapes.getX() + allShapes.getWidth() + PADDING; } public int getHeight() { return allShapes.getY() + allShapes.getHeight() + PADDING; } void refresh() { this.setSize(getWidth(), getHeight()); frame.pack(); } public void paint(Graphics graphics) { allShapes.paint(graphics); } } } Main.java: 客户端代码 package com.pnkx.designpatterns.composite; import com.pnkx.designpatterns.composite.editor.ImageEditor; import com.pnkx.designpatterns.composite.shapes.Circle; import com.pnkx.designpatterns.composite.shapes.CompoundShape; import com.pnkx.designpatterns.composite.shapes.Dot; import com.pnkx.designpatterns.composite.shapes.Rectangle; import java.awt.*; public class Main { public static void main(String[] args) { ImageEditor editor = new ImageEditor(); editor.loadShapes( new Circle(10, 10, 10, Color.BLUE), new CompoundShape( new Circle(110, 110, 50, Color.RED), new Dot(160, 160, Color.RED) ), new CompoundShape( new Rectangle(250, 250, 100, 100, Color.GREEN), new Dot(240, 240, Color.GREEN), new Dot(240, 360, Color.GREEN), new Dot(360, 360, Color.GREEN), new Dot(360, 240, Color.GREEN) ) ); } } 执行结果 总结 组合模式是一种强大且灵活的设计模式,它允许我们以统一的方式处理对象集合,并将对象组织成树状结构。通过定义共同接口和使用叶节点和容器节点,组合模式使得处理复杂对象结构变得简单而一致。在开发具有层次结构的系统时,组合模式是一个有用的工具。 源码 组合模式源码下载 Java 23种设计模式(含源码)
发布于 2023-11-23
Java 23种设计模式——适配器模式(Adapter)
2068 热度
0 条评论
Java
适配器模式是一种常见的设计模式,它可以用来将一个类的接口转换成客户希望的另外一个接口,从而让原本由于接口不兼容而不能一起工作的类能够协同工作。在Java中,适配器模式通常涉及三个角色:目标接口、适配器和被适配者。 目标接口 目标接口是客户端所期望使用的接口,也可以是一个抽象类。它定义了客户端需要调用的方法,但是这些方法的参数类型或返回值类型可能与原有的接口不符。目标接口的存在是为了让客户端可以通过统一的接口来访问不同的子系统,而不需要关注具体的实现细节。 下面是一个示例代码,演示了如何定义一个目标接口: public interface Target {void request(); } 被适配者 被适配者是需要被适配的类,它定义了客户端不关心的接口。被适配者通常是一个已经存在的类,它的接口与目标接口不兼容,需要通过适配器进行转换。被适配者的存在是为了提供一些功能,但是其接口与目标接口不匹配,无法直接使用。 以下是一个示例代码,演示了如何定义一个被适配者类: public class Adaptee { public void specificRequest() { System.out.println("Adaptee's specific request"); } } 适配器 适配器是实现了目标接口的类,并且包装了一个需要适配的类的实例,以便将其接口转换成客户端所期望的接口。适配器通常包含一个被适配者对象的引用,在目标接口方法中调用被适配者对象的方法,并将其返回值转换成客户端期望的类型。 以下是一个示例代码,演示了如何定义一个适配器类: public class Adapter implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } public void request() { adaptee.specificRequest(); } } 在这个示例中,Adapter 类实现了 Target 接口,并且包装了 Adaptee 的实例。在 request 方法中调用了 Adaptee 的 specificRequest 方法。 客户端代码 客户端代码需要创建一个适配器对象,并且通过适配器对象来调用被适配者的方法。客户端不需要知道适配器和被适配者之间的具体关系,只需要知道目标接口即可。 以下是一个示例代码,演示了如何在客户端代码中使用适配器模式: public class Main { public static void main(String[] args) { Adaptee adaptee = new Adaptee(); Target adapter = new Adapter(adaptee); adapter.request(); } } 在这个示例中,客户端首先创建一个 Adaptee 对象,然后创建一个适配器对象,并将 Adaptee 对象作为参数传递给适配器。最后,客户端通过适配器来调用目标接口的方法。 总结 适配器模式是一种非常有用的设计模式,它可以帮助我们解决接口不兼容的问题。通过使用适配器模式,我们可以将一个类的接口转换成客户端所期望的另外一个接口,从而让原本由于接口不兼容而不能一起工作的类能够协同工作。在Java中,实现适配器模式通常需要定义目标接口、被适配者和适配器三个角色。 Java 23种设计模式(含源码)
发布于 2023-11-22
SSR、SSG和 SPA 是什么,有什么优缺点?
1741 热度
0 条评论
JavaScript
当谈论 Web 应用程序开发的时候,你可能会听到一些术语,如 SSR、SPA 和 SSG。这些术语代表了不同的开发模式,每种模式都有其独特的优点和适用场景。在本文中,我们将深入探讨 SSR、SPA 和 SSG,并比较它们之间的异同。 SSR(Server-Side Rendering,服务器端渲染 SSR 是一种将页面在服务器端进行渲染的开发模式。在 SSR 中,服务器会将页面渲染为完整的 HTML 内容,然后将其发送到客户端。这意味着在客户端收到 HTML 页面之前,页面的初始内容已经存在,可以提供更快的首屏加载速度。SSR 还具有良好的 SEO(Search Engine Optimization)性能,因为搜索引擎能够直接读取页面的 HTML 内容。 SSR 的一些优点包括: 更快的首屏加载速度:由于页面的初始内容已在服务器端生成,用户可以更快地看到页面。 良好的 SEO 性能:搜索引擎可以直接读取和索引服务器端生成的完整 HTML 页面。 然而,SSR 也存在一些限制和挑战: 更高的服务器负载:由于服务器需要处理页面的渲染和数据请求,因此需要更强大的服务器性能。 部署复杂度增加:SSR 的部署相对来说更加复杂,需要考虑服务器环境和后端数据交互等问题。 SSG(Static Site Generation,静态网站生成) SSG 是一种在构建时生成所有 HTML 页面的开发模式。在 SSG 中,开发人员在构建过程中将页面内容预先生成为静态 HTML 文件,并将其提供给服务器或托管在 CDN 上。当用户请求页面时,服务器直接返回相应的静态 HTML 文件,无需动态生成页面。这可以提供快速的加载速度、低成本和良好的 SEO 性能。 SSG 的一些优点包括: 快速的加载速度:由于页面已经预先生成为静态 HTML 文件,无需动态生成,因此可以提供快速的加载速度。 低成本:由于不需要动态服务器渲染,托管静态文件的成本较低。 良好的 SEO 性能:静态 HTML 页面对搜索引擎更友好,有利于索引和排名。 然而,SSG 也存在一些限制和挑战: 对于动态内容的支持较差:由于页面在构建时生成,并且不会根据用户的请求动态更新,因此对于某些动态内容(如用户生成内容)的支持较差。 更新内容需要重新构建:当需要更新页面内容时,需要重新构建整个应用程序并重新部署。 SPA(Single-Page Application,单页应用) SPA 是一种在客户端进行渲染的开发模式。在 SPA 中,页面的内容和路由控制通过 JavaScript 在客户端进行管理。SPA 的主要优点是提供了更好的用户体验和交互性。由于只需要加载一次页面,之后的页面切换都是通过异步加载数据和更新视图实现的,用户可以快速地浏览页面,无需频繁地与服务器进行交互。 SPA 的一些优点包括: 更好的用户体验:页面切换快速,无需等待服务器返回完整的 HTML 页面。 丰富的交互性:SPA 可以使用前端框架(如 Vue.js 或 React)来处理用户交互,实现复杂的功能和动画效果。 然而,SPA 也存在一些限制和挑战: 更多的前端资源:由于整个应用程序的逻辑和渲染都在客户端完成,因此需要下载更多的前端资源(如 JavaScript、CSS 和图像),可能导致页面加载速度变慢。 SEO 不友好:由于搜索引擎通常只读取和索引初始 HTML 页面,SPA 对于搜索引擎的抓取和索引性能较差。这需要额外的工作来实现服务端渲染或预渲染以改善 SEO。 综上所述,SSR、SPA 和 SSG 是三种不同的 Web 应用程序开发模式,每种模式都有其独特的优点和适用场景。选择合适的模式取决于您的应用程序需求和目标。如果您追求更好的首屏加载速度和 SEO 性能,可以选择 SSR 或 SSG;如果您注重用户体验和交互性,可以选择 SPA。在实际开发中,您也可以根据具体情况结合使用这些模式来达到最佳效果。
发布于 2023-11-15
JavaScript 的事件循环机制
3241 热度
1 条评论
JavaScript
JavaScript 的事件循环机制(Event Loop)是一种管理和调度异步任务执行的机制,它确保 JavaScript 代码能够以非阻塞的方式执行。 JavaScript 运行时包含以下几个重要的组成部分: 调用栈(Call Stack):用于存储执行上下文(函数调用)的栈结构。当执行一个函数时,将其执行上下文推入调用栈,函数执行完成后将其弹出。调用栈是单线程执行的,同一时间只能执行一个任务。 消息队列(Message Queue):用于存储待执行的消息(任务)。每个消息都与一个或多个回调函数相关联。当异步任务完成后,会将其对应的回调函数推入消息队列。 事件循环(Event Loop):负责监听调用栈和消息队列,并决定何时将消息队列中的任务推入调用栈执行。事件循环不断地检查调用栈和消息队列的状态,并根据一定的规则进行调度。 事件循环的基本流程如下: 执行同步任务:从全局上下文开始,按顺序执行同步任务,直到遇到第一个异步任务。 处理异步任务:当遇到异步任务时,会将其挂起并注册回调函数。异步任务可以是定时器、网络请求、事件监听等。 异步任务完成后:将其对应的回调函数推入消息队列。 判断调用栈是否为空:当调用栈为空时,事件循环会检查消息队列。 执行回调函数:将消息队列中的第一个任务(回调函数)推入调用栈执行。 重复上述过程:循环执行上述流程,保证异步任务得到处理并执行回调函数。 需要注意的是,事件循环机制中还存在微任务(Microtask)和宏任务(Macrotask)的概念,它们影响着异步任务的执行顺序和优先级。微任务具有比宏任务更高的优先级,会在当前宏任务执行完毕后立即执行。 通过事件循环机制,JavaScript 可以处理异步操作,并确保代码执行的非阻塞性,提供了一种灵活的方式来处理异步任务。 微任务(Microtask)和宏任务(Macrotask)都是在事件循环中用于处理异步操作的机制,它们之间有一些重要的区别。 微任务(Microtask): 执行时机:微任务会在当前宏任务执行完成后立即执行。也就是说,当一个宏任务执行完毕后,在开始下一个宏任务之前,会先检查是否存在微任务队列,如果有,则依次执行微任务队列中的所有任务。 优先级:微任务具有比宏任务更高的优先级。在事件循环中,微任务总是优先于宏任务执行,即使它们是在同一个事件循环阶段中产生的。 触发方式:常见的触发微任务的方式包括Promise的resolve/reject、MutationObserver以及Node.js中的process.nextTick等。 示例应用:微任务适合处理一些优先级较高、需要尽快执行的任务,例如处理状态更新、执行DOM操作后的回调等。 宏任务(Macrotask): 执行时机:宏任务会在当前事件循环的下一个阶段执行。在事件循环中,每个宏任务之间会有一个渲染帧的间隔,这样可以保证浏览器在执行宏任务时能够进行页面渲染和响应用户交互。 优先级:宏任务的优先级相对较低,在事件循环中会在微任务之后执行。 触发方式:常见的触发宏任务的方式包括setTimeout、setInterval、I/O操作、UI交互事件(如点击、滚动)等。 示例应用:宏任务适合处理一些耗时较长、优先级较低的任务,例如网络请求、定时器回调、用户交互事件回调等。 需要注意的是,微任务和宏任务的执行顺序是固定的:一个宏任务执行完毕后,会先检查并执行所有的微任务,然后再执行下一个宏任务。这种机制保证了微任务的即时性和优先级。 在实际开发中,合理使用微任务和宏任务可以提高代码的性能和响应能力。微任务适合处理一些重要的、需要立即执行的任务,而宏任务则适合处理一些延时较长或者对实时性要求不高的任务。 事件循环经典案例 参考资料 什么是Event Loop?