基于VueCli自定义创建项目
代码规范:一套写代码的约定规则。 比如 赋值符号的左右是否需要空格 一句话结束是否要加;
正规的团队 需要统一的编码风格
https://standardjs.com/rules-zhcn.html
规则查找 https://zh-hans.eslint.org/docs/latest/rules
eslint插件
安装ESLint
和Prettier ESLint
setting.json
中配置
{
"editor.tabSize": 2,
"editor.linkedEditing": true,
"security.workspace.trust.untrustedFiles": "open",
"git.autofetch": true,
"workbench.editor.untitled.hint": "hidden",
"emmet.includeLanguages": {
"editor.formatOnType": "true",
"editor.formatOnSave": "true"
},
"editor.formatOnType": true,
"editor.formatOnPaste": true,
"git.openRepositoryInParentFolders": "never",
"cssrem.rootFontSize": 75,
"[vue]": {
"editor.defaultFormatter": "Vue.volar"
},
"vetur.validation.template": false,
// 保存时,eslint自动帮我们修复错误
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"files.autoSave": "afterDelay",
// 保存代码,自动格式化
"editor.formatOnSave": true,
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[javascript]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[html]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
}
}
组件库:第三方封装好了很多很多的组件,整合到一起就是一个组件库
https://vant-contrib.gitee.io/vant/v2/#/zh-CN/
pc端: element-ui(element-plus) ant-design-vue
移动端: vant-ui , Mint UI(饿了么) ,Cube UI(滴滴)
自动按需导入
# 安装插件
npm i babel-plugin-import -D
// 对于使用 babel7 的用户,可以在 babel.config.js 中配置
module.exports = {
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
};
// 接着你可以在代码中直接引入 Vant 组件
// 插件会自动将代码转化为方式二中的按需引入形式
import { Button } from 'vant';
手动按需导入
在不使用插件的情况下,可以手动引入需要的组件。
import Button from 'vant/lib/button';
import 'vant/lib/button/style';
import Vue from 'vue';
import Vant from 'vant';
import 'vant/lib/index.css';
Vue.use(Vant);
1.按需引入
babel.config.js
中配置
module.exports = {
plugins: [
[
'import',
{
libraryName: 'vant',
libraryDirectory: 'es',
// 指定样式路径
style: (name) => `${name}/style/less`,
},
'vant',
],
],
};
2.修改样式变量
vue.config.js
中进行配置
// vue.config.js
module.exports = {
css: {
loaderOptions: {
less: {
// 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
lessOptions: {
modifyVars: {
// 直接覆盖变量
'blue: 'orange',
},
},
},
},
},
};
基于postcss插件 实现项目vw适配
安装插件
yarn add [email protected]
根目录新建postcss.config.js文件
// postcss.config.js
module.exports = {
plugins: {
'postcss-px-to-viewport': {
viewportWidth: 375,
},
},
};
使用axios来请求后端接口,会对axios进行一些配置,(配置基础地址,请求响应拦截器等)
项目开发中,会对axios进行二次封装,单独封装到一个request模块中,便于维护使用
安装axios-------新建request模块(utils/request.js)------创建实例,自定义配置,导出实例-----------使用
import request from '../utils/request'
export const registerFn = (data) => {
return request.post('/user/register', data)
}
export const loginFn = (data) => {
return request.post('/user/login', data)
}
使用
Login.vue
Register.vue类似
<script>
import { loginFn } from '@/api/user'
// import request from '../utils/request'
export default {
name: 'MyLogin',
methods: {
async onSubmit(values) {
try {
console.log('submit', values)
// const res = await request.post('/user/login', values)
// console.log(res)
const res = await loginFn(values)
console.log(res)
this.$toast('登录成功')
} catch (err) {
console.log(err)
}
}
}
}
问题:每次请求,都会又可能会错误,就需要错误提示
每次try catch很麻烦,能不能统一处理呢?
// 添加响应拦截器
instance.interceptors.response.use(
function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
console.log(response)
return response
},
function (error) {
console.log(error, 0)
// 超出 2xx 范围的状态码都会触发该函数。
// 有错误响应 后台会正常返回错误信息
if (error.response) {
Toast(error.response.data.message)
}
// 对响应错误做点什么
return Promise.reject(error)
}
)
<script>
/*
/user/register post请求
username password
*/
import { registerFn } from '../api/user'
export default {
name: 'RegisterName',
methods: {
async onSubmit(values) {
console.log(values)
await registerFn(values)
// toast已经被挂载到了原型上 通过this.$toast直接调用
this.$toast.success('注册成功')
console.log('注册成功了啦啦啦啦啦')
this.$router.push('/login')
this.$toast.fail('注册失败')
}
}
}
</script>
本地存入token,为了防止重名,起的名字很长,方便使用—local模块(getToken,setToken,delToken)
utils/storage.js
// 定义常量
const KEY = 'vant-mobile-token'
export const getToken = () => {
return localStorage.getItem(KEY)
}
export const setToken = (token) => {
localStorage.setItem(KEY, token)
}
export const sdelToken = () => {
localStorage.removeItem(KEY)
}
封装请求api----登录操作—跳转首页
<script>
import { loginFn } from '@/api/user'
import { setToken } from '@/api/storage'
export default {
name: 'MyLogin',
methods: {
async onSubmit(values) {
const { data } = await loginFn(values)
// 登录成功提示
this.$toast('登录成功')
// 本地存储token
setToken(data.data.token)
// 跳转主页
this.$router.push('/')
}
}
}
</script>
基于全局前置守卫,进行页面访问拦截处理
项目中,只能登录用户开放,如果未登录,一律拦截到登录
路由导航守卫----全局前置守卫
// 放行白名单
const whiteList = ['/login', '/register']
router.beforeEach((to, from, next) => {
// console.log(to, from, next)
const token = getToken()
console.log(token)
if (token) {
// 有token就直接放行
next()
} else {
// 没有token看是否在白名单中
if (whiteList.includes(to.path)) {
// 在白名单中就放行
next()
} else {
// 不在白名单中就拦截到登录页
next('/login')
}
}
})
每次请求自己携带token,太麻烦,通过请求拦截器统一携带token 更方便
// 添加请求拦截器--请求头统一携带token
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
const token = getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
token是由过期的时间的(2h) 一旦过期 或失效 就无法正确获取到数据—401
过期token进行请求-----------后台返回401 ---------跳转到登录页
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response
}, function (error) {
// console.log(error, 0)
// 有错误响应 后台正常返回了错误信息
if (error.response) {
if (error.response.status === 401) {
// 清除掉无效的token
delToken()
// 拦截到登录
router.push('/login')
} else {
// 有错误响应 提示错误信息
Toast(error.response.data.message)
}
}
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error)
})
// 触底触发事件
async onLoad () {
const res = await getArticles({
current: this.current,
sorter: this.sorter
})
// 需要在this.list基础上 累加res.data.rows
// this.list = res.data.rows
this.list.push(...res.data.rows)
console.log(this.list)
// console.log('可以请求更多数据')
// 如果数据已经请求完毕 将loading改成false 才能加载下一页的数据
// 一旦loading 改为ifalse load事件可以再次触发
this.loading = false
this.current++ // 当前页+1
// 没有更多数据的处理
if (this.current > res.data.pageTotal) {
this.finished = true
}
}
点击事件 传递不同值 推荐 weight_desc 最新 null
重置数据,根据新条件,重新请求第一页数据
this.current = 1
this.list = []
this.finished = false
处理导航高亮
changeSorter (value) {
// 修改排序规则(推荐/更新)
this.sorter = value
// 重置数据
this.current = 1
this.list = []
this.finished = false
// 标记需要开始加载了 因为我们是手动调用加载更多
// 所以loading需要自己改为true 避免重复触发
this.loading = true
// 根据最新的条件重新渲染
this.onLoad()
}
配置动态路由----添加跳转–获取params参数----请求渲染详情
详情页结构
<template>
<div class="detail-page">
<van-nav-bar
title="面经详情"
left-text="返回"
left-arrow
@click-left="$router.back()"
/>
<header class="header">
<h1>大标题</h1>
<p>
2050-04-06 | 300 浏览量 | 222 点赞数
</p>
<p>
<img src="头像" alt="" />
<span>作者</span>
</p>
</header>
<main class="body">
<p>我是内容</p>
<p>我是内容</p>
<p>我是内容</p>
<p>我是内容</p>
</main>
<div class="opt">
<van-icon class="active" name="like-o"/>
<van-icon name="star-o"/>
</div>
</div>
</template>
<script>
export default {
name: 'RegisterName',
created () {
console.log(this.$route.params.id)
}
}
</script>
<style lang="less" scoped>
.detail-page {
overflow: hidden;
padding: 0 15px;
.header {
h1 {
font-size: 24px;
}
p {
color: #999;
font-size: 12px;
display: flex;
align-items: center;
}
img {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
}
}
.opt {
position: fixed;
bottom: 100px;
right: 0;
> .van-icon {
margin-right: 20px;
background: #fff;
width: 40px;
height: 40px;
line-height: 40px;
text-align: center;
border-radius: 50%;
box-shadow: 2px 2px 10px #ccc;
font-size: 18px;
&.active {
background: #FEC635;
color: #fff;
}
}
}
}
</style>
封装接口------注册事件------调用接口请求------修改状态,修改文本,提示消息
article.js
中封装接口
// 点赞&收藏
export const changeArticleOpt = (data) => {
request.post('/interview/opt', {
id: data.id,
optType: data.optType
})
}
articleDetail.vue
详情页中创建点击事件
<div class="opt">
<van-icon :class="{ active: article.likeFlag }" name="like-o" @click="changeOpt(1)"></van-icon>
<van-icon :class="{ active: article.collectFlag }" name="star-o" @click="changeOpt(2)"></van-icon>
</div>
methods: {
async changeOpt(val) {
await changeArticleOpt({ id: this.article.id, optType: val })
if (val === 1) {
this.article.likeFlag = !this.article.likeFlag // 点赞状态取反
console.log(this.article.likeFlag)
if (this.article.likeFlag) {
this.article.likeCount++
this.$toast('点赞成功')
} else {
this.article.likeCount--
this.$toast('取消点赞')
}
} else {
this.article.collectFlag = !this.article.collectFlag // 收藏状态取反
console.log(this.article.collectFlag)
if (this.article.collectFlag) {
this.$toast('收藏成功')
} else {
this.$toast('取消收藏')
}
}
}
}
article.js
中封装收藏接口方法
// 收藏列表
export const getCollection = (data) => {
return request.get('/interview/opt/list', {
params: {
page: data.page || 1,
pageSize: data.pageSize || 5,
optType: 2
}
})
}
我的收藏页面collect.vue
<template>
<div>
<van-nav-bar title="我的收藏" />
<van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
<ArticleItem v-for="item in list" :key="item.id" :item=item></ArticleItem>
</van-list>
</div>
</template>
<script>
import { getCollection } from '@/api/article'
export default {
name: 'MyCollect',
data() {
return {
loading: false,
finished: false,
list: [],
page: 1
}
},
methods: {
async onLoad() {
const { data } = await getCollection({ page: this.page })
this.list.push(...data.rows)
this.loading = false
console.log(data)
this.page++
console.log(this.page)
console.log(data.pageTotal)
if (this.page > data.pageTotal) {
this.finished = true
}
}
}
}
</script>
<style scoped lang="less">
.van-cell {
.header {
display: flex;
height: 40px;
align-items: center;
.son {
display: flex;
flex-direction: column;
font-size: 12px;
p {
line-height: 12px;
margin: 2px 0;
&.ut {
color: #ccc;
}
}
}
}
.content {
color: gray;
overflow: hidden;
text-overflow: ellipsis;
/* 弹性伸缩盒子模型显示 */
display: -webkit-box;
/* 限制在一个块元素显示的文本的行数 */
-webkit-line-clamp: 2;
/* 设置或检索伸缩盒对象的子元素的排列方式 */
-webkit-box-orient: vertical;
}
}
</style>
article.js
接口中获取封装喜欢列表接口方法
// 喜欢列表
export const getLike = (data) => {
return request.get('/interview/opt/list', {
params: {
page: data.page || 1,
pageSize: data.pageSize || 5,
optType: 1
}
})
}
like.vue
喜欢列表页
<template>
<div>
<van-list>
<ArticleItem v-for="item in list" :key="item.id" :item="item"></ArticleItem>
</van-list>
</div>
</template>
<script>
import { getLike } from '@/api/article'
export default {
name: 'MyLike',
data() {
return {
list: [],
page: 1,
pageType: 1,
loading: false,
finished: false
}
},
async created() {
const { data } = await getLike({ page: this.page })
console.log(data)
this.list.push(...data.rows)
}
}
</script>
<style scoped></style>
user.js
封装获取用户信息接口方法
// 用户信息获取
export const getUserInfo = () => {
return request.get('/user/currentUser')
}
my.vue
用户信息页面
<template>
<div>
<van-list>
<ArticleItem v-for="item in list" :key="item.id" :item="item"></ArticleItem>
</van-list>
</div>
</template>
<script>
import { getLike } from '@/api/article'
export default {
name: 'MyLike',
data() {
return {
list: [],
page: 1,
pageType: 1,
loading: false,
finished: false
}
},
async created() {
const { data } = await getLike({ page: this.page })
console.log(data)
this.list.push(...data.rows)
}
}
</script>
<style scoped></style>
vue脚手架只是在开发过程中,协助开发的工具,当真正开发完了===》脚手架不参与上线
打包后,可以生成,浏览器能够直接运行的网页 ===》就是需要上线的源码
作用:
vue脚手架工具提供了打包命令,直接使用 yarn build
npm run build
在项目的根目录会自动创建一个文件夹 dist dist中的文件就是打包后的文件,只需要放到服务器中即可
vue.config.js
中配置
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
// 将资源访问路径从 / 配置成 ./ 相对路径
publicPath: './',
css: {
loaderOptions: {
less: {
// 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
lessOptions: {
modifyVars: {
// 直接覆盖变量
blue: 'orange'
}
}
}
}
}
})