【VUE】day08黑马头条小项目

【VUE】day08黑马头条小项目

  • [1. 项目创建](#1. 项目创建)
  • [2. 安装和使用Vant](#2. 安装和使用Vant)
  • [3. 使用Tabbar组件并开启路由模式](#3. 使用Tabbar组件并开启路由模式)
  • [4. 使用Navbar组件](#4. 使用Navbar组件)
  • [5. 覆盖Navbar的默认样式](#5. 覆盖Navbar的默认样式)
  • [6. 了解获取列表数据的API接口](#6. 了解获取列表数据的API接口)

1. 项目创建







2. 安装和使用Vant

官网地址:https://vant.pro/vant/#/zh-CN

Vant 是一个轻量、可定制的移动端组件库,于 2017 年开源。

目前 Vant 官方提供了 Vue 2 版本、Vue 3 版本和微信小程序版本,并由社区团队维护 React 版本和支付宝小程序版本。

main.js

javascript 复制代码
import Vue from 'vue'
import App from './App.vue'
import router from './router'

// 导入并安装 Vant 组件库
import Vant from 'vant'
// 切记:为了能够覆盖默认的 less 变量,这里一定要把后缀名改为 .less
import 'vant/lib/index.less'

Vue.use(Vant)

# 在生产环境中vue2中自动移除开发模式的调试信息
Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

index.js

javascript 复制代码
import Vue from 'vue'
import VueRouter from 'vue-router'

// 导入需要的组件
import Home from '@/views/Home/Home.vue'
import User from '@/views/User/User.vue'

// 把 VueRouter 安装为 Vue 的插件
Vue.use(VueRouter)

// 路由规则的数组
const routes = [
  // 定义首页的路由规则
  { path: '/', component: Home },
  // 定义我的路由规则
  { path: '/user', component: User }
]

// 创建路由实例对象
const router = new VueRouter({
  routes
})

export default router

注意1: 为什么不需要Vue.use(router)

在 Vue 2 中,不需要显式调用 Vue.use(router),这是因为 Vue Router 是一个 插件,但它的注册方式与其他 Vue 插件(如 Vant)有所不同。

Vue.use(plugin)主要用于全局注册 Vue 插件,这个方法会调用插件的 install 方法,例如:

javascript 复制代码
Vue.use(Vant); // Vant 组件库是 Vue 插件

然而,Vue Router 并不是通过 Vue.use(router)来注册的,而是通过 在 Vue 实例中传递 router 配置项 来使用的:

javascript 复制代码
new Vue({
  router, // 这里直接传入了 router
  render: h => h(App)
}).$mount('#app');

这样 Vue 会自动将 router 实例挂载到 Vue 实例上,并让所有组件可以通过 this.$routerthis.$route 访问路由对象。

注意2: 什么时候需要 Vue.use(VueRouter)?

虽然 Vue.use(router) 不会被调用,但 Vue Router 本身仍然需要 Vue.use(VueRouter) 来注册 Vue 插件,通常在 router.js 文件中会有类似:

javascript 复制代码
import Vue from 'vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter); // 这里才是 Vue Router 的正确注册方式

const routes = [
  { path: '/', component: () => import('@/views/Home.vue') },
  { path: '/about', component: () => import('@/views/About.vue') }
];

const router = new VueRouter({
  mode: 'history',
  routes
});

export default router;

然后在 main.js 中直接导入 router 并传递给 Vue 实例,这样就不需要 Vue.use(router) 了。

总结:

  1. Vue Router 需要 Vue.use(VueRouter),但它是在 router.js 里注册的,所以 main.js 里不需要再 Vue.use(router)。

  2. Vant 组件库等插件需要 Vue.use(Vant),否则组件无法使用。

  3. new Vue({...}) 里传入 router,Vue 就会自动绑定路由,不需要额外 Vue.use(router)。

所以,在 main.js 里:

  • 你需要 Vue.use(Vant),否则 Vant 组件不可用;
  • 但不需要 Vue.use(router),因为 router 作为 Vue 实例的参数传入即可。

一个是实例一个是插件

3. 使用Tabbar组件并开启路由模式

javascript 复制代码
<template>
  <div class="home-container">
    <!-- 头部区域 -->
    <van-nav-bar title="黑马头条" fixed />

    <!-- 导入,注册,并使用 ArticleInfo 组件 -->
    <!-- 在使用组件的时候,如果某个属性名是"小驼峰"形式,则绑定属性的时候,建议改写成"连字符"格式。例如: -->
    <!-- cmtCount 建议写成 cmt-count -->
    <van-pull-refresh v-model="isLoading" :disabled="finished" @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>
  </div>
</template>

<script>
// 按需导入 API 接口
import { getArticleListAPI } from '@/api/articleAPI.js'

// 导入需要的组件
import ArticleInfo from '@/components/Article/ArticleInfo.vue'

export default {
  name: 'Home',
  data() {
    return {
      // 页码值
      page: 1,
      // 每页显示多少条数据
      limit: 10,
      // 文章的数组
      artlist: [],
      // 是否正在加载下一页数据,如果 loading 为 true,则不会反复触发 load 事件
      // 每当下一页数据请求回来之后,千万要记得,把 loading 从 true 改为 false
      loading: true,
      // 所有数据是否加载完毕了,如果没有更多数据了,一定要把 finished 改成 true
      finished: false,
      // 是否正在下拉刷新
      isLoading: false
    }
  },
  created() {
    this.initArticleList()
  },
  methods: {
    // 封装获取文章列表数据的方法
    async initArticleList(isRefresh) {
      // 发起 GET 请求,获取文章的列表数据
      const { data: res } = await getArticleListAPI(this.page, this.limit)

      if (isRefresh) {
        // 证明是下拉刷新;新数据在前,旧数据在后
        // this.artlist = [新数据在后, 旧数据在前]
        this.artlist = [...res, ...this.artlist]
        this.isLoading = false
      } else {
        // 证明是上拉加载更多;旧数据在前,新数据在后
        // this.artlist = [旧数据在前, 新数据在后]
        this.artlist = [...this.artlist, ...res]
        this.loading = false
      }

      if (res.length === 0) {
        // 证明没有下一页数据了,直接把 finished 改为 true,表示数据加载完了!
        this.finished = true
      }
    },
    // 只要 onLoad 被调用,就应该请求下一页数据
    onLoad() {
      console.log('触发了 load 事件!')
      // 1. 让页码值 +1
      this.page++
      // 2. 重新请求接口获取数据
      this.initArticleList()
    },
    // 下拉刷新的处理函数
    onRefresh() {
      console.log('触发了下拉刷新!')
      // 1. 让页码值 +1
      this.page++
      // 2. 重新请求接口获取数据
      this.initArticleList(true)
    }
  },
  // 注册组件
  components: {
    ArticleInfo
  }
}
</script>

<style lang="less" scoped>
.home-container {
  padding: 46px 0 50px 0;
}
</style>


App.vue

javascript 复制代码
<template>
  <div>
    <!-- 路由占位符 -->
    <router-view></router-view>

    <!-- Tabbar 区域 -->
    <van-tabbar route>
      <van-tabbar-item icon="home-o" to="/">首页</van-tabbar-item>
      <van-tabbar-item icon="user-o" to="/user">我的</van-tabbar-item>
    </van-tabbar>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style lang="less" scoped></style>

4. 使用Navbar组件


Home.vue

javascript 复制代码
<template>
  <div class="home-container">
    <!-- 头部区域 -->
    <van-nav-bar title="黑马头条" fixed />

    <!-- 导入,注册,并使用 ArticleInfo 组件 -->
    <!-- 在使用组件的时候,如果某个属性名是"小驼峰"形式,则绑定属性的时候,建议改写成"连字符"格式。例如: -->
    <!-- cmtCount 建议写成 cmt-count -->
    <van-pull-refresh v-model="isLoading" :disabled="finished" @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>
  </div>
</template>

<script>
// 按需导入 API 接口
import { getArticleListAPI } from '@/api/articleAPI.js'

// 导入需要的组件
import ArticleInfo from '@/components/Article/ArticleInfo.vue'

export default {
  name: 'Home',
  data() {
    return {
      // 页码值
      page: 1,
      // 每页显示多少条数据
      limit: 10,
      // 文章的数组
      artlist: [],
      // 是否正在加载下一页数据,如果 loading 为 true,则不会反复触发 load 事件
      // 每当下一页数据请求回来之后,千万要记得,把 loading 从 true 改为 false
      loading: true,
      // 所有数据是否加载完毕了,如果没有更多数据了,一定要把 finished 改成 true
      finished: false,
      // 是否正在下拉刷新
      isLoading: false
    }
  },
  created() {
    this.initArticleList()
  },
  methods: {
    // 封装获取文章列表数据的方法
    async initArticleList(isRefresh) {
      // 发起 GET 请求,获取文章的列表数据
      const { data: res } = await getArticleListAPI(this.page, this.limit)

      if (isRefresh) {
        // 证明是下拉刷新;新数据在前,旧数据在后
        // this.artlist = [新数据在后, 旧数据在前]
        this.artlist = [...res, ...this.artlist]
        this.isLoading = false
      } else {
        // 证明是上拉加载更多;旧数据在前,新数据在后
        // this.artlist = [旧数据在前, 新数据在后]
        this.artlist = [...this.artlist, ...res]
        this.loading = false
      }

      if (res.length === 0) {
        // 证明没有下一页数据了,直接把 finished 改为 true,表示数据加载完了!
        this.finished = true
      }
    },
    // 只要 onLoad 被调用,就应该请求下一页数据
    onLoad() {
      console.log('触发了 load 事件!')
      // 1. 让页码值 +1
      this.page++
      // 2. 重新请求接口获取数据
      this.initArticleList()
    },
    // 下拉刷新的处理函数
    onRefresh() {
      console.log('触发了下拉刷新!')
      // 1. 让页码值 +1
      this.page++
      // 2. 重新请求接口获取数据
      this.initArticleList(true)
    }
  },
  // 注册组件
  components: {
    ArticleInfo
  }
}
</script>

<style lang="less" scoped>
.home-container {
  padding: 46px 0 50px 0;
}
</style>

5. 覆盖Navbar的默认样式



6. 了解获取列表数据的API接口

后端Java写一个接口

java 复制代码
package com.study.docker.dockertest.service.impl;

import com.study.docker.dockertest.service.TouTiaoService;
import com.study.docker.dockertest.vo.ArticleVo;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Service
public class TouTiaoServiceImpl implements TouTiaoService {
    /**
     * 获取文章列表
     *
     * @return List<ArticleVo> 文章列表
     */
    @Override
    public List<ArticleVo> list() {
        List<ArticleVo> articles = new ArrayList<>();

        // 第一篇文章(图片中完整展示的)
        ArticleVo article1 = new ArticleVo();
        article1.setArt_id("8163");
        article1.setTitle("ios原生混合RN开发最佳实践");
        article1.setAut_id("1111");
        article1.setComm_count("254");
        article1.setPubdate("2019-03-11 09:00:00");
        article1.setAut_name("黑马先锋");
        article1.setIs_top(0);

        ArticleVo.Cover cover1 = article1.new Cover();
        cover1.setType(3);
        cover1.setImages(Arrays.asList(
                "https://inews.gtimg.com/om_bt/O17etAI6P8U6pnm0sfosjXWwgPp-PFOsiHCu0wxhhBK6MAA/1000",
                "https://inews.gtimg.com/om_bt/O6G9Or2buWkNlHJlLnn9c_9g1GLLPyIpRz8P5y4MzeE90AA/641",
                "https://inews.gtimg.com/om_bt/ORmcG_mJOQJ7iSRNsd9fTekT4T5ReR9o0mGUSxAIrAYVoAA/1000"
        ));
        article1.setCover(cover1);
        articles.add(article1);

        // 第二篇文章(图片中部分展示的)
        ArticleVo article2 = new ArticleVo();
        article2.setArt_id("8089");
        article2.setTitle("Typescript玩转设计模式之创建型模式");
        article2.setAut_id("2222");
        article2.setComm_count("189");
        article2.setPubdate("2019-03-10 14:30:00");
        article2.setAut_name("技术达人");
        article2.setIs_top(1);

        ArticleVo.Cover cover2 = article2.new Cover();
        cover2.setType(1);
        cover2.setImages(Arrays.asList(
                "https://inews.gtimg.com/om_bt/OMFylDibTEK3jdSDnrdFppgs2veVhDmbgdIAbGCPngDJkAA/641"
        ));
        article2.setCover(cover2);
        articles.add(article2);

        // 补充第三篇文章
        ArticleVo article3 = new ArticleVo();
        article3.setArt_id("7952");
        article3.setTitle("Spring Boot微服务架构实战");
        article3.setAut_id("3333");
        article3.setComm_count("312");
        article3.setPubdate("2019-03-09 10:15:00");
        article3.setAut_name("架构师老王");
        article3.setIs_top(0);

        ArticleVo.Cover cover3 = article3.new Cover();
        cover3.setType(2);
        cover3.setImages(Arrays.asList(
                "https://img14.360buyimg.com/n1/jfs/t1/221090/2/40815/112550/6645a642F1e63e250/e3a9ea63c4692f2d.jpg",
                "https://img10.360buyimg.com/img/jfs/t1/236460/19/20812/60229/66b2f8adF04ae08f4/b33b92ffacb11e39.jpg.avif"
        ));
        article3.setCover(cover3);
        articles.add(article3);

        // 补充第四篇文章
        ArticleVo article4 = new ArticleVo();
        article4.setArt_id("7821");
        article4.setTitle("Flutter跨平台开发指南");
        article4.setAut_id("4444");
        article4.setComm_count("176");
        article4.setPubdate("2019-03-08 16:45:00");
        article4.setAut_name("移动开发专家");
        article4.setIs_top(1);

        ArticleVo.Cover cover4 = article4.new Cover();
        cover4.setType(3);
        cover4.setImages(Arrays.asList(
                "https://img10.360buyimg.com/img/jfs/t1/68728/39/27995/65022/66b2f8deFdaedabc0/3ff4998c66b69a12.jpg.avif",
                "https://img10.360buyimg.com/img/jfs/t1/74904/21/26801/184607/66b310d1F964a7169/947ad3d66611a423.jpg.avif",
                "https://img10.360buyimg.com/img/jfs/t1/84175/19/27426/113247/66b3107bFdea12089/a405df49190b43d4.jpg.avif"
        ));
        article4.setCover(cover4);
        articles.add(article4);

        return articles;
    }
}

前端调用这个接口


javascript 复制代码
Home.vue

<template>
  <div class="home-container">
    <!-- 头部区域 -->
    <van-nav-bar title="黑马头条" fixed />

    <!-- 导入,注册,并使用 ArticleInfo 组件 -->
    <!-- 在使用组件的时候,如果某个属性名是"小驼峰"形式,则绑定属性的时候,建议改写成"连字符"格式。例如: -->
    <!-- cmtCount 建议写成 cmt-count -->
    <van-pull-refresh v-model="isLoading" :disabled="finished" @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>
  </div>
</template>

<script>
// 按需导入 API 接口
import { getArticleListAPI } from '@/api/articleAPI.js'

// 导入需要的组件
import ArticleInfo from '@/components/Article/ArticleInfo.vue'

export default {
  name: 'Home',
  data() {
    return {
      // 页码值
      page: 1,
      // 每页显示多少条数据
      limit: 10,
      // 文章的数组
      artlist: [],
      // 是否正在加载下一页数据,如果 loading 为 true,则不会反复触发 load 事件
      // 每当下一页数据请求回来之后,千万要记得,把 loading 从 true 改为 false
      loading: true,
      // 所有数据是否加载完毕了,如果没有更多数据了,一定要把 finished 改成 true
      finished: false,
      // 是否正在下拉刷新
      isLoading: false
    }
  },
  created() {
    this.initArticleList()
  },
  methods: {
    // 封装获取文章列表数据的方法
    async initArticleList(isRefresh) {
      // 发起 GET 请求,获取文章的列表数据
      const { data: { data: res = [] } = {} } = await getArticleListAPI(this.page, this.limit)

      if (isRefresh) {
        // 证明是下拉刷新;新数据在前,旧数据在后
        // this.artlist = [新数据在后, 旧数据在前]
        this.artlist = [...res, ...this.artlist]
        this.isLoading = false
      } else {
        // 证明是上拉加载更多;旧数据在前,新数据在后
        // this.artlist = [旧数据在前, 新数据在后]
        this.artlist = [...this.artlist, ...res]
        this.loading = false
      }

      if (res.length === 0) {
        // 证明没有下一页数据了,直接把 finished 改为 true,表示数据加载完了!
        this.finished = true
      }
    },
    // 只要 onLoad 被调用,就应该请求下一页数据
    onLoad() {
      console.log('触发了 load 事件!')
      // 1. 让页码值 +1
      this.page++
      // 2. 重新请求接口获取数据
      this.initArticleList()
    },
    // 下拉刷新的处理函数
    onRefresh() {
      console.log('触发了下拉刷新!')
      // 1. 让页码值 +1
      this.page++
      // 2. 重新请求接口获取数据
      this.initArticleList(true)
    }
  },
  // 注册组件
  components: {
    ArticleInfo
  }
}
</script>

<style lang="less" scoped>
.home-container {
  padding: 46px 0 50px 0;
}
</style>
javascript 复制代码
ArticleInfo.vue

<template>
  <div>
    <van-cell>
      <!-- 标题区域的插槽 -->
      <template #title>
        <div class="title-box">
          <!-- 标题 -->
          <span>{{ title }}</span>
          <!-- 单张图片 -->
          <img :src="cover.images[0]" alt="" class="thumb" v-if="cover.type === 1" />
        </div>
        <!-- 三张图片 -->
        <div class="thumb-box" v-if="cover.type === 3">
          <img :src="item" alt="" class="thumb" v-for="(item, i) in cover.images" :key="i" />
        </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: ''
    },
    // 封面的信息对象
    cover: {
      type: Object,
      // 通过 default 函数,返回 cover 属性的默认值
      default: function() {
        // 这个 return 的对象就是 cover 属性的默认值
        return { type: 0 }
      }
    }
  }
}
</script>

<style lang="less" scoped>
.label-box {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.thumb {
  // 矩形黄金比例:0.618
  width: 113px;
  height: 70px;
  background-color: #f8f8f8;
  object-fit: cover;
}

.title-box {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
}

.thumb-box {
  display: flex;
  justify-content: space-between;
}
</style>
相关推荐
Fri_3 分钟前
Vue 使用 xlsx 插件导出 excel 文件
javascript·vue.js·excel
熙曦Sakura12 分钟前
【C++】map
前端·c++
黑贝是条狗15 分钟前
html 列表循环滚动,动态初始化字段数据
前端·javascript·html
萌萌哒草头将军32 分钟前
🔥🔥🔥4 月 1 日尤雨溪突然宣布使用 Go 语言重写 Rolldown 和 Oxc!
前端·javascript·vue.js
搬砖的阿wei35 分钟前
从零开始学 Flask:构建你的第一个 Web 应用
前端·后端·python·flask
萌萌哒草头将军1 小时前
🏖️ TanStack:一套为现代 Web 开发打造的强大、无头且类型安全的库集合 🔥
前端·javascript·vue.js
指针满天飞1 小时前
同步、异步、Promise、then、async/await
前端·javascript·vue.js
Alang1 小时前
记一次错误使用 useEffect 导致电脑差点“报废”
前端·react.js
牛奶1 小时前
前端学AI:LangGraph学习-基础概念
前端·langchain·ai编程
welkin1 小时前
算法区间合并问题
前端·算法