vue前端工程化
前言
本文介绍的是有关于vue方面的前端工程化实践,主要通过实践操作让开发人员更好的理解整个前端工程化的流程。
本文通过开发准备阶段、开发阶段和开发完成三个阶段开介绍vue前端工程化的整体过程。
准备阶段
准备阶段我将其分为:框架选择、规范制定、脚手架搭建。
框架选择
vue的框架有很多,有脚手架框架例如vue提供脚手架、nuxt等,还有vue版本对应的版本;UI组件库可选择性更多,element、Ant Design、Naive等,还有自封的ui组件库。
本文的实践选择的脚手架框架为:vue3+vite+typescript。
使用的UI组件库为:element组件库。
框架选择:
脚手架:vue3+vite+typescript
UI组件库:element
规范制定
规范制定主要有一下几个点

结构规范
即项目的目录规范,每个项目的文件夹命名代表的含义,公共组件和页面组件应该存在在哪,工具类,静态文件应该存放在哪个文件夹,应该如何命名等。
1.规范一:功能分离结构
作者自己取得名,即各个文件夹功能分离。

如上图所示,每个文件夹又有其含义,每个有特定含义的文件都应该放到对应的文件夹内。
api文件夹
用于存放接口请求的文件夹,内部文件命名有两种规则(这个是根据作者自身开发经验总结的)
1.api文件夹内不允许子文件夹的存在,文件名应该以路由名+api作为文件名;
2.api文件夹内可以有子文件夹,子文件夹名称应该与路由文件名同名,文件名按照其含义进行命名+api,切文件夹结构应该只允许存在二级结构;
对于第一种规则,如下图所示:

页面路由home-page下的全部请求,应该放在home-page-api文件中。这种规则适合于项目接口较少的项目,一般一个页面路由全部请求不超过20个推荐使用这种方式。
对于第二种规则:如下图所示

在api文件夹内建一个与页面路由同名的文件夹,该文件夹命名规则为页面路由子路由文件夹名+api的形式,即该子路由内的全部请求方法放到对应的api中的文件内。这种规则适用于路由页面请求方法比较多的情况下使用。
assets文件夹
文件夹要求:建于路由页面同名的文件,用于存放该路由下的图片资源,文件夹不可有三级结构,对于组件所需的图片资源,应该统一存放在一个统一的文件夹内。

components文件夹
按照组件名命名文件夹

pages-view文件夹
用于存放页面组件,pages页面路由入口,pages-view存放对应页面。具有共性的页面组件可以统一放到一个文件夹或者提到components文件夹。
例如下图所示:红色框表示的是首页路由,则绿色框怎就是对应的页面组件(关系为路由组件为页面组件提供出口,路由组件只做简单props接收和简单逻辑运算)


router
用于存放路由配置,如果路由过多建议拆分路由。

store
用于存放vuex等配置,但是项目中贡献数据不建议使用vuex而是建议存放在缓存中。
utils
用于存放工具类和工具方法
2.规范二:模块分离结构
所谓的模块聚合结构,则表示的是将api请求,页面组件,路由配置都存放到view文件夹中,router作为主配置引用子路由配置。对于根路径下的components、assets和store作用和规范一的作用一样。

此处下的pages-views、api和router文件夹下的文件名要求不太高,只要文件名有特定含义即可,但是文件夹下目录不可找过三级。
3.总结
- 规范一:适用于小项目,协作人员少的项目,结构清晰明了,维护方便,弊端是协作性比较差。
- 规范二:适用于中大型项目,协作人员多的项目,开发人员只要在自己的目录下开发即可,不相互影响,提交代码不容易发生冲突。
命名规范
命名规范主要有:文件和文件夹命名规范、变量&方法&计算属性&类&接口命名规范、css类名命名规范、html组件引用规范&导入组件名和组件名规范。
1.文件和文件夹命名规范
文件和文件夹命名:全为小写字母,多单词之前用横线隔开,主要是为了适配window和mac,windows不区分大小写,mac区分大小写。

2.变量&方法&类&接口命名规范
变量&方法&计算属性:使用小驼峰命名法,其中方法末尾需要加Fn,计算属性末尾添加Computed(这个是作者个人习惯)。
类&接口:使用大驼峰命名法,其中接口末尾加Types(作者个人习惯,用于区分类)。
3.css类名命名规范——BEM
使用BEM命名规范。
BEM(Block,Element,Modifier)是一种基于组件的web开发思想。这种开发思想主张将用户界面划分为独立的块。这使得网页开发变得简单快捷,即使是复杂的用户界面,也可以重用现有的代码,而无需复制和粘贴。
Block - 一个功能独立的页面组件,可以重用。
- block名称描述了此模块的用途,它是什么?
- 各个模块可以相互嵌套,嵌套层级数量不受限
例如:
<!-- `header` block -->
<header class="header"><!-- Nested `logo` block --><div class="logo"></div><!-- Nested `search-form` block --><form class="search-form"></form>
</header>
header 模块嵌套了logo模块和搜索表单模块
Element - 块的组成模块,不能与块分开使用,也不能自己单独使用。
- element名称描述了在这模块中的用途,它是什么?
- element名称的语法结构为 **block-name__element-name**,使用双下划线将block名称和element名称连接起来。
- element 元素彼此之间可以相互嵌套,嵌套层级数量不受限
- 一个element元素里可以嵌套包含一个block块,这就意味着,element名称定义不能为多层级结构,如 block__elem1__elem2 ,这种命名是不被允许的。
例如:
<form class="search-form"><div class="search-form__content"><input class="search-form__input"><button class="search-form__button">Search</button></div>
</form>
search-forminput,search-formbutton,search-form__content 即为element元素。
search-form__content 元素中嵌套了element元素。
但是search-form__content__input 这种多层级命名element元素是不被允许的,类名过长,层级结构过多,不清晰。
Modifier - 定义block块或element元素的外观、状态或行为。
- modifier 的名称一般描述了block块或element元素的外观,它的大小?它的状态?它的颜色
- modifier 的名称语法结构为:block-name_modifier-name;block-name__element-name_modifier-name
- 一般使用单下划线将它跟block元素或者element元素连接起来;布尔形式,区分是或不是的状态,完整的语法结构为:block-name_modifier-name;block-name__element-name_modifier-name
例如:
<form class="search-form search-form_theme_islands"><input class="search-form__input"><!-- The `button` element has the `size` modifier with the value `m` --><button class="search-form__button search-form__button_disabled">Search</button>
</form>
search-form__button_disabled 这种命名结构是布尔形式。
key-value键值对的形式,区分不同的状态。完整的语法结构则为:
- block-name_modifier-name_modifier-value
- block-name__element-name_modifier-name_modifier-value
例如:
<form class="search-form search-form_theme_islands"><input class="search-form__input"><!-- The `button` element has the `size` modifier with the value `m` --><button class="search-form__button search-form__button_size_m">Search</button>
</form>
search-form__button_size_m 这种命名结构就是键值对的形式
- modifier 不能被单独使用,必须与block元素或者element元素联合使用。因为一个modifier就是用来描述此元素的外观、大小、一个实体的状态。
BEM的优点与缺点?
优点
- 结构简单,一目了然
- 组件化,代码复用
- 不使用标签选择器,避免父级元素内的标签的受影响。举个例子,商品详情页是允许商家自定义标签的,那么商家展示区域标签的祖先元素,一旦用标签选择器定义了样式,子子孙孙都要背负.
例如,将这个网页拆分成BEM的写法
无BEM写法:
<section><h1>Sterling Calculator</h1><form action="process.php" method="post"><p>Please enter an amount: (e.g. 92p, £2.12)</p><p><input name="amount"> <input type="submit" value="Calculate"></p></form>
</section>
BEM 写法:
<section class="widget"><h1 class="widget__header">Sterling Calculator</h1><form class="widget__form" action="process.php" method="post"><p>Please enter an amount: (e.g. 92p, £2.12)</p><p><input name="amount" class="widget__input widget__input_amount"> <input type="submit" value="Calculate" class="widget__input widget__input_submit"></p></form>
</section>
元素清单:
- widget
- widget__header
- widget__form
- widget__input
这样就形成了一个可复用的块
注意其中的 widget__input_amount 和 widget__input_submit为Modifier
缺点
- 类名变的更长,一个元素可能拥有多个class
- id选择器无用武之地
- class命名不能重复
- Block的抽象至关重要
谁适用于BEM
项目复杂,复用模块较多,多人协作团队使用。
4.html组件引入规范&导入组件名和组件名规范
html组件引入的组件名称使用大驼峰,带结束标签

导入组件名使用大驼峰,
import TipsDialog from '../tips-dialog/index.vue'
组件名规范使用大驼峰
defineOptions({
name: 'CompModel'
})
代码规范
代码规范主要是代码校验和自动格式化以及git提交规范校验。
1.代码校验和自动格式化
代码校验和代码格式化两个是相辅相成的,通过插件对代码进行校验并自动格式化成符合要求的格式。要实现需要依赖两个插件eslint和prettier。
配置eslint
下载依赖
yarn add -D eslint eslint-plugin-vue
npx eslint --init
init 命令会自动生成 .eslintrc.js

修改配置为:
module.exports = {root: true,ignorePatterns: ['node_moduls/*'],env: {browser: true,es2021: true,node: true,commonjs: true},extends: ['eslint:recommended', 'plugin:vue/essential', 'prettier'],overrides: [],parserOptions: {ecmaVersion: 'latest',sourceType: 'module'},plugins: ['vue', 'prettier'],rules: {'linebreak-style': ['error', 'unix'],'no-multiple-empty-lines': [1, { max: 2 }], //空行最多不能超过2行'vue/multi-word-component-names': 'off' //vue组件名去掉多单词限制}
}
配置文档
规则 | Rules_Eslint_参考手册_非常教程 (verydoc.net)
配置代码风格工具prettier
安装
yarn add -D prettier eslint-config-prettier eslint-plugin-prettier
创建 .prettierrc
{"useTabs": false,"tabWidth": 4,"printWidth": 80,"singleQuote": true,"trailingComma": "none","semi": false,"endOfLine": "lf"
}
官方地址
Configuration File · Prettier 中文网
2.git提交规范
相关配置在这篇文章,内容有点多
git提交规范-CSDN博客
api和数据规范
这个部分需要后端配置,后端返回的code需要代表一定含义,这样才能做统一数据处理,例如token失效的code为多少,接口超时的code或者接口报错的返回code为多少。
1.api封装
这个部分比较灵活,需要按照公司的业务或者开发的个人习惯进行封装,以下是一个例子:
import axios, { AxiosRequestConfig } from 'axios'
import qs from 'qs'
import { getToken } from './userInfo/token'
import { subscribe, MetaDataHelper } from '@gogoal/fund-utils'
import { blob2File } from './tools'
import { isFileList, isObject } from './types'const SUCCESS_CODE = 0
const service = axios.create({// timeout: 200000, // request timeout
})
// response interceptor
service.interceptors.response.use((response) => {const res = response.dataif (res.code === SUCCESS_CODE) {return res.data} else {subscribe.notify('xhr-fail', res)console.log('xhr-fail response', response)return Promise.reject(res)// if (res.code == '1100' || res.code == '1103') {// dealLogout()// } else {// return Promise.reject(res)// }}},(error) => {return Promise.reject({code: 1,message: '服务器错误',error})}
)interface RequestDataParams extends AxiosRequestConfig {needToken?: boolean /* 是否需要token */needToString?: boolean /* 是否需要将参数转成token */data?: Record<string, any>headers?: Record<string, any>config?: Record<string, any>
}
// 请求接口数据
// interface ResponseData<T = any> {
// code: number
// data: T
// message: string
// }const getData = function <T>({url = '',params = {},data,method = 'GET',needToken = false,needToString = false,headers,config
}: RequestDataParams): Promise<T> {if (needToken) {const token = getToken()params.token = token}const _params = {url,method}// deleteEmptyProperty(params);if (['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) {Object.assign(_params, {data: needToString ? qs.stringify(data) : data,params: params})} else {Object.assign(_params, {params: params})}if (headers) {Object.assign(_params, {headers})}if (config) {Object.assign(_params, {config})}return service(_params)
}/*** @description 公用 GET 请求*/
const get = function <T>({url,params,needToken = false,needToString = false
}: RequestDataParams) {return getData<T>({url,params,needToken,method: 'GET',needToString})
}/*** @description 公用 POST 请求*/
const post = function <T>({url,params,data,needToken = false,needToString = false
}: RequestDataParams) {return getData<T>({url,params,data,needToken,method: 'POST',needToString})
}
const upload = function ({ url, data, params = {}, needToken = false }) {// debuggerconst _url = url || '/file/comm_upload_file'const _data = new FormData()// 将得到的文件流添加到FormData对象Object.keys(data).forEach((_k) => {const _p = data[_k]if (isObject(_p)) {_data.append(_k, JSON.stringify(_p))} else if (isFileList(_p)) {Array.from(_p).forEach((res) => {_data.append(_k, res)})} else if (isArray(_p)) {_p.forEach((res) => {getType(res) == 'File' && _data.append(_k, res)})} else {_data.append(_k, _p)}})return getData({url: _url,method: 'POST',data: _data,params,needToken})
}function formatFile(query) {const _params = new FormData()// 将得到的文件流添加到FormData对象Object.keys(query).forEach((_k) => {const _p = query[_k]if (isObject(_p)) {// _params.append(_k, JSON.stringify(_p))_params.append(_k, _p)} else if (isFileList(_p)) {_p.forEach((res) => {_params.append(_k, res)})} else {_params.append(_k, _p)}})return _params
}
const downloadBlob = function (url, fileName) {const xhr = new XMLHttpRequest()xhr.open('get', url, true)xhr.responseType = 'blob' // 返回类型blob// 定义请求完成的处理函数,请求前也可以增加加载框/禁用下载按钮逻辑xhr.onload = function () {// 请求完成if (this.status === 200) {// 返回200const blob = this.responseblob2File(blob, fileName)}}// 发送ajax请求xhr.send()
}type File = {url: stringquery?: stringparams?: stringfileName: stringconfig: any
}
const downloadFile = ({ url, query, params, fileName, config = {} }: File) => {const _config: AxiosRequestConfig = Object.assign({method: 'post',headers: {'Content-Type': 'application/x-www-form-urlencoded' // 请求的数据类型为form data格式},responseType: 'blob'},config)let _url = url || '/file_export/excel/common'if (query) {_url += `?${qs.stringify(query)}`}_config.url = _url_config.headers['X-Pro'] = MetaDataHelper.getId()if (params) {_config.data = qs.stringify(params)}return new Promise<void>((resolve, reject) => {axios(_config as AxiosRequestConfig).then((response) => {const { data, message } = responseif (!data && message) {reject(response)return}blob2File(data, fileName)resolve()}).catch((error) => {reject(error)})})
}export default getDataexport {service /* 导出方便修改默认参数 */,get,post,upload,downloadFile,downloadBlob,formatFile
}
使用:
const response = await service({url: '/api/v1/zyfp_account_home/bind_mobile',method: 'post',data: {source: 'login',mobile: bindForm.value.mobile,sms_code: bindForm.value.sms_code}})
2.数据规范
这个需要后端定,主要是接口返回的数据格式,例如返回code规范,返回对象规范等,这个需要根据具体情况具体分析。
脚手架搭建
相关文章:
vue前端工程化
前言 本文介绍的是有关于vue方面的前端工程化实践,主要通过实践操作让开发人员更好的理解整个前端工程化的流程。 本文通过开发准备阶段、开发阶段和开发完成三个阶段开介绍vue前端工程化的整体过程。 准备阶段 准备阶段我将其分为:框架选择、规范制…...
面向对象:继承
文章目录 一、什么叫继承?二、单继承三、多继承3.1多继承的各种情况3.1.1一般情况3.1.1特殊情况(菱形继承) 四、菱形继承引发的问题4.1 问题1:数据冗余4.2 问题2:二义性(无法确定到底是访问哪个) 五、虚拟继承解决菱形…...
ES学习日记(一)-------单节点安装启动
基于ES7.4.1编写,其实一开始用的最新的8.1,但是问题太多了!!!!不稳定,降到7.4 下载好的安装包上传到服务器或虚拟机,创建ES目录,命令mkdir -p /路径xxxx 复制安装包到指定路径并解压: tar zxvf elasticsearch-8.1.0-linux-x86_64.tar.gz -C /usr/local/es/ 进入bin目录安装,命…...
【管理咨询宝藏59】某大型汽车物流战略咨询报告
本报告首发于公号“管理咨询宝藏”,如需阅读完整版报告内容,请查阅公号“管理咨询宝藏”。 【管理咨询宝藏59】某大型汽车物流战略咨询报告 【格式】PDF 【关键词】HR调研、商业分析、管理咨询 【核心观点】 - 重新评估和调整商业模式,开拓…...
ArcGIS Pro横向水平图例
终于知道ArcGIS Pro怎么调横向图例了! 简单的像0一样 旋转,左转右转随便转 然后调整图例项间距就可以了,参数太多就随便试,总有一款适合你! 要调整长度,就调整图例块的大小。完美! 好不容易…...
线程创建的几种方式
1.继承Thread类 class MyThread extends Thread {public void run() {// 线程执行的任务for (int i 0; i < 5; i) {System.out.println("Thread: " i);try {Thread.sleep(1000); // 使线程休眠 1 秒} catch (InterruptedException e) {e.printStackTrace();}}}…...
Python教程:一文掌握Python多线程(很详细)
目录 1.什么是多线程? 1.1多线程与单线程的区别 1.2 Python 中的多线程实现方式 2.使用 threading 模块创建和管理线程 2.1创建线程:Thread 类的基本用法 2.2线程的启动和执行:start() 方法 2.3线程的同步和阻塞:join() 方…...
华为防火墙配置指引超详细(包含安全配置部分)以USG6320为例
华为防火墙USG6320 华为防火墙USG6320是一款高性能、高可靠的下一代防火墙,适用于中小型企业、分支机构等场景。该防火墙支持多种安全功能,可以有效抵御网络攻击,保护网络安全。 目录 华为防火墙USG6320 1. 初始配置 2. 安全策略配置 3. 防火墙功能配置 4. 高可用性配…...
(含react-draggable库以及相关BUG如何解决)固定在左上方某盒子内(如按钮)添加可拖动功能,使用react hook语法实现
原生写法 // 封装组件 import React, { useState, useRef } from react;const DraggableModal ({ children }) > {const [position, setPosition] useState({ x: 0, y: 0 });const modalRef useRef(null);const handleMouseDown (e) > {const modal modalRef.curre…...
选择最佳图像处理工具OpenCV、JAI、ImageJ、Thumbnailator和Graphics2D
文章目录 1、前言2、 图像处理工具效果对比2.1 Graphics2D实现2.2 Thumbnailator实现2.3 ImageJ实现2.4 JAI(Java Advanced Imaging)实现2.5 OpenCV实现 3、图像处理工具结果 1、前言 SVD(stable video diffusion)开放了图生视频的API,但是限…...
微信小程序版本更新检测
app.vue文件 <script>export default {onLaunch: function() {console.log(App Launch)// #ifdef MP-WEIXINthis.getUpdateManager();// #endif},methods: {// 检测小程序更新getUpdateManager() {const updateManager wx.getUpdateManager();updateManager.onCheckFor…...
【每日力扣】343. 整数拆分与63. 不同路径 II
🔥 个人主页: 黑洞晓威 😀你不必等到非常厉害,才敢开始,你需要开始,才会变的非常厉害 343. 整数拆分 给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k > 2 ),并使…...
洛谷 Cut Ribbon
思路:我们可以看出,这是一道完全背包问题,但是呢,有一点需要注意:那就是我们在装背包的时候并不能保证一定能装满背包,但是这里的背包要求是让我们装满的,所以我们需要判断这个背包装满才行&…...
#AS,idea,maven,gradle
Jdk,sdk。提前都是需要下好的。 Maven与gradle的思考: 用AS开发app时,gradle本就有,自己也可以指定,AGP同样。要注意gradle,AGP,jdk版本的事情。还有依赖库。 用idea开发网络程序时,也有内置的maven&…...
FPGA结构与片上资源
文章目录 0.总览1.可配置逻辑块CLB1.1 6输入查找表(LUT6)1.2 选择器(MUX)1.3 进位链(Carry Chain)1.4 触发器(Flip-Flop) 2.可编程I/O单元2.1 I/O物理级2.2 I/O逻辑级 3.布线资源4.其…...
【分布式】——分布式事务
分布式事务 ⭐⭐⭐⭐⭐⭐ Github主页👉https://github.com/A-BigTree 笔记链接👉https://github.com/A-BigTree/tree-learning-notes ⭐⭐⭐⭐⭐⭐ Spring专栏👉https://blog.csdn.net/weixin_53580595/category_12279588.html SpringMVC专…...
第6章:“让我们思考这个”的提示
“让我们思考这个”这一提示词,是深度对话的钥匙,鼓励ChatGPT生成反思性、沉思性的文本。 对于论文写作、诗歌创作或创意任务的完成,非常实用。 当你想要深究某主题时,只需向ChatGPT提问。 它会基于提示,结合算法和…...
安卓Activity上滑关闭效果实现
最近在做一个屏保功能,需要支持如图的上滑关闭功能。 因为屏保是可以左右滑动切换的,内部是一个viewpager 做这个效果的时候,关键就是要注意外层拦截触摸事件时,需要有条件的拦截,不能影响到内部viewpager的滑动处理…...
使用conda管理python环境
为什么需要管理环境? 每个python程序依赖的库版本可能不同,因此我们需要隔离不同的环境。 创建环境: conda create --name myenv python3.8这将创建一个名为myenv的新环境,并在其中安装Python 3.8版本。 列出所有环境…...
MR混合现实情景实训教学系统在军事演练课堂中的教学应用
MR混合现实情景实训教学系统在军事演练课堂中的教学应用具有以下优势: 1. 增强现实感:通过MR技术,学生可以在军事演练中更真实地感受到战场环境,增强他们的实战经验。 2. 提高训练效率:通过MR技术,可以模…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程
STM32F1 本教程使用零知标准板(STM32F103RBT6)通过I2C驱动ICM20948九轴传感器,实现姿态解算,并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化,适合嵌入式及物联网开发者。在基础驱动上新增…...
