安装路由
npm i [email protected] -S
创建路由模块 src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const router = new VueRouter()
export default router
3.main.js
挂载路由
// 1.导入模块
import router from '@/router'
new Vue({
render: h => h(App),
router // 2.挂载路由
}).$mount('#app')
router/index.js
路由模块声明路由规则// 1.导入登录组件
import MyLogin from '@/components/MyLogin.vue'
const router = new VueRouter({
routes: [
// 2.登录的路由规则
{
path: '/login',
component: MyLogin
}
]
})
App.vue
组件启用 路由占位符
<div>
<router-view>router-view>
div>
const router = new VueRouter({
routes: [{
// 路由重定向
path: '/',
redirect: '/login'
},
// 登录的路由规则
{
path: '/login',
component: Login
}
]
})
1. `MyLogin.vue` 输入框双向绑定
- trim 去除多余空格
<input type="text" class="form-control ml-2" id="username" placeholder="请输入登录名称" autocomplete="off" v-model.trim="username" />
<input type="password" class="form-control ml-2" id="password" placeholder="请输入登录密码" v-model.trim="password" />
data() {
return {
username: '',
password: ''
}
}
<button type="button" class="btn btn-secondary mr-2" @click="reset">重置button>
// 重置
reset() {
this.username = ''
this.password = ''
},
<button type="button" class="btn btn-primary" @click="login">登录button>
// 登录
login() {
if (this.username === 'admin' && this.password === '123456') {
localStorage.setItem('token', 'Bearer xxxx')
this.$router.push('/home')
} else {
localStorage.removeItem('token')
}
}
Token 认证时 token格式
Bearer
Bearer + 空格 固定写法
声明后台主页/home
的路由规则
router/index.js
// 导入主页组件
import Home from '@/components/MyHome.vue'
// 后台主页
{
path: '/home',
component: Home
}
MyHome.vue
挂载组件
<div class="home-container">
<MyHeader>MyHeader>
<div class="home-main-box">
<MyAside>MyAside>
<div class="home-main-body">
div>
div>
src/components/subcomponents/MyHeader.vue
// 退出登录
logout() {
localStorage.removeItem('token')
this.$router.push('/login')
}
访问权限控制 全局前置守卫
/src/router/index.js
router.beforeEach(function(to,from,next){
if (to.path === '/home') {
// 要访问后台主页,需要判断是否有 token
const token = localStorage.getItem('token')
if (token) {
next()
} else {
// 没有登录,强制跳转到登录页
next('/login')
}
} else {
next()
}
})
MyAside.vue
用户管理
权限管理
商品管理
订单管理
系统设置
MyHome.vue
子路由占位符
<div class="home-main-body">
<router-view>router-view>
div>
router/index.js
// 后台主页
{
path: '/home',
component: Home,
children: [{
path: 'users',
component: Users
}, {
path: 'rights',
component: Rights
}, {
path: 'goods',
component: Goods
}, {
path: 'orders',
component: Orders
}, {
path: 'settings',
component: Settings
}, ]
}
使用v-for
打印数据
MyUsers.vue
<tr v-for="item in userlist" :key="item.id">
<td>{{ item.id }}td>
<td>{{ item.name }}td>
<td>{{ item.age }}td>
<td>{{ item.position }}td>
<td>
<a href="#" @click.prevent="gotoDetail">详情a>
td>
tr>
gotoDetail(){
this.$router.push('userinfo')
}
// 用户详情页
{
path:'userinfo',
component:UserDetail
}
<button type="button" class="btn btn-light btn-sm" @click="$router.back()">后退button>
<a href="#" @click.prevent="gotoDetail(item.id)">详情a>
gotoDetail(id){
this.$router.push('userinfo/:id')
}
// 用户详情页
{
path:'userinfo/:id',
component:UserDetail
}
MyUserDetail.vue
接收参数
props: ['id']
打印参数
<h4 class="text-center">用户详情 --- {{ id }}h4>
home页面重定向到子路由
router/index.js
// 后台主页
{
path: '/home',
component: Home,
redirect: '/home/users',
router/pathArr.js
export default ['/home', '/home/users/', '/home/rights']
router/index.js
import pathArr from '@/router/pathArr.js'
router.beforeEach(function (to, from, next) {
if (pathArr.indexOf(to.path) !== -1) {
// 要访问后台主页,需要判断是否有 token
const token = localStorage.getItem('token')
if (token) {
next()
} else {
// 没有登录,强制跳转到登录页
next('/login')
}
} else {
next()
}
})
构建项目
vue create demo-toutiao
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HsLNXk5f-1648141354056)(https://s2.loli.net/2022/03/24/4uvOh2yeofMX8bg.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sAoqmw78-1648141354058)(https://s2.loli.net/2022/03/24/IX5rn6eBYH4TQts.png)]
如果某个组件是通过路由进行动态切换的,这种组件要放到views
里面
如果某个组件不是通过路由进行切换的,是一个可复用的组件,需要放到components
App.vue
去除初始化代码
<template>
<div>App根组件div>
template>
<script>
export default {}
script>
<style lang="less" scoped>style>
router/index,js
import Vue from 'vue'
import VueRouter from 'vue-router'
// 把VueRouter安装为Vue的插件
Vue.use(VueRouter)
// 路由规则的数组
const routes = []
// 创建路由实例对象
const router = new VueRouter({
routes
})
export default router
vant3中文文档
vant2中文文档
# Vue 2 项目,安装 Vant 2:
npm i vant@latest-v2 -S
导入所有组件
import Vant from 'vant';
import 'vant/lib/index.css';
Vue.use(Vant);
App.vue
引入Tabber组件
<van-tabbar>
<van-tabbar-item icon="home-o">标签van-tabbar-item>
<van-tabbar-item icon="user-o">标签van-tabbar-item>
van-tabbar>
创建views/Home/Home.vue
模板
Home 组件
④ 通过路由展示对应组件
1.开启组件路由模式
App.vue
首页
我的
当前测试时间2022.3.24 vant 版本2.12.45
App.vue
tabbar 需加上v-model=“active” 并绑定数据域
首页
我的
补充,eslint去除方法后面需要空格规则
.eslintrc.js
'space-before-function-paren': ['error', {
anonymous: 'never',
named: 'never',
asyncArrow: 'never'
}]
1.使用组件 并固定在页面上
Home.vue
<template>
<div class="home-container">
.home-container {
padding: 46px 0 50px 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ps8e4hna-1648141354058)(C:\Users\hanwu\AppData\Roaming\Typora\typora-user-images\image-20220324193330663.png)]
.van-nav-bar {
background-color: #007bff;
/deep/ .van-nav-bar__title {
color: white;
}
}
参数名 | 数据类型 | 说明 |
---|---|---|
_page | Number | 页码值。从 1 开始 |
_limit | Number | 每页展示的数据条数。 |
[
{
"art_id": "8163",
"title": "iOS原生混合RN开发最佳实践",
"aut_id": "1111",
"comm_count": "254",
"pubdate": "2019-03-11 09:00:00",
"aut_name": "黑马先锋",
"is_top": 0,
"cover": {
"type": 3,
"images": [
"http://www.liulongbin.top:8000/resources/images/32.jpg",
"http://www.liulongbin.top:8000/resources/images/80.jpg",
"http://www.liulongbin.top:8000/resources/images/32.jpg"
]
}
},
{
"art_id": "8089",
"title": "Typescript玩转设计模式 之 创建型模式",
"aut_id": "1111",
"comm_count": "24",
"pubdate": "2019-03-11 09:00:00",
"aut_name": "黑马先锋",
"is_top": 0,
"cover": {
"type": 1,
"images": [
"http://www.liulongbin.top:8000/resources/images/11.jpg"
]
}
},
{
"art_id": "8145",
"title": "JAVA消息确认机制之ACK模式",
"aut_id": "1111",
"comm_count": "99",
"pubdate": "2019-03-11 09:00:00",
"aut_name": "黑马先锋",
"is_top": 0,
"cover": {
"type": 0
}
}
]
参数名 | 类型 | 说明 |
---|---|---|
art_id | string | 文章 id |
title | string | 文章标题 |
aut_id | string | 作者的 id |
comm_count | string | 评论数 |
pubdate | string | 发布日期 |
aut_name | string | 作者名字 |
|- cover | object | 文章封面 |
|---- type | number | 封面的数量,可选值:0、1、3 |
|---- images | array | 文章封面图片的 URL 数组 |
安装axios
npm i axios -S
src/utils/request.js
import axios from 'axios'
const request = axios.create({
// 指定请求的根路径
baseURL: 'https://www.escook.cn'
})
export default request
// 封装获取文章列表数据的方法
async initArticleList() {
// 发起get请求,获取文章的列表数据
const { data: res } = await request.get('/articles', {
// 请求参数
params: {
_page: this.page,
_limit: this.limit
}
})
console.log(res)
}
④ 封装articleAPI模块
发现以上写法在每个组件上都要重写一次
将发送请求封装到模块中,src/api/articleAPI.js
/* eslint-disable space-before-function-paren */
// 文章相关的API接口,都封装到这个模块中
// 导入request.js
import request from '@/utils/request.js'
export const getArticleListAPI = function (_page, _limit) {
return request.get('/articles', {
// 请求参数
params: {
_page,
_limit
}
})
}
Home.vue
// 按需导入API接口
import { getArticleListAPI } from '@/api/articleAPI.js'
data() {
return {
// 页码值
page: 1,
// 每页显示多少条数据
limit: 10
}
},
created() {
this.initArticleList()
},
methods: {
// 封装获取文章列表数据的方法
async initArticleList() {
// 发起get请求,获取文章的列表数据
const { data: res } = await getArticleListAPI(this.page, this.limit)
console.log(res)
}
}
创建src/components/Article
article组件
// 1.导入文章组件
import ArticleInfo from '@/components/Article/ArticleInfo.vue'
// 2. 声明组件
components: {
ArticleInfo
}
// 3. 使用组件
<ArticleInfo v-for="item in artlist" :key="item.id"></ArticleInfo>
将src/conponents.ArticleInfo.vue
替换为以下模板
文章的标题噢
作者 0评论 发布日期
src/conponents.ArticleInfo.vue
自定义属性<script>
export default {
name: 'ArticleInfo',
// 自定义属性
props: {
// 文章标题
title: {
type: String,
default: ''
}
}
}
</script>
src/conponents.ArticleInfo.vue
将标题动态绑定
<span>{{ title }}span>
src/views/Home/home.vue
父组件传值 :title="item.title"
<ArticleInfo v-for="item in artlist" :key="item.id" :title="item.title">ArticleInfo>
注意 如果子组件中有自定义属性是小驼峰写法例如cmtCount
,在父组件绑定的时候建议换成cmt-count
,这是一种规则
其余属性封装相同,完整如下:
src/conponents.ArticleInfo.vue
<template>
<div>
<van-cell>
<!-- 标题区域的插槽 -->
<template #title>
<div class="title-box">
<!-- 标题 -->
<span>{{ title }}</span>
<!-- 单张图片 -->
<img src="https://www.escook.cn/vuebase/pics/1.png" alt="" class="thumb" />
</div>
<!-- 三张图片 -->
<div class="thumb-box">
<img src="https://www.escook.cn/vuebase/pics/2.png" alt="" class="thumb" />
<img src="https://www.escook.cn/vuebase/pics/2.png" alt="" class="thumb" />
<img src="https://www.escook.cn/vuebase/pics/2.png" alt="" class="thumb" />
</div>
</template>
<!-- label 区域的插槽 -->
<template #label>
<div class="label-box">
<span>{{ author }} {{ cmtCount }}评论 {{ time }}</span>
<!-- 关闭按钮 -->
<van-icon name="cross" />
</div>
</template>
</van-cell>
</div>
</template>
<script>
export default {
name: 'ArticleInfo',
// 自定义属性
props: {
// 文章标题
title: {
type: String,
default: ''
},
// 作者的名字
author: {
type: String,
default: ''
},
// 评论数
cmtCount: {
type: [Number, String], // 既可以是数字也可以是字符串
default: 0
},
// 发布日期
time: {
type: String,
default: ''
}
}
}
</script>
src/views/Home/home.vue
src/conponents.ArticleInfo.vue
// 封面的信息对象
cover: {
type: Object,
// 传入一个对象时,需要用function传入一个默认值
// eslint-disable-next-line space-before-function-paren
default: function () {
return {
cover: 0
}
}
}
cover对象中存在type属性,用来表示组件需要展示几张图片,我们使用v-if
来展示图片
<img :src="cover.images[0]" alt="" class="thumb" v-if="cover.type === 1" />
<div class="thumb-box" v-if="cover.type === 3">
<img :src="cover.images[i]" alt="" class="thumb" v-for="(item, i) in cover.images" :key="i" />
List组件 官文介绍
List 组件通过 loading
和 finished
两个变量控制加载状态,当组件滚动到底部时,会触发 load
事件并将 loading
设置成 true
。此时可以发起异步操作并更新数据,数据更新完毕后,将 loading
设置成 false
即可。若数据已全部加载完毕,则直接将 finished
设置成 true
即可
export default {
data() {
return {
list: [],
loading: false,
finished: false,
};
},
methods: {
onLoad() {
// 异步更新数据
// setTimeout 仅做示例,真实场景中一般为 ajax 请求
setTimeout(() => {
for (let i = 0; i < 10; i++) {
this.list.push(this.list.length + 1);
}
// 加载状态结束
this.loading = false;
// 数据全部加载完成
if (this.list.length >= 40) {
this.finished = true;
}
}, 1000);
},
},
};
src/views/Home/home.vue
<ArticleInfo v-for="item in artlist" :key="item.id" :title="item.title" :author="item.aut_name" :cmt-count="item.comm_count" :time="item.pubdate" :cover="item.cover">ArticleInfo>
// 是否正在加载下一页数据 如果loding为true,则不会反复触发load事件
// 每当下一页数据请求回来之后,需要把loding从true改为false
// 由于页面初始化请求了一次数据。所以loading默认为true,即不会自动调用
loading: true,
// 所有数据是否加载完成了,如果没有更多数据,设置为true
finished: false
loading
设为false
,放行下次调用 // 封装获取文章列表数据的方法
async initArticleList() {
// 发起get请求,获取文章的列表数据
const { data: res } = await getArticleListAPI(this.page, this.limit)
this.artlist = res
// 第一页数据请求完之后,开启下一页请求判断
this.loading = false
},
load 做两件事 改变page值 重新请求数据
// 只要onLoad被调用,就应该请求一次数据
onLoad() {
console.log('触发了load')
// 1.当触发了load事件之后,让页码值+1
this.page++
// 2.重新请求接口获取数据
this.initArticleList()
}
此时存在一个问题,新数据会把旧数据给覆盖了,需要修改artlist
= [旧数据在前,新数据在后]
…
数组合并
const arr1 = [1,2,3]
const arr2 = [4,5,6]
arr1.push(arr2)
[1,2,3,[4,5,6]]
const newArr = [...arr1,...arr2]
[1,2,3,4,5,6]
List 组件可以与 PullRefresh
组件结合使用,实现下拉刷新的效果
下拉刷新时会触发 refresh
事件,在事件的回调函数中可以进行同步或异步操作,操作完成后将 v-model
设置为 false
,表示加载完成。
Lis
t组件包裹在PullRefresh
组件中 <van-pull-refresh v-model="isLoading" @refresh="onRefresh">
<van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
<ArticleInfo
v-for="item in artlist"
:key="item.id"
:title="item.title"
:author="item.aut_name"
:cmt-count="item.comm_count"
:time="item.pubdate"
:cover="item.cover">
ArticleInfo>
van-list>
van-pull-refresh>
// 是否正在下拉刷新 false 为加载完成
isLoading: false
onRefresh
函数 // 下拉刷新
onRefresh() {
// 1.让页码值+1
this.page++
// 2.重新请求接口获取数据
this.initArticleList(1)
}
注 :下拉刷新应在头部拼接 下拉加载 应在尾部拼接数据,我们之前实现了将数据拼接在了尾部
initArticleList
方法,使其接收一个参数,判断是下拉还是上拉 // 封装获取文章列表数据的方法
async initArticleList(isRefresh) {
// 发起get请求,获取文章的列表数据
const { data: res } = await getArticleListAPI(this.page, this.limit)
// 判断数据拼接顺序
if (isRefresh) {
// 下拉属性:新数据在前,旧数据在后
this.artlist = [...res, ...this.artlist]
this.isLoading = false
} else {
// 上拉加载:旧数据在前,新数据在后
this.artlist = [...this.artlist, ...res]
}
// 第一页数据请求完之后,开启下一页请求判断
this.loading = false
if (res.length === 0) {
// 证明没有下一页的数据了,直接把finished改为true,表示数据加载完了
this.finished = true
}
},
@nav-bar-background-color
src/main.js
中把原来引入的css语句替换为引入less语句import 'vant/lib/index.less'
vue.config.js
配置文件直接覆盖变量,这种方法需要重启服务器生效module.exports = {
css: {
loaderOptions: {
less: {
// 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
lessOptions: {
modifyVars: {
// 直接覆盖变量
'nav-bar-background-color': 'red'
// 或者可以通过 less 文件覆盖(文件路径为绝对路径)
// hack: 'true; @import "your-less-file-path.less";'
}
}
}
}
}
}
src/theme.less
文件@blue:#007bff;
@white:#fff;
// 覆盖Navbar的less样式
@nav-bar-background-color:@blue;
@nav-bar-title-text-color:@white;
webpack 在进行打包的时候,底层用到了node.js
因此,在vue.config,js配置文件中,可以导入并使用node.js中的核心模块
6.完整vue.config.js
const path = require('path')
const themePath = path.join(__dirname, './src/theme.less')
// vue-cli 自己创建的
const {
defineConfig
} = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
})
module.exports = {
css: {
loaderOptions: {
less: {
// 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
lessOptions: {
modifyVars: {
// 直接覆盖变量
// 'nav-bar-background-color': 'red'
// 或者可以通过 less 文件覆盖(文件路径为绝对路径)
hack: `true; @import "${themePath}";`
}
}
}
}
}
}
扩展知识:打包后如何文件形式访问
publicPath
当前测试版本 vue-cli 5.0 直接添加publicPath: './'
无效
to-do待完成功能 组件切换回时 用户界面滚动条停留在切换前位置
to-do待学习lodash.js库
lodash