-
2048 游戏是一个基于网格的数字益智游戏,玩家需要通过滑动相同的数字来合并它们,并最终得到一个值为 2048 的方块。以下是分别用Vue和Java来实现的 2048 游戏,包含运行效果。 1、Vue实现 首先,创建一个名为Game.vue的 Vue 单文件组件,代码如下: <template> <div class="game-container"> <div class="grid"> <div v-for="(row, rowIndex) in board" :key="rowIndex" class="cell"> <div v-if="row.length"> <div v-for="(cell, colIndex) in row" :key="colIndex" :class="{ 'highlight': cell === current }"> {{ cell }} </div> </div> </div> </div> <div class="score"> <p>得分:{{ score }}</p> </div> <button @click="newGame">重新开始</button> </div> </template> <script> export default { data() { return { board: [ [1, 1, 2, 2], [3, 3], [4, 4], [4, 4], [2, 2], [1, 1, 3, 3], [2, 2], [4, 4], ], current: null, score: 0, }; }, methods: { move(direction) { if (direction === 'left' && this.current && this.current.leftCell) { this.current.leftCell = this.current.leftCell.left; if (!this.current.leftCell) { this.current = null; } } else if (direction === 'right' && this.current && this.current.rightCell) { this.current.rightCell = this.current.rightCell.right; if (!this.current.rightCell) { this.current = null; } } }, newGame() { this.board = [ [Math.floor(Math.random() * 4) + 1, Math.floor(Math.random() * 4) + 1], [Math.floor(Math.random() * 4) + 1, Math.floor(Math.random() * 4) + 1], [Math.floor(Math.random() * 4) + 1, Math.floor(Math.random() * 4) + 1], [Math.floor(Math.random() * 4) + 1, Math.floor(Math.random() * 4) + 1], ]; this.score = 0; this.current = null; }, slide() { if (this.current) { if (this.current.leftCell) { this.move('left'); } else if (this.current.rightCell) { this.move('right'); } } }, }, }; </script> <style scoped> .game-container { width: 100%; max-width: 800px; margin: 0 auto; ———————————————— 版权声明:本文为CSDN博主「Web3&Basketball」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/superdangbo/article/details/132230690
-
中奖结果公示感谢各位小伙伴参与本次活动,本次活动获奖名单如下:请各位获奖的伙伴在10月16日之前点击此处填写收货地址,如逾期未填写视为弃奖。再次感谢各位小伙伴参与本次活动,欢迎关注华为云DTSE Tech Talk 技术直播更多活动~直播简介【直播主题】0基础玩转OpenTiny跨端跨框架组件库,实现一站式前端进阶【直播时间】2023年10月11日 16:30-18:00【直播专家】莫春辉 华为云前端开发 DTSE技术布道师【直播简介】你还在为构建web页面却不知道选哪个组件库而苦恼吗? 你还在为从Vue2项目升级到Vue3的工作量大而惆怅吗? 你还在为不同框架的组件库由于API不同,需要重复开发而薅头发吗? 本期直播将聚焦于OpenTiny跨端、跨框架、跨版本的TinyVue组件库,其中包含了无渲染Renderless设计理念的实现方式,以及分析业务逻辑的思路及方法。让开发者能够通过一套代码在不同版本、不同框架、不同终端中使用,从而提升开发效率,保证用户在不同场景下的极致交互体验。直播链接:cid:link_1活动介绍【互动方式】直播前您可以在本帖留下您疑惑的问题,专家会在直播时为您解答。直播后您可以继续在本帖留言,与专家互动交流。我们会在全部活动结束后对参与互动的用户进行评选。【活动时间】即日起—2023年10月12日【奖励说明】评奖规则:活动1:直播期间在直播间提出与直播内容相关的问题,对专家评选为优质问题的开发者进行奖励。奖品:华为云定制U型按摩枕活动2:在本帖提出与直播内容相关的问题,由专家在所有互动贴中选出最优问题贴的开发者进行奖励。奖品:华为云定制POLO衫更多直播活动直播互动有礼:官网直播间发口令“华为云 DTSE”抽华为云定制棒球帽、填写问卷抽华为云定制长袖卫衣等好礼分享问卷有礼 :邀请5位朋友以上完成问卷即可获得华为云定制飞盘。老观众专属福利:连续报名并观看DTT直播3期以上抽送华为云DTT定制T恤。【注意事项】1、所有参与活动的问题,如发现为复用他人内容,则取消获奖资格。2、为保证您顺利领取活动奖品,请您在活动公示奖项后2个工作日内私信提前填写奖品收货信息,如您没有填写,视为自动放弃奖励。3、活动奖项公示时间截止2023年10月13日,如未反馈邮寄信息视为弃奖。本次活动奖品将于奖项公示后30个工作日内统一发出,请您耐心等待。4、活动期间同类子活动每个ID(同一姓名/电话/收货地址)只能获奖一次,若重复则中奖资格顺延至下一位合格开发者,仅一次顺延。5、如活动奖品出现没有库存的情况,华为云工作人员将会替换等价值的奖品,获奖者不同意此规则视为放弃奖品。6、其他事宜请参考【华为云社区常规活动规则】。
-
1 技术背景 1.1 Vue.js 简介和特点 Vue.js 是一种用于构建用户界面的渐进式框架。它具有以下特点: 易学易用:Vue.js 的 API 设计简单直观,使得开发者可以快速上手。 响应式数据绑定:Vue.js 使用了响应式的数据绑定机制,当数据发生变化时,页面会自动更新。 组件化开发:Vue.js 支持组件化开发,将界面拆分为多个独立可复用的组件,提高代码的可维护性和复用性。 虚拟 DOM:Vue.js 使用虚拟 DOM 技术,在内存中维护一个虚拟的 DOM 树,通过比较新旧 DOM 树的差异,最小化操作真实 DOM 的次数,提升性能。 1.2 PDF.js 库简介和功能概述 PDF.js 是一个由 Mozilla 开发的 JavaScript 库,用于在 Web 上显示 PDF 文件。它具有以下功能: 在浏览器中原生渲染 PDF:PDF.js 可以直接在浏览器中渲染 PDF 文件,无需依赖外部插件或软件。 支持基本的查看和导航功能:PDF.js 提供了一些基本的查看和导航功能,如缩放、翻页、搜索等。 自定义样式和交互:PDF.js 允许开发者通过 API 自定义 PDF 文件的显示样式和交互行为。 跨平台支持:PDF.js 可以在各种现代浏览器和操作系统上运行,包括桌面和移动设备。 1.3 为什么选择 Vue 和 PDF.js 结合实现 PDF 预览功能 结合 Vue 和 PDF.js 实现 PDF 预览功能有以下优势: Vue 提供了响应式数据绑定和组件化开发的特性,可以方便地管理 PDF 预览组件的状态和逻辑。 PDF.js 是一个功能强大且易于使用的 JavaScript 库,提供了原生渲染 PDF 的能力,并且具有自定义样式和交互的灵活性。 Vue 和 PDF.js 都是流行的前端技术,社区支持和文档资源丰富,可以帮助开发者更快速地实现 PDF 预览功能。 结合 Vue 和 PDF.js 还可以充分利用 Vue 的生态系统和插件库,如 Vuex、Vue Router 等,进一步扩展和增强 PDF 预览功能。 2 开发环境准备 在开始使用 Vue.js 和 PDF.js 结合实现 PDF 预览功能之前,你需要准备开发环境。以下是一些步骤来帮助您完成这个过程: 2.1 安装 Node.js 和 Vue CLI 首先,你需要安装 Node.js 和 npm(Node 包管理器)。Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,用于在服务器端运行 JavaScript 代码。npm 是 Node.js 的默认软件包管理器,用于安装和管理项目所需的依赖项。 你可以从 Node.js 官方网站(https://nodejs.org)下载并安装适合你操作系统的最新版本的 Node.js。安装完毕后,打开终端或命令提示符窗口,并输入以下命令来验证 Node.js 和 npm 是否成功安装: node -vnpm -v接下来,你需要全局安装 Vue CLI(Vue Command Line Interface),它是一个用于快速创建 Vue 项目的工具。在终端或命令提示符窗口中运行以下命令进行安装: npm install -g @vue/cli 安装完成后,你可以通过运行以下命令来检查 Vue CLI 是否成功安装: vue --version 2.2 创建 Vue 项目 安装 Vue CLI 后,你可以使用它来创建一个新的 Vue 项目了。在终端或命令提示符窗口中,进入你想要创建项目的目录,并运行以下命令: vue create my-project 这将提示你选择一些配置选项来创建项目你可以使用默认选项,也可以根据需要进行自定义配置。完成配置后,Vue CLI 将下载所需的依赖项并创建一个新的 Vue 项目。 在项目创建完成后,进入项目目录: cd my-project 现在,你已经准备好开始开发了!你可以使用任何喜欢的代码编辑器打开项目文件夹,并按照下一步的指导继续进行 PDF 预览功能的实现。 3 集成 PDF.js 到 Vue 项目 为了将PDF.js集成到Vue项目中,您可以按照以下步骤进行操作: 3.1 下载和引入 PDF.js 库 首先,您需要下载PDF.js库。您可以从官方GitHub仓库(https://github.com/mozilla/pdf.js)下载最新版本的PDF.js。 一旦您下载了PDF.js,将其解压缩并复制到您的Vue项目的文件夹中。然后,在您的Vue项目中创建一个名为pdfjs的文件夹,并将解压缩后的PDF.js文件粘贴到该文件夹中。 接下来,在您的Vue项目中找到public/index.html文件,并在文件的<head>标签内添加以下代码以引入PDF.js库: <script src="./pdfjs/build/pdf.js"></script> 这样就完成了PDF.js库的引入。 3.2 在 Vue 组件中使用<canvas>元素展示 PDF 页面 要在Vue组件中显示PDF页面,您可以使用HTML5的<canvas>元素。在您希望显示PDF的组件模板中,添加一个<canvas>元素作为容器: <template> <div> <canvas ref="pdfCanvas"></canvas> </div></template>这个<canvas>元素将用于渲染PDF页面。 3.3 使用 PDF.js 提供的 API 加载和渲染 PDF 文件 现在,您可以在Vue组件的JavaScript部分编写加载和渲染PDF文件的逻辑。在Vue组件的<script>标签中,添加以下代码: export default { mounted() { this.loadPDF(); }, methods: { async loadPDF() { const pdfUrl = 'path/to/your/pdf/file.pdf'; // 替换为您的PDF文件路径 const loadingTask = window.PDFJS.getDocument(pdfUrl); const pdf = await loadingTask.promise; const canvas = this.$refs.pdfCanvas; const context = canvas.getContext('2d'); const page = await pdf.getPage(1); // 加载第一页 const viewport = page.getViewport({ scale: 1 }); canvas.height = viewport.height; canvas.width = viewport.width; const renderContext = { canvasContext: context, viewport: viewport }; await page.render(renderContext); } }}上述代码首先使用window.PDFJS.getDocument()方法加载PDF文件,并返回一个Promise对象。然后,我们获取<canvas>元素和其上下文(context)。接下来,我们使用pdf.getPage()方法加载PDF的第一页,并通过page.getViewport()方法获取页面的视口信息。 之后,我们设置<canvas>元素的高度和宽度以适应页面视图,并创建一个渲染上下文对象。最后,我们调用page.render()方法将PDF页面渲染到<canvas>元素上。 3.4 实现页面切换和缩放功能 要实现PDF页面的切换和缩放功能,您可以编写一些额外的方法并在模板中绑定相应的事件。 例如,您可以添加两个按钮来实现上一页和下一页的切换功能: <template> <div> <canvas ref="pdfCanvas"></canvas> <button @click="previousPage">Previous Page</button> <button @click="nextPage">Next Page</button> </div></template>然后,在Vue组件的JavaScript部分添加以下代码: export default { data() { return { pdf: null, currentPage: 1 }; }, mounted() { this.loadPDF(); }, methods: { async loadPDF() { // ... }, async previousPage() { if (this.currentPage > 1) { this.currentPage--; await this.renderPage(this.currentPage); } }, async nextPage() { if (this.currentPage < this.pdf.numPages) { this.currentPage++; await this.renderPage(this.currentPage); } }, async renderPage(pageNumber) { const page = await this.pdf.getPage(pageNumber); const viewport = page.getViewport({ scale: 1 }); const canvas = this.$refs.pdfCanvas; const context = canvas.getContext('2d'); canvas.height = viewport.height; canvas.width = viewport.width; const renderContext = { canvasContext: context, viewport: viewport }; await page.render(renderContext); } }}在这个例子中,我们使用data()方法初始化了一个名为currentPage的变量,并将其绑定到模板中。然后,我们编写了previousPage()和nextPage()方法来更新currentPage并调用renderPage()方法重新渲染页面。 通过这种方式,你可以实现简单的页面切换功能。类似地,你还可以编写其他方法来实现缩放、页码跳转等功能。 4 处理 PDF 加载和错误 在 Vue 项目中集成 PDF.js 时,你可以通过以下步骤来处理 PDF 加载和错误: 4.1 显示加载进度条 要显示加载进度条,你可以使用 Vue 框架的组件和状态管理。首先,在你的 Vue 组件中创建一个loading变量来表示 PDF 是否正在加载: data() { return { loading: true, };},然后,在模板中根据loading变量的值来显示或隐藏加载进度条。你可以使用 Vue 的条件渲染指令(v-if)来实现这一点。例如,你可以在模板中添加一个全屏的加载动画组件,并将其与loading变量关联起来: <template> <div> <loading-spinner v-if="loading"></loading-spinner> <!-- 其他内容 --> </div></template>当 PDF 开始加载时,将loading变量设置为true,加载完成后将其设置为false。 4.2 处理加载错误和异常情况 PDF.js 提供了一些 API 来处理加载错误和异常情况。你可以使用这些 API 来捕获并处理加载过程中可能发生的错误。 首先,在 Vue 组件的方法中,使用 PDF.js 的getDocument()函数来加载 PDF 文件。这个函数返回一个 Promise 对象,你可以使用.catch()方法来捕获加载过程中的错误: loadPDF() { PDFJS.getDocument('/path/to/pdf/file.pdf') .then((pdf) => { // 加载成功后的处理逻辑 }) .catch((error) => { // 加载错误时的处理逻辑 });},在catch()方法中,你可以根据具体的错误类型来执行相应的操作。例如,如果加载失败,你可以显示一个错误提示信息: .catch((error) => { console.error('PDF 加载错误:', error); this.showErrorMessage = true;});在模板中,你可以使用条件渲染指令(v-if)来显示错误消息: <template> <div> <div v-if="showErrorMessage" class="error-message">PDF 加载失败,请重试。</div> <!-- 其他内容 --> </div></template>这样,当发生加载错误时,错误消息将被显示出来。 除了捕获加载错误外,你还可以使用 PDF.js 提供的其他 API 来处理异常情况。例如,你可以使用pdf.numPages属性获取 PDF 文件的总页数,并在加载完成后进行一些额外的处理。 5 实现其他功能 5.1 页码控制 要实现页码控制功能,你可以在 Vue 项目中创建一个输入框或下拉列表,用于用户输入或选择所需的页面号码。然后,在用户提交表单或选择页码后,你可以使用 PDF.js 提供的 API 将视图定位到指定的页面。 首先,你需要在 Vue 组件中定义一个变量来存储当前页码,例如currentPage。然后,你可以在模板中创建一个输入框或下拉列表,并绑定它与currentPage变量。当用户更改页码时,currentPage变量会自动更新。 接下来,你需要在 Vue 组件的方法中处理页码变化的逻辑。你可以使用 PDF.js 提供的pdfViewer.scrollPageIntoView()方法将视图滚动到指定的页面。例如: methods: { goToPage() { // 将字符串转换为数字类型 const pageNumber = parseInt(this.currentPage, 10); if (pageNumber >= 1 && pageNumber <= this.totalPages) { pdfViewer.scrollPageIntoView({ pageNumber, }); } },},在上面的代码中,我们首先将用户输入的页码转换为数字类型,并确保其在有效范围内(从 1 到总页面数)。然后,我们使用scrollPageIntoView()方法将视图滚动到指定的页面。 最后,你可以在模板中添加一个按钮或提交表单的事件监听器,以便在用户点击按钮或提交表单时调用goToPage()方法。 5.2 缩略图导航 要实现缩略图导航功能,你可以使用 PDF.js 提供的pdfThumbnailViewer对象来显示缩略图。首先,你需要在 Vue 组件中创建一个元素,用于容纳缩略图。然后,在 Vue 组件的生命周期钩子函数(如mounted)中初始化缩略图,并将其绑定到相应的元素上。 mounted() { const thumbnailContainer = document.getElementById('thumbnail-container'); pdfThumbnailViewer.initialize(thumbnailContainer, pdfDocument);},在上面的代码中,我们首先通过getElementById()方法获取缩略图容器的 DOM 元素。然后,我们使用initialize()方法将缩略图初始化并绑定到容器上。 最后,你可以在模板中添加一个具有唯一 ID 的元素,作为缩略图容器的占位符。 <div id="thumbnail-container"></div>这样,当 Vue 组件被挂载时,缩略图就会自动加载和显示在指定的容器中了。 5.3 文本搜索功能 要实现文本搜索功能,你可以使用 PDF.js 提供的pdfFindController对象来执行文本搜索操作。首先,你需要在 Vue 组件中创建一个输入框,用于用户输入要搜索的关键字。然后,在 Vue 组件的方法中处理搜索逻辑。 首先,你需要定义一个变量来存储用户输入的关键字,例如searchKeyword。然后,在用户提交表单或按下回车键时,你可以使用 PDF.js 提供的pdfFindController.executeCommand()方法执行搜索操作。 methods: { search() { pdfFindController.executeCommand('find', { query: this.searchKeyword, highlightAll: true, }); },},在上面的代码中,我们使用executeCommand()方法执行搜索命令,并传递一个包含查询关键字和是否高亮所有匹配项的配置对象。 最后,你可以在模板中添加一个按钮或提交表单的事件监听器,以便在用户点击按钮或提交表单时调用search()方法。 这样,当用户进行文本搜索时,PDF.js 会自动查找并高亮与关键字匹配的文本内容。 6 优化和性能调优 优化和性能调优是确保应用程序高效运行的重要方面。在实现 PDF 预览功能时,以下是一些优化和性能调优的建议: 6.1 懒加载 PDF 页面 懒加载是指在需要显示页面时才进行加载,而不是一次性加载所有页面。这可以提高初始加载速度并减少资源占用。你可以使用 Vue 的异步组件或按需加载来实现懒加载 PDF 页面。 6.2 缓存已加载的页面 为了避免每次切换页面都重新加载 PDF 文件,你可以将已加载的页面缓存在客户端(如浏览器)中。这样,在用户再次访问相同页面时,可以直接从缓存中获取页面,而不必重新下载和渲染 PDF 文件。 6.3 压缩和优化 PDF 文件大小 PDF 文件的大小对加载时间和性能有很大影响。你可以使用各种工具和技术来压缩和优化 PDF 文件的大小。例如,可以使用 Adobe Acrobat 等专业工具进行优化,删除不必要的元数据、嵌入字体子集、压缩图像等。另外,还可以考虑使用 WebP 格式替代 JPEG 格式来进一步减小文件大小。 通过以上优化和性能调优措施,你可以改善 PDF 预览功能的加载速度和性能,并提供更好的用户体验。 7 测试和排错 7.1 使用测试工具进行功能和性能测试 在开发 Vue 项目中实现 PDF 预览功能后,使用测试工具可以帮助我们验证功能的正确性并评估性能。以下是一些常用的测试工具: Jest:Jest 是一个流行的 JavaScript 测试框架,适用于单元测试和集成测试。你可以编写针对 PDF 预览组件的各种测试用例,并使用 Jest 运行这些测试。 Puppeteer:Puppeteer 是一个 Node.js 库,提供了控制 Headless Chrome 浏览器的 API。你可以使用 Puppeteer 模拟用户与 PDF 预览界面的交互操作,并检查预期的结果是否符合预期。 Cypress:Cypress 是一个端到端的前端测试框架,可以模拟用户在真实浏览器环境下与应用程序进行交互。你可以使用 Cypress 编写自动化测试脚本来测试 PDF 预览功能,并生成详细的测试报告。 7.2 排查和解决常见问题和错误 在开发过程中,可能会遇到一些常见的问题和错误。以下是一些排查和解决问题的建议: 查看浏览器控制台输出:当 PDF 预览功能出现问题时,打开浏览器的开发者工具,查看控制台输出以获取潜在的错误信息。 检查网络请求:确保 PDF 文件正确加载并返回了预期的内容。检查网络请求的状态码、响应头和响应体,以确定是否存在问题。 检查依赖项版本:如果使用了第三方库或插件来实现 PDF 预览功能,请确保所使用的版本与你的项目兼容,并且没有已知的问题或错误。 阅读文档和社区支持:阅读相关库的官方文档,查找常见问题和解决方案。此外,参与开发者社区,寻求帮助和建议。 调试代码:使用调试工具(如 Chrome DevTools)在代码中设置断点,逐步执行代码并观察变量和函数的值,以找出潜在的问题。 缩小范围:如果问题无法解决,尝试缩小问题范围,创建一个简化的示例项目或复制到 CodeSandbox 等在线编辑器上进行测试,以确定问题是源于你的代码还是环境配置。 通过以上方法,你可以更好地排查和解决常见问题和错误,确保 PDF 预览功能的正常运行。 8 总结 在本文中,我们学习了如何在 Vue 环境中实现 PDF 预览功能。我们引导读者完成了项目的依赖安装和配置,并介绍了如何选择和使用适合的 PDF 渲染库。我们展示了如何加载和显示 PDF 文件,添加了导航工具和其他功能,为用户提供了更好的阅读体验。 通过本文的指导,读者可以快速上手并在自己的 Vue 项目中实现 PDF 预览功能。无论你是初学者还是有经验的开发者,本文都为你提供了清晰的步骤和示例代码,帮助你轻松完成任务。 PDF 预览功能可以在各种场景中发挥重要作用,例如在线文档阅读、电子书阅读器等。希望本文能够帮助你添加这一功能,并提升用户体验。 转载自https://www.cnblogs.com/chenyuanrumeng/p/17639410.html
-
最近接手了一个后台管理项目,项目使用了Element-ui组件, 使用了Select 下拉菜单展示并选择内容,使用了cascader做级联城市选择器 看了官方文档我们知道可以使用v-model绑定数据,设置初始值/默认值 <el-form-item label="班级" prop="classId"> <el-select v-model="form.classId" placeholder="请选择班级"> <el-option v-for="(value) of classList" :key="value.classId" :label="value.className" :value="value.classId"></el-option> </el-select> </el-form-item> <el-form-item label="机构地址" prop="district"> <el-cascader style="width: 216px;" v-model="form.district" :options="dataDistrict" @change="handleChange" > </el-cascader> </el-form-item> 问题来了,明明已经设置了v-model但是没有显示,百度发现问题在于数据类型,form.classId的值为 String ,而 el-select列表的 value 为 Number,el-cascader的value是Array,form.district却是JSON字符串,并不全等,导致无法找到对应的项,把数据类型进行转换后就能正常显示啦 ———————————————— 原文链接:https://blog.csdn.net/weixin_43144209/article/details/120891475
-
一、背景 在写项目的过程中,突然发现el-select这个组件,绑定的值没办法显示name,而是直接显示value。在编辑项目的弹框中,要显示项目绑定了哪个数据库(项目外连数据库表主键ID),结果发现直接把关联数据库的ID显示了出来,这与我的设想不一致,因此来找找是什么问题 二、解决 有问题的,有文档的当然第一时间去看文档,一看数据类型是string。 value-key 作为 value 唯一标识的键名,绑定值为对象类型时必填 数据类型string 再去检查项目传值,发现el-option绑定的list中key的类型是string,但v-model绑定的值是int,所以就导致了无法显示正确的名字,而显示主键ID 知道问题解决起来就很简单了,直接parseInt()转一下数据类型 <el-form-item label="归属数据库:" prop="databaseId"> <el-select v-model="projectForm.databaseId" placeholder="归属项目" style="width: 400px"> <el-option v-for="item in databaseList" :key="item.id" :value="parseInt(item.id)" :label="item.name" /> </el-select> </el-form-item> 但是可以的话,还是尽量数据格式一致,如果都是int的话,也是能可以正确显示的,那就无须做额外的处理了 ———————————————— 原文链接:https://blog.csdn.net/weixin_40295575/article/details/124250581
-
前言 // 呵呵, 1024 发一波 基础知识 的库存 缘于一些 小的需求 因为一些原因 可能到这里就不会再往后面设计了 主要分为了两个版本, html + js 的版本 和 vue 的版本 核心的意义是 面向对象的程序设计 不过是 基于了这种常见的小游戏 展现出来了 关于设计 关于 面向对象的程序设计 内容很多很杂 这里 也不想 一一文字描述, 需要自己去领悟了 具体的 代码实现的设计, 这里 也不多说了, 文字描述有限 整个流程大概如下, 其中第一点至关重要 1. 设计各个对象 Game, Snake, SnakeNode, Arena, WallNode, FoodNode 的各个属性, 以及方法, 方法可以留空, 定义好方法名字就行 2. 实现 Arena 以及 墙体节点, 并绘制 Arena 的整个墙体 3. 实现 食物节点 并绘制食物, 需要按照频率闪烁 4. 实现按键操作 贪吃蛇 的方向, 按空格键暂停游戏 5. 实现 贪吃蛇 触碰到食物之后, 吃掉食物, 贪吃蛇 的长度+1 6. 实现 FoodNode 的随机初始化, 以及吃掉食物之后随机初始化 [需要避开墙体 和 贪吃蛇 占用的节点] 7. 实现贪吃蛇 撞到墙 或者 自己 暂停游戏, 整条蛇闪烁, 标志着游戏结束 8. 拆分整个 H5DrawImage.html 按照对象拆分到各自的 js 9. 增加元素 Grass, Water, Steel Grass 的特征为可以穿过, 无任何影响 Water 的特征为到 Water 之后不在向前走, 只能左右调整方向 Steel 的特征和墙一样, 碰到之后游戏结束 10. 增加分数的显示, 更新 11. 写一个简单的自动导航贪吃蛇的 "外挂" 12. 将一些配置提取出来, 比如 墙体的长宽, 所有元素节点的宽高, 贪吃蛇的移动频率, 外部导入 "地图" 等等 另外还有一些想法, 因为种种原因 算了, 不想继续再做了 增加道具, 比如 坦克大战的船, 获得船之后, 再规定的时间内可以穿过水 增加 坦克大战的星星, 获得两个之后, 可以撞开 Steel html + js 版本 主要包含了一个 html 来驱动, 各个对象 js 来处理核心业务 H5DrawImage.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>H5DrawImage.html</title> </head> <body> <canvas id="canvas" width="1500" height="1000"></canvas> <div id="scoreDiv" style="position: absolute; left: 1000px; top:50px" >分数 : 12</div> </body> <script src="./js/jquery.min.js" type="text/javascript"></script> <script src="./js/constants.js" type="text/javascript"></script> <script src="./js/utils.js" type="text/javascript"></script> <script src="./js/food.js" type="text/javascript"></script> <script src="./js/arena.js" type="text/javascript"></script> <script src="./js/snake.js" type="text/javascript"></script> <script src="./js/game.js" type="text/javascript"></script> <script> // $('html').css('font-size', $(window).width() / 10) // --------------------------- game initiator --------------------------- let game = new Game() game.init(canvasCtx) game.draw(canvasCtx) game.run(canvasCtx) // key event register document.onkeydown = function (event) { game.onKeyPressed(event, canvasCtx) } // // auto direction game.auto(canvasCtx, moveIntervalInMs/2); // update score panel setInterval(function() { let score = game.score() document.getElementById("scoreDiv").innerText = `分数 : ${score}` }, moveIntervalInMs) </script> </html>
-
目录: 1. 监听数据变化的实现原理不同 2. 数据流的不同 3. HoC和mixins 4. 组件通信的区别 5. 模板渲染方式的不同 6. 渲染过程不同 7. 框架本质不同 8. 开发过程中 9. 开发应用时 应用场景: react 和 vue 是我们做前端中必不可少的,一般看公司都在使用什么,你就要用什么,一般规模大的公司都会使用react+ts/react+hook,小公司使用vue2.0多一点,但不是全方面的,那么我们来说说react和vue有什么区别: 1. 监听数据变化的实现原理不同 Vue通过 getter/setter以及一些函数的劫持,能精确知道数据变化 React默认是通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的 VDOM的重新渲染 2. 数据流的不同 Vue1.0中可以实现两种双向绑定:父子组件之间,props可以双向绑定;组件与DOM 之间可以通过v-model双 向绑定 React一直不支持双向绑定,提倡的是单向数据流,称之为onChange/setState()模式 3. HoC和mixins Vue组合不同功能的方式是通过mixin,Vue中组件是一个被包装的函数,并不简单的就是我们定义组件的时候传 入的对象或者函数 React组合不同功能的方式是通过HoC(高阶组件) 4. 组件通信的区别 React 本身并不支持自定义事件,而Vue中子组件向父组件传递消息有两种方式:事件和回调函数,但Vue更倾 向于使用事件 在React中我们都是使用回调函数的,这可能是他们二者最大的区别 5. 模板渲染方式的不同 在表层上,模板的语法不同,React是通过JSX渲染模板;而Vue是通过一种拓展的HTML语法进行渲染 模板的原理不同,React是在组件JS代码中,通过原生JS实现模板中的常见语法,比如插值,条件,循环等,都 是通过JS语法实现的,更加纯粹更加原生。而Vue是在和组件JS代码分离的单独的模板中,通过指令来实现的 6. 渲染过程不同 Vue可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要 重新渲染整个组件树 React在应用的状态被改变时,全部子组件都会重新渲染。通过shouldComponentUpdate这个生命周期方法可 以进行控制,但Vue将此视为默认的优化 7. 框架本质不同 Vue本质是MVVM框架,由MVC发展而来 React是前端组件化框架,由后端组件化发展而来 8. 开发过程中 vue适合开发小型应用 react适合开发大型应用 9. 开发应用时 vue使用uni-app开发app react使用 react-Native ———————————————— 版权声明:本文为CSDN博主「樱颜」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_58160355/article/details/124544957
-
一、UI组件及框架 element - 饿了么出品的Vue2的web UI工具套件 mint-ui - Vue 2的移动UI元素 iview - 基于 Vuejs 的开源 UI 组件库 Keen-UI - 轻量级的基本UI组件合集 vue-material - 通过Vue Material和Vue 2建立精美的app应用 muse-ui - 三端样式一致的响应式 UI 库 vuetify - 为移动而生的Vue JS 2组件框架 vonic - 快速构建移动端单页应用 vue-blu - 帮助你轻松创建web应用 vue-multiselect - Vue.js选择框解决方案 VueCircleMenu - 漂亮的vue圆环菜单 vue-chat - vuejs和vuex及webpack的聊天示例 radon-ui - 快速开发产品的Vue组件库 vue-waterfall - Vue.js的瀑布布局组件 vue-carbon - 基于 vue 开发MD风格的移动端 vue-beauty - 由vue和ant design创建的优美UI组件 bootstrap-vue - 应用于Vuejs2的Twitter的Bootstrap 4组件 vueAdmin - 基于vuejs2和element的简单的管理员模板 vue-ztree - 用 vue 写的树层级组件 vue-tree - vue树视图组件 vue-tabs - 多tab页轻型框架 二、滚动scroll组件 vue-scroller - Vonic UI的功能性组件 vue-mugen-scroll - 无限滚动组件 vue-infinite-loading - VueJS的无限滚动插件 vue-virtual-scroller - 带任意数目数据的顺畅的滚动 vue-infinite-scroll - VueJS的无限滚动指令 vue-scrollbar - 最简单的滚动区域组件 vue-scroll - vue滚动 vue-pull-to-refresh - Vue2的上拉下拉 mint-loadmore - VueJS的双向下拉刷新组件 vue-smoothscroll - smoothscroll的VueJS版本 三、slider组件 vue-awesome-swiper - vue.js触摸滑动组件 vue-slick - 实现流畅轮播框的vue组件 vue-swipe - VueJS触摸滑块 vue-swiper - 易于使用的滑块组件 vue-images - 显示一组图片的lightbox组件 vue-carousel-3d - VueJS的3D轮播组件 vue-slide - vue轻量级滑动组件 vue-slider - vue 滑动组件 vue-m-carousel - vue 移动端轮播组件 dd-vue-component - 订单来了的公共组件库 vue-easy-slider - Vue 2.x的滑块组件 四、编辑器 markcook - 好看的markdown编辑器 eme - 优雅的Markdown编辑器 vue-syntax-highlight - Sublime Text语法高亮 vue-quill-editor - 基于Quill适用于Vue2的富文本编辑器 Vueditor - 所见即所得的编辑器 vue-html5-editor - html5所见即所得编辑器 vue2-editor - HTML编辑器 vue-simplemde - VueJS的Markdown编辑器组件 vue-quill - vue组件构建quill编辑器 五、图表 vue-table - 简化数据表格 vue-chartjs - vue中的Chartjs的封装 vue-charts - 轻松渲染一个图表 vue-chart - 强大的高速的vue图表解析 vue-highcharts - HighCharts组件 chartjs - Vue Bulma的chartjs组件 vue-chartkick - VueJS一行代码实现优美图表 六、日历 vue-calendar - 日期选择插件 vue-datepicker - 日历和日期选择组件 vue-datetime-picker - 日期时间选择控件 vue2-calendar - 支持lunar和日期事件的日期选择器 vue-fullcalendar - 基于vue.js的全日历组件 vue-datepicker - 漂亮的Vue日期选择器组件 datepicker - 基于flatpickr的时间选择组件 vue2-timepicker - 下拉时间选择器 vue-date-picker - VueJS日期选择器组件 vue-datepicker-simple - 基于vue的日期选择器 七、地址选择 vue-city - 城市选择器 vue-region-picker - 选择中国的省份市和地区 八、地图 vue-amap - 基于Vue 2和高德地图的地图组件 vue-google-maps - 带有双向数据绑定Google地图组件 vue-baidu-map- 基于 Vue 2的百度地图组件库 vue-cmap - Vue China map可视化组件 九、播放器 vue-video-player - VueJS视频及直播播放器 vue-video - Vue.js的HTML5视频播放器 vue-music-master - vue手机端网页音乐播放器 十、文件上传 vue-upload-component - Vuejs文件上传组件 vue-core-image-upload - 轻量级的vue上传插件 vue-dropzone - 用于文件上传的Vue组件 十一、图片处理 vue-lazyload-img - 移动优化的vue图片懒加载插件 vue-image-crop-upload - vue图片剪裁上传组件 vue-svgicon - 创建svg图标组件的工具 vue-img-loader - 图片加载UI组件 vue-image-clip- 基于vue的图像剪辑组件 vue-progressive-image - Vue的渐进图像加载插件 十二、提示 vue-toast-mobile - VueJS的toast插件 vue-msgbox - vuejs的消息框 vue-tooltip - 带绑定信息提示的提示工具 vue-verify-pop - 带气泡提示的vue校验插件 十三、进度条 vue-radial-progress - Vue.js放射性进度条组件 vue-progressbar - vue轻量级进度条 vue2-loading-bar - 最简单的仿Youtube加载条视图 十四、开发框架汇总 vue-admin - Vue管理面板框架 electron-vue - Electron及VueJS快速启动样板 vue-2.0-boilerplate - Vue2单页应用样板 vue-webgulp - 仿VueJS Vue loader示例 vue-bulma - 轻量级高性能MVVM Admin UI框架 vue-spa-template - 前后端分离后的单页应用开发 Framework7-Vue - VueJS与Framework7结合 vue-element-starter - vue启动页 十五、实用库汇总 vuelidate - 简单轻量级的基于模块的Vue.js验证 qingcheng - qingcheng主题 vuex - 专为 Vue.js 应用程序开发的状态管理模式 vue-axios - 将axios整合到VueJS的封装 vue-desktop - 创建管理面板网站的UI库 vue-meta - 管理app的meta信息 avoriaz - VueJS测试实用工具库 vue-framework7 - 结合VueJS使用的Framework7组件 vue-lazy-render - 用于Vue组件的延迟渲染 vue-svg-icon - vue2的可变彩色svg图标方案 vue-online - reactive的在线和离线组件 vue-password-strength-meter - 交互式密码强度计 vuep - 用实时编辑和预览来渲染Vue组件 vue-bootstrap-modal - vue的Bootstrap样式组件 element-admin - 支持 vuecli 的 Element UI 的后台模板 vue-shortkey - 应用于Vue.js的Vue-ShortKey 插件 cleave - 基于cleave.js的Cleave组件 vue-events - 简化事件的VueJS插件 http-vue-loader - 从html及js环境加载vue文件 vue-electron - 将选择的API封装到Vue对象中的插件 vue-router-transition - 页面过渡插件 vuemit - 处理VueJS事件 vue-cordova - Cordova的VueJS插件 vue-qart - 用于qartjs的Vue2指令 vue-websocket - VueJS的Websocket插件 vue-gesture - VueJS的手势事件插件 vue-local-storage - 具有类型支持的Vuejs本地储存插件 lazy-vue - 懒加载图片 vue-lazyloadImg - 图片懒加载插件 vue-bus - VueJS的事件总线 vue-observe-visibility - 当元素在页面上可见或隐藏时检测 vue-notifications - 非阻塞通知库 v-media-query - vue中添加用于配合媒体查询的方法 vuex-shared-mutations - 分享某种Vuex mutations vue-lazy-component - 懒加载组件或者元素的Vue指令 vue-reactive-storage - vue插件的Reactive层 vue-ts-loader - 在Vue装载机检查脚本 vue-pagination-2 - 简单通用的分页组件 vuex-i18n - 定位插件 Vue.resize - 检测HTML调整大小事件的vue指令 vue-zoombox - 一个高级zoombox leo-vue-validator - 异步的表单验证组件 modal - Vue Bulma的modal组件 Famous-Vue - Famous库的vue组件 vue-input-autosize - 基于内容自动调整文本输入的大小 vue-file-base64 - 将文件转换为Base64的vue组件 Vue-Easy-Validator - 简单的表单验证 vue-truncate-filter - 截断字符串的VueJS过滤器 十六、服务端 vue-*** - 结合Express使用Vue2服务端渲染 nuxt.js - 用于服务器渲染Vue app的最小化框架 vue-*** - 非常简单的VueJS服务器端渲染模板 vue-easy-renderer - Nodejs服务端渲染 express-vue - 简单的使用服务器端渲染vue.js 十七、辅助工具 DejaVue - Vuejs可视化及压力测试 vue-generate-component - 轻松生成Vue js组件的CLI工具 vscode-VueHelper - 目前vscode最好的vue代码提示插件 vue-play - 展示Vue组件的最小化框架 VuejsStarterKit - vuejs starter套件 vue-multipage-cli - 简单的多页CLI 十八、应用实例 pagekit - 轻量级的CMS建站系统 vuedo - 博客平台 koel - 基于网络的个人音频流媒体服务 CMS-of-Blog - 博客内容管理器 vue-cnode - 重写vue版cnode社区 vue-ghpages-blog - 依赖GitHub Pages无需本地生成的静态博客 swoole-vue-webim - Web版的聊天应用 fewords - 功能极其简单的笔记本 jackblog-vue - 个人博客系统 vue-blog - 使用Vue2.0 和Vuex的vue-blog vue-dashing-js - nuvo-dashing-js的fork rss-reader - 简单的rss阅读器 十九、Demo示例 eleme - 高仿饿了么app商家详情 NeteaseCloudWebApp - 高仿网易云音乐的webapp vue-zhihu-daily - 知乎日报 with Vuejs Vue-cnodejs - 基于vue重写Cnodejs.org的webapp vue2-demo - 从零构建vue2 + vue-router + vuex 开发环境 vue-wechat - vue.js开发微信app界面 vue-music - Vue 音乐搜索播放 maizuo - vue/vuex/redux仿卖座网 vue-demo - vue简易留言板 spa-starter-kit - 单页应用启动套件 zhihudaily-vue - 知乎日报web版 douban - 模仿豆瓣前端 vue-Meizi - vue最新实战项目 vue-demo-kugou - vuejs仿写酷狗音乐webapp vue2.0-taopiaopiao - vue2.0与express构建淘票票页面 node-vue-server-webpack - Node.js+Vue.js+webpack快速开发框架 VueDemo_Sell_Eleme - Vue2高仿饿了么外卖平台 vue-leancloud-blog - 一个前后端完全分离的单页应用 vue-fis3 - 流行开源工具集成demo mi-by-vue - VueJS仿小米官网 vue-demo-maizuo - 使用Vue2全家桶仿制卖座电影 vue2.x-douban - Vue2实现简易豆瓣电影webApp vue-adminLte-vue-router - vue和adminLte整合应用 vue-zhihudaily - 知乎日报 Web 版本 Zhihu-Daily-Vue.js - Vuejs单页网页应用 vue-axios-github - 登录拦截登出功能 vue2.x-Cnode - 基于vue全家桶的Cnode社区 hello-vue-django - 使用带有Django的vuejs的样板项目 websocket_chat - 基于vue和websocket的多人在线聊天室 x-blog - 开源的个人blog项目 vue-cnode - vue单页应用demo vue-express-mongodb - 简单的前后端分离案例 photoShare - 基于图片分享的社交平台 notepad - 本地存储的记事本 vue-zhihudaily-2.0 - 使用Vue2.0+vue-router+vuex创建的zhihudaily vueBlog - 前后端分离博客 Zhihu_Daily - 基于Vue和Nodejs的Web单页应用 vue-ruby-china - VueJS框架搭建的rubychina平台 vue-koa-demo - 使用Vue2和Koa1的全栈demo life-app-vue - 使用vue2完成多功能集合到小webapp vue-trip - vue2做的出行webapp github-explorer - 寻找最有趣的GitHub库 vue-***-boilerplate - 精简版的ofvue-hackernews-2 vue-bushishiren - 不是诗人应用 houtai - 基于vue和Element的后台管理系统 ios7-vue - 使用vue2.0 vue-router vuex模拟ios7 Framework7-VueJS - 使用移动框架的示例 cnode-vue - 基于vue和vue-router构建的cnodejs web网站SPA vue-cli-multipage-bootstrap - 将vue官方在线示例整合到组件中 vue-cnode - 用 Vue 做的 CNode 官网 seeMusic - 跨平台云音乐播放器 HyaReader - 移动友好的阅读器 zhihu-daily - 轻松查看知乎日报内容 vue-cnode - 使用cNode社区提供的接口 zhihu-daily-vue - 知乎日报 vue-dropload - 用以测试下拉加载与简单路由 vue-cnode-mobile - 搭建cnode社区 Vuejs-SalePlatform - vuejs搭建的售卖平台demo vue-memo - 用 vue写的记事本应用 sls-vuex2-demo - vuex2商城购物车demo v-notes - 简单美观的记事本 vue-starter - VueJs项目的简单启动页 二十、其他实用插件汇总 vue-dragging- 使元素可以拖拽 Vue.Draggable- 实现拖放和视图模型数组同步 vue-picture-input- 移动友好的图片文件输入组件 rubik- 基于Vuejs2的开源 UI 组件库 VueStar- 带星星动画的vue点赞按钮 vue-tables-2- 显示数据的bootstrap样式网格 DataVisualization- 数据可视化 vue-drag-and-drop-list- 创建排序列表的Vue指令 vuwe- 基于微信WeUI所开发的专用于Vue2的组件库 vue-typer- 模拟用户输入选择和删除文本的Vue组件 vue-impression- 移动Vuejs2 UI元素 vue-datatable- 使用Vuejs创建的DataTableView vue-instant- 轻松创建自动提示的自定义搜索控件 vue-slider-component- 在vue1和vue2中使用滑块 vue-touch-ripple- vuejs的触摸ripple组件 coffeebreak- 实时编辑CSS组件工具 vue-datasource- 创建VueJS动态表格 handsontable- 网页表格组件 vue-bootstrap-table- 可排序可检索的表格 vue-google-signin-button- 导入谷歌登录按钮 vue-float-label- VueJS浮动标签模式 vue-tagsinput- 基于VueJS的标签组件 vue-social-sharing- 社交分享组件 vue-popup-mixin- 用于管理弹出框的遮盖层 cubeex- 包含一套完整的移动UI vue-fullcalendar- vue FullCalendar封装 vue-material-design- Vue MD风格组件 vue-morris- Vuejs组件封装Morrisjs库 we-vue- Vue2及weui1开发的组件 vue-form-2- 全面的HTML表单管理的解决方案 vue-side-nav- 响应式的侧边导航 mint-indicator- VueJS移动加载指示器插件 vue-ripple- 制作谷歌MD风格涟漪效果的Vue组件 vue-touch-keyboard- VueJS虚拟键盘组件 vue-parallax- 整洁的视觉效果 vue-typewriter- vue组件类型 vue-ios-alertview- iOS7+ 风格的alertview服务 paco-ui-vue- PACOUI的vue组件 vue-button- Vue按钮组件 UI组件 element - 饿了么出品的Vue2的web UI工具套件 Vux - 基于Vue和WeUI的组件库 mint-ui - Vue 2的移动UI元素 iview - 基于 Vuejs 的开源 UI 组件库 Keen-UI - 轻量级的基本UI组件合集 vue-material - 通过Vue Material和Vue 2建立精美的app应用 muse-ui - 三端样式一致的响应式 UI 库 vuetify - 为移动而生的Vue JS 2组件框架 vonic - 快速构建移动端单页应用 eme - 优雅的Markdown编辑器 vue-multiselect - Vue.js选择框解决方案 vue-table - 简化数据表格 VueCircleMenu - 漂亮的vue圆环菜单 vue-chat - vuejs和vuex及webpack的聊天示例 radon-ui - 快速开发产品的Vue组件库 vue-waterfall - Vue.js的瀑布布局组件 vue-carbon - 基于 vue 开发MD风格的移动端 vue-beauty - 由vue和ant design创建的优美UI组件 vue-blu - 帮助你轻松创建web应用 vueAdmin - 基于vuejs2和element的简单的管理员模板 vue-syntax-highlight - Sublime Text语法高亮 vue-infinite-scroll - VueJS的无限滚动指令 Vue.Draggable - 实现拖放和视图模型数组同步 vue-awesome-swiper - vue.js触摸滑动组件 vue-calendar - 日期选择插件 bootstrap-vue - 应用于Vuejs2的Twitter的Bootstrap 4组件 vue-swipe - VueJS触摸滑块 vue-amap - 基于Vue 2和高德地图的地图组件 vue-chartjs - vue中的Chartjs的封装 vue-datepicker - 日历和日期选择组件 markcook - 好看的markdown编辑器 vue-google-maps - 带有双向数据绑定Google地图组件 vue-progressbar - vue轻量级进度条 vue-picture-input - 移动友好的图片文件输入组件 vue-infinite-loading - VueJS的无限滚动插件 vue-upload-component - Vuejs文件上传组件 vue-datetime-picker - 日期时间选择控件 vue-scroller - Vonic UI的功能性组件 vue2-calendar - 支持lunar和日期事件的日期选择器 vue-video-player - VueJS视频及直播播放器 vue-fullcalendar - 基于vue.js的全日历组件 rubik - 基于Vuejs2的开源 UI 组件库 VueStar - 带星星动画的vue点赞按钮 vue-mugen-scroll - 无限滚动组件 mint-loadmore - VueJS的双向下拉刷新组件 vue-tables-2 - 显示数据的bootstrap样式网格 vue-virtual-scroller - 带任意数目数据的顺畅的滚动 DataVisualization - 数据可视化 vue-quill-editor - 基于Quill适用于Vue2的富文本编辑器 Vueditor - 所见即所得的编辑器 vue-html5-editor - html5所见即所得编辑器 vue-msgbox - vuejs的消息框 vue-slider - vue 滑动组件 vue-core-image-upload - 轻量级的vue上传插件 vue-slide - vue轻量级滑动组件 vue-lazyload-img - 移动优化的vue图片懒加载插件 vue-drag-and-drop-list - 创建排序列表的Vue指令 vue-progressive-image - Vue的渐进图像加载插件 vuwe - 基于微信WeUI所开发的专用于Vue2的组件库 vue-dropzone - 用于文件上传的Vue组件 vue-charts - 轻松渲染一个图表 vue-swiper - 易于使用的滑块组件 vue-images - 显示一组图片的lightbox组件 vue-carousel-3d - VueJS的3D轮播组件 vue-region-picker - 选择中国的省份市和地区 vue-typer - 模拟用户输入选择和删除文本的Vue组件 vue-impression - 移动Vuejs2 UI元素 vue-datatable - 使用Vuejs创建的DataTableView vue-instant - 轻松创建自动提示的自定义搜索控件 vue-dragging - 使元素可以拖拽 vue-slider-component - 在vue1和vue2中使用滑块 vue2-loading-bar - 最简单的仿Youtube加载条视图 vue-datepicker - 漂亮的Vue日期选择器组件 vue-video - Vue.js的HTML5视频播放器 vue-toast-mobile - VueJS的toast插件 vue-image-crop-upload - vue图片剪裁上传组件 vue-tooltip - 带绑定信息提示的提示工具 vue-highcharts - HighCharts组件 vue-touch-ripple - vuejs的触摸ripple组件 coffeebreak - 实时编辑CSS组件工具 vue-datasource - 创建VueJS动态表格 vue2-timepicker - 下拉时间选择器 vue-date-picker - VueJS日期选择器组件 vue-scrollbar - 最简单的滚动区域组件 vue-quill - vue组件构建quill编辑器 vue-google-signin-button - 导入谷歌登录按钮 vue-svgicon - 创建svg图标组件的工具 vue-float-label - VueJS浮动标签模式 vue-baidu-map - 基于 Vue 2的百度地图组件库 vue-social-sharing - 社交分享组件 vue2-editor - HTML编辑器 vue-tagsinput - 基于VueJS的标签组件 vue-easy-slider - Vue 2.x的滑块组件 datepicker - 基于flatpickr的时间选择组件 vue-chart - 强大的高速的vue图表解析 vue-music-master - vue手机端网页音乐播放器 handsontable - 网页表格组件 vue-simplemde - VueJS的Markdown编辑器组件 vue-popup-mixin - 用于管理弹出框的遮盖层 cubeex - 包含一套完整的移动UI vue-fullcalendar - vue FullCalendar封装 vue-material-design - Vue MD风格组件 vue-morris - Vuejs组件封装Morrisjs库 we-vue - Vue2及weui1开发的组件 vue-image-clip - 基于vue的图像剪辑组件 vue-bootstrap-table - 可排序可检索的表格 vue-radial-progress - Vue.js放射性进度条组件 vue-slick - 实现流畅轮播框的vue组件 vue-pull-to-refresh - Vue2的上拉下拉 vue-form-2 - 全面的HTML表单管理的解决方案 vue-side-nav - 响应式的侧边导航 mint-indicator - VueJS移动加载指示器插件 chartjs - Vue Bulma的chartjs组件 vue-scroll - vue滚动 vue-ripple - 制作谷歌MD风格涟漪效果的Vue组件 vue-touch-keyboard - VueJS虚拟键盘组件 vue-chartkick - VueJS一行代码实现优美图表 vue-ztree - 用 vue 写的树层级组件 vue-m-carousel - vue 移动端轮播组件 vue-datepicker-simple - 基于vue的日期选择器 vue-tabs - 多tab页轻型框架 vue-verify-pop - 带气泡提示的vue校验插件 vue-parallax - 整洁的视觉效果 vue-img-loader - 图片加载UI组件 vue-typewriter - vue组件类型 vue-smoothscroll - smoothscroll的VueJS版本 vue-city - 城市选择器 vue-tree - vue树视图组件 vue-ios-alertview - iOS7+ 风格的alertview服务 dd-vue-component - 订单来了的公共组件库 paco-ui-vue - PACOUI的vue组件 vue-cmap - Vue China map可视化组件 vue-button - Vue按钮组件 开发框架 vue.js - 流行的轻量高效的前端组件化方案 vue-admin - Vue管理面板框架 electron-vue - Electron及VueJS快速启动样板 vue-2.0-boilerplate - Vue2单页应用样板 vue-spa-template - 前后端分离后的单页应用开发 Framework7-Vue - VueJS与Framework7结合 vue-bulma - 轻量级高性能MVVM Admin UI框架 vue-webgulp - 仿VueJS Vue loader示例 vue-element-starter - vue启动页 实用库 vuex - 专为 Vue.js 应用程序开发的状态管理模式 vuelidate - 简单轻量级的基于模块的Vue.js验证 qingcheng - qingcheng主题 vue-desktop - 创建管理面板网站的UI库 vue-meta - 管理app的meta信息 vue-axios - 将axios整合到VueJS的封装 vue-svg-icon - vue2的可变彩色svg图标方案 avoriaz - VueJS测试实用工具库 vue-framework7 - 结合VueJS使用的Framework7组件 vue-bootstrap-modal - vue的Bootstrap样式组件 vuep - 用实时编辑和预览来渲染Vue组件 vue-online - reactive的在线和离线组件 vue-lazy-render - 用于Vue组件的延迟渲染 vue-password-strength-meter - 交互式密码强度计 element-admin - 支持 vuecli 的 Element UI 的后台模板 vue-electron - 将选择的API封装到Vue对象中的插件 cleave - 基于cleave.js的Cleave组件 vue-events - 简化事件的VueJS插件 vue-shortkey - 应用于Vue.js的Vue-ShortKey 插件 vue-cordova - Cordova的VueJS插件 vue-router-transition - 页面过渡插件 vue-gesture - VueJS的手势事件插件 http-vue-loader - 从html及js环境加载vue文件 vue-qart - 用于qartjs的Vue2指令 vuemit - 处理VueJS事件 vue-websocket - VueJS的Websocket插件 vue-local-storage - 具有类型支持的Vuejs本地储存插件 lazy-vue - 懒加载图片 vue-bus - VueJS的事件总线 vue-reactive-storage - vue插件的Reactive层 vue-notifications - 非阻塞通知库 vue-lazy-component - 懒加载组件或者元素的Vue指令 v-media-query - vue中添加用于配合媒体查询的方法 vue-observe-visibility - 当元素在页面上可见或隐藏时检测 vue-ts-loader - 在Vue装载机检查脚本 vue-pagination-2 - 简单通用的分页组件 vuex-i18n - 定位插件 Vue.resize - 检测HTML调整大小事件的vue指令 vuex-shared-mutations - 分享某种Vuex mutations vue-file-base64 - 将文件转换为Base64的vue组件 modal - Vue Bulma的modal组件 Famous-Vue - Famous库的vue组件 leo-vue-validator - 异步的表单验证组件 Vue-Easy-Validator - 简单的表单验证 vue-truncate-filter - 截断字符串的VueJS过滤器 vue-zoombox - 一个高级zoombox vue-input-autosize - 基于内容自动调整文本输入的大小 vue-lazyloadImg - 图片懒加载插件 服务端 nuxt.js - 用于服务器渲染Vue app的最小化框架 express-vue - 简单的使用服务器端渲染vue.js vue-*** - 非常简单的VueJS服务器端渲染模板 vue-*** - 结合Express使用Vue2服务端渲染 vue-easy-renderer - Nodejs服务端渲染 辅助工具 DejaVue - Vuejs可视化及压力测试 vue-play - 展示Vue组件的最小化框架 vscode-VueHelper - 目前vscode最好的vue代码提示插件 vue-generate-component - 轻松生成Vue js组件的CLI工具 vue-multipage-cli - 简单的多页CLI VuejsStarterKit - vuejs starter套件 应用实例 koel - 基于网络的个人音频流媒体服务 pagekit - 轻量级的CMS建站系统 vuedo - 博客平台 jackblog-vue - 个人博客系统 vue-cnode - 重写vue版cnode社区 CMS-of-Blog - 博客内容管理器 rss-reader - 简单的rss阅读器 vue-ghpages-blog - 依赖GitHub Pages无需本地生成的静态博客 swoole-vue-webim - Web版的聊天应用 vue-dashing-js - nuvo-dashing-js的fork fewords - 功能极其简单的笔记本 vue-blog - 使用Vue2.0 和Vuex的vue-blog Demo示例 Vue-cnodejs - 基于vue重写Cnodejs.org的webapp NeteaseCloudWebApp - 高仿网易云音乐的webapp vue-zhihu-daily - 知乎日报 with Vuejs vue-wechat - vue.js开发微信app界面 vue2-demo - 从零构建vue2 + vue-router + vuex 开发环境 eleme - 高仿饿了么app商家详情 vue-demo - vue简易留言板 maizuo - vue/vuex/redux仿卖座网 spa-starter-kit - 单页应用启动套件 vue-music - Vue 音乐搜索播放 douban - 模仿豆瓣前端 vue-Meizi - vue最新实战项目 zhihudaily-vue - 知乎日报web版 vue-demo-kugou - vuejs仿写酷狗音乐webapp VueDemo_Sell_Eleme - Vue2高仿饿了么外卖平台 vue2.0-taopiaopiao - vue2.0与express构建淘票票页面 vue-leancloud-blog - 一个前后端完全分离的单页应用 node-vue-server-webpack - Node.js+Vue.js+webpack快速开发框架 mi-by-vue - VueJS仿小米官网 vue-fis3 - 流行开源工具集成demo vue2.x-douban - Vue2实现简易豆瓣电影webApp vue-demo-maizuo - 使用Vue2全家桶仿制卖座电影 vue-zhihudaily - 知乎日报 Web 版本 vue-adminLte-vue-router - vue和adminLte整合应用 vue-axios-github - 登录拦截登出功能 Zhihu-Daily-Vue.js - Vuejs单页网页应用 hello-vue-django - 使用带有Django的vuejs的样板项目 vue-cnode - vue单页应用demo x-blog - 开源的个人blog项目 vue-express-mongodb - 简单的前后端分离案例 websocket_chat - 基于vue和websocket的多人在线聊天室 photoShare - 基于图片分享的社交平台 vue-zhihudaily-2.0 - 使用Vue2.0+vue-router+vuex创建的zhihudaily notepad - 本地存储的记事本 vueBlog - 前后端分离博客 vue-ruby-china - VueJS框架搭建的rubychina平台 Zhihu_Daily - 基于Vue和Nodejs的Web单页应用 vue-koa-demo - 使用Vue2和Koa1的全栈demo vue2.x-Cnode - 基于vue全家桶的Cnode社区 life-app-vue - 使用vue2完成多功能集合到小webapp github-explorer - 寻找最有趣的GitHub库 vue-trip - vue2做的出行webapp vue-***-boilerplate - 精简版的ofvue-hackernews-2 vue-bushishiren - 不是诗人应用 houtai - 基于vue和Element的后台管理系统 ios7-vue - 使用vue2.0 vue-router vuex模拟ios7 Framework7-VueJS - 使用移动框架的示例 cnode-vue - 基于vue和vue-router构建的cnodejs web网站SPA vue-cli-multipage-bootstrap - 将vue官方在线示例整合到组件中 vue-cnode - 用 Vue 做的 CNode 官网 HyaReader - 移动友好的阅读器 zhihu-daily - 轻松查看知乎日报内容 seeMusic - 跨平台云音乐播放器 vue-cnode - 使用cNode社区提供的接口 zhihu-daily-vue - 知乎日报 sls-vuex2-demo - vuex2商城购物车demo vue-dropload - 用以测试下拉加载与简单路由 vue-cnode-mobile - 搭建cnode社区 Vuejs-SalePlatform - vuejs搭建的售卖平台demo v-notes - 简单美观的记事本 vue-starter - VueJs项目的简单启动页 vue-memo - 用 vue写的记事本应用 ———————————————— 版权声明:本文为CSDN博主「喵不可阉」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/beifanggongy/article/details/129661207
-
Vue虚拟列表(Virtual List)可以在大量数据的场景下,提高页面渲染性能和用户体验。其实现方式是只渲染当前可视区域内的数据部分,而非全部渲染数据,可以通过监听滚动事件,动态计算当前可视区域的起始索引和结束索引,并只渲染这一部分的数据。 简单来说就是在固定数量的dom节点上去对大量的返回数据进行渲染(dom节点具体有多少还是取决于前端列表需要展示的数量),例如,一个列表,需要接收后端返回的10w条数据,此时我们只需要10个dom节点来展示,那么这10w条数据,会根据滚动事件在这10个dom节点上来进行视图层的刷新渲染,从而实现虚拟列表的展示 代码放下面!(下方是组件哈,可以自己引用一下,非常简单) 1. 在模板中 <div class="list-container"> <div class="list-item" v-for="(item, index) in displayedItems" :key="startIndex + index" :style="{ height: itemHeight + 'px' }"> <!-- 渲染每一项的内容 --> </div> </div> 2. 列表组件中,定义如下 props 和 data props: { // 列表每一项数据的固定高度 itemHeight: { type: Number, required: true, }, // 列表展示的数据源 items: { type: Array, required: true, }, // 可视区域的高度 viewHeight: { type: Number, required: true, }, }, data() { return { // 当前可视区域首位元素的索引 startIndex: 0, // 当前可视区域末尾元素的索引 endIndex: 0, // 实际渲染的数据 displayedItems: [], } }, 3. 组件的mounted和watch钩子中,初始化和更新当前可视区域数据 mounted() { this.updateVisibleData() window.addEventListener('scroll', this.updateVisibleData) }, watch: { items() { this.startIndex = 0 this.endIndex = 0 this.updateVisibleData() }, }, methods: { // 计算当前可视区域的起始索引和结束索引 updateVisibleData() { const scrollTop = window.pageYOffset const visibleHeight = this.viewHeight + scrollTop const startIndex = Math.floor(scrollTop / this.itemHeight) const endIndex = Math.ceil(visibleHeight / this.itemHeight) if (startIndex !== this.startIndex || endIndex !== this.endIndex) { this.startIndex = startIndex this.endIndex = endIndex this.displayedItems = this.items.slice(startIndex, endIndex) } }, } ———————————————— 版权声明:本文为CSDN博主「大家都是小菜鸡」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_68330110/article/details/130624136
-
首先解释一下什么是分片上传 分片上传就是把一个大的文件分成若干块,一块一块的传输。这样做的好处可以减少重新上传的开销。比如:如果我们上传的文件是一个很大的文件,那么上传的时间应该会比较久,再加上网络不稳定各种因素的影响,很容易导致传输中断,用户除了重新上传文件外没有其他的办法,但是我们可以使用分片上传来解决这个问题。通过分片上传技术,如果网络传输中断,我们重新选择文件只需要传剩余的分片。而不需要重传整个文件,大大减少了重传的开销。 但是我们要如何选择一个合适的分片呢?因此我们要考虑如下几个事情: 1.分片越小,那么请求肯定越多,开销就越大。因此不能设置太小。 2.分片越大,灵活度就少了。 3.服务器端都会有个固定大小的接收Buffer。分片的大小最好是这个值的整数倍。 分片上传的步骤 1.先对文件进行md5加密。使用md5加密的优点是:可以对文件进行唯一标识,同样可以为后台进行文件完整性校验进行比对。 2.拿到md5值以后,服务器端查询下该文件是否已经上传过,如果已经上传过的话,就不用重新再上传。 3.对大文件进行分片。比如一个100M的文件,我们一个分片是5M的话,那么这个文件可以分20次上传。 4.向后台请求接口,接口里的数据就是我们已经上传过的文件块。(注意:为什么要发这个请求?就是为了能断点续传,比如我们使用百度网盘对吧,网盘里面有续传功能,当一个文件传到一半的时候,突然想下班不想上传了,那么服务器就应该记住我之前上传过的文件块,当我打开电脑重新上传的时候,那么它应该跳过我之前已经上传的文件块。再上传后续的块)。 5.开始对未上传过的文件块进行上传。(这个是第二个请求,会把所有的分片合并,然后上传请求)。 6.上传成功后,服务器会进行文件合并。最后完成。 话不多说,直接开始干代码 <template> <div> <!-- on-preview 点击文件列表中已上传的文件时的钩子 --> <!-- http-request 覆盖默认的上传行为,可以自定义上传的实现 --> <!-- limit 最大允许上传个数 --> <!-- before-upload 上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。 --> <!-- accept 接受上传的文件类型(thumbnail-mode 模式下此参数无效) --> <!-- multiple 是否支持多选文件 --> <!-- on-change 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用 --> <!-- on-remove 文件列表移除文件时的钩子 --> <!-- file-list 上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}] --> <!-- on-exceed 文件超出个数限制时的钩子 --> <!-- auto-upload 是否在选取文件后立即进行上传 --> <!-- action 必选参数,上传的地址 例如 action="https://jsonplaceholder.typicode.com/posts/"--> <el-upload drag multiple :auto-upload="true" :http-request="checkedFile" :before-remove="removeFile" :limit="10" action="/tools/upload_test/" > <i class="el-icon-upload"></i> <div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em> </div> </el-upload> <el-progress type="circle" :percentage="progress" class="progress" v-if="showProgress"></el-progress> </div> </template> 文件上传时,会走http-request方法,如果定义了这个方法,组件的submit方法就会被拦截掉(注意别在这个方法里面调用组件的submit 方法,会造成死循环),在这个方法里面我就可以搞我想搞的事情了。 http-request 这个传入的回调函数应该返回一个Promise,所以我自己定义了一个无用的Promise进去 const prom = new Promise((resolve, reject) => {}) prom.abort = () => {} return prom 如果要实现断点续传,需要和后端确定好,如何配合。 我这里的方案是,在我把所有的分片全部上传一遍后,会请求一个查询接口,后端在这个接口里面返回给我哪些分片没有上传成功(会给我序号),我这个时候,再去重新上传那些没有被上传成功的分片 直接贴完整代码,注释都在里面,看不懂的可以直接联系我,博客上有联系方式(依赖element-ui、axios、spark-md5) <template> <div> <!-- on-preview 点击文件列表中已上传的文件时的钩子 --> <!-- http-request 覆盖默认的上传行为,可以自定义上传的实现 --> <!-- limit 最大允许上传个数 --> <!-- before-upload 上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。 --> <!-- accept 接受上传的文件类型(thumbnail-mode 模式下此参数无效) --> <!-- multiple 是否支持多选文件 --> <!-- on-change 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用 --> <!-- on-remove 文件列表移除文件时的钩子 --> <!-- file-list 上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}] --> <!-- on-exceed 文件超出个数限制时的钩子 --> <!-- auto-upload 是否在选取文件后立即进行上传 --> <!-- action 必选参数,上传的地址 例如 action="https://jsonplaceholder.typicode.com/posts/"--> <el-upload drag multiple :auto-upload="true" :http-request="checkedFile" :before-remove="removeFile" :limit="10" action="/tools/upload_test/" > <i class="el-icon-upload"></i> <div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em> </div> </el-upload> <el-progress type="circle" :percentage="progress" class="progress" v-if="showProgress"></el-progress> </div> </template> <script> import axios from "axios"; import SparkMD5 from "spark-md5"; export default { data() { return { maxSize: 5 * 1024 * 1024 * 1024, // 上传最大文件限制 最小单位是b multiUploadSize: 100 * 1024 * 1024, // 大于这个大小的文件使用分块上传(后端可以支持断点续传) 100mb eachSize: 100 * 1024 * 1024, // 每块文件大小 100mb requestCancelQueue: [], // 请求方法队列(调用取消上传 url: "/tools/upload_test/", //上传进度 progress: 0, showProgress: false, // 每上传一块的进度 eachProgress: 0, // 总共有多少块。断点续传使用 chunksKeep:0, // 切割后的文件数组 fileChunksKeep:[], // 这个文件,断点续传 fileKeep:null }; }, mounted() { }, methods: { async checkedFile(options) { console.log(options); const { maxSize, multiUploadSize, getSize, splitUpload, singleUpload } = this; // 解构赋值 const { file, onProgress, onSuccess, onError } = options; // 解构赋值 if (file.size > maxSize) { return this.$message({ message: `您选择的文件大于${getSize(maxSize)}`, type: "error" }); } this.fileKeep = file const uploadFunc = file.size > multiUploadSize ? splitUpload : singleUpload; // 选择上传方式 try { await uploadFunc(file, onProgress); this.$message({ message: "上传成功", type: "success" }); this.showProgress = false; this.progress = 0; onSuccess(); } catch (e) { console.error(e); this.$message({ message: e.message, type: "error" }); this.showProgress = false; this.progress = 0; onError(); } const prom = new Promise((resolve, reject) => {}); // 上传后返回一个promise prom.abort = () => {}; return prom; }, // 格式化文件大小显示文字 getSize(size) { return size > 1024 ? size / 1024 > 1024 ? size / (1024 * 1024) > 1024 ? (size / (1024 * 1024 * 1024)).toFixed(2) + "GB" : (size / (1024 * 1024)).toFixed(2) + "MB" : (size / 1024).toFixed(2) + "KB" : size.toFixed(2) + "B"; }, // 单文件直接上传 async singleUpload(file, onProgress) { await this.postFile( { file, uid: file.uid, fileName: file.fileName ,chunk:0}, onProgress ); var spark = new SparkMD5.ArrayBuffer(); spark.append(file); var md5 = spark.end(); console.log(md5); const isValidate = await this.validateFile({ fileName: file.name, uid: file.uid, md5:md5, chunks:1 }); }, // 大文件分块上传 splitUpload(file, onProgress) { console.log('file11') console.log(file) return new Promise(async (resolve, reject) => { try { const { eachSize } = this; const chunks = Math.ceil(file.size / eachSize); this.chunksKeep = chunks const fileChunks = await this.splitFile(file, eachSize, chunks); this.fileChunksKeep = fileChunks console.log('fileChunks,文件数组切割后') console.log(fileChunks) //判断每上传一个文件,进度条涨多少,保留两位小数 this.eachProgress = parseInt(Math.floor(100 / chunks * 100) / 100); this.showProgress = true; let currentChunk = 0; for (let i = 0; i < fileChunks.length; i++) { // 服务端检测已经上传到第currentChunk块了,那就直接跳到第currentChunk块,实现断点续传 console.log(currentChunk, i); // 此时需要判断进度条 if (Number(currentChunk) === i) { // 每块上传完后则返回需要提交的下一块的index await this.postFile( { chunked: true, chunk: i, chunks, eachSize, fileName: file.name, fullSize: file.size, uid: file.uid, file: fileChunks[i] }, onProgress ); currentChunk++ // 上传完一块后,进度条增加 this.progress += this.eachProgress; // 不能超过100 this.progress = this.progress > 100 ? 100 : this.progress; } } var spark = new SparkMD5.ArrayBuffer(); spark.append(file); var md5 = spark.end(); console.log(md5); const isValidate = await this.validateFile({ chunks: fileChunks.length, // chunk: fileChunks.length, fileName: file.name, uid: file.uid, md5:md5, // task_id:file.uid }); // if (!isValidate) { // throw new Error("文件校验异常"); // } resolve(); } catch (e) { reject(e); } }); }, againSplitUpload(file, array) { console.log('file,array') console.log(file) console.log(array) return new Promise(async (resolve, reject) => { try { const { eachSize , fileKeep } = this; const chunks = this.chunksKeep const fileChunks = this.fileChunksKeep this.showProgress = true; // let currentChunk = 0; for (let i = 0; i < array.length; i++) { // 服务端检测已经上传到第currentChunk块了,那就直接跳到第currentChunk块,实现断点续传 // console.log(currentChunk, i); // 此时需要判断进度条 // 每块上传完后则返回需要提交的下一块的index await this.postFile( { chunked: true, chunk: array[i], chunks, fileName: file.fileName, fullSize: fileKeep.size, uid: file.uid, file: fileChunks[array[i]] }, ); // currentChunk++ // 上传完一块后,进度条增加 // this.progress += this.eachProgress; // 不能超过100 this.progress = this.progress > 100 ? 100 : this.progress; } var spark = new SparkMD5.ArrayBuffer(); spark.append(fileKeep); var md5 = spark.end(); console.log(md5); const isValidate = await this.validateFile({ chunks: fileChunks.length, // chunk: fileChunks.length, fileName: file.fileName, uid: file.uid, md5:md5, // task_id:file.uid }); // if (!isValidate) { // throw new Error("文件校验异常"); // } resolve(); } catch (e) { reject(e); } }); }, // 文件分块,利用Array.prototype.slice方法 splitFile(file, eachSize, chunks) { return new Promise((resolve, reject) => { try { setTimeout(() => { const fileChunk = []; for (let chunk = 0; chunks > 0; chunks--) { fileChunk.push(file.slice(chunk, chunk + eachSize)); chunk += eachSize; } resolve(fileChunk); }, 0); } catch (e) { console.error(e); reject(new Error("文件切块发生错误")); } }); }, removeFile(file) { this.requestCancelQueue[file.uid](); delete this.requestCancelQueue[file.uid]; return true; }, // 提交文件方法,将参数转换为FormData, 然后通过axios发起请求 postFile(param, onProgress) { console.log(param); const formData = new FormData(); // for (let p in param) { // formData.append(p, param[p]); // } formData.append('file', param.file) // 改了 formData.append('uid',param.uid) formData.append('chunk',param.chunk) const { requestCancelQueue } = this; const config = { cancelToken: new axios.CancelToken(function executor(cancel) { if (requestCancelQueue[param.uid]) { requestCancelQueue[param.uid](); delete requestCancelQueue[param.uid]; } requestCancelQueue[param.uid] = cancel; }), onUploadProgress: e => { if (param.chunked) { e.percent = Number( ( ((param.chunk * (param.eachSize - 1) + e.loaded) / param.fullSize) * 100 ).toFixed(2) ); } else { e.percent = Number(((e.loaded / e.total) * 100).toFixed(2)); } onProgress(e); } }; // return axios.post('/api/v1/tools/upload_test/', formData, config).then(rs => rs.data) return this.$http({ url: "/tools/upload_test/", method: "POST", data: formData // config }).then(rs => rs.data); }, // 文件校验方法 validateFile(file) { // return axios.post('/api/v1/tools/upload_test/', file).then(rs => rs.data) console.log(2) console.log(file) return this.$http({ url: "/tools/upload_test/upload_success/", method: "POST", data: file }).then(res => { if(res && res.status == 1){ this.againSplitUpload(file,res.data.error_file) return true } }); } } }; </script> <style scoped> .progress{ /* 在当前页面居中 */ position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); /* 宽度 */ } </style> 更新代码,上面的代码使用md5加密后,与后端加密的MD5值不一样,下面的加密过后是一样的 <template> <div :class="showProgress == true ? 'loading' : ''"> <!-- on-preview 点击文件列表中已上传的文件时的钩子 --> <!-- http-request 覆盖默认的上传行为,可以自定义上传的实现 --> <!-- limit 最大允许上传个数 --> <!-- before-upload 上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。 --> <!-- accept 接受上传的文件类型(thumbnail-mode 模式下此参数无效) --> <!-- multiple 是否支持多选文件 --> <!-- on-change 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用 --> <!-- on-remove 文件列表移除文件时的钩子 --> <!-- file-list 上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}] --> <!-- on-exceed 文件超出个数限制时的钩子 --> <!-- auto-upload 是否在选取文件后立即进行上传 --> <!-- action 必选参数,上传的地址 例如 action="https://jsonplaceholder.typicode.com/posts/"--> <el-upload drag multiple :auto-upload="true" :http-request="checkedFile" :before-remove="removeFile" :limit="10" action="/tools/upload_chunk/" :disabled="showProgress"> <i class="el-icon-upload"></i> <div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em> </div> </el-upload> <!-- 正在上传的弹窗 --> <el-dialog title="正在上传" :visible.sync="showProgress" width="50%"> <el-progress type="circle" :percentage="progress" class="progress" v-if="showProgress"></el-progress> </el-dialog> <!-- <el-progress type="circle" :percentage="progress" class="progress" v-if="showProgress"></el-progress> --> </div> </template> <script> import axios from "axios"; import SparkMD5 from "spark-md5"; export default { data() { return { maxSize: 5 * 1024 * 1024 * 1024, // 上传最大文件限制 最小单位是b multiUploadSize: 100 * 1024 * 1024, // 大于这个大小的文件使用分块上传(后端可以支持断点续传) 100mb eachSize: 100 * 1024 * 1024, // 每块文件大小 100mb requestCancelQueue: [], // 请求方法队列(调用取消上传 url: "/tools/upload_chunk/", //上传进度 progress: 0, showProgress: false, // 每上传一块的进度 eachProgress: 0, // 总共有多少块。断点续传使用 chunksKeep: 0, // 切割后的文件数组 fileChunksKeep: [], // 这个文件,断点续传 fileKeep: null, // 断点续传,文件md5 fileMd5Keep: "" }; }, mounted() { }, methods: { async checkedFile(options) { // console.log(options); const { maxSize, multiUploadSize, getSize, splitUpload, singleUpload } = this; // 解构赋值 const { file, onProgress, onSuccess, onError } = options; // 解构赋值 if (file.size > maxSize) { return this.$message({ message: `您选择的文件大于${getSize(maxSize)}`, type: "error" }); } this.fileKeep = file; const uploadFunc = file.size > multiUploadSize ? splitUpload : singleUpload; // 选择上传方式 try { await uploadFunc(file, onProgress); onSuccess(); } catch (e) { console.error(e); this.$message({ message: e.message, type: "error" }); this.showProgress = false; this.progress = 0; onError(); } const prom = new Promise((resolve, reject) => { }); // 上传后返回一个promise prom.abort = () => { }; return prom; }, // 格式化文件大小显示文字 getSize(size) { return size > 1024 ? size / 1024 > 1024 ? size / (1024 * 1024) > 1024 ? (size / (1024 * 1024 * 1024)).toFixed(2) + "GB" : (size / (1024 * 1024)).toFixed(2) + "MB" : (size / 1024).toFixed(2) + "KB" : size.toFixed(2) + "B"; }, // 单文件直接上传 async singleUpload(file, onProgress) { await this.postFile( { file, uid: file.uid, fileName: file.fileName, chunk: 0 }, onProgress ); // var spark = new SparkMD5.ArrayBuffer(); // spark.append(file); // var md5 = spark.end(); // console.log(md5); const reader = new FileReader(); reader.readAsArrayBuffer(file); let hashMd5 = ""; console.log(hashMd5); const that = this; function getHash(cb) { console.log("进入单个上传的getHash"); reader.onload = function (e) { console.log("进入单个上传的getHash的函数2"); console.log(hashMd5); console.log(this); // console.log(e) const hash = SparkMD5.ArrayBuffer.hash(e.target.result); // const hash = SparkMD5.ArrayBuffer.hash(file); console.log(hash); that.hashMd5 = hash; console.log(that.hashMd5); that.fileMd5Keep = hash; cb(hash); }; } await getHash(function (hash) { console.log(hash); console.log(that); // 请求接口 that.validateFile({ name: file.name, uid: file.uid, md5: hash, chunks: 1, filter_type: "user_data_file" }); }); }, // getMd5(file, chunkCount) { // const spark = new SparkMD5.ArrayBuffer(); // let currentChunk = 0; // const reader = new FileReader(); // reader.onload = function(e) { // spark.append(e.target.result); // currentChunk++; // if (currentChunk < chunkCount) { // console.log(currentChunk); // loadNext(); // } else { // console.log(spark.end()); // // 在这里请求接口 // return spark.end(); // } // }; // function loadNext() { // const start = currentChunk * chunkSize; // const end = // start + chunkSize >= file.size ? file.size : start + chunkSize; // reader.readAsArrayBuffer(file.slice(start, end)); // } // loadNext(); // }, // 大文件分块上传 splitUpload(file, onProgress) { return new Promise(async (resolve, reject) => { try { const { eachSize } = this; const chunks = Math.ceil(file.size / eachSize); this.chunksKeep = chunks; const fileChunks = await this.splitFile(file, eachSize, chunks); this.fileChunksKeep = fileChunks; console.log("fileChunks,文件数组切割后"); console.log(fileChunks); //判断每上传一个文件,进度条涨多少,保留两位小数 this.eachProgress = parseInt(Math.floor((100 / chunks) * 100) / 100); this.showProgress = true; let currentChunk = 0; for (let i = 0; i < fileChunks.length; i++) { // 服务端检测已经上传到第currentChunk块了,那就直接跳到第currentChunk块,实现断点续传 console.log(currentChunk, i); // 此时需要判断进度条 if (Number(currentChunk) === i) { // 每块上传完后则返回需要提交的下一块的index await this.postFile( { chunked: true, chunk: i, chunks, eachSize, fileName: file.name, fullSize: file.size, uid: file.uid, file: fileChunks[i] }, onProgress ); currentChunk++; // 上传完一块后,进度条增加 this.progress += this.eachProgress; // 不能超过100 this.progress = this.progress > 100 ? 100 : this.progress; } } // this.getMd5(file, chunks); // var spark = new SparkMD5.ArrayBuffer(); // spark.append(file); // var md5 = spark.end(); // console.log(md5); const spark = new SparkMD5.ArrayBuffer(); let currentChunkMd5 = 0; const that = this; const reader = new FileReader(); reader.onload = async function (e) { spark.append(e.target.result); currentChunkMd5++; if (currentChunkMd5 < chunks) { loadNext(); } else { // console.log(spark.end()); var hashMd5111 = spark.end(); that.fileMd5Keep = hashMd5111; console.log(that); console.log(hashMd5111); // 在这里请求接口 await that.validateFile({ name: file.name, uid: file.uid, md5: hashMd5111, chunks: fileChunks.length, filter_type: "git_secret_file" // chunk: fileChunks.length, }); } }; async function loadNext() { const start = currentChunkMd5 * eachSize; const end = start + eachSize >= file.size ? file.size : start + eachSize; await reader.readAsArrayBuffer(file.slice(start, end)); } this.$message({ message: "正在进行文件加密校验", type: "info" }); await loadNext(); // let hashMd5 = ""; // // console.log(hashMd5) // const that = this; // console.log("进入分片上传的getHash"); // function getHash(cb) { // reader.onload = function(e) { // console.log("进入分片上传的getHash的函数"); // const hash = SparkMD5.ArrayBuffer.hash(e.target.result); // // const hash = SparkMD5.ArrayBuffer.hash(file); // console.log(hash); // that.hashMd5 = hash; // console.log(that.hashMd5); // that.fileMd5Keep = hash; // cb(hash); // }; // reader.readAsArrayBuffer(file); // } // await getHash(function() { // console.log(that); // that.validateFile({ // name: file.name, // uid: file.uid, // md5: that.hashMd5, // chunks: fileChunks.length // // chunk: fileChunks.length, // }); // }); // 请求接口 // console.log('fileChunks.length') // 请求接口 // this.validateFile({ // fileName: file.name, // uid: file.uid, // md5:md5, // chunks:1 // }); resolve(); } catch (error) { reject(error); } }); }, // 断点续传 againSplitUpload(file, array) { console.log("file,array"); console.log(file); console.log(array); return new Promise(async (resolve, reject) => { try { const { eachSize, fileKeep } = this; const chunks = this.chunksKeep; const fileChunks = this.fileChunksKeep; this.showProgress = true; // let currentChunk = 0; for (let i = 0; i < array.length; i++) { // 服务端检测已经上传到第currentChunk块了,那就直接跳到第currentChunk块,实现断点续传 // console.log(currentChunk, i); // 此时需要判断进度条 // 每块上传完后则返回需要提交的下一块的index await this.postFile({ chunked: true, chunk: array[i], chunks, name: file.name, fullSize: fileKeep.size, uid: file.uid, file: fileChunks[array[i]] }); // currentChunk++ // 上传完一块后,进度条增加 // this.progress += this.eachProgress; // 不能超过100 this.progress = this.progress > 100 ? 100 : this.progress; } // var spark = new SparkMD5.ArrayBuffer(); // spark.append(fileKeep); // var md5 = spark.end(); // console.log(md5); var fileMd5KeepTwo = this.fileMd5Keep; const isValidate = await this.validateFile({ chunks: fileChunks.length, // chunk: fileChunks.length, name: file.name, uid: file.uid, md5: fileMd5KeepTwo, filter_type: "git_secret_file" // task_id:file.uid }); // if (!isValidate) { // throw new Error("文件校验异常"); // } // 关闭进度条 this.showProgress = false; // 重置进度条 this.progress = 0; resolve(); } catch (e) { reject(e); } }); }, // 文件分块,利用Array.prototype.slice方法 splitFile(file, eachSize, chunks) { return new Promise((resolve, reject) => { try { setTimeout(() => { const fileChunk = []; for (let chunk = 0; chunks > 0; chunks--) { fileChunk.push(file.slice(chunk, chunk + eachSize)); chunk += eachSize; } resolve(fileChunk); }, 0); } catch (e) { console.error(e); reject(new Error("文件切块发生错误")); } }); }, removeFile(file) { this.requestCancelQueue[file.uid](); delete this.requestCancelQueue[file.uid]; return true; }, // 提交文件方法,将参数转换为FormData, 然后通过axios发起请求 postFile(param, onProgress) { // console.log(param); const formData = new FormData(); // for (let p in param) { // formData.append(p, param[p]); // } formData.append("file", param.file); // 改了 formData.append("uid", param.uid); formData.append("chunk", param.chunk); formData.append("filter_type", "git_secret_file"); const { requestCancelQueue } = this; const config = { cancelToken: new axios.CancelToken(function executor(cancel) { if (requestCancelQueue[param.uid]) { requestCancelQueue[param.uid](); delete requestCancelQueue[param.uid]; } requestCancelQueue[param.uid] = cancel; }), onUploadProgress: e => { if (param.chunked) { e.percent = Number( ( ((param.chunk * (param.eachSize - 1) + e.loaded) / param.fullSize) * 100 ).toFixed(2) ); } else { e.percent = Number(((e.loaded / e.total) * 100).toFixed(2)); } onProgress(e); } }; // return axios.post('/api/v1/tools/upload_chunk/', formData, config).then(rs => rs.data) return this.$http({ url: "/tools/upload_chunk/", method: "POST", data: formData // config }).then(rs => rs.data); }, // 文件校验方法 validateFile(file) { // return axios.post('/api/v1/tools/upload_chunk/', file).then(rs => rs.data) return this.$http({ url: "/tools/upload_chunk/upload_success/", method: "POST", data: file }).then(res => { if (res && res.status == 1) { this.againSplitUpload(file, res.data.error_file); this.$message({ message: "有文件上传失败,正在重新上传", type: "warning" }); } else if (res && res.status == 0) { this.$message({ message: "上传成功", type: "success" }); this.showProgress = false; this.progress = 0; } else if (res && res.status == 40008) { this.$message.error(res.message); this.showProgress = false; this.progress = 0; } }); } } }; </script> <style scoped> .loading { /* 整体页面置灰 */ /* background: rgba(0, 0, 0, 0.5); */ } .progress { /* 在当前页面居中 */ position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); margin-top: 40px; /* 宽度 */ } /deep/ .el-dialog { position: relative; height: 500px; } </style> ———————————————— 版权声明:本文为CSDN博主「yjxkq99」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/yjxkq99/article/details/128942133
-
van-uploader参考文档 限制上传数量 通过 max-count 属性可以限制上传文件的数量,上传数量达到限制后,会自动隐藏上传区域。 限制上传大小 通过 max-size 属性可以限制上传文件的大小,超过大小的文件会被自动过滤,文件信息通过 oversize 事件获取。 文件上传前进行校验和处理 通过before-read 传入函数,可以在上传前进行校验和处理,支持返回 Promise 对 file 对象进行自定义处理。例如压缩图片:使用compressorjs压缩图片,优化功能,压缩所有格式的图片。 文件上传完毕后获取数据 文件上传后会触发 after-read 回调函数,获取到对应的 file 对象。 更多细节请参考vant官方文档 <template> <div> <van-uploader v-model="fileList" :max-count="1" :max-size="2048 * 1024" :after-read="OnAfterRead" :before-read="onBeforeRead" @oversize="onOversize" @delete="onDelete" /> </div> </template> <script> import Compressor from "compressorjs"; export default { data() { return { fileList: [], IMG_LIST: "", //图片路径 }; }, methods: { OnAfterRead(result) { let newImage = new Image(); // 这里将src传入需要获取信息的图片地址或base64 newImage.src = result.content; newImage.onload = () => { // 输出图片信息 比如可以获取图片宽高 console.log("图片宽", newImage.width); console.log("图片高", newImage.height); this.fileList[0].url = result.content; // 点击上传图片的结果(base64的图片字符串)IMG_LIST this.IMG_LIST = this.fileList[0].url; // 控制台打印 console.log(" this.IMG_LIST ", this.IMG_LIST); }; }, //上传图片压缩,需要安装依赖: npm i compressorjs,并引入(import) 图片0.6倍压缩 onBeforeRead(file) { return new Promise((resolve) => { new Compressor(file, { quality: 0.6, success: resolve, error(err) { console.log('图片压缩失败---->>>>>',err.message); }, }); }); }, //图片大小超过2M提示 onOversize(file) { this.$toast("图片大小不能超过 2M"); }, //点击移除图片 onDelete() { this.fileList = []; }, }, }; </script> <style> </style> ———————————————— 版权声明:本文为CSDN博主「前端小程」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/CSSAJBQ_/article/details/128012890
-
Vue.js 是一个流行的 JavaScript 框架,可以用于构建交互式的 Web 应用程序。要在 Vue.js 中使用 v-viewer 插件,您可以按照以下步骤进行操作:安装 v-viewer:您可以使用 npm 或 yarn 安装 v-viewer 插件。使用 npm 进行安装:npm install v-viewer --save或使用 yarn 进行安装:yarn add v-viewer导入和使用 v-viewer:在您的 Vue 组件中,导入 v-viewer 并使用它。// 在您的组件文件中 import Viewer from 'v-viewer'; import 'viewerjs/dist/viewer.css'; export default { // 组件的其他选项 // 在组件中注册 v-viewer mounted() { this.$nextTick(() => { this.initViewer(); }); }, methods: { initViewer() { this.$viewer = new Viewer(this.$refs.viewerWrapper); }, }, };您需要引入 viewer.css 样式文件,确保 v-viewer 的样式正确加载。在模板中使用 v-viewer:在需要展示图片的模板中,使用 v-for 循环来渲染图片,并使用 ref 添加引用以便在组件中使用。<template> <div> <div ref="viewerWrapper"> <img v-for="(image, index) in images" :src="image" :key="index" @click="$viewer.show()"> </div> </div> </template> <script> export default { // 组件的其他选项 data() { return { images: ['image1.jpg', 'image2.jpg', 'image3.jpg'], }; }, }; </script>在上述示例中,images 数组包含了要展示的图片地址。当点击图片时,使用 $viewer.show() 方法来显示图片。以上是在 Vue.js 中使用 v-viewer 的简单示例。您可以根据自己的需求进行进一步的配置和定制。请确保按照 v-viewer 的文档了解更多可用的选项。
-
京东微前端和阿里微前端都是当前比较流行的微前端框架,它们有各自的优点和特点。下面是对两者进行简要对比的一些关键点:总体架构:京东微前端:京东微前端采用了基于 Web Components 的技术栈,使用自研的微前端框架来进行组织和管理微前端应用。阿里微前端:阿里微前端采用了基于微前端架构飞冰(Ice)的技术栈,使用自研的插件化中台框架来实现微前端的组织和运行。技术栈和生态系统:京东微前端:京东微前端使用了 Web Components 技术,不依赖特定的前端框架,可以与任意前端技术栈集成。它提供了一套完整的解决方案,包括构建、打包、路由、通信等。阿里微前端:阿里微前端基于飞冰(Ice)框架,使用 React 和 Vue 作为主要的前端技术栈。它也提供了一套完整的解决方案,包括构建、打包、路由、通信等,并且有丰富的生态系统和插件可用。生态和社区支持:京东微前端:虽然京东微前端是京东自研的框架,但它在社区中的影响力逐渐增强,受到一些开发者的关注和支持。但它的生态系统相对较小。阿里微前端:阿里微前端基于飞冰(Ice)框架,得到了阿里巴巴集团内众多项目的使用和支持,具有较大的社区和生态系统,有更多的插件和工具可供选择。适用场景和定位:京东微前端:京东微前端定位于构建复杂应用系统的微前端架构,适用于需要将多个独立的前端应用集成在一起的场景。阿里微前端:阿里微前端定位于构建企业级中后台应用的微前端架构,适用于大型系统和组织内部的微服务架构。需要注意的是,以上对比仅是针对一些关键点进行的简要对比,具体选型还需要根据实际需求和具体情况进行综合考虑。对于特定的项目和团队,这些微前端框架都可能是合适的选择。建议在选择之前详细了解它们的特点、文档和使用案例,并根据实际需求进行评估和测试。
-
canvas是什么? 一个html5支持的新标签,见名知意,canvas就是画板的意思,可以在canvas上画画。css画三角形很简单,但是要画五角星呢,不妨试试canvas。 在html中使用canvas 1、canvas是html5中的一个标签。 新建一个html。并在body中加入canvas标签。 <body> <canvas height="600" width="600"></canvas> </body> 此时canvas已经显示在画板中,只不过因为和body的颜色一样,所以看不出来。 在head中加入css样式。 <style> canvas { border:1px solid; } </style> 这时我们就可以看到canvas了。 2、获取CanvasRenderingContext2D对象用于绘图。 先给canvas一个id属性, <canvas id='canvas' height="600" width="600"></canvas> 1 获取。 <script> const ctx=document.querySelector('#canvas').getContext('2d'); </script> 注意,script标签应该在body标签后(至少在canvas标签后),只有在canvas渲染后才能通过JavaScript代码获取到canvas中的CanvasRenderingContext2D对象。 <body> <canvas height="600" width="600"></canvas> </body> <script> const ctx=document.querySelector('.canvas').getContext('2d'); </script> 3、使用JavaScript代码进行绘画。 <script> const ctx=document.querySelector('#canvas').getContext('2d'); ctx.moveTo(100,100); ctx.lineTo(100,400); ctx.stroke(); </script> 该代码绘制了一条直线。 关于CanvasRenderingContext2D对象更多的绘制方法请参考官方文档。至少现在我们知道canvas是如何使用的了。(一定要注意要在渲染完成后才能通过JavaScript代码获取CanvasRenderingContext2D对象) 在vue3中使用canvas 1、创建vite+vue3项目并运行。 npm init vue@latest 1 2、创建我们的canvas。 这是我们的App.vue文件 <script setup> </script> <template> </template> <style scoped> </style> 创建我们的canvas <script setup> </script> <template> <canvas height="600" width="600"></canvas> </template> <style scoped> canvas { border: 1px solid; } </style> 3、获取CanvasRenderingContext2D对象并绘制直线。 给canvas标签添加一个ref属性 <canvas ref='canvas' height="600" width="600"></canvas> 1 获取canvas对象 <script setup> import {ref} from 'vue' const canvas = ref(); </script> 渲染完成后获取CanvasRenderingContext2D对象 <script setup> import { onMounted, ref } from 'vue' const canvas = ref(); onMounted(() => { const ctx = canvas.value.getContext('2d'); }) </script> 画一条直线 <script setup> import { onMounted, ref } from 'vue' const canvas = ref(); onMounted(() => { const ctx = canvas.value.getContext('2d'); ctx.moveTo(100, 100); ctx.lineTo(100, 400); ctx.stroke(); }) </script> 4、模板 <script setup> import { onMounted, ref } from 'vue' const canvas = ref(); let ctx = ref(); const drawLine = () => { ctx.moveTo(100, 100); ctx.lineTo(100, 400); ctx.stroke(); } const initContext = () => { ctx = canvas.value.getContext('2d'); } onMounted(() => { initContext(); drawLine(); }) </script> <template> <canvas ref='canvas' height="600" width="600"></canvas> </template> <style scoped> canvas { border: 1px solid; } </style> canvas快速入门 绘制折线 一个moveTo配合多个lineTo。可以通过lineWidth设置线宽,还可以设置两个端点和转折处的形状(使用lineCap和lineJoin) // 使用moveTo,lineTo,lineWidth,lineCap,lineJoin const drawCurvedLine = () => { ctx.moveTo(100, 100); ctx.lineTo(400, 100); ctx.lineTo(100, 400); ctx.lineTo(400, 400); ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.stroke(); } 绘制矩形 rect方法以及strokeRect和fillRect。效果等效:strokeRect=rect+stroke,fillRect=rect+stroke。 绘制方式:绘制边框,使用stroke,绘制填充,使用fill。strokeStyle可以设置边框颜色,fillStyle可以设置填充颜色。 // 使用rect,srokeStyle,stroke,fillStyle,fill const drawStrokeRect = () => { ctx.rect(100, 100, 100, 100); ctx.strokeStyle = 'green'; ctx.stroke(); } const drawFillRect = () => { ctx.rect(300, 100, 100, 100); ctx.fillStyle = 'blue'; ctx.fill(); } 将绘制一个绿色边框的矩形和蓝色的矩形。然而,当一同调用时,会发现变成了两个一模一样的矩形(绿色边框或者蓝色填充)。 属性作用域:解决上述问题,使用beginPath方法即可。beginPath将后面对于属性的设置隔离开来,以避免覆盖前面的属性。 // 这里还尝试了使用strokeRect和fillRect替代了rect、stroke、fill const drawStrokeRect = () => { ctx.beginPath(); ctx.strokeStyle='green'; ctx.strokeRect(100,100,100,100); } const drawFillRect = () => { ctx.beginPath(); ctx.fillStyle = 'blue'; ctx.fillRect(300, 100, 100, 100); } 绘制弧线 圆圈 ctx.beginPath(); ctx.arc(100,75,50,0,2*Math.PI); ctx.stroke(); 圆弧 ctx.beginPath(); ctx.arc(100,75,50,90/180*Math.PI,2*Math.PI); ctx.stroke(); 扇形 ctx.beginPath(); ctx.moveTo(100,75); ctx.arc(100,75,50,90/180*Math.PI,2*Math.PI); ctx.closePath(); ctx.fill(); 圆环 const RINGWIDTH = 10; ctx.beginPath(); ctx.arc(100, 75, 90, 0, 2 * Math.PI); ctx.fill(); ctx.beginPath(); ctx.arc(100, 75, 90-2*RINGWIDTH, 0, 2 * Math.PI); ctx.fillStyle = 'white'; ctx.fill(); 补充: 如你所见,绘制扇形时使用了closePath,意思是将所有端点连接起来(一般是将终点和起点连接起来,形成一个闭合图形。只有图形闭合时,fill才能生效)。 所有函数的参数不需要单位。(设置字体时,ctx.font=‘40px’,需要带单位,但确实不是函数的参数) 需要角度作为参数时,都是以弧度的形式提供。计算公式,弧度=角度*Math.PI/180。90度,记为90*Math.PI/180。 更多关于画布的使用,可以查看HTML Canvas 参考手册 (w3school.com.cn) ———————————————— 版权声明:本文为CSDN博主「46590928」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_46590928/article/details/127358493
-
最近在工作中遇到一个需求,就是需要在前端实现一个错误模板Excel的下载功能。 实现下载有两种方式,一种是后端生成一个excel,放在服务器指定目录下,然后前端直接去后端拿。第二种是后端传给前端一个json的list,前端用后端传过来的json的list直接在前端合成一个excel。 第一种:后端生成excel java后端生成excel代码 生成excel工具方法 /** * @author: wu linchun * @creat: 2021-07-29 9:49 * @desc: 将list集合转成Excel文件 * list: 内容list * path: 上传的excel路径 * fileName: 上传的excel名称 **/public static String createExcel(List<? extends Object> list, String path, String fileName) {String result = "";if (list.size() == 0 || list == null) {result = "没有对象信息";} else {Object o = list.get(0);Class<? extends Object> clazz = o.getClass();String className = clazz.getSimpleName();//这里通过反射获取字段数组Field[] fields = clazz.getDeclaredFields();File folder = new File(path);if (!folder.exists()) {folder.mkdirs();}String name = fileName.concat(".xls");WritableWorkbook book = null;File file = null;try {file = new File(path.concat(File.separator).concat(name));//创建xls文件book = jxl.Workbook.createWorkbook(file);WritableSheet sheet = book.createSheet(className, 0);//列int i = 0;//行int j = 0;for (Field f : fields) {j = 0;//这里把字段名称写入excel第一行中Label label = new Label(i, j, f.getName());sheet.addCell(label);j = 1;for (Object obj : list) {Object temp = getFieldValueByName(f.getName(), obj);String strTemp = "";if (temp != null) {strTemp = temp.toString();}//把每个对象此字段的属性写入这一列excel中sheet.addCell(new Label(i, j, strTemp));j++;}i++;}book.write();result = file.getPath();} catch (Exception e) {// TODO Auto-generated catch blockresult = "SystemException";e.printStackTrace();} finally {fileName = null;name = null;folder = null;file = null;if (book != null) {try {book.close();} catch (WriteException e) {// TODO Auto-generated catch blockresult = "WriteException";e.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blockresult = "IOException";e.printStackTrace();}}}}//最后输出文件路径return result;} 1 <img src=“https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” /> 下载文件 /** * @author: wu linchun * @creat: 2021-07-29 13:28 * @desc: 下载错误模板 **/@Overridepublic ResponseEntity<Object> downloadErrorModel() throws FileNotFoundException {// 这里的fileName是指路径+文件名String fileName="move_cms/src/main/resources/static/errorList.xls";File file = new File(fileName);InputStreamResource resource = new InputStreamResource(new FileInputStream((file)));org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();headers.add("Content-Disposition", String.format("attachment;filename="%s"", file.getName()));headers.add("Cache-Control", "no-cache,no-store,must-revalidate");headers.add("Pragma", "no-cache");headers.add("Expires", "0");ResponseEntity<Object> responseEntity = ResponseEntity.ok().headers(headers).contentLength(file.length()).contentType(MediaType.parseMediaType("application/text")).body(resource);return responseEntity;} 1 <img src=“https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” /> 下载excel的接口 /** * @author: wu linchun * @creat: 2021-07-29 11:41 * @desc: 下载错误模板 **/@ApiOperation(value = "下载错误模板")@GetMapping(value = "/downloadErrorModel")@PassTokenpublic ResponseEntity<Object> downloadErrorModel() throws FileNotFoundException {System.out.println("下载错误模板");return welfareGrantService.downloadErrorModel();} 1 <img src=“https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” /> 在掉/downloadErrorModel这个接口是要注意,只能用超链接 “<a:href />调用,不能使用axios调这个接口。这是由于axios获取的是json 而这个接口返回的是file类型,file会以byte流的形式在http上面传输,因此调/downloadErrorModel这个接口将会接收到byte流,axios默认是接收不了byte的,需要进行一些特殊的设置。可以参考一下这篇:vue使用axios接收流文件_weixin_43869439的博客-CSDN博客 <img src=“https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” /> 如果使用超链接的方式访问接口进行文件下载的话,则需要获取到后端服务器的ip+port (实际中不建议这么做,因为get请求会暴露ip地址和端口,可通过浏览器f12看到ip地址和端口,不安全) ip地址可通过 InetAddress.getLocalHost()获取到,端口号直接用@Value从配置文件中拿到。 第二种:前端合成excel 在vue工程中引入两个依赖:file-saver和xlsx <img src=“https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” /> 使用“npm install”,安装package.json中新增的依赖 npm install 1 <img src=“https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” /> <img src=“https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” /> 新增 ExportExcelUtil.js,用来根据list生成excel /* eslint-disable */ import { saveAs } from 'file-saver' import XLSX from 'xlsx' function generateArray(table) {var out = [];var rows = table.querySelectorAll('tr');var ranges = [];for (var R = 0; R < rows.length; ++R) {var outRow = [];var row = rows[R];var columns = row.querySelectorAll('td');for (var C = 0; C < columns.length; ++C) {var cell = columns[C];var colspan = cell.getAttribute('colspan');var rowspan = cell.getAttribute('rowspan');var cellValue = cell.innerText;if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;//Skip rangesranges.forEach(function (range) {if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);}});//Handle Row Spanif (rowspan || colspan) {rowspan = rowspan || 1;colspan = colspan || 1;ranges.push({s: {r: R,c: outRow.length},e: {r: R + rowspan - 1,c: outRow.length + colspan - 1}});};//Handle ValueoutRow.push(cellValue !== "" ? cellValue : null);//Handle Colspanif (colspan)for (var k = 0; k < colspan - 1; ++k) outRow.push(null);}out.push(outRow);}return [out, ranges]; }; function datenum(v, date1904) {if (date1904) v += 1462;var epoch = Date.parse(v);return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000); } function sheet_from_array_of_arrays(data, opts) {var ws = {};var range = {s: {c: 10000000,r: 10000000},e: {c: 0,r: 0}};for (var R = 0; R != data.length; ++R) {for (var C = 0; C != data[R].length; ++C) {if (range.s.r > R) range.s.r = R;if (range.s.c > C) range.s.c = C;if (range.e.r < R) range.e.r = R;if (range.e.c < C) range.e.c = C;var cell = {v: data[R][C]};if (cell.v == null) continue;var cell_ref = XLSX.utils.encode_cell({c: C,r: R});if (typeof cell.v === 'number') cell.t = 'n';else if (typeof cell.v === 'boolean') cell.t = 'b';else if (cell.v instanceof Date) {cell.t = 'n';cell.z = XLSX.SSF._table[14];cell.v = datenum(cell.v);} else cell.t = 's';ws[cell_ref] = cell;}}if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);return ws; } function Workbook() {if (!(this instanceof Workbook)) return new Workbook();this.SheetNames = [];this.Sheets = {}; } function s2ab(s) {var buf = new ArrayBuffer(s.length);var view = new Uint8Array(buf);for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;return buf; } export function export_table_to_excel(id) {var theTable = document.getElementById(id);var oo = generateArray(theTable);var ranges = oo[1];/* original data */var data = oo[0];var ws_name = "SheetJS";var wb = new Workbook(),ws = sheet_from_array_of_arrays(data);/* add ranges to worksheet */// ws['!cols'] = ['apple', 'banan'];ws['!merges'] = ranges;/* add worksheet to workbook */wb.SheetNames.push(ws_name);wb.Sheets[ws_name] = ws;var wbout = XLSX.write(wb, {bookType: 'xlsx',bookSST: false,type: 'binary'});saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), "test.xlsx") } export function export_json_to_excel({multiHeader = [],header,data,filename,merges = [],autoWidth = true,bookType = 'xlsx' } = {}) {/* original data */filename = filename || 'excel-list'data = [...data]data.unshift(header);for (let i = multiHeader.length - 1; i > -1; i--) {data.unshift(multiHeader[i])}var ws_name = "SheetJS";var wb = new Workbook(),ws = sheet_from_array_of_arrays(data);if (merges.length > 0) {if (!ws['!merges']) ws['!merges'] = [];merges.forEach(item => {ws['!merges'].push(XLSX.utils.decode_range(item))})}if (autoWidth) {/*设置worksheet每列的最大宽度*/const colWidth = data.map(row => row.map(val => {/*先判断是否为null/undefined*/if (val == null) {return {'wch': 10};}/*再判断是否为中文*/else if (val.toString().charCodeAt(0) > 255) {return {'wch': val.toString().length * 2};} else {return {'wch': val.toString().length};}}))/*以第一行为初始值*/let result = colWidth[0];for (let i = 1; i < colWidth.length; i++) {for (let j = 0; j < colWidth[i].length; j++) {if (result[j]['wch'] < colWidth[i][j]['wch']) {result[j]['wch'] = colWidth[i][j]['wch'];}}}ws['!cols'] = result;}/* add worksheet to workbook */wb.SheetNames.push(ws_name);wb.Sheets[ws_name] = ws;var wbout = XLSX.write(wb, {bookType: bookType,bookSST: false,type: 'binary'});saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), `${filename}.${bookType}`); } <img src=“https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” /> 在.vue中添加下载excel方法 // 下载ExceldownloadExcel () {import('@/utils/ExportExcelUtil').then(excel => {const filterVal = ['item', 'error']const tHeader = ['item', 'error']const data = this.formatModelJson(filterVal, this.errList)console.log(data)console.info(data)excel.export_json_to_excel({header: tHeader,data,filename: 'errorList'})})},// 格式化JSONformatModelJson (filterVal, list) {return list.map(v => filterVal.map(j => {return v[j]}))}, 1 <img src=“https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” /> errorList是从后端传过来的 exportExcel () {getErrorListExcel().then(response => {this.errList = response.data})} 1 <img src=“https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” /> 使用created方法,在加载.vue时,就调用/getErrorListExcel接口,获取errorList的值。 created () {this.exportExcel()}, 1 <img src=“https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” /> 调用后端接口的api import axios from 'axios' export function getErrorListExcel () {return axios.get('http://127.0.0.1:8082/login/getErrorListExcel', {}).then(response => response).catch(error => error) } <img src=“https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” /> 后端接口设置errorList中的值 @GetMapping("/getErrorListExcel")public List<ErrorItemBO> getErrorListExcel() {List<ErrorItemBO> list = new ArrayList<>();for (int i = 0; i < 10; i++) {ErrorItemBO errorItemBO = new ErrorItemBO();errorItemBO.setItem("item" + i);errorItemBO.setError("error" + i);list.add(errorItemBO);}return list;} 1 <img src=“https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” /> <img src=“https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” /> <img src=“https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” /> 总结 如果涉及到文件下载,尽量后端传一个list,然后都在前端合成相应的文件以到达减轻服务器负担的作用。 最后 最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。 ———————————————— 版权声明:本文为CSDN博主「梅花十三儿」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/Android062005/article/details/127207166
推荐直播
-
华为云码道-玩转OpenClaw,在线养虾2026/03/11 周三 19:00-21:00
刘昱,华为云高级工程师/谈心,华为云技术专家/李海仑,上海圭卓智能科技有限公司CEO
OpenClaw 火爆开发者圈,华为云码道最新推出 Skill ——开发者只需输入一句口令,即可部署一个功能完整的「小龙虾」智能体。直播带你玩转华为云码道,玩转OpenClaw
回顾中 -
华为云码道-AI时代应用开发利器2026/03/18 周三 19:00-20:00
童得力,华为云开发者生态运营总监/姚圣伟,华为云HCDE开发者专家
本次直播由华为专家带你实战应用开发,看华为云码道(CodeArts)代码智能体如何在AI时代让你的创意应用快速落地。更有华为云HCDE开发者专家带你用码道玩转JiuwenClaw,让小艺成为你的AI助理。
回顾中 -
Skill 构建 × 智能创作:基于华为云码道的 AI 内容生产提效方案2026/03/25 周三 19:00-20:00
余伟,华为云软件研发工程师/万邵业(万少),华为云HCDE开发者专家
本次直播带来两大实战:华为云码道 Skill-Creator 手把手搭建专属知识库 Skill;如何用码道提效 OpenClaw 小说文本,打造从大纲到成稿的 AI 原创小说全链路。技术干货 + OPC创作思路,一次讲透!
回顾中
热门标签