• [问题求助] mini版TinyVue组件大家有用过吗?跟antd比咋样?
    mini版TinyVue组件大家有用过吗?跟antd比咋样?
  • [技术干货] Java智慧校园系统源码springboot + vue智慧学校源码 微信小程序+电子班牌
    Java智慧校园系统源码springboot + vue智慧学校源码 微信小程序+电子班牌智慧校园的建设逐渐被师生、家长认可接受,智慧校园通过对在校师生、教务等所有人员的信息以及各种信息搜集与储存,进行数据优化与管理,为师生们提供更加智能化的校园服务。未来智慧校园将不再是一个陌生词,而会真正地应用在更多的校园管理中,让我们的校园生活变得更加美好智慧校园技术架构:后端:Java  框架:springboot 前端页面:vue 小程序:小程序原生开发智慧校园移动家长端应用:通知管理、图片管理、班级考勤、综合素质评价、视频管理、请假管理、成绩管理、个人信息、进离校管理、教师通讯录、家长留言、课堂点名、家长会签到、活动报名、放学管理、学生评价。智慧校园移动移动教师端应用:设备管理、通知管理、图片管理、班级考勤、综合素质评价、视频管理、请假管理、成绩管理、个人信息、进离校管理、家长通讯录、教师通讯录、教师课表、AI智能分析、课堂点名、课堂授课、家长会签到、活动报名、积分商城、倒计时、班级德育、体温检测、放学管理、学生评价。▷学校信息:学校信息自定义格式展示,可上传学校相册,自定义学校基础信息栏。支持管理员或教师对学校的基本学校信息进行编辑并浏览,通过编辑提交后全校可查看▷学科设置:学校学科展示,添加学科,双击修改学科、删除学科。▷组织架构:支持管理员单个创建以及批量创建部门,部门层级最少不低于四级架构;组织架构支持可视展示。▷教师数据:支持教师多部门多角色,支持粘贴复制简易化批量导入,批量导入支持字段错误检索,支持一键重置密码。▷学生数据:支持单个学生信息录入、修改、删除,支持粘贴复制简易化批量导入,支持一键重置密码▷教室管理:班级绑定教室,教室绑定设备,通过教室切换班级即可实现设备一键切换班级,支持添加、删除、修改教室信息。▷权限管理:数据权限、功能权限、角色管理、应用权限。▷课表管理:支持支持管理员课表模板设置,支持复制粘贴简易化批量导入,批量导入支持学科及任课教师错误检索。智慧校园系统框架技术参数要求(提供不少于3张的证明截图,并附上原厂公章,原件备查):1、系统后台采用Java开发语言,前端采用VUE开发框架2、★系统采用微服务springcloud架构的作为后台服务器架构3、★系统支持搭建开发具备服务注册发现、客户负载均衡、服务间通信的微服务架构4、系统支持使用SpringCloud Eurek、SpringCloud Ribbon、restTemplate 等组件进行开发5、注册中心:接受服务提供者的注册,提供服务注册者的存储信息(如:IP、端口号、服务名)与微服务保持心跳6、服务提供者:注册自己的服务到服务中心,服务提供者向注册中心发送自己的信息以及一些健康状态。7、服务消费者:定期向注册中心发送查询请求,以定期获得服务提供者的一些信息(如:IP,端口号,服务名)。8、使用Quartz框架实现任务调度(如:对不同的学校在不同的时间段执行上课提醒)9、使用Mina网络应用型框架(实时接收第三方进离校情况,闸机,人脸机等),后续可发展实时通信组件有:feing(实现服务对服务之间的调用)zuul(网关负载均衡,反向代理,隐藏真实ip地址)
  • [热门活动] 【前端开发专题直播有奖提问】DTSE Tech Talk 技术直播 NO.56:看直播提问题赢华为云定制长袖卫衣、华为云定制U型按摩枕等好礼!
    中奖结果公示感谢各位小伙伴参与本次活动,欢迎关注华为云DTSE Tech Talk 技术直播更多活动~本次活动获奖名单如下:请于4月23日下班前在此问卷中反馈您的中奖邮寄信息~直播简介【直播主题】手把手教你实现 mini版TinyVue组件库【直播时间】2024年4月17日 16:30-18:00【直播专家】阿健 华为云开源 DTSE 技术布道师【直播简介】在前端 Web 开发过程中,跨版本兼容性问题是一个普遍存在的挑战。为了解决这些痛点,OpenTiny 推出跨端、跨框架、跨版本组件库 TinyVue。本期直播聚焦于华为云的前端开源组件库 TinyVue,通过 Mini版 TinyVue 的代码实践与大家共同深入解读 Vue2/Vue3 不同版本间的差异。这对于提升用户体验,减低维护成本,提升开发者技术洞察有重要意义。活动介绍【互动方式】直播前您可以在本帖留下您疑惑的问题,专家会在直播时为您解答。直播后您可以继续在本帖留言,与专家互动交流。我们会在全部活动结束后对参与互动的用户进行评选。【活动时间】即日起—2024年4月18日【奖励说明】评奖规则:活动1:直播期间在直播间提出与直播内容相关的问题,对专家评选为优质问题的开发者进行奖励。奖品:华为云定制长袖卫衣活动2:在本帖提出与直播内容相关的问题,由专家在所有互动贴中选出最优问题贴的开发者进行奖励。奖品:华为云定制U型按摩枕更多直播活动直 播互动有礼:官网直播间发口令“华为云 DTSE”抽华为云定制雨伞、填写问卷抽华为云定制保温杯等好礼。有奖实操 :4月17日-4月18日,完成课后动手实验即送华为云定制T恤。【注意事项】1、活动期间同类子活动每个ID(同一姓名/电话/收货地址)提问数≤20个;所有参与活动的问题,如发现为复用他人内容或直播间中重复内容,则取消获奖资格。2、为保证您顺利领取活动奖品,请您在活动公示奖项后2个工作日内私信提前填写奖品收货信息,如您没有填写,视为自动放弃奖励。3、活动奖项公示时间截止2024年4月19日,如未反馈邮寄信息视为弃奖。本次活动奖品将于奖项公示后30个工作日内统一发出,请您耐心等待。4、活动期间同类子活动每个ID(同一姓名/电话/收货地址)只能获奖一次,若重复则中奖资格顺延至下一位合格开发者,仅一次顺延。5、如活动奖品出现没有库存的情况,华为云工作人员将会替换等价值的奖品,获奖者不同意此规则视为放弃奖品。6、其他事宜请参考【华为云社区常规活动规则】。
  • [技术干货] blog-engine-09-nuxt 构建快速、SEO友好和可扩展的Web应用程序变得轻松【转】
    nuxtNuxt是一个免费且开源的框架,提供直观且可扩展的方式来创建基于Vue.js的类型安全、高性能和生产级别的全栈网站和应用程序。它提供了许多功能,使得构建快速、SEO友好和可扩展的Web应用程序变得轻松,包括:服务器端渲染、静态站点生成、混合渲染和边缘渲染带有代码分割和预取的自动路由数据获取和状态管理SEO优化和元标签定义组件、组合和实用工具的自动导入无需配置的TypeScript使用我们的server/目录进行全栈开发通过200多个模块进行扩展部署到各种托管平台…等等 🚀🚀 入门指南使用以下命令创建一个新的入门项目。这将创建一个带有所有必要文件和依赖项的入门项目:  [bash]npx nuxi@latest init <my-project>还可以发现 nuxt.new:在 CodeSandbox、StackBlitz 或本地打开一个 Nuxt 入门项目,几秒钟内即可开始运行。💻 Vue 开发简单、直观且强大,Nuxt 让您以合理的方式编写 Vue 组件。每个重复的任务都是自动化的,因此您可以放心地专注于编写全栈 Vue 应用程序。app.vue 示例:  [vue]<script setup lang="ts"> useSeoMeta({ title: '遇见 Nuxt', description: '直观的 Vue 框架。' }) </script> <template> <div id="app"> <AppHeader /> <NuxtPage /> <AppFooter /> </div> </template> <style scoped> #app { background-color: #020420; color: #00DC82; } </style>📖 文档我们强烈建议您查看 Nuxt 文档 来提升技能。这是一个了解框架更多信息的好资源。它涵盖了从入门到高级主题的所有内容。🧩 模块探索我们的 模块列表 来加速您的 Nuxt 项目,这些模块是由windows 实际测试初始化创建文件夹:  [bat]d: mkdir D:\blogs\nuxt cd D:\blogs\nuxt初始化:  [sh]npx nuxi@latest init test报错:  [plaintext][19:02:41] ERROR Error: Failed to download template from registry: Failed to download https://raw.githubusercontent.com/nuxt/starter/templates/templates/v3.json: TypeError: fetch failed网络不同,暂时不看了。参考资料https://github.com/nuxt/nuxt?tab=readme-ov-file转载自https://houbb.github.io/2016/04/13/blog-engine-09-nuxt-01-intro
  • [技术干货] blog-engine-08-vuepress 以 Markdown 为中心的静态网站生成器【转】
    vuepressVuePress 是一个以 Markdown 为中心的静态网站生成器。你可以使用 Markdown 来书写内容(如文档、博客等),然后 VuePress 会帮助你生成一个静态网站来展示它们。VuePress 诞生的初衷是为了支持 Vue.js 及其子项目的文档需求,但是现在它已经在帮助大量用户构建他们的文档、博客和其他静态网站。它是如何工作的?一个 VuePress 站点本质上是一个由 Vue 和 Vue Router 驱动的单页面应用 (SPA)。路由会根据你的 Markdown 文件的相对路径来自动生成。每个 Markdown 文件都通过 markdown-it 编译为 HTML ,然后将其作为 Vue 组件的模板。因此,你可以在 Markdown 文件中直接使用 Vue 语法,便于你嵌入一些动态内容。在开发过程中,我们启动一个常规的开发服务器 (dev-server) ,并将 VuePress 站点作为一个常规的 SPA。如果你以前使用过 Vue 的话,你在使用时会感受到非常熟悉的开发体验。在构建过程中,我们会为 VuePress 站点创建一个服务端渲染 (SSR) 的版本,然后通过虚拟访问每一条路径来渲染对应的 HTML 。这种做法的灵感来源于 Nuxt 的 nuxt generate 命令,以及其他的一些项目,比如 Gatsby。为什么不是 …?NuxtNuxt 是一套出色的 Vue SSR 框架, VuePress 能做的事情,Nuxt 实际上也同样能够胜任。但 Nuxt 是为构建应用程序而生的,而 VuePress 则更为轻量化并且专注在以内容为中心的静态网站上。VitePressVitePress 是 VuePress 的孪生兄弟,它同样由 Vue.js 团队创建和维护。VitePress 甚至比 VuePress 要更轻更快,但它在灵活性和可配置性上作出了一些让步,比如它不支持插件系统。当然,如果你没有进阶的定制化需求, VitePress 已经足够支持你将你的内容部署到线上。这个比喻可能不是很恰当,但是你可以把 VuePress 和 VitePress 的关系看作 Laravel 和 Lumen 。Docsify / Docute这两个项目同样都是基于 Vue,然而它们都是完全的运行时驱动,因此对 SEO 不够友好。如果你并不关注 SEO,同时也不想安装大量依赖,它们仍然是非常好的选择!HexoHexo 一直驱动着 Vue 2.x 的文档。Hexo 最大的问题在于他的主题系统太过于静态以及过度地依赖纯字符串,而我们十分希望能够好好地利用 Vue 来处理我们的布局和交互。同时,Hexo 在配置 Markdown 渲染方面的灵活性也不是最佳的。GitBook过去我们的子项目文档一直都在使用 GitBook 。GitBook 最大的问题在于当文件很多时,每次编辑后的重新加载时间长得令人无法忍受。它的默认主题导航结构也比较有限制性,并且,主题系统也不是 Vue 驱动的。GitBook 背后的团队如今也更专注于将其打造为一个商业产品而不是开源工具。在线体验可以直接使用 cid:link_0 在线体验。安装依赖环境Node.js v18.16.0+包管理器,如 pnpm、yarn、npm 等。本机测试  [plaintext]>node -v v20.10.0 >npm -v 10.2.3创建项目通过命令行创建你可以通过 create-vuepress 直接创建项目模板。创建文件夹:  [bat]d: mkdir D:\blogs\vuepress cd D:\blogs\vuepress执行创建命令:  [plaintext]npm init vuepress vuepress-starter配置项根据个人需要选择:  [plaintext]D:\>cd D:\blogs\vuepress D:\blogs\vuepress>npm init vuepress vuepress-starter Need to install the following packages: create-vuepress@2.0.0-rc.12 Ok to proceed? (y) y ? Select a language to display / 选择显示语言 简体中文 ? 选择包管理器 npm ? 你想要使用哪个打包器? vite ? 你想要创建什么类型的项目? blog 生成 package.json... ? 设置应用名称 my-vuepress-site ? 设置应用版本号 0.0.1 ? 设置应用描述 A VuePress project ? 设置协议 MIT ? 是否需要一个自动部署文档到 GitHub Pages 的工作流? No 生成模板... ? 选择你想使用的源 国内镜像源 安装依赖... 这可能需要数分钟,请耐心等待. 我们无法正确输出子进程的进度条,所以进程可能会看似未响应使用 demo 模式查看:  [plaintext]added 210 packages in 34s 模板已成功生成! ? 是否想要现在启动 Demo 查看? Yes 启动开发服务器... 启动成功后,请在浏览器输入给出的开发服务器地址(默认为 'localhost:8080') > my-vuepress-site@0.0.1 docs:dev > vuepress dev docs vite v5.1.6 dev server running at: ➜ Local: cid:link_1 ➜ Network: http://192.168.1.6:8080/访问浏览器访问:cid:link_1启动后就是非常简洁的页面,类似 vuepress 的官方风格。目录结构创建完成后,你项目的目录结构应该是这样的:  [plaintext]├─ docs │ ├─ .vuepress │ │ └─ config.js │ └─ README.md └─ package.jsondocs 目录是你放置 Markdown 文件的地方,它同时也会作为 VuePress 的源文件目录。docs/.vuepress 目录,即源文件目录下的 .vuepress 目录,是放置所有和 VuePress 相关的文件的地方。当前这里只有一个配置文件。默认还会在该目录下生成临时文件、缓存文件和构建输出文件。建议你把它们添加到 .gitignore 文件中。.gitignore  [plaintext]# VuePress 默认临时文件目录 .vuepress/.temp # VuePress 默认缓存目录 .vuepress/.cache # VuePress 默认构建生成的静态文件目录 .vuepress/dist开始使用 VuePress启动开发服务器你可以在 package.json 中添加一些 scripts :  [js]{ "scripts": { "docs:dev": "vuepress dev docs", "docs:build": "vuepress build docs" } }运行 docs:dev 脚本可以启动开发服务器:  [sh]npm run docs:devVuePress 会在 http://localhost:8080 启动一个热重载的开发服务器。当你修改你的 Markdown 文件时,浏览器中的内容也会自动更新。构建你的网站运行 docs:build 脚本可以构建你的网站:  [sh]npm run docs:build在 docs/.vuepress/dist 目录中可以找到构建生成的静态文件。你可以查看 部署 来了解如何部署你的网站。小结不得不说,感觉 vuepress 设计的非常友好,使用起来很简单,国内文档写的也很不错。参考资料https://v2.vuepress.vuejs.org/zh/guide/getting-started.html转载自https://houbb.github.io/2016/04/13/blog-engine-08-vuepress-01-intro
  • [技术干货] blog-engine-02-通过博客引擎 jekyll 构建 github pages 博客实战笔记【转】
    jekyll 搭建教程jekyll 搭建教程https://segmentfault.com/a/1190000002539546?_ea=304992Gem安装 Ruby,请访问 下载地址。http://rubyinstaller.org/downloadsJekyllJekyll 是一个简单且具备博客特性的静态网站生成器。https://jekyllrb.com/docs/home/使用以下命令安装 Jekyll。  [plaintext]$ gem install jekyll在中国可能需要使用代理软件。然后,请等待并学习如何使用它。或者,您可以使用 rails 替代 Jekyll。如果您使用的是 Mac,请使用以下命令。  [plaintext]$ sudo gem install jekyllGit您需要了解如何使用 Git。Git 文档http://www.runoob.com/git/git-tutorial.html在 Windows 下安装 Jekyll有时候工作需要,在 Windows 系统下编辑博客,想要看到效果却十分麻烦。因此尝试在 Windows 下安装 Jekyll。安装 Ruby下载下载 适合你计算机的合适版本的安装程序;http://rubyinstaller.org/downloads/安装安装时勾选上 Add Ruby executables to your PATH;查看版本在命令行中输入如下命令:  [plaintext]$ ruby -v ruby 2.3.3p222 (2016-11-21 revision 56859) [x64-mingw32]安装 DevKit下载下载 适合你计算机的合适版本的安装程序;选择匹配的软件。本次选择 DevKit-mingw64-64-4.7.2-20130224-1432-sfx.exe, For use with Ruby 2.0 to 2.3 (x64 - 64bits only)解压下载并解压文件至路径如D:\Devkit下:运行  [plaintext]$ cd d:\Devkit [D:\Devkit]$ ruby dk.rb init [INFO] found RubyInstaller v2.3.3 at D:/Ruby23-x64 Initialization complete! Please review and modify the auto-generated 'config.yml' file to ensure it contains the root directories to all of the installed Rubies you want enhanced by the DevKit.查看 config.yml 应该有如下内容  [yml]# This configuration file contains the absolute path locations of all # installed Rubies to be enhanced to work with the DevKit. This config # file is generated by the 'ruby dk.rb init' step and may be modified # before running the 'ruby dk.rb install' step. To include any installed # Rubies that were not automagically discovered, simply add a line below # the triple hyphens with the absolute path to the Ruby root directory. # # Example: # # --- # - C:/ruby19trunk # - C:/ruby192dev # --- - D:/Ruby23-x64查看和安装  [plaintext][D:\Devkit]$ ruby dk.rb review Based upon the settings in the 'config.yml' file generated from running 'ruby dk.rb init' and any of your customizations, DevKit functionality will be injected into the following Rubies when you run 'ruby dk.rb install'. D:/Ruby23-x64 [D:\Devkit]$ ruby dk.rb install [INFO] Updating convenience notice gem override for 'D:/Ruby23-x64' [INFO] Installing 'D:/Ruby23-x64/lib/ruby/site_ruby/devkit.rb'安装 Jekyll确保 gem 已安装  [plaintext][D:\Devkit]$ gem -v 2.5.2安装 Jekyll  [plaintext]$ gem install jekyll问题错误:Permission denied - bind(2) for 127.0.0.1:4000bind localhost:4000 failed使用如下命令:  [plaintext]$ netstat -ano $ tasklist /svc /FI "PID eq ${pid}"pid 是占用 localhost:4000 的线程的结果;使用任务管理器结束该线程;请在 Gemfile 中添加以下内容以避免轮询更改:  [plaintext]gem 'wdm', '>= 0.1.0' if Gem.win_platform?gem ‘wdm’, ‘>= 0.1.0’ if Gem.win_platform?我尝试了很多次,但仍然失败。幸运的是,Jekyll 仍然可以正常工作jekyll-paginate  [plaintext]配置文件: /Users/houbinbin/blog/houbb.github.io/_config.yml 依赖错误: 哎呀!看起来你没有安装 jekyll-paginate 或者它的某个依赖项。为了按照当前配置使用 Jekyll,你需要安装这个 gem。来自 Ruby 的完整错误信息是: 'cannot load such file -- jekyll-paginate' 如果遇到问题,你可以在 http://jekyllrb.com/help/ 找到有用的资源! jekyll 3.1.3 | 错误: jekyll-paginate解决方法:  [plaintext]$ sudo gem install jekyll-paginate谷歌文件加载慢fonts.googleapis.com加载慢解决办法我是直接将semantic引用此文件的地方注释掉了。TOC 目录TOC把这个添加到文件中,就可以生成目录。  [plaintext]* any list {:toc}分页base page zh_CN最多页数限制分页功能插件使得 paginator liquid 对象具有下列属性:属性说明page当前页码per_page每页文章数量posts当前页的文章列表total_posts总文章数total_pages总页数previous_page上一页页码 或 nil(如果上一页不存在)previous_page_path上一页路径 或 nil(如果上一页不存在)next_page下一页页码 或 nil(如果下一页不存在)next_page_path下一页路径 或 nil(如果下一页不存在)Searchnice querySEO掌心 SEORang SEO优化 jekyll 站点的 SEO 技巧博客写好之后,直接搜索基本搜索不到。双边括号特殊符号,比如使用 vue 学习的时候,就会用到双边花括号,但是这个是 jekyll 的内置符号。解决方案如下:  [plaintext]{% comment %} 这里是各种包含奇怪花括号 {{{0}}} 的地方 {% endcomment %} TODO文章置顶has done!多说sitemap报错  [plaintext]$ gem install jekyll-sitemap ERROR: Could not find a valid gem 'Jekyll-sitemap' (>= 0), here is why: Unable to download data from https://gems.ruby-china.org/ - bad response Not Found 404 (https://gems.ruby-china.org/specs.4.8.gz)解决  [plaintext]gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/日志  [plaintext]https://gems.ruby-china.com/ added to sources source https://rubygems.org/ not present in cache重新执行  [plaintext]sudo gem install jekyll-sitemap日志  [plaintext]Fetching: jekyll-sitemap-1.2.0.gem (100%) Successfully installed jekyll-sitemap-1.2.0 Parsing documentation for jekyll-sitemap-1.2.0 Installing ri documentation for jekyll-sitemap-1.2.0 Done installing documentation for jekyll-sitemap after 0 seconds WARNING: Unable to pull data from 'https://gems.ruby-china.org/': bad response Not Found 404 (https://gems.ruby-china.org/specs.4.8.gz) 1 gem installed参考资料seohttp://vdaubry.github.io/2014/10/21/SEO-for-your-Jekyll-blog/https://crispgm.com/page/48-tips-for-jekyll-you-should-know.htmlJekyll自动生成站点地图如何让谷歌和百度搜索到自己GitHub上的博客Jekyll SEO Taghttps://github.com/jekyll/jekyll-seo-tagsitemaphttp://faso.me/notes/2013/07/27/jekyll-sitemap-without-plugin/index.htmlrubyhttps://www.jianshu.com/p/60f3707cb3ce?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendationGitHub 博客– Jekyll–代码高亮,Liquid 转义字符Jekyll 中文文档极客学院中文文档转载自https://houbb.github.io/2016/04/13/blog-engine-02-jekyll-01-install
  • [技术干货] blog-engine-01-常见博客引擎 jekyll/hugo/Hexo/Pelican/Gatsby/VuePress/Nuxt.js/Middleman 对比【转】
    前言由于个人一直喜欢使用 markdown 来写 个人博客,最近就整理了一下有哪些博客引擎。感兴趣的小伙伴也可以选择自己合适的。常见的博客引擎有哪些?除了 Jekyll、Hugo 和 Hexo,还有许多其他流行的博客引擎可供选择。以下是一些常见的博客引擎:WordPress:WordPress 是最流行的博客引擎之一,它提供了丰富的插件和主题,使用户能够轻松地创建和管理博客。Ghost:Ghost 是一个专注于博客的开源平台,它提供了简洁的界面和强大的编辑工具,适合那些追求写作体验的用户。Pelican:Pelican 是一个用 Python 编写的静态博客生成器,它使用简单的 Markdown 或 reStructuredText 格式来撰写内容。Gatsby:Gatsby 是一个基于 React 的静态网站生成器,它可以用来创建博客和其他类型的静态网站,具有出色的性能和灵活性。VuePress:VuePress 是一个由 Vue 驱动的静态网站生成器,它具有简单的配置和强大的插件系统,适合那些熟悉 Vue.js 的用户。Nuxt.js:Nuxt.js 是一个基于 Vue.js 的通用应用框架,它可以用来创建静态网站和单页面应用(SPA),具有灵活的路由和组件系统。Harp:Harp 是一个用 Node.js 编写的静态网站生成器,它支持使用 Markdown、Jade 和 EJS 等模板语言来创建内容。Middleman:Middleman 是一个用 Ruby 编写的静态网站生成器,它支持使用 Haml、Slim 和 Sass 等语言来构建网站。这些博客引擎各有特点,你可以根据自己的需求和偏好选择最适合的。博客引擎的对比表格以下是一份对一些常见博客引擎的比较表格。特性/博客引擎JekyllHugoHexoPelicanGatsbyVuePressNuxt.jsMiddleman语言RubyGoNode.jsPythonJavaScript (React)JavaScript (Vue)JavaScript (Vue)Ruby模板引擎LiquidGo TemplatesEJS (Embedded JavaScript)Jinja2React (JSX)VueVueERB (Embedded Ruby)速度中等快速中等中等快速快速快速快速主题系统有有有有有有有有插件系统有有有有插件生态较小插件系统较小有有社区活跃度高高高中等高高高中等数据源YAML、JSON、CSVTOML、YAMLYAML、JSONreStructuredText、Markdown数据源插件MarkdownMarkdownYAML、JSON构建时间取决于网站规模和内容量快速取决于网站规模和内容量取决于网站规模和内容量快速快速快速取决于网站规模和内容量托管支持GitHub Pages、Netlify、自己的服务器GitHub Pages、Netlify、自己的服务器GitHub Pages、Netlify、自己的服务器GitHub Pages、Netlify、自己的服务器Netlify、Vercel、自己的服务器GitHub Pages、Netlify、自己的服务器GitHub Pages、Netlify、自己的服务器GitHub Pages、Netlify、自己的服务器适用场景个人博客、文档个人博客、文档个人博客个人博客、文档博客、文档、应用官网文档、博客文档、博客小型网站、博客小结以下是对各个博客引擎的简单总结:Jekyll:静态网站生成器,使用 Ruby 编写。简单易用,支持 Markdown 和 Liquid 模板语言。社区活跃,有丰富的主题和插件可用。Hugo:快速且高效的静态网站生成器,使用 Go 编写。构建速度快,适合大型网站和博客。支持 Markdown 和自定义模板,具有灵活的主题系统。Hexo:快速的静态网站生成器,使用 Node.js 编写。支持 Markdown,使用 EJS 模板语言。插件丰富,可扩展性强。WordPress:最流行的博客平台之一,使用 PHP 和 MySQL。功能强大,支持插件和主题定制。需要服务器支持,适合有一定技术基础的用户。Ghost:简洁的博客平台,使用 Node.js 和 Ember.js。提供良好的写作体验和编辑工具。面向写作者和博客专业人士。Pelican:Python 编写的静态网站生成器。使用简单,支持 Markdown 和 reStructuredText。社区活跃,有各种插件可用。Gatsby:基于 React 的静态网站生成器,使用 GraphQL 查询数据。具有出色的性能和灵活性,支持 Markdown 和 React 组件。适合构建高度交互的博客和网站。VuePress:基于 Vue.js 的静态网站生成器,使用 Markdown 和 Vue 组件。简单易用,支持自定义主题和插件。适合开发者和技术博客。转载自cid:link_02016/04/13/blog-engine-01-overview
  • [技术干货] Babel解决ES6不能被所有浏览器解析问题【转】
    一、简介1.ES6的某些高级语法在浏览器环境甚至是Node.js环境中无法执行。2.Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行执行。二、安装BabelBabel提供babel-cli工具,用于命令行转码安装:npm i -g babel-cli检查安装:babel --version三、Babel的使用1)安装Node.js环境:cid:link_02)进入项目,初始化项目:npm init -y3)创建文件 src/example.js4)安装转码器,在项目中安装:npm i --save-dev babel-preset-env 或 npm i --save-dev babel-preset-es20155)创建文件并配置:.babelrc {"presets":["env","es2015"],"plugins":[]}注:Babel的配置文件是.babelrc,存放在项目的根目录下,该文件用来设置转码规则和插件。四、文件转化文件:babel src/index.js -o dist/index.js注:-o(--out-file):输出的意思文件夹:babel src -d dist注:-d(--out-dir):指定输入目录实时监控:babel src -w -d dist注:-w:watch监控五、自定义脚本1)改写package.json{ // ... "scripts":{ // ... "build": "babel src\\index.js -o dist\\index.js" },}2)转码时,执行下面命令mkdir distnpm run build  转载自https://www.cnblogs.com/yulingzhiling/p/15844424.html
  • [技术干货] Vue中的$nextTick有什么作用?【转】
    一、NextTick是什么官方对其的定义在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM什么意思呢?我们可以理解成,Vue 在更新 DOM 时是异步执行的。当数据发生变化,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新举例一下Html结构<div id="app"> {{ message }} </div>构建一个vue实例const vm = new Vue({  el: '#app',  data: {    message: '原始值'  }})修改messagethis.message = '修改后的值1'this.message = '修改后的值2'this.message = '修改后的值3'这时候想获取页面最新的DOM节点,却发现获取到的是旧值console.log(vm.$el.textContent) // 原始值这是因为message数据在发现变化的时候,vue并不会立刻去更新Dom,而是将修改数据的操作放在了一个异步操作队列中如果我们一直修改相同数据,异步操作队列还会进行去重等待同一事件循环中的所有数据变化完成之后,会将队列中的事件拿来进行处理,进行DOM的更新为什么要有nexttick举个例子{{num}}for(let i=0; i<100000; i++){    num = i}如果没有 nextTick 更新机制,那么 num 每次更新值都会触发视图更新(上面这段代码也就是会更新10万次视图),有了nextTick机制,只需要更新一次,所以nextTick本质是一种优化策略二、使用场景如果想要在修改数据后立刻得到更新后的DOM结构,可以使用Vue.nextTick()第一个参数为:回调函数(可以获取最近的DOM结构)第二个参数为:执行函数上下文// 修改数据vm.message = '修改后的值'// DOM 还没有更新console.log(vm.$el.textContent) // 原始的值Vue.nextTick(function () {  // DOM 更新了  console.log(vm.$el.textContent) // 修改后的值})组件内使用 vm.$nextTick() 实例方法只需要通过this.$nextTick(),并且回调函数中的 this 将自动绑定到当前的 Vue 实例上this.message = '修改后的值'console.log(this.$el.textContent) // => '原始的值'this.$nextTick(function () {    console.log(this.$el.textContent) // => '修改后的值'})$nextTick() 会返回一个 Promise 对象,可以是用async/await完成相同作用的事情this.message = '修改后的值'console.log(this.$el.textContent) // => '原始的值'await this.$nextTick()console.log(this.$el.textContent) // => '修改后的值'三、实现原理源码位置:/src/core/util/next-tick.jscallbacks也就是异步操作队列callbacks新增回调函数后又执行了timerFunc函数,pending是用来标识同一个时间只能执行一次01112131415161718192021222324252627282930export function nextTick(cb?: Function, ctx?: Object) {  let _resolve;  // cb 回调函数会经统一处理压入 callbacks 数组  callbacks.push(() => {    if (cb) {      // 给 cb 回调函数执行加上了 try-catch 错误处理      try {        cb.call(ctx);      } catch (e) {        handleError(e, ctx, 'nextTick');      }    } else if (_resolve) {      _resolve(ctx);    }  });  // 执行异步延迟函数 timerFunc  if (!pending) {    pending = true;    timerFunc();  }  // 当 nextTick 没有传入函数参数的时候,返回一个 Promise 化的调用  if (!cb && typeof Promise !== 'undefined') {    return new Promise(resolve => {      _resolve = resolve;    });  }}timerFunc函数定义,这里是根据当前环境支持什么方法则确定调用哪个,分别有:Promise.then、MutationObserver、setImmediate、setTimeout通过上面任意一种方法,进行降级操作131415161718192021222324252627282930313233343536export let isUsingMicroTask = falseif (typeof Promise !== 'undefined' && isNative(Promise)) {  //判断1:是否原生支持Promise  const p = Promise.resolve()  timerFunc = () => {    p.then(flushCallbacks)    if (isIOS) setTimeout(noop)  }  isUsingMicroTask = true} else if (!isIE && typeof MutationObserver !== 'undefined' && (  isNative(MutationObserver) ||  MutationObserver.toString() === '[object MutationObserverConstructor]')) {  //判断2:是否原生支持MutationObserver  let counter = 1  const observer = new MutationObserver(flushCallbacks)  const textNode = document.createTextNode(String(counter))  observer.observe(textNode, {    characterData: true  })  timerFunc = () => {    counter = (counter + 1) % 2    textNode.data = String(counter)  }  isUsingMicroTask = true} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {  //判断3:是否原生支持setImmediate  timerFunc = () => {    setImmediate(flushCallbacks)  }} else {  //判断4:上面都不行,直接用setTimeout  timerFunc = () => {    setTimeout(flushCallbacks, 0)  }}无论是微任务还是宏任务,都会放到flushCallbacks使用这里将callbacks里面的函数复制一份,同时callbacks置空依次执行callbacks里面的函数12345678function flushCallbacks () {  pending = false  const copies = callbacks.slice(0)  callbacks.length = 0  for (let i = 0; i < copies.length; i++) {    copies[i]()  }}小结:把回调函数放入callbacks等待执行将执行函数放到微任务或者宏任务中事件循环到了微任务或者宏任务,执行函数依次执行callbacks中的回调转载自https://www.cnblogs.com/smileZAZ/p/18027869
  • [技术干货] pinia初学习
    两种写法定义pinia第一种:对象形式不需要写ref state直接就是响应式数据import { defineStore } from "pinia" export const useCounterStore = defineStore("useCounterStore ", { state:() =>{ return{ } }, actions:{ }, getters:{ } })第二种: 匿名函数形式需要写ref定义state 不然不是响应式 用了ref 在actions和getters中使用的时候就需要.valueimport { defineStore } from "pinia" import { ref, computed } from "vue" export const useCounterStore = defineStore("useCounterStore ", () => { let num = ref(1) // 自动识别为state const dobuleNum = computed(() => { // 使用了computed 自动识别为计算属性 return num.value * 2 }) function addNum(){ //自动识别为actions pinia没有muctions了 actions就可以同步异步都可以 } return { num, dobuleNum,addNum } })state重置状态将state的状态重置到初始值let data = useCountPinia() data.$reset() //重置同时修改多个状态// 第一种方法 接受一个对象 let data = useCountPinia() data.$patch({ name:xxx, num:xxx }) // 第二种方法:接受一个函数 data.$patch((state)=>{ state.name = xxxx state.num = xxx })替换整个state通过 store.$state = {}来替换let data = useCountPinia() data.$state = { xxx }订阅状态在vue中可以使用watch来对vuex的数据进行监听 但是如果想在js中使用的话就要借助$subscribevuex中也有$subscribe 具体看文档吧pinia中的$subscribe写法如下import { useCountPinia } from "@/store/index" const data = useCountPinia() data.$subscribe((mutation,state)=>{ // mutation是记录state变化信息的对象 state是state对象 // 修改state之后会触发此回调函数 }) // 分析mutation和state是什么 // mutaton: { "type": "patch function", // 修改的类型 /** "patch function"通过$patch传入函数修改 "patch object" 通过$patch传入对象修改 "direct" 直接修改state */ "storeId": "useCounterStore ", // 库的id "events": [ // 存储变化的数据信息 {...} ] } // state 是一个Proxy对象 是state的代理对象 那么 watch 和 subscribe 捕获数据变化的区别是什么?watch只会捕获新旧值不同的情况subscribe不仅会捕获新旧值不同 只要是进行修改 就会捕获二、Getters箭头函数没有this所以需要使用接收参数state来实现getters: { // 自动将返回类型推断为数字 doubleCount(state) { return state.counter * 2 }, // 返回类型必须明确设置 doublePlusOne(): number { return this.counter * 2 + 1 }, doubleNum:(state) =>{ return state.num *2 } },传递参数给getter没有办法直接获取 但是可以再返回一个函数 在这个函数中去接收 doubleNum:(state) =>{ return (value) =>{ // 接收的参数 return state.num + value } }<h1> {{store.doubleNum('我是传参')}} </h1>需要注意的是 当使用了传参之后 getter则不再缓存 只是您调用的参数三、ActionsActions相当于组件中的methods 可以使用actions进行定义export const useStore = defineStore('main',() =>{ state:()=>{ return { num:0 } }, actions:{ add(){ this.num ++ } } })actions可以是异步的 代替了vuex 中的mutations订阅actionsxxxxxxx四、plugins用于补充扩展store。本质其实就是一个函数,可以有以下操作向Store添加新属性定义Store时添加新选项为Store添加新方法包装现有方法更改甚至取消操作实现本地存储等副作用仅适用于特定Store在mian.js文件中使用pinia.use()将插件添加到pinia实例中。下面举例为所有的store添加一个静态属性//main文件 import { createPinia } from "pinia" // 为安装此插件后创建的每个store添加一个名为 `level` 的属性 function SecretPiniaPlugin() { return { level:'1.982' } } // 将插件提供给pinia const pinia = createPinia() pinia.use(SecretPiniaPlugin) // 在另一个文件中 const store = useStore() store.level // 'the cake is a lie'添加新属性有两种写法// 第一种 const pinia = createPinia() pinia.use(()=>{ return { name:'我是第一种' } }) // 第二种 const pinia = createPinia() pinia.use(({store}) =>{ store.name = "我是第二种" })转载自https://www.cnblogs.com/zxl327/p/17911621.html
  • [技术干货] 为什么需要使用$nextTick?【转】
    首先我们来看看官方对于$nextTick的定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。由于vue的试图渲染是异步的,生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,原因是在created()钩子函数执行的时候DOM其实并未进行渲染,而此时进行DOM操作是徒劳的,所以一定要将DOM操作的js代码放到Vue.nextTick()的回调函数中。除了在created()钩子函数中使用之外咱们还会遇到很多种需要使用到Vue.nextTick()的场景,如下所示:咱们日常生活中常常会遇上上述场景,当我们点击按钮更新数据时候,如下示例:<template> <div> <input type="text" v-if = "isShow" ref="input"/> <button @click="handleClick">点击显示输入框,并且获取输入框焦点</button> </div> </template> <script> export default { data() { return { isShow: false } }, methods : { handleClick () { this.isShow = true this.$refs.input.focus() //控制栏会报错,因为还没有这个dom } } } </script>点击控制栏显示效果:控制栏报错,提示没有获取到dom元素;所以现在Vue.nextTick()派上了用场,Vue.nextTick() 方法的作用正是等待上一次事件循环执行完毕,并在下一次事件循环开始时再执行回调函数。这样可以保证回调函数中的 DOM 操作已经被 Vue.js 进行过更新,从而避免了一些潜在的问题,如下代码所示:<template> <div> <input type="text" v-if = "isShow" ref="input"/> <button @click="handleClick">点击显示输入框,并且获取输入框焦点</button> </div> </template> <script> export default { data() { return { isShow: false } }, methods : { handleClick () { this.isShow = true this.$nextTick(()=>{ this.$refs.input.focus() }) } } } </script>加上this.$nextTick后就能够使得输入框获取到焦点;总而言之Vue.nextTick()就是下次 DOM 更新渲染后执行延迟回调函数。在日常开发中,我们在修改数据之后使用这个方法,就可以获取更新后的 DOM的同时进行在对DOM进行相对应操作的 js代码;2.$nextTick如何实现的?JS是单线程执行的,所有的同步任务都是在主线程上执行的,形成了一个执行栈,从上到下依次执行,异步代码会放在任务队列里面。•同步任务在主线程里执行,当浏览器第一遍过滤html文件的时候可以执行完;(在当前作用域直接执行的所有内容,包括执行的方法、new出来的对象)•异步任务耗费时间较长或者性能较差的,浏览器执行到这些的时候会将其丢到异步任务队列中,不会立即执行同时异步任务分为宏任务(如setTimeout、setInterval、postMessage、setImmediate等)和微任务(Promise、process.nextTick等),浏览器执行这两种任务的优先级不同;会优先执行微任务队列的代码,微任务队列清空之后再执行宏任务的队列,这样循环往复;JS自上向下进行代码的编译执行,遇到同步代码压入JS执行栈执行后出栈,遇到异步代码放入任务队列,当JS执行栈清空,去执行异步队列中的回调函数,先去执行微任务队列,当微任务队列清空后,去检测执行宏任务队列中的回调函数,直至所有栈和队列清空整体流程如下图所示:接下来让我们看看nextTick的源码~vue将nextTick的源码放在了vue/core/util/next-tick.js中。如下图所示:我们把这个文件拆成三个部分来看:1.nextTick定义函数我们将nextTick函数单独拿出来,callbacks是一个回调队列,其实调用nextTick就是往这个数组里面传执行任务,callbacks新增回调函数之后执行timerFunc函数,pending是用来限制同一个事件循环内只能执行一次的pending锁;const callbacks = [] // 回调队列 let pending = false // export function nextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { // cb 回调函数会经统一处理压入 callbacks 数组 if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) // 执行异步延迟函数 timerFunc if (!pending) { pending = true timerFunc() } // $flow-disable-line // 当 nextTick 没有传入函数参数的时候,返回一个 Promise 化的调用 if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }2.timerFunc函数 做了四个判断,先后尝试当前环境是否能够使用原生的Promise.then、MutationObserver和setImmediate,不断的降级处理,如果以上三个都不支持,则最后就会直接使用setTimeOut,主要操作就是将flushCallbacks中的函数放入微任务或者宏任务,等待下一个事件循环开始执行;宏任务耗费的时间是大于微任务的,所以在浏览器支持的情况下,优先使用微任务。如果浏览器不支持微任务,使用宏任务;但是,各种宏任务之间也有效率的不同,需要根据浏览器的支持情况,使用不同的宏任务;export let isUsingMicroTask = false let timerFunc if (typeof Promise !== 'undefined' && isNative(Promise)) { //是否支持Promise const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) if (isIOS) setTimeout(noop) } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]' )) { //是否支持MutationObserver let counter = 1 const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { //是否支持setImmediate setImmediate(flushCallbacks) } } else { // Fallback to setTimeout. timerFunc = () => { //上面都不行,直接使用setTimeout setTimeout(flushCallbacks, 0) } }3.flushCallbacks函数flushCallbacks函数只有几行,也很好理解,将pending锁置为false,同时将callbacks数组复制一份之后再将callbacks置为空,接下来将复制出来的callbacks数组的每个函数依次进行执行,简单来说它的主要作用就是用来执行callbacks中的回调函数;function flushCallbacks () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }值得注意的是,$nextTick 并不是一个真正意义上的微任务microtask,而是利用了事件循环机制来实现异步更新。因此,它的执行时机相对于微任务可能会有所延迟,但仍能保证在 DOM 更新后尽快执行回调函数。总的来说,nextTick就是1.将传入的回调函数放入callbacks数组等待执行,定义pending判断锁保证一个事件循环中只能调用一次timerFunc函数;2.根据环境判断使用异步方式,调用timerFunc函数调用flushCallbacks函数依次执行callbacks中的回调函数;3.个人小结nextTick可避免数据更新后导致DOM的数据不一致的问题,提供了更稳定的异步更新机制,解决了created钩子函数DOM未渲染会造成的异步数据渲染问题,但如果过多的使用nextTick会导致事件循环中任务数量和回调函数增多,有可能出现可怕的回调地狱,导致性能下降,同时过度依赖nextTick也会降低代码的可读性,所以大家还是"按需加载"的好~作者:京东保险 卓雅倩来源:京东云开发者社区 转载请注明来源
  • [技术干货] Vue自动生成组件名【转】
    unplugin-generate-component-name一款用于以文件夹名或者setup标签写入名字来自动生成Vue组件名的插件。项目地址功能💚 支持 Vue 3 开箱即用。⚡️ 支持 Vite、Webpack、Rspack、Vue CLI、Rollup、esbuild 等,由 unplugin 提供支持。🪐 支持文件夹名称和 Setup extend 两种模式。🦾 完全支持 TypeScript。安装12345# Yarn$ yarn add unplugin-generate-component-name -D# pnpm$ pnpm i unplugin-generate-component-name -DVite 配置12345678// vite.config.tsimport GenerateComponentName from 'unplugin-generate-component-name/vite'export default defineConfig({  plugins: [    GenerateComponentName({ /* options */ }),  ],})Rollup 配置12345678// rollup.config.jsimport GenerateComponentName from 'unplugin-generate-component-name/rollup'export default {  plugins: [    GenerateComponentName({ /* options */ }),  ],}Webpack 配置1234567// webpack.config.jsmodule.exports = {  /* ... */  plugins: [    require('unplugin-generate-component-name/webpack').default({ /* options */ }),  ],}使用方法(开箱即用)文件夹名称你可以使用index组件所在的文件夹名作为组件的名字。自动生成 Vue 组件名称在Vue中,我们可以使用unplugin-generate-component-name插件自动基于目录名称生成组件名称。这个插件使得在大型代码库中找到和管理组件更加容易和直观。例如,假设我们有一个Vue组件名为Index.vue,此组件在Home目录中。通过unplugin-generate-component-name插件,此组件会自动命名为Home。123456src/home/├── index.vue // 组件名称是 Home├── about.vue└── users/    ├── index.vue // 组件名称是 Users    └── info.vueSetup Extend在 <script setup> 标签中,我们设置了 name 属性为 "Home"。这显式地将组件命名为 "Home",unplugin-generate-component-name 插件会使用这个名字而不是 "Index"。1234567891011<template>  <!-- 你的组件标记 --></template><script setup name="Home">  // 你的脚本逻辑</script><style>  <!-- 你的组件样式 --></style>选项1234567891011121314type GenComponentName = (opt: {    filePath: string;    dirname: string;    originalName: string;    attrName: string | undefined;}) => string;interface PattenOptions {    include?: string | RegExp | (string | RegExp)[];    exclude?: string | RegExp | (string | RegExp)[];    genComponentName: GenComponentName;}interface Options extends Omit<PattenOptions, 'genComponentName'> {    enter?: PattenOptions[];}includeinclude 选项能够明确说明哪些文件应被包含在组件名称的自动创建过程中。excludeexclude 选项则用于指明哪些文件应排除在组件名称的自动创建过程之外。enter在 Options 接口中,有一个 enter 选项。enter 是一个数组,该数组中每个对象都被视作一种特定的规则用于处理不同的文件路径。每一种规则都可以包含 include 和 exclude 选项,用以指明哪些文件是应特别对待的。你也可以要求对 genComponentName 函数进行特定的设置,以达到自定义组件名称生成的效果。下面是一个例子:123456789101112131415161718// vite.config.tsimport GenerateComponentName from 'unplugin-generate-component-name/vite'export default defineConfig({  plugins: [     GenerateComponentName({      include: ['**/*.vue'],      enter: [{        include: ["**/*index.vue"],        genComponentName: ({ attrName, dirname }) => attrName ?? dirname      }, {        exclude: ['**/*.index.vue'],        include: ["src/components/**/*.vue"],        genComponentName: ({ dirname, originalName }) => `${dirname}-${originalName}`      }]    }),  ],});在这个例子中,unplugin-generate-component-name 插件被配置为处理所有的 .vue 文件。在 enter 选项中有两个对象适用于不同的文件路径:第一个对象覆盖所有以"index.vue" 结尾的文件。genComponentName 函数返回组件名称。如果 script setup 标签 中已经指定了 名称 ,那么优先级将会很高; 如果没有,文件夹名称(dirname )就将会使用。第二个对象排除了所有以"index.vue" 结尾的文件, 仅包括 "src/components/" 目录下的 .vue 文件。genComponentName 函数用来以 ${dirname}-${originalName} 的格式生成组件名。例如,对于一个名为 src/component/Button 中的 MyButton.vue 文件,它将会是 Button-MyButton 。本文转载于:https://juejin.cn/post/7314301236098269236
  • [技术干货] 工程化第一步这个package.json要真的搞明白才行【转】
    工程化最开始就是package.json开始的,很多人学了很多年也没搞清楚这个为什么这么神奇,其实有些字段是在特定场景才有效的,那每个属性的适用场景和作用是什么,又牵扯很多知识点,今天先解读一些常见的属性,关注我,后期在遇到特定场景也会再逐步的补充这些属性,只有真正清楚知道每个自动的属性和场景你才能真正使用它得心应手,也才能真正掌握并帮助你解决你的问题。创建一个package.json 你可以使用npm init 按指令创建,也可以通过npm init -y来快速创建,当然也可以手动来创建,那现在我们创建一个。 package.json{ "name": "package-demo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC"}这个是原装的属性,当然还有一些额外的场景才有的,接下来我们来逐步分析:常用属性这些属性没有没有太复杂,但还是要仔细看看每个都可以做什么,适用于什么场景,如何去使用和配置namepackage.json 中最重要字段之一就是名称name 字段,有人的说为什么我没有配置也没有影响我项目的正常运行呢?这个是因为你没有发布npm 包,这个name就是你发布到npm的包名,如果你不打算发布你的包到npm上这个是可以省略的。当然这个名称也不是随便乱起的,它也有一定的规则,否则有警告的如下:1字符串与 "^(?:(?:@(?:[a-z0-9-*~][a-z0-9-*._~]*)?/[a-z0-9-._~])|[a-z0-9-~])[a-z0-9-._~]*$" 的模式不匹配。name的规则如下:名称必须少于或等于 214 个字符。 这包括范围包的范围。范围包的名称可以以点或下划线开头。 没有范围是不允许的。新包的名称中不得包含大写字母。该名称最终成为 URL、命令行参数和文件夹名称的一部分。 因此,名称不能包含任何非 URL 安全字符version这个是版本,跟name 字段差不多,基本是一起的,因为它们共同构成一个假定完全唯一的标识符,如果你不发布包也是这个省略这个字段的,当然这个字段值也不是随意写的,它完全基于符合语义化版本(Semantic Versioning)来管理依赖的版本,所以版本号的更新需要符合语义化版本规范,简单总结:可以被 semver 包可解析的,当然你也可以使用这个semver 进行版本的操作,具体可以看我的另一篇版本号文章版本号keywords这个也是发布到npm 上才有用的,它的作用也是别人可以通过搜索来查找你的包,或是通过npm search xxx开查找你的包 如下,我有两个运行时的包处理react preact 的className的,可以看看查出了什么:是不是几个包的信息,所以这个是搜索查找用的,如果你不发布这个也可以不写的homepage项目主页的 url。随便找个npm 包,可以看一下,该包的右侧有个地址就是这个字段进行配置的bugs这个就是要需要作者给个地址,比如你写的包有问题,别人怎么给你提问题,联系到你 通常我们设置的issue地址,那如何打开呢,可以通过如下方式:npm bugs [<pkgname> [<pkgname> ...]]license指定一个许可证,以便人们知道他们如何被允许使用它,以及你对其施加的任何限制,常见的开源协议是 MIT 和 BSD,如果我们不确定我们应该如何写,可以看一下网上这个图说的比较清晰借用一下:author它表示项目的作者信息,它既可以是一个字符串,又可以是一个对象"author":{  "name": "lvz",  "email": "xxxx@xx.com",  "url": "https://xxxxx"}或者使用字符串的形式也可以"author": "lvz <xxxxx@xx.com> (https://xxxxx)"contributorscontributors 它表示的是贡献者,是一个数组,当然这个也有两种写法"contributors": [  "lvz1 <xxxxx@xx.com> (https://xxxxx)" ]或者对象的写法"contributors":[    {      "name": "lvz1",      "email": "xxxx@xx.com",      "url": "https://xxxxx"    }]funding如果你不开源,对资金没有要求或有其它渠道可以不使用,这个是在npm 6.13.0添加了funding命令,针对开源者,让维护 npm 的开发人员,为有意愿的捐赠者指明捐赠平台。在 package.json 文件中添加了一个funding字段,指向在线捐赠服务的 url,如 Patreon、Open 或者其他支付网站,并可以通过npm fund命令列出这些捐赠平台及其 url。"funding": [    {      "type": "patreon",      "url": "http://XXXXXX"    },    "http://XXXXX",    {          "type": "open",      "url": "https://XXXXXX"    }  ]files项目在进行 npm 发布时,可以通过 files 指定需要跟随一起发布的内容来控制 npm 包的大小,避免安装时间太长,比如我们包体有很多文件,但我们只想把dist放进去,其他src,test文件并不想放在包体中,可以使用这个字段但无论设置如何,总是会包含某些文件:当然除了files 也有其它方式可以来忽略文件进入包体,我们先看一下我们什么也不处理的文件格式添加.gitignore文件12src/*example/*我们发布之后在看一下文件结构,只有index.js 也就是main字段指向的index.js还有package.json两个文件添加.npmignore 文件12src/*example同样我们发布之后看一下文件结构,如下图所示:.npmignore在程序包的根目录下,它不会覆盖“files”字段,但在子目录中,它将覆盖。该.npmignore文件的工作方式与一样 .gitignore。如果有.gitignore文件但.npmignore缺少文件,.gitignore则将使用的内容repository简单点说就是配置仓库地址,这个配置后,可以在npm 包首页看到仓库地址入口,可以进行点击查看12345"repository": {   "type": "git",   "url": "https://github.com/xxxxx",   "directory": "xxxx/xxx" }当然你也可以直接写字符串1"repository":"https://github.com/xxxxx/xxxx"config配置对象可用于设置在升级后持续存在的包脚本中使用的配置参数123"config": {  "type": "chrome"},我们在index.js中进行打印一下1console.log(process.env.npm_package_config_type)此时如果你直接node index.js 你会发现打印的是个undefined,我们在将其放在scripts中:123"scripts": {   "start": "node index.js" },再执行就可以打印出值了,这样我们就可以在脚本中使用了这个环境变量了。依赖配置项这个是我们这次的重点之一,其实有些朋友经常分不清,这些到底怎么回事,我们先整体分析一下,我们安装一个包有两种,一种是当前项目package.json 已有的,一种是安装包中package.json的依赖,那针对这个我们做个实验再总结一下看看它们都是什么妖魔鬼怪。我们在介绍version这个字段的时候,讲解了如何安装一个指定范围的npm包,如果有疑问可以进去查看哈,这里就不再赘述了背景 有两个项目,一个是project-demo,一个是package-demo,包名是lucky-package-demo,project-demo会依赖lucky-package-demo这个包。 如下project-demo 中的package.json123"dependencies": {    "lucky-package-demo": "*"}devDependencies简单理解就是这个包是开发环境用到了,生产环境并不需要它,比如我们的webpack,vite,或是eslint等我们在package-demo 中安装 vite123"devDependencies": {    "vite": "4.3.0"}然后我们在project-demo更新lucky-package-demo后,可以看到project-demo的node_modules中发现react 和 react-demo 已经被安装了,虽然暂时我们并没有去使用这两个包,也就是说dependencies中的包将会被安装peerDependencies也叫同等依赖,它主要用于确保多个模块在同一个主模块的上下文中使用,并共享依赖的版本,简单来说就是project-demo项目中已经安装react了,package-demo中我没必要在dependencies依赖这个react了。从而避免项目中和依赖包中出现重复安装包所导致的包版本不相容、打包了多份不同版本的库等问题,我们举几个常遇到的问题来解释一下这个属性。我有一个组件库,依赖了react,项目中我也要使用react并且已经安装好了,组件库和项目可以共享react但有个问题,我项目更新了,react的版本和组件库react的版本不一致了,还是共享,如何避免冲突安装方式:我们可以配合一些属性使用,在依赖包中,因为我们不会在项目包中安装,可以放在devDependencies中,我们又想告诉项目包我们需要什么范围内的版本,就需要设置peerDependencies,如下:package-demo中如下配置1234567"devDependencies": {    "react": "17.0.2",    "vite": "4.3.0"},"peerDependencies": {    "react":">16.8.0 <18.0.0"}如果我们在project-demo中安装的是 react:"17.0.1",此时是满足我们依赖包中peerDependencies中对react的版本范围要求的,安装很顺利,项目和依赖包会共享17.0.1的react的包,也不会存在任何警告。12345dependencies:- lucky-package-demo+ lucky-package-demo 0.0.10- react+ react 17.0.1 (18.2.0 is available)但如果此时我们将project-demo中react缓存16.8.0此时会有如下提示,因为此时16.8.0 是不满足peerDependencies中react版本范围要求的,此时你手动处理一下,要么安装正确的版本,要么通知作者更新一下peerDependencies12345678910dependencies:- lucky-package-demo+ lucky-package-demo 0.0.10- react+ react 16.8.0 (18.2.0 is available) WARN  Issues with peer dependencies found.└─┬ lucky-package-demo 0.0.10  └── ✕ unmet peer react@">16.8.0 <18.0.0": found 16.8.0peerDependenciesMeta如果 peerDependencies 中指定的包尚未安装,npm 将发出警告,如下配置将对等依赖标记为可选,如果用户没有安装对等依赖,npm不会发出警告12345"peerDependenciesMeta":{   "react":{     "optional":true   } }在project-demo中安装lucky-package-demo,可以发现node_modules中并不存在react的包,控制台也没有任何的警告出现1npm i -S lucky-package-demo如果设置成optional:false中,在project-demo中安装lucky-package-demo后将会自动安装符合范围的react的包bundleDependenciesnpm的命令 -B, --save-bundle语法:npm i -B [package-name]这个属性使用的较少,通过在 bundleDependencies 数组中指定包名称,在发布包时,包名的数组会被打包进去,有的说我试过但发布包什么也没有呢?因为单单配置bundleDependencies这个是没有效果的,需要再在依赖包中安装devDependencies或dependencies中才能将其打包到node_modules中。举例说明:在package-demo 中进行如下安装和配置,并在project-demo中npm i -S lucky-package-demo安装,你会发现project-demo中的node_modules中lucky-package-demo的node_modules中是bundleDependencies中配置的react-runtime-clsx包123456"dependencies": {    "react-runtime-clsx": "0.0.1" },"bundleDependencies": [    "react-runtime-clsx"]当然你可以在package-demo跟目录执行npm pack,和安装lucky-package-demo一样,可以看看目录结构:123456project-demo----node_modules-------lucky-package-demo-----------node_modules---------------react-runtime-clsx //这个是bundleDependencies配置的---------------clsx  这个是react-runtime-clsx的依赖optionalDependencies通过npm 命令 -O, --save-optionalnpm i -O [package-name]optionalDependencies 用于定义可选依赖项,和 dependencies 非常类似,主要的差别在于:在 optionalDependencies 中的依赖包安装报错甚至找不到时不会影响到包管理器的安装行为12345npm i -O react-runtime-clsx// package-demo package.json 如下:"optionalDependencies": {    "react-runtime-clsx": "0.0.1"}optionalDependencies 中的条目将覆盖 dependencies 中的同名条目,因此通常最好只放在一个位置overrides如果需要对依赖项的依赖项进行特定更改,例如将依赖项的版本替换为已知的安全问题,将现有依赖项替换为分支,或者确保在任何地方都使用相同版本的包,则可以添加覆盖。package-demo中package.json 配置123"optionalDependencies": {    "react-runtime-clsx": "0.0.1"  }具体的大家可以看一下文档overrides文档 )项目中的依赖项目中我们也会有依赖,有些是辅助开发的工具,有些是项目引用的代码包,因为项目我们是不会发布它作为npm包来使用的,它作为的是一个工程,所以在依赖上是有区分的,你是不是也有下面的困惑呢?我随便装的并不影响到开发和构建,似乎这两者没有区别,安装哪里都一样呢?感觉是对的,其实项目中这两者并没有明显的划分,只是我们通常是将开发中使用的安装到devDependencies,运行使用的放在dependencies,其实这两个都是安装了的,无论你放哪都可以使用,所以也有部分刚接触时很困惑,因为这个针对的是npm发布包有用的,在项目中还是按规范了养成习惯,清楚每个的含义,对我们做npm包的发布是有用的哈本文转载于:https://juejin.cn/post/7315606159742058530
  • [技术干货] 关于vite的跨域问题【转】
    报错Access to XMLHttpRequest at '<http://localhost:3000/player>' from origin '<http://localhost:4000/>' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.解决vue需要配置自定义代理规则进行跨域访问配置跨域官方文档:cid:link_0在vite.config.ts修改://vite.config.ts export default defineConfig({ //...... server: { //...... port: 4000, //vite的默认端口(别和后端冲突了) proxy: { "/api": { //代理的请求 target: "http://localhost:8000", //后端的地址 changeOrigin: true, //开启跨域访问 rewrite: (path) => path.replace(/^\/api/,''), //重写前缀(如果后端本身就有api这个通用前缀,那么就不用重写) }, }, }, })发起请求的地方:axios.defaults.baseURL ='/api'; //配置前缀 axios.get('info') //这里会发送到http://localhost:4000/info生产环境配置跨域生产环境配置跨域,还需要编辑nginx的配置文件,在server对象中再添加一个location对象(别忘了上一个对象末尾的分号;)        server { //...... location /api/ { proxy_pass http://localhost:8000/; //后端的地址 } }转载自https://www.cnblogs.com/korin5/p/17931093.html
  • [技术干货] 前端无感知刷新token & 超时自动退出【转】
    前端无感知刷新token&超时自动退出一、token的作用因为http请求是无状态的,是一次性的,请求之间没有任何关系,服务端无法知道请求者的身份,所以需要鉴权,来验证当前用户是否有访问系统的权限。以oauth2.0授权码模式为例:每次请求资源服务器时都会在请求头中添加 Authorization: Bearer access_token 资源服务器会先判断token是否有效,如果无效或过期则响应 401 Unauthorize。此时用户处于操作状态,应该自动刷新token保证用户的行为正常进行。刷新token:使用refresh_token获取新的access_token,使用新的access_token重新发起失败的请求。二、无感知刷新token方案2.1 刷新方案当请求出现状态码为 401 时表明token失效或过期,拦截响应,刷新token,使用新的token重新发起该请求。如果刷新token的过程中,还有其他的请求,则应该将其他请求也保存下来,等token刷新完成,按顺序重新发起所有请求。2.2 原生AJAX请求2.2.1 http工厂函数function httpFactory({ method, url, body, headers, readAs, timeout }) { const xhr = new XMLHttpRequest() xhr.open(method, url) xhr.timeout = isNumber(timeout) ? timeout : 1000 * 60​ if(headers){ forEach(headers, (value, name) => value && xhr.setRequestHeader(name, value)) } const HTTPPromise = new Promise((resolve, reject) => { xhr.onload = function () { let response;​ if (readAs === 'json') { try { response = JSONbig.parse(this.responseText || null); } catch { response = this.responseText || null; } } else if (readAs === 'xml') { response = this.responseXML } else { response = this.responseText }​ resolve({ status: xhr.status, response, getResponseHeader: (name) => xhr.getResponseHeader(name) }) }​ xhr.onerror = function () { reject(xhr) } xhr.ontimeout = function () { reject({ ...xhr, isTimeout: true }) }​ beforeSend(xhr)​ body ? xhr.send(body) : xhr.send()​ xhr.onreadystatechange = function () { if (xhr.status === 502) { reject(xhr) } } })​ // 允许HTTP请求中断 HTTPPromise.abort = () => xhr.abort()​ return HTTPPromise;}2.2.2 无感知刷新token// 是否正在刷新token的标记let isRefreshing = false​// 存放因token过期而失败的请求let requests = []​function httpRequest(config) { let abort let process = new Promise(async (resolve, reject) => { const request = httpFactory({...config, headers: { Authorization: 'Bearer ' + cookie.load('access_token'), ...configs.headers }}) abort = request.abort try { const { status, response, getResponseHeader } = await request​ if(status === 401) { try { if (!isRefreshing) { isRefreshing = true // 刷新token await refreshToken()​ // 按顺序重新发起所有失败的请求 const allRequests = [() => resolve(httpRequest(config)), ...requests] allRequests.forEach((cb) => cb()) } else { // 正在刷新token,将请求暂存 requests = [ ...requests, () => resolve(httpRequest(config)), ] } } catch(err) { reject(err) } finally { isRefreshing = false requests = [] } } } catch(ex) { reject(ex) } }) process.abort = abort return process}​// 发起请求httpRequest({ method: 'get', url: 'http://127.0.0.1:8000/api/v1/getlist' })2.3 Axios 无感知刷新token// 是否正在刷新token的标记let isRefreshing = false​let requests: ReadonlyArray<(config: any) => void> = []​// 错误响应拦截axiosInstance.interceptors.response.use((res) => res, async (err) => { if (err.response && err.response.status === 401) { try { if (!isRefreshing) { isRefreshing = true // 刷新token const { access_token } = await refreshToken()​ if (access_token) { axiosInstance.defaults.headers.common.Authorization = `Bearer ${access_token}`;​ requests.forEach((cb) => cb(access_token)) requests = []​ return axiosInstance.request({ ...err.config, headers: { ...(err.config.headers || {}), Authorization: `Bearer ${access_token}`, }, }) }​ throw err }​ return new Promise((resolve) => { // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行 requests = [ ...requests, (token) => resolve(axiosInstance.request({ ...err.config, headers: { ...(err.config.headers || {}), Authorization: `Bearer ${token}`, }, })), ] }) } catch (e) { isRefreshing = false throw err } finally { if (!requests.length) { isRefreshing = false } } } else { throw err }})三、长时间无操作超时自动退出当用户登录之后,长时间不操作应该做自动退出功能,提高用户数据的安全性。3.1 操作事件操作事件:用户操作事件主要包含鼠标点击、移动、滚动事件和键盘事件等。特殊事件:某些耗时的功能,比如上传、下载等。3.2 方案用户在登录页面之后,可以复制成多个标签,在某一个标签有操作,其他标签也不应该自动退出。所以需要标签页之间共享操作信息。这里我们使用 localStorage 来实现跨标签页共享数据。在 localStorage 存入两个字段:当有操作事件时,将当前时间戳存入 lastActiveTime。当有特殊事件时,将特殊事件名称存入 activeEvents ,等特殊事件结束后,将该事件移除。设置定时器,每1分钟获取一次 localStorage 这两个字段,优先判断 activeEvents 是否为空,若不为空则更新 lastActiveTime 为当前时间,若为空,则使用当前时间减去 lastActiveTime 得到的值与规定值(假设为1h)做比较,大于 1h 则退出登录。3.3 代码实现const LastTimeKey = 'lastActiveTime'const activeEventsKey = 'activeEvents'const debounceWaitTime = 2 * 1000const IntervalTimeOut = 1 * 60 * 1000​export const updateActivityStatus = debounce(() => { localStorage.set(LastTimeKey, new Date().getTime())}, debounceWaitTime)​/** * 页面超时未有操作事件退出登录 */export function timeout(keepTime = 60) { document.addEventListener('mousedown', updateActivityStatus) document.addEventListener('mouseover', updateActivityStatus) document.addEventListener('wheel', updateActivityStatus) document.addEventListener('keydown', updateActivityStatus)​ // 定时器 let timer;​ const doTimeout = () => { timer && clearTimeout(timer) localStorage.remove(LastTimeKey) document.removeEventListener('mousedown', updateActivityStatus) document.removeEventListener('mouseover', updateActivityStatus) document.removeEventListener('wheel', updateActivityStatus) document.removeEventListener('keydown', updateActivityStatus)​ // 注销token,清空session,回到登录页 logout() }​ /** * 重置定时器 */ function resetTimer() { localStorage.set(LastTimeKey, new Date().getTime())​ if (timer) { clearInterval(timer) }​ timer = setInterval(() => { const isSignin = document.cookie.includes('access_token') if (!isSignin) { doTimeout() return }​ const activeEvents = localStorage.get(activeEventsKey) if(!isEmpty(activeEvents)) { localStorage.set(LastTimeKey, new Date().getTime()) return } const lastTime = Number(localStorage.get(LastTimeKey))​ if (!lastTime || Number.isNaN(lastTime)) { localStorage.set(LastTimeKey, new Date().getTime()) return }​ const now = new Date().getTime() const time = now - lastTime​ if (time >= keepTime) { doTimeout() } }, IntervalTimeOut) }​ resetTimer()}​// 上传操作function upload() { const current = JSON.parse(localStorage.get(activeEventsKey)) localStorage.set(activeEventsKey, [...current, 'upload']) ... // do upload request ... const current = JSON.parse(localStorage.get(activeEventsKey)) localStorage.set(activeEventsKey, Array.isArray(current) ? current.filter((item) => itme !== 'upload'))}本文转载于:https://juejin.cn/post/7320044522910269478