vue基础案例

文章目录

    • 后台管理案例
      • 一、安装和配置路由
      • 二、基于路由渲染登录组件
      • 三、模拟登录功能
      • 四、后台主页基础布局
      • 五、退出登录并控制访问权限
      • 六、子路由嵌套显示
      • 七、点击进入用户详情页
      • 八、升级用户详情页的路由
        • 路由path的注意点
        • 扩展:控制页面的权限
    • 黑马头条
      • 一、 初始化
        • ① 创建并梳理项目结构
        • ② 安装和配置Vant组件
        • ③ 使用Tabber组件
        • ⑤ 使用Navbar组件
        • ⑥ 覆盖Navbar的默认样式
      • 二、文章列表
      • ①. 文章列表数据
          • 请求方式
          • 请求根路径
          • 请求URL 地址
          • 查询参数
          • 响应的数据结构
          • 返回参数说明
      • ② 封装utils目录下
      • ③ *原始写法* 在Home组件中封装initArticleList
      • ⑤ 封装articleInfo组件
      • ⑥ 为articleInfo组件封装props 属性
      • ⑦ 为articleInfo组件封装cover属性
      • 三、上拉加载更多
      • 四、下拉刷新
      • 五、[定制主题](https://vant-contrib.gitee.io/vant/v2/#/zh-CN/theme)

后台管理案例

一、安装和配置路由

  1. 安装路由

    npm i [email protected] -S
    
  2. 创建路由模块 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')

二、基于路由渲染登录组件

  1. router/index.js路由模块声明路由规则
// 1.导入登录组件
import MyLogin from '@/components/MyLogin.vue'

const router = new VueRouter({
  routes: [
    // 2.登录的路由规则
    {
      path: '/login',
      component: MyLogin
    }
  ]
})
  1. App.vue 组件启用 路由占位符

      <div>
        
        <router-view>router-view>
      div>
    
    1. 路由重定向
    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: ''
    }
  }
  1. 给重置按钮实现重置功能
<button type="button" class="btn btn-secondary mr-2" @click="reset">重置button>
// 重置
reset() {
  this.username = ''
  this.password = ''
},
  1. 给登录按钮实现登录功能
    • 登录成功 –> 存储token
    • 登录失败 –> 移除token
<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 + 空格 固定写法

四、后台主页基础布局

  1. 声明后台主页/home的路由规则

    • router/index.js
    // 导入主页组件
    import Home from '@/components/MyHome.vue'
    
        // 后台主页
        {
          path: '/home',
          component: Home
        }
    
  2. MyHome.vue挂载组件

  <div class="home-container">
    
    <MyHeader>MyHeader>
    
    <div class="home-main-box">
      
      <MyAside>MyAside>
      
      <div class="home-main-body">
  
      div>
    div>

五、退出登录并控制访问权限

  1. 实现退出功能
  2. 绑定按钮
  3. 退出
    • 清空token
    • 跳转登录页面
src/components/subcomponents/MyHeader.vue

 // 退出登录
    logout() {
      localStorage.removeItem('token')
      this.$router.push('/login')
    }
  1. 访问权限控制 全局前置守卫

    /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()
  }
})

六、子路由嵌套显示

  1. 左侧边栏 添加路由跳转链接
MyAside.vue
 
      
      
      
      
  1. MyHome.vue 子路由占位符
 
 <div class="home-main-body">
   <router-view>router-view>
 div>
  1. 声明子路由规则

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
      }, ]
    }

七、点击进入用户详情页

  1. 使用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>
  1. 实现详情点击功能
gotoDetail(){
  this.$router.push('userinfo')
}
  1. 声明用户详情页的路由规则
// 用户详情页
{
  path:'userinfo',
  component:UserDetail
}
  1. 实现用户详情页后退按钮功能
 <button type="button" class="btn btn-light btn-sm" @click="$router.back()">后退button>

八、升级用户详情页的路由

  1. 将用户id传入详情页
<a href="#" @click.prevent="gotoDetail(item.id)">详情a>
gotoDetail(id){
  this.$router.push('userinfo/:id')
}
  1. 更改路由规则
// 用户详情页
{
  path:'userinfo/:id',
  component:UserDetail
}
  1. MyUserDetail.vue

    接收参数

props: ['id']

​ 打印参数

<h4 class="text-center">用户详情 --- {{ id }}h4>
路由path的注意点

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

② 安装和配置Vant组件

vant3中文文档

vant2中文文档

# Vue 2 项目,安装 Vant 2:
npm i vant@latest-v2 -S

导入所有组件

import Vant from 'vant';
import 'vant/lib/index.css';

Vue.use(Vant);
③ 使用Tabber组件
  1. 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>
    
  2. 创建views/Home/Home.vue 模板







④ 通过路由展示对应组件

1.开启组件路由模式

App.vue
    
      首页
      我的
    
  1. 解决问题 路由无法正常跳转

当前测试时间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'
    }]
⑤ 使用Navbar组件

1.使用组件 并固定在页面上

Home.vue

  1. 给Home组件加上下边距
<template>
  <div class="home-container">
.home-container {
  padding: 46px 0 50px 0;
}
⑥ 覆盖Navbar的默认样式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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;
    }
  }

二、文章列表

①. 文章列表数据

请求方式
  • GET
请求根路径
  • https://www.escook.cn
请求URL 地址
  • /articles
查询参数
参数名 数据类型 说明
_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 数组

② 封装utils目录下

安装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

原始写法 在Home组件中封装initArticleList

    // 封装获取文章列表数据的方法
    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)
    }
  }

⑤ 封装articleInfo组件

创建src/components/Article







// 1.导入文章组件
import ArticleInfo from '@/components/Article/ArticleInfo.vue'
  // 2. 声明组件
  components: {
    ArticleInfo
  }
// 3. 使用组件
<ArticleInfo v-for="item in artlist" :key="item.id"></ArticleInfo>

⑥ 为articleInfo组件封装props 属性

src/conponents.ArticleInfo.vue替换为以下模板







  1. src/conponents.ArticleInfo.vue 自定义属性
<script>
export default {
  name: 'ArticleInfo',
  // 自定义属性
  props: {
    // 文章标题
    title: {
      type: String,
      default: ''
    }
  }
}
</script>
  1. src/conponents.ArticleInfo.vue 将标题动态绑定
 
 <span>{{ title }}span>
  1. 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 }} &nbsp;&nbsp; {{ cmtCount }}评论 &nbsp;&nbsp; {{ 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

    

⑦ 为articleInfo组件封装cover属性

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 组件通过 loadingfinished 两个变量控制加载状态,当组件滚动到底部时,会触发 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);
    },
  },
};

  1. 使用List组件

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>

  1. 定义data数据
 // 是否正在加载下一页数据 如果loding为true,则不会反复触发load事件
 // 每当下一页数据请求回来之后,需要把loding从true改为false
 // 由于页面初始化请求了一次数据。所以loading默认为true,即不会自动调用
 loading: true,
 // 所有数据是否加载完成了,如果没有更多数据,设置为true
 finished: false
  1. 在每次请求数据之后,将loading设为false,放行下次调用
 // 封装获取文章列表数据的方法
       async initArticleList() {
         // 发起get请求,获取文章的列表数据
         const { data: res } = await getArticleListAPI(this.page, this.limit)
         this.artlist = res
         // 第一页数据请求完之后,开启下一页请求判断
         this.loading = false
       },
  1. 定义onLoad方法

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,表示加载完成。

  1. List组件包裹在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>
  1. data 数据域定义数据
// 是否正在下拉刷新  false 为加载完成
isLoading: false
  1. 定义下拉刷新onRefresh函数
    // 下拉刷新
    onRefresh() {
      // 1.让页码值+1
      this.page++
      // 2.重新请求接口获取数据
      this.initArticleList(1)
    }

:下拉刷新应在头部拼接 下拉加载 应在尾部拼接数据,我们之前实现了将数据拼接在了尾部

  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
      }
    },

五、定制主题

  1. 删除之前自定义的css 颜色样式
  2. 找到官文的导航组件 查看样式变量,找到@nav-bar-background-color
  3. 引入less文件,在src/main.js中把原来引入的css语句替换为引入less语句
import 'vant/lib/index.less'
  1. 不用这个 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";'
          }
        }
      }
    }
  }
}
  1. 创建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

你可能感兴趣的:(vue,经验分享,vue.js)