构建项目
环境配置
-
全局安装vue脚手架
npm install -g @vue/cli-init
-
打开脚手架图形化界面
vue ui
创建项目
- 在图形化界面创建项目
- 根据要求填写项目相关信息
- 选择手动配置
- 勾选配置项目
- 选择配置项目
- 然后我们就搭建完成啦🥳,构建可能需要一点时间,大家稍等久一会。
安装依赖
-
在右侧边栏找到
依赖
-
安装
axios
依赖 -
运行项目
npm run serve
配置git commit规范
什么是commit规范?
Git commit规范是指为了使版本控制更加清晰、易于追踪和维护,而在提交代码时遵循的一系列约定。一个良好的Git commit规范能够帮助团队成员理解每一次提交的目的和范围,同时便于后续的代码审查、问题追踪以及生成变更日志。
以下是一些常见的Git commit规范要点:
- feat:添加新特性
- fix:修复bug
- docs:仅仅修改了文档
- style:仅仅修改了空格、格式缩进、逗号等等,不改变代码逻辑
- refactor:代码重构,没有加新功能或者修复bug
- perf:优化相关,比如提升性能、体验
- test:增加测试用例
- chore:改变构建流程、或者增加依赖库、工具等
- revert:回滚到上一个版本
下面是提交规范的配置步骤:
-
在vscode终端中,安装commitizen和cz-customizable
npm install -g commitizen@4.2.4 npm i cz-customizable@6.3.0--save-dev //如果第二个安装不成功可以使用下面这个 npm i cz-customizable@6.3.0 --save-dev --force
-
在package.json中进行新增下面语句:
"config": { "commitizen": { "path": "node_modules/cz-customizable" } }
-
在根目录下新建.cz-config.js文件并写入配置 之后就可以用
git cz
来代替git commit
-
最后我们可以尝试git添加上传,再使用
git cz
检测是否安装成功//上传git git add . //git commit提交 git cz
-
出现以下👇情况就是安装成功啦🥳
内容补充:
Commitizen的介绍:是一个用于撰写Git提交信息的工具。它可以帮助开发人员遵循一个规范,以便更容易地阅读和维护Git仓库历史记录。Commitizen采用了一个交互式的命令行界面,引导你逐步填写必要的数据,从而生成符合规范的Git提交信息。
cz-customizable的介绍:是一个Commitizen的插件,它允许你使用自定义的Git提交规范。通过为项目添加一个配置文件,你可以指定你自己的提交格式,并在使用Commitizen时使用该格式。你可以轻松地定义自己的提交类型、作用域和描述等信息。
配置强制commit规范
为什么需要强制约束commit规范?因为正常工作中多人协作的时候每个人的习惯不一样,所以我们需要强制性的进行规范化处理。
我们通过安装commitlint
来实现强制性规范commit。
npm install --save-dev @commitlint/config-conventional@12.1.4 @commitlint/cli@12.1.4
然后安装husky
进行强制git代码提交规范。
npm install husky@7.0.1 --save-dev
最后对husky
进行初始化。
npx husky install
如果在文件夹中出现了多出来了这一个文件就说明成功了🥳
问题补充:
如果安装
commitlint
未成功,可能是因为这是因为@vue/eslint-config-standard@6.1.0
依赖于eslint-plugin-vue@^7.0.0
,但是你的项目中已经安装了eslint-plugin-vue@8.7.1
,版本过高导致的。这里我也写了一片解决办法
别急!我们还有后续步骤:
-
添加
commitlint.config.js
文件到项目当中 -
在
package.json
文件中添加以下👇指令:"prepare": "husky install"
-
执行指令
npm run prepare
-
在
.husky
中添加commit配置文件,在终端输入以下指令npx husky add .husky/commit-msg
如果在这个文件夹中新增了这个
commit
文件就说明配置成功了 -
最后修改
commit-msg
文件,是为了hosky和commitlik实现关联npx --no-install commitlint --edit
强制规范我们就配置完啦,接下来测试一下
首先将文件添加到仓库当中:
git add .
然后来一个不符合规矩的提交:
git commit -m '这是不符合规矩的'
再来一个符合规矩的操作:
git add .
git commit -m 'feat: 符合规矩的'
这样强制规范我们就配置好啦🥳!!!
代码格式化
同样的,代码格式我们也要实现强制格式化,这样防止我们写着写着就忘记了代码规范,以免错乱。
-
执行指令添加代码强制格式化文件
npx husky add .husky/pre-commit
添加成功就会多出一个代码强制格式化文件出来:
-
在新增加的文件中写入下面这段话:
npx lint-staged
-
在
package.json
文件中添加:"lint-staged": { "src/**/*.{js,vue}": [ //src目录下所有的js和vue文件 "eslint --fix", // 自动修复 "git add" // 自动提交时修复 ] } 注意:上面的注释需要删掉!!!
安装UI组件库
这里我们使用到的是Element Plus UI组件库,这个是他的安装 | Element Plus (element-plus.org)地址。我这里也简要描述一下他的安装步骤:
-
在项目终端中执行安装组件库命令:
npm install element-plus --save
-
按需导入组件
npm install -D unplugin-vue-components unplugin-auto-import
-
创建
vue.config.js
文件,将以下👇代码导入//两个版本,如果第一个报错的话就用第二个,第二个是我自己找来配置的,建议先使用up提供的,这样才能保证与视频项目配置一致。 const AutoImport = require('unplugin-auto-import/webpack') const Components = require('unplugin-vue-components/webpack') const { ElementPlusResolver } = require('unplugin-vue-components/resolvers') module.exports = { configureWebpack: config => { config.plugins.push(AutoImport({ resolvers: [ElementPlusResolver()], })) config.plugins.push(Components({ resolvers: [ElementPlusResolver()], })) }, } //第二个版本,我自己配置的(上面一个不可用,在使用我这个) const { defineConfig } = require('@vue/cli-service') const AutoImport = require("unplugin-auto-import/webpack"); const Components = require("unplugin-vue-components/webpack"); const { ElementPlusResolver } = require("unplugin-vue-components/resolvers"); module.exports = defineConfig({ transpileDependencies: true, configureWebpack: { plugins: [ AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), ], }, });
-
启动项目检验
npm run serve
问题一:UI无法安装
如果Vs code安装UI组件库安装不了,是版本问题,在命令后面加上
--legacy-peer-deps
问题二:启动项目报错
如果在启动检验项目的时候报错:
ERROR TypeError: AutoImport is not a function
unplugin-auto-import版本高了,降版本成0.16.7,这个问题就不报错了。
npm install unplugin-auto-import@0.16.1 -force
ERROR TypeError: Components is not a function
unplugin-vue-components版本过高,回退插件版本。
npm install unplugin-vue-components@0.25.2
如何查看是否生效呢?
我们可以在组件库里面引入几个按钮组件到Homeview.vue
文件中,然后运行项目。组件按钮如果出现了就说明引入成功啦🥳🥳🥳
引入按钮组件的代码:
<el-button>Default</el-button>
<el-button type="primary">Primary</el-button>
<el-button type="success">Success</el-button>
<el-button type="info">Info</el-button>
<el-button type="warning">Warning</el-button>
<el-button type="danger">Danger</el-button>
创建项目
Vue3.2新特性
下面我将介绍两个新特性:
第一个新特性就是不再需要
作为根目录。
在 Vue 3 中,特别是 Vue 3.2 版本,引入了一个新的 RFC (Request for Comments) 特性,即允许在单文件组件(SFC)的 <template>
中使用 <router-view>
作为根节点,而不再是必须使用 <div>
或其他元素作为根节点。这项特性是在 Vue Router 4 中实现的,它与 Vue 3 的 Composition API 和新的 <script setup>
语法糖相辅相成,提供了更灵活的路由视图设置。
这项改变允许开发者在 Vue 3 的单文件组件中这样写:
vue<template>
<router-view/>
</template>
而不是必须将 <router-view>
包裹在一个 <div>
中:
vue复制<template>
<div>
<router-view/>
</div>
</template>
第二个介绍的特性就是CSS可以直接绑定JS变量。
在 Vue 3.2 版本中,引入了一项新特性,允许开发者直接在 CSS 中绑定 JavaScript 变量,这是通过 <style>
标签的 v-bind
属性实现的。这项特性特别适用于在使用 <script setup>
语法的单文件组件(SFC)中。
具体来说,你可以在 <style scoped>
或 <style module>
中使用 v-bind
来动态地应用 CSS 属性,这些属性的值可以是 JavaScript 表达式。例如:
vue<script setup>
import { ref } from 'vue';
const themeColor = ref('blue');
</script>
<template>
<div :class="`color-${themeColor.value}`">Hello World</div>
</template>
<style scoped>
.color-blue {
color: v-bind(themeColor);
}
</style>
在上面的示例中,color-blue
类的 color
属性会绑定到 themeColor
变量的值。当 themeColor
的值改变时,应用到 DOM 元素上的样式也会相应地更新。
初始化项目
先将App.vue
文件里面一些没有用的文件和代码片段删掉。删完的代码如下:
<template>
<router-view/>
</template>
<style lang="scss">
</style>
之后导入styles
文件到项目中的src
文件夹中,这个包包含了一些js的初始化操作以及一些Sass文件。
第三步在main.js
当中导入index.scss
文件。
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/styles/index.scss' //导入`index.scss`文件
createApp(App).use(store).use(router).mount('#app')
登录组件的实现
编写登录页面
-
在
src
下的views
文件夹中创建一个login
文件夹。并新建文件index.vue
-
在新建的文件书写一些内容
<template> <div> 这是一个登录界面 </div> </template> <script setup> </script> <style lang="scss" scoped> </style>
-
配置路由(转到
src
的router
文件)//删掉后的文件内容 // 路由配置文件 import { createRouter, createWebHashHistory } from 'vue-router' const routes = [ { //配置登录界面 path: '/login', //设置路径 name: 'Login', //设置名称 component: () => import('../views/login/index.vue') //导入文件 } ] const router = createRouter({ history: createWebHashHistory(), routes }) export default router
-
删除以下👇这两个文件
如果这个时候发现出现了以下👇错误导致无法运行了:
webpack compiled with 1 error
解决办法:
选中并打开"vue.config.js"
输入"lintOnSave:false"来关闭eslint校验
lintOnSave:false
-
访问地址,因为是添加了路由,所以我们要在原来URL路径上添加
login
才能跳转到新添加的页面
编写页面
添加输入框
在登录页面文件中,引入一个表单组件
<template>
<div class="login-container">
<!-- 添加一个表单项 -->
<el-form ref="form" :model="form" class="login-form">
<div class="title-container">
<h3 class="title">用户登录</h3>
</div>
<el-form-item>
<el-input v-model="form.name"></el-input>
</el-form-item>
</el-form>
</div>
</template>
创建双向数据绑定
<script setup>
import { ref } from 'vue'
const form = ref({
name: ''
})
</script>
添加图标
<template>
<div class="login-container">
<!-- 添加一个表单项 -->
<el-form ref="formRef" :model="form" class="login-form">
<div class="title-container">
<h3 class="title">用户登录</h3>
</div>
<el-form-item>
<!-- 添加图标 -->
<el-icon :size="20" class="svg-container">
<edit />
</el-icon>
<el-input v-model="form.username"></el-input>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import { ref } from 'vue'
import {Edit} from "@element-plus/icons-vue" //添加图标
const form = ref({
name: ''
})
</script>
回到界面中就能看到输入框和图标啦🥳
自定义图标
-
我们导入准备好的图标资源文件进入项目的
src文件夹
当中 -
在
src/components
文件夹下新建Svgicon
文件夹,再新建一个index.vue
文件。 -
定义新建文件
<template> <svg class="svg-icon" aria-hidden="true"> <use :xlink:href="iconName"></use> </svg> </template> <script setup> import { defineProps, computed } from 'vue' // eslint-disable-next-line no-unused-vars const props = defineProps({ icon: { type: String, required: true } }) // eslint-disable-next-line no-unused-vars const iconName = computed(() => { return `#icon-${props.icon}` }) </script> <style lang="scss" scoped> .svg-icon { width: 1em; height: 1em; vertical-align: -0.15em; fill: currentColor; overflow: hidden; } </style>
aria-hidden的值为"true"
表示屏幕阅读器会将该元素对于可访问性的处理视为隐藏,不会将其读取给用户听。这通常用于隐藏纯装饰性的图像或其他元素,以避免干扰屏幕阅读器用户。<use :xlink:href="icon"></use>
它是SVG的一个功能,用于引用其他SVG元素,实现图标的重用。fill: currentColor;
: 这个属性用于指定 SVG 图标的填充颜色。currentColor 表示使用当前元素的文本颜色作为填充颜色。因此,该图标的颜色将与其所在的父元素的文本颜色一致
-
使用webpack获取到文件夹所有的文件,相当于批量获取上面svg文件夹里的图标导入。所以我们再新建一个
src/icons/下面新建一个文件index.js
文件,导入以下👇代码import SvgIcon from '@/components/SvgIcon' const svgRequired = require.context('./svg',false,/\.svg$/) svgRequired.keys().forEach((item) => svgRequired(item)) export default (app) => { app.component('svg-icon',SvgIcon) }
-
在
main.js
中导入import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' import '@/styles/index.scss' import SvgIcon from '@/icons' const app = createApp(App) SvgIcon(app) app.use(store).use(router).mount('#app')
-
在项目终端中安装
svg-loader
npm i --save-dev svg-sprite-loader@6.0.9
-
配置webpack,的
vue.config.js
粘贴以下代码:(完全粘贴我的就可以了,以免导致不同)const AutoImport = require('unplugin-auto-import/webpack') const Components = require('unplugin-vue-components/webpack') const { ElementPlusResolver } = require('unplugin-vue-components/resolvers') const path = require('path') function resolve(dir) { return path.join(__dirname, dir) } const webpack = require('webpack') module.exports = { configureWebpack: config => { config.plugins.push(AutoImport({ resolvers: [ElementPlusResolver()], })) config.plugins.push(Components({ resolvers: [ElementPlusResolver()], })) }, chainWebpack(config) { // 设置 svg-sprite-loader // config 为 webpack 配置对象 // config.module 表示创建一个具名规则,以后用来修改规则 config.module // 规则 .rule('svg') // 忽略 .exclude.add(resolve('src/icons')) // 结束 .end() // config.module 表示创建一个具名规则,以后用来修改规则 config.module // 规则 .rule('icons') // 正则,解析 .svg 格式文件 .test(/\.svg$/) // 解析的文件 .include.add(resolve('src/icons')) // 结束 .end() // 新增了一个解析的loader .use('svg-sprite-loader') // 具体的loader .loader('svg-sprite-loader') // loader 的配置 .options({ symbolId: 'icon-[name]' }) // 结束 .end() config .plugin('ignore') .use( new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn$/) ) config.module .rule('icons') .test(/\.svg$/) .include.add(resolve('src/icons')) .end() .use('svg-sprite-loader') .loader('svg-sprite-loader') .options({ symbolId: 'icon-[name]' }) .end() } }
之后就是使用新建的组件,我们用下面的语句替换上面讲的添加图标内容:
<svg-icon icon="username" class="svg-container"></svg-icon>
<svg-icon icon="password" class="svg-container"></svg-icon>
<script setup>
import { ref } from 'vue'
// import { Edit } from '@element-plus/icons-vue' 还需要注释掉不用的edit
</script>
添加密码框
下面是添加密码框,做法也是和上面一样的,我们直接复制上面写好的图标和输入框代码
<!-- 下面是密码框 -->
<el-form-item>
<!-- 添加图标 -->
<svg-icon icon="user" class="svg-container"></svg-icon>
<el-input v-model="form.password"></el-input>
<!-- 注意这里将绑定值改成密码password -->
</el-form-item>
<script setup>
import { ref } from 'vue'
import { Edit } from "@element-plus/icons-vue"
const form = ref({
username: '',
password: '' //注入password
})
</script>
添加按钮
<el-button type="primary" class="login-button">点击登录</el-button>
添加样式
注意:这个样式up没有带敲,所有大家就直接拿过来用吧(*´▽`)ノノ。
$bg: #2d3a4b;
$dark_gray: #889aa4;
$light_gray: #eee;
$cursor: #fff;
.login-container {
min-height: 100%;
width: 100%;
background-color: $bg;
overflow: hidden;
.login-form {
position: relative;
width: 520px;
max-width: 100%;
padding: 160px 35px 0;
margin: 0 auto;
overflow: hidden;
::v-deep .el-form-item {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.1);
border-radius: 5px;
color: #454545;
}
::v-deep .el-input {
display: inline-block;
height: 47px;
width: 85%;
input {
background: transparent;
border: 0px;
-webkit-appearance: none;
border-radius: 0px;
padding: 12px 5px 12px 15px;
color: $light_gray;
height: 47px;
caret-color: $cursor;
}
}
.login-button {
width: 100%;
box-sizing: border-box;
}
}
.tips {
font-size: 16px;
line-height: 28px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.svg-container {
padding: 6px 5px 6px 15px;
color: $dark_gray;
vertical-align: middle;
display: inline-block;
}
.title-container {
position: relative;
.title {
font-size: 26px;
color: $light_gray;
margin: 0px auto 40px auto;
text-align: center;
font-weight: bold;
}
::v-deep .lang-select {
position: absolute;
top: 4px;
right: 0;
background-color: white;
font-size: 22px;
padding: 4px;
border-radius: 4px;
cursor: pointer;
}
}
.show-pwd {
// position: absolute;
// right: 10px;
// top: 7px;
font-size: 16px;
color: $dark_gray;
cursor: pointer;
user-select: none;
}
}
最后就实现了整个页面啦🥳
报错问题汇总
补充:如果在重启项目的时候报错,这个时候就需要重新安装一遍UI组件。
npm install --save element-plus
如果出现了以下问题:
error '_' is defined but never used no-unused-vars
在前面加一行注释:(表示忽略下一行校验)
// eslint-disable-next-line
error 'xxx' is assigned a value but never used
在前面加一行注释:(通常表示你给一个变量赋了值,但在代码中没有使用它)
// eslint-disable-next-line no-unused-vars
error Newline required at end of file but not found eol-last
- 这里有了格式冲突问题,最后应该加一行空行就行(最后想吐槽一下eslint要求太严格了😨)例如:
Expected space or tab after '//' in comment.(spaced-comment)
这个也一样是少了空格,就是在双斜杠//
后面还要添加一个空格才行。
例如:// 先空格,然后再接内容
实现登录功能
表单验证
Form 表单 | Element Plus (element-plus.org)转到表单验证一栏中,
这个rules就是使用规则,我们复制他并粘贴到<el-form>
标签上,如下:
<el-form ref="formRef" :model="form" class="login-form" :rules="rules">
之后定义校验规则:
const rules = ref({
username: [
{
// 必填项
required: true,
// 提示语
message: 'Please input Activity name',
// 触发条件方式
trigger: 'blur'
}
],
password: [
{
required: true,
message: 'Please input Activity name',
trigger: 'blur'
}
]
})
在form-item
标签上绑定检验规则:
<el-form-item prop="username">
<el-form-item prop="password">
实现统一校验
这里特别声明一下:校验规则不需要响应式,因为校验规则没有状态变化。所以我们只需要再添加一个点击事件,触发到登录按钮即可实现。
给按钮添加一个点击事件,并定义了一个handleLogin
方法:
<el-button type="primary" class="login-button" @click="handleLogin">点击登录</el-button>
定义方法:
const formRef = ref(null)
const handleLogin = () => {
formRef.value.validate((valid) => {
if (valid) {
alert('submit!')
} else {
console.log('error submit!!')
return false
}
})
}
检验方法,打开我们的项目,点击登录按钮,如果弹出提示语就说明应用正确✔️了
登录请求
在src
下新建一个api文件夹
然后再创建一个request.js
文件。
-
导入axios
import axios from "axios"
-
创建axios基础配置
import axios from 'axios'
// eslint-disable-next-line
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
})export default service
因为我们有开发环境和生产环境,所以我们要在根目录下面创建:开发环境.env.development
和生产环境.env.production
。
//.env.development文件中添加代码
ENV = 'development'
VUE_APP_BASE_API = '/api'
//.env.production文件中添加代码
ENV = 'production'
VUE_APP_BASE_API = '/prod-api'
在vue.config.js配置代理来解决跨越问题
devServer: {
https: false,
hot: false,
proxy: {
'/api': {
target: 'https://lianghj.top:8888/api/private/v1/',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
在api文件夹中新建一个login.js
文件用来发起post请求。
import request from './request'
export const login = (data) => {
return request({
url: '/login',
method: 'POST',
data
})
}
回到登录页面,导入登录方法
import { login } from '@/api/login'
在登录校验方法中添加登录触发方法:
const formRef = ref(null)
const handleLogin = () => {
formRef.value.validate(async (valid) => {
if (valid) {
// alert('submit!')
await login(form.value)
} else {
console.log('error submit!!')
return false
}
})
}
密码隐藏
ElementPlus 自带切换功能 <el-input type="password" show - password />
即可
<el-input type="password" show-password/>
响应拦截器
响应拦截器是一种编程技术,通常在网络请求库中使用。它允许开发者在服务器响应返回给应用程序之前,对响应数据进行处理或修改。这可以用于日志记录、错误处理、数据验证、修改响应内容等目的。
例如,在JavaScript的Axios库中,可以通过配置响应拦截器来处理HTTP响应。这样,无论请求成功还是失败,都可以统一处理响应数据。
-
定义res接受数据
const handleLogin = () => {
formRef.value.validate(async (valid) => {
if (valid) {
// alert('submit!')
const res = await login(form.value)// 添加一个res接受传递过来的数据
console.log(res)// 反馈
} else {
console.log('error submit!!')
return false
}
})
} -
在
request.js
文件添加响应请求拦截器service.interceptors.response.use( (response) => { const { data, meta } = response.data if (meta.status === 200 || meta.status === 201) { return data } else { ElMessage.error(meta.msg) return Promise.reject(new Error(meta.msg)) } }, (error) => { console.log(error.response) error.response && ElMessage.error(error.response.data) return Promise.reject(new Error(error.response.data)) } ) //request.js完整代码 import axios from 'axios' import { ElMessage } from 'element-plus' import { diffTokenTime } from '@/utils/auth' import store from '@/store' const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 5000 }) service.interceptors.request.use( (config) => { if (localStorage.getItem('token')) { if (diffTokenTime()) { store.dispatch('app/logout') return Promise.reject(new Error('token 失效了')) } } config.headers.Authorization = localStorage.getItem('token') return config }, (error) => { return Promise.reject(new Error(error)) } ) service.interceptors.response.use( (response) => { const { data, meta } = response.data if (meta.status === 200 || meta.status === 201) { return data } else { ElMessage.error(meta.msg) return Promise.reject(new Error(meta.msg)) } }, (error) => { console.log(error.response) error.response && ElMessage.error(error.response.data) return Promise.reject(new Error(error.response.data)) } ) export default service
-
改正报错样式
在我们密码错误的时候,报错的样式却不是我们意料当中的弹窗提醒.那是因为我们的UI组件库样式还没有导入.
将一下代码导入带到main.js
当中:
import 'element-plus/dist/index.css'
这样密码错误样式就成功啦!!!