-
eslint
:代码质量检测(用var
还是let
,用==
还是===
…) -
prettier
:代码风格检测(加不加尾逗号,单引号还是双引号…) -
eslint-config-prettier
:解决ESLint与Prettier的风格冲突 -
eslint-plugin-prettier
:ESLint的插件,集成Prettier的功能 -
eslint-plugin-vue
:ESLint的插件,增加Vue的检测能力
yarn add eslint prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-vue -D
复制代码
接下来在项目根目录下创建两个文件.eslintrc.js
和prettier.config.js
(拓展名可以自由选择),我们把下面内容加进去:
.eslintrc.js
module.exports = {
parser: ‘vue-eslint-parser’,
env: {
browser: true,
node: true,
es2021: true
},
extends: [‘plugin:vue/vue3-recommended’, ‘plugin:prettier/recommended’],
parserOptions: {
ecmaVersion: 12,
sourceType: ‘module’
},
rules: {
‘prettier/prettier’: ‘error’
}
}
复制代码
prettier.config.js
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: false, // 未尾逗号
vueIndentScriptAndStyle: true,
singleQuote: true, // 单引号
quoteProps: ‘as-needed’,
bracketSpacing: true,
trailingComma: ‘none’, // 未尾逗号
arrowParens: ‘always’,
insertPragma: false,
requirePragma: false,
proseWrap: ‘never’,
htmlWhitespaceSensitivity: ‘strict’,
endOfLine: ‘lf’
}
复制代码
然后使用ctrl+shift+P
调出控制台输入Reload Window
配置即可生效,以后想拓展代码的风格都在prettier.config.js
进行配置,而代码语法相关规则在.eslintrc
中的rules配置即可。相关规则文档:
-
ESLint Rules
-
Prettier中文网
2.1.2 Git提交约束
接下来我们需要对我们的提交进行约定,先安装一下这几个包:
-
husky
:触发Git Hooks,执行脚本 -
lint-staged
:检测文件,只对暂存区中有改动的文件进行检测,可以在提交前进行Lint操作 -
commitizen
:使用规范化的message
提交 -
commitlint
: 检查message
是否符合规范 -
cz-conventional-changelog
:适配器。提供conventional-changelog
标准(约定式提交标准)。基于不同需求,也可以使用不同适配器(比如:cz-customizable
)。
yarn add husky lint-staged commitizen @commitlint/config-conventional @commitlint/cli -D
复制代码
先配置适配器:
# yarn
npx commitizen init cz-conventional-changelog --yarn --dev --exact
# npm
npx commitizen init cz-conventional-changelog --save-dev --save-exact
复制代码
它会在本地项目中配置适配器,然后去安装cz-conventional-changelog
这个包,最后在package.json
文件中生成下面代码:
“config”: {
“commitizen”: {
“path”: “cz-conventional-changelog”
}
}
复制代码
现在我们可以添加一个脚本就可以编写规范化的提交:
“scripts”: {
“cz”: “git cz”
}
复制代码
接下来你可以执行yarn cz
命令来编写一些约定好的提交规范:
image.png
此时我们已经根据约定规范提交了消息,但是我们怎么知道提交的消息是不是正确的呢,那么接下来就需要配置刚刚介绍到的commitlint
,只需要一句命令即可完成配置,它会在项目根目录下面创建一个commitlint.config.js
配置文件:
echo “module.exports = {extends: [‘@commitlint/config-conventional’]};” > commitlint.config.js
复制代码
它会使用@commitlint/config-conventional
这个包里面提供的校验规则进行校验,你可以理解为ESLint的规则。
有了这个校验工具,怎么才可以触发校验呢,我们希望在提交代码的时候就进行校验,这时候husky
就可以出场了,他可以触发Git Hook
来执行相应的脚本,而我们只需要把刚刚的校验工具加入脚本就可以了,我们使用的是6.0的新版本,下面是具体使用方法:
我们需要定义触发hook时要执行的Npm脚本:
-
提交前对暂存区的文件进行代码风格语法校验
-
对提交的信息进行规范化校验
“scripts”: {
“lint-staged”: “lint-staged”,
“commitlint”: “commitlint --config commitlint.config.js -e -V”
},
“lint-staged”: {
“src/**/*.{js,vue,md,json}”: [
“eslint --fix”,
“prettier --write”
]
}
复制代码
接下来就是配置husky通过触发Git Hook执行脚本:
# 设置脚本prepare
并且立马执行来安装,此时在根目录下会创建一个.husky
目录
npm set-script prepare “husky install” && npm run prepare
# 设置pre-commit
钩子,提交前执行校验
npx husky add .husky/pre-commit “yarn lint-staged”
# 设置pre-commit
钩子,提交message执行校验
npx husky add .husky/commit-msg “yarn commitlint”
复制代码
此时已经完成配置了,现在团队里面任何成员的提交必须按照严格的规范进行了。
2.1.3 IDE环境约束
除此之外,我们还统一了VSCode编码环境,通过Setting Sync
插件使用Public Gist进行同步。
有人会说小团队做这个有必要吗?我实践了几个月后,我个人还是觉得很有必要的,虽然刚开始配置起来很麻烦,也踩了不少坑,但实际去执行这套流程其实不需要花太多时间,至少可以在开发阶段避免除了代码逻辑以外的错误。
2.2 持续集成(CI)
持续集成
是自动化流程中一个十分重要的部分,我们的前端应用在传统部署模式下需要自己打包然后上传服务器,这样很麻烦也很浪费时间。而持续集成就帮我们解决了这个问题,我们只要开发一个功能,它就能上传到线上的制品库
,再配合持续部署(CD)就能够提高我们的效率,让我们专注开发。这一套流程需要有以下技术或平台支撑:
-
Docker(容器化技术)
-
Linux
-
Nginx:高性能Web服务器
-
Jenkins:持续构建平台
-
GitLab(本地部署的仓库)
-
Nexus3:用来部署Npm、Docker私有仓库,提供镜像制品库
由于篇幅原因,上述平台的搭建我不会给大家演示了。主要是给大家说一下这个CI流程:
-
开发功能
-
Git提交到本地GitLab
-
GitLab触发Webhook
-
Jenkins开发执行脚本构建成Docker镜像
-
上传Nexus私有仓库
image.png
现在有了制品仓库就需要持续部署(CI)
,但是我技术能力不够,只能借助Jenkins执行脚本来实现,但是原则上来说在Docker容器里面部署Docker容器这样的做法并不好,很容易出现一些问题,我想学习完Kubernetes
后再来对这个流程进行优化。
搭建完流程后,我们只需要在项目中写好Dockerfile
和Nginx
的配置文件就可以了,下面是我项目中的一个案例:
Dockerfile
# build stage
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY . .
RUN yarn && yarn build
# production stage
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist/ /usr/share/nginx/html/
COPY --from=build-stage /app/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD [“nginx”, “-g”, “daemon off;”]
复制代码
nginx.conf
server {
listen 80;
server_name localhost;
#charset koi8-r;
access_log /var/log/nginx/host.access.log main;
error_log /var/log/nginx/error.log error;
location / {
root /usr/share/nginx/html;
try_files u r i uri uri uri/ @router;
index index.html;
expires -1;
}
location @router {
rewrite ^.*$ /index.html last;
}
# SPA应用的history模式路由需要在前端配置500、400错误
}
复制代码
这里涉及到很多知识,篇幅有限,就不详细说了。
2.3 CSS样式管理
由于 Vite 的目标仅为现代浏览器,因此建议使用原生 CSS 变量和实现 CSSWG 草案的 PostCSS 插件(例如 postcss-nesting)来编写简单的、符合未来标准的 CSS。
官方其实是建议使用CSS变量来编写CSS,但是考虑到现阶段大家用得不是很熟练,所以还是采用了Sass,而且脚手架已经内置了对Sass的支持,我们只需要安装即可,不用像Webpack
那样需要先安装Loader
。
yarn add sass -D
复制代码
这样我们在模板里面的只要给<style>
标签加上lang
就可以了:
复制代码
我是按照下面的文件组织来对CSS进行统一管理的:
src/assets/styles:
-
variables.scss(存放全局Sass变量)
-
mixins.scss(mixin)
-
common.scss(公共样式)
-
transition.scss(过渡动画样式)
-
index.scss(导出上面三个样式)
index.scss:
@import ‘./variables.scss’;
@import ‘./mixins.scss’;
@import ‘./common.scss’;
@import ‘./transition.scss’;
复制代码
然后在main.js
导入index.scss
就可以使用了:
import ‘/src/assets/styles/index.scss’
复制代码
但是这里会有一个坑,那就是我在variables.scss
中定义的变量、在mixins.scss
定义的mixin全部失效了,而且控制台也报错:
image.png
如果不使用这个变量,我在Chrome是可以看到其他样式已经被编译好的,所以我采取了第二种方式导入index.scss
。我们需要在vite的配置文件给css的预处理器进行配置,它的使用方式和Vue CLI
中的配置差不多:
vite.config.js
export default defineConfig({
plugins: [vue()],
css: {
preprocessorOptions: {
scss: {
additionalData: @import "src/assets/styles/index.scss";
}
}
}
})
复制代码
这样就完成了CSS的管理啦,当然我这种比较简单,现在还有一种比较新的用法就是使用CSS Module,希望将来能用上吧。
Sass
的编写指南,大家可以参考一下:Sass Guidelines
2.4 接口管理
2.4.1 基于Axios二次封装
基于Axios二次封装已经是一种常规操作了,下面来看看项目中我是如何对API进行管理的:
-
抽象一个HttpRequest类,主要有请求拦截/取消、REST请求(GET、POST、PUT、Delete)、统一错误处理等功能
-
实例化这个类,然后分模块编写API请求函数,URL这种常量单独放一个文件,接口请求参数和请求体由使用者决定,最后导出一个对象出口
-
在组件引入对应的模块即可使用
Vue3.0中最推荐的使用方式是Composition API
,组件中this
不推荐使用,所以如果想全局引入,需要这么做:
import { createApp } from ‘vue’
import App from ‘./App.vue’
import http from ‘@/api’
const app = createApp(App)
app.config.globalProperties.http = http
app.mount(‘#app’)
复制代码
在组件中使用:
import { getCurrentInstance } from ‘vue’
const {
proxy: { http }
} = getCurrentInstance()
// demo…
async fetchData() {
await http.getData(…)
}
复制代码
而之前在Vue2中我们只需要在Vue.prototype
上定义属性,然后在组件中使用this
引入就可以了。但是全局引入会导致Vue原型很臃肿,每个组件的实例都会有这个属性,会造成一定的性能开销。
Vue3这种全局引入的做法我觉得也很麻烦,所以我的做法是在使用的组件中导入对应的API模块。
打个小广告:详细的Axios封装可以参考我的另外一篇文章在Vue项目中对Axios进行二次封装。
2.4.2 载入不同模式下全局变量
此外,我们也可以通过使用.env
文件来载入不同环境下的全局变量,Vite中也使用了 dotenv来加载额外的环境变量,设置的全局变量必须以VITE_
为前缀才可以正常被加载,使用方式如下:
.env.development
# 以下变量在development
被载入
VITE_APP_BASE_API = ‘/api/v1’
复制代码
.env.production
# 以下变量在production
被载入
VITE_APP_BASE_API = ‘http://192.168.12.116:8888/api/v1’
复制代码
全局变量使用方式:
import.meta.env.VITE_APP_BASE_API
复制代码
2.4.3 跨域问题
Vite是基于Node服务器开发的,所以它也提供了一些配置来实现本地代理,使用方式大家应该很熟悉,这里直接上一个例子:
vite.config.js
server: {
open: true,
proxy: {
‘/api/v1/chart’: {
target: ‘http://192.168.12.116:8887’,
changeOrigin: true
},
‘/api/v1’: {
target: ‘http://192.168.12.116:8888’,
changeOrigin: true,
rewrite: (path) => path.replace(/^/api/v1, ‘’)
}
}
}
复制代码
如果线上的服务器后端服务器不是同源部署也会有跨域问题,那么需要在Ngnix
中配置反向代理,好在后端实现了CORS规范,那我们不需要操心线上的跨域问题了。当然解决跨域的方式有很多,下面要介绍的WebSocket
就没有这个问题。
到这里接口管理相关问题也差不多说完了,项目接口数量并不是特别多,就20多个吧,所以并没有把全部接口全部交给Vuex
接管,只有一少部分组件依赖的全局状态才放到Vuex
中。
2.4.4 WebSocket+Vuex状态管理方案
大屏项目有将近20多个图表都是实时数据,包括设备健康度状态、设备运行指标等等,必须使用WebSocket
。但是我们项目是SPA应用,每个组件都需要发消息,并且需要共享一个WebSocket
实例,跨组件通信很麻烦,所以需要对这一块进行封装。
网上找了很多方案都没有解决我的问题,但是偶然却翻到了一个大佬的文章(websocket长连接和公共状态管理方案),他的文章里提到了基于WebSocket+Vuex
实现“发布-订阅”模式对全局组件状态进行统一管理。我看完后受益匪浅,我这才知道如果把设计模式用于开发中能有如此“功效”。
我基于大佬的封装又优化了一些:
-
对一些代码细节进行了修改和优化
-
使用
Class
语法糖增强代码可读性 -
增加了数据分发的功能
下面是一个简单的图:
image.png
组件通过emit
方法来发送消息,消息里面标识了任务名,后端返回的数据里面也会返回这个任务名,这就形成了一个“管道”。Vuex通过订阅所有消息,然后根据任务名commit
对应的mutation
来完成状态变更,最后组件通过Vuex的store
或者getter
就能拿到数据了。
下面是一个完整的例子:
首先封装一个VueSocket
类,它有发布、订阅、断线重连、心跳检测、错误调度等功能。组件只需要通过emit
方法来发布消息,通过subscribe
方法订阅服务端消息,然后通过Vuex的mutation
来分发消息。
我们目前只需要关注emit
和subscribe
两个方法,当然handleData
这个函数也很重要,主要是对来自不同任务的数据进行分发。
src/utils/VueSocket.js
class VueSocket {
/**
* VueSocket构造器
* @param {string} url socket服务器URL
* @param {function} commit Vuex中的commit函数,提交mutation触发全局状态变更
* @param {function} handleData 数据分发处理函数,根据订阅将数据分发给不同的任务处理
*/
constructor(url, commit, handleData = null) {
this.url = url // socket连接地址
this.commit = commit
this.distributeData = handleData
this.ws = null // 原生WebSocket对象
this.heartbeatTimer = null
this.errorResetTimer = null // 错误重连轮询器
this.disconnectSource = ‘’ // 断开来源: ‘close’ 由close事件触发断开, 'error’由error事件触发断开
this.reconnectNumber = 0 // 重连次数
this.errorDispatchOpen = true // 开启错误调度
this.closeSocket = false // 是否关闭socket
this.init()
}
/**
* 错误调度
* @param {string} type 断开来源=> ‘close’ | ‘error’
* @returns {function}
*/
static errorDispatch(type) {
return () => {
if (this.disconnectSource === ‘’ && this.errorDispatchOpen) {
this.disconnectSource = type
}
console.log([Disconnected] WebSocket disconnected from ${type} event
)
// socket断开处理(排除手动断开的可能)
if (this.disconnectSource === type && !this.closeSocket) {
this.errorResetTimer && clearTimeout(this.errorResetTimer)
VueSocket.handleDisconnect()
}
}
}
/**
* 断开处理
* @returns {undefined}
*/
static handleDisconnect() {
// 重连超过4次宣布失败
if (this.reconnectNumber >= 4) {
this.reconnectNumber = 0
this.disconnectSource = ‘’
this.errorResetTimer = null
this.errorDispatchOpen = false
this.ws = null
console.log(‘[failed] WebSocket connect failed’)
return
}
// 重连尝试
this.errorResetTimer = setTimeout(() => {
this.init()
this.reconnectNumber++
console.log([socket reconnecting ${this.reconnectNumber} times...]
)
}, this.reconnectNumber * 1000)
}
/**
* 事件轮询器
* @param {function} event 事件
* @param {number|string} outerConditon 停止条件
* @param {number} time
* @param {function} callback
*/
static eventPoll(event, outerConditon, time, callback) {
let timer
let currentCondition
timer = clearInterval(() => {
if (currentCondition === outerConditon) {
clearInterval(timer)
callback && callback()
}
currentCondition = event()
}, time)
}
/**
* 初始化连接,开始订阅消息
* @param {function} callback
*/
init(callback) {
// 如果已经手动关闭socket,则不允许初始化
if (this.closeSocket) {
throw new Error(‘[Error] WebSocket has been closed.’)
}
// 清除心跳检测计时器
this.heartbeatTimer && clearTimeout(this.heartbeatTimer)
this.ws = new WebSocket(this.url)
this.ws.onopen = () => {
callback && callback()
this.reconnectNumber = 0
this.disconnectSource = ‘’
this.errorResetTimer = null
this.errorDispatchOpen = true
// 订阅消息
this.subscribe()
// 开启心跳侦测
this.heartbeatDetect()
console.log(‘[Open] Connected’)
}
this.ws.onclose = VueSocket.errorDispatch(‘close’)
this.ws.onerror = VueSocket.errorDispatch(‘error’)
}
/**
* 订阅器
*/
subscribe() {
this.ws.onmessage = (res) => {
if (res.data) {
const data = JSON.parse(res.data)
// 根据任务类型,分发数据
try {
this.distributeData && this.distributeData(data, this.commit)
} catch (e) {
console.log(e)
}
}
// 收到消息关闭上一个心跳定时器并启动新的定时器
this.heartbeatDetect()
}
}
/**
* 发布器(组件发消息的)
* @param {String} data
* @param {Function} callback
*/
emit(data, callback) {
const state = this.getSocketState()
if (state === this.ws.OPEN) {
this.ws.send(JSON.stringify(data))
callback && callback()
this.heartbeatDetect()
} else if (state === this.ws.CONNECTING) {
// 连接中轮询
VueSocket.eventPoll(state, this.ws.OPEN, 500, () => {
this.ws.send(JSON.stringify(data))
callback && callback()
this.heartbeatDetect()
})
} else {
this.init(() => {
this.emit(data, callback)
})
}
}
/**
* 心跳侦测
*/
heartbeatDetect() {
this.heartbeatTimer && clearTimeout(this.heartbeatTimer)
this.heartbeatTimer = setTimeout(() => {
const state = this.getSocketState()
if (state === WebSocket.OPEN || state === WebSocket.CONNECTING) {
// 发送心跳
this.ws.send(‘ping’)
} else {
this.init()
}
}, 50000)
}
/**
* 手动关闭连接
*/
close() {
this.heartbeatTimer && clearTimeout(this.heartbeatTimer)
this.errorResetTimer && clearTimeout(this.errorResetTimer)
this.closeSocket = true
this.ws.close()
}
/**
* 手动连接
*/
open() {
if (!this.closeSocket) {
throw new Error(‘[Error] WebSocket is connected’)
}
this.heartbeatTimer = null
this.reconnectNumber = 0
this.disconnectSource = 0
this.errorResetTimer = null
this.errorDispatchOpen = true
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数同学面临毕业设计项目选题时,很多人都会感到无从下手,尤其是对于计算机专业的学生来说,选择一个合适的题目尤为重要。因为毕业设计不仅是我们在大学四年学习的一个总结,更是展示自己能力的重要机会。
因此收集整理了一份《2024年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!
由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频
如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
allback)
})
}
}
/**
* 心跳侦测
*/
heartbeatDetect() {
this.heartbeatTimer && clearTimeout(this.heartbeatTimer)
this.heartbeatTimer = setTimeout(() => {
const state = this.getSocketState()
if (state === WebSocket.OPEN || state === WebSocket.CONNECTING) {
// 发送心跳
this.ws.send(‘ping’)
} else {
this.init()
}
}, 50000)
}
/**
* 手动关闭连接
*/
close() {
this.heartbeatTimer && clearTimeout(this.heartbeatTimer)
this.errorResetTimer && clearTimeout(this.errorResetTimer)
this.closeSocket = true
this.ws.close()
}
/**
* 手动连接
*/
open() {
if (!this.closeSocket) {
throw new Error(‘[Error] WebSocket is connected’)
}
this.heartbeatTimer = null
this.reconnectNumber = 0
this.disconnectSource = 0
this.errorResetTimer = null
this.errorDispatchOpen = true
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数同学面临毕业设计项目选题时,很多人都会感到无从下手,尤其是对于计算机专业的学生来说,选择一个合适的题目尤为重要。因为毕业设计不仅是我们在大学四年学习的一个总结,更是展示自己能力的重要机会。
因此收集整理了一份《2024年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
[外链图片转存中…(img-k1dH3m8e-1712570404230)]
[外链图片转存中…(img-5OHXJDdq-1712570404231)]
[外链图片转存中…(img-bZtwPbRF-1712570404231)]
既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!
由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频
如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
[外链图片转存中…(img-fnlzy0oX-1712570404232)]