【全栈之巅】Node.js + Vue.js 全栈开发王者荣耀手机端官网和管理后台学习笔记(3.13-3.15)

【全栈之巅】Node.js + Vue.js 全栈开发王者荣耀手机端官网和管理后台学习笔记(3.13-3.15)

本项目是 学习Bilibili 全栈之巅 视频教程相关源码和体会
https://gitee.com/blaunicorn/node-vue-wangzherongyao
持续更新中…

3.13 web页新闻资讯的数据接口

// server\models\Category.js
// 优化完善分类模型,建立虚拟联接
const mongoose = require('mongoose')

// 定义模型字段
const schema = new mongoose.Schema({
    name: {
        type: String,
        required: true
    },

    parent: { type: mongoose.SchemaTypes.ObjectId, ref: 'Category' } // 类型为ObjectId 并关联Category表
})

// 3.13 设置虚拟字段:子分类,类似vue中的计算属性,它是通过已定义的schema属性的计算\组合\拼接得到的新的值
schema.virtual('children', {
    localField: '_id', // 内键,schema对应的模型Title的_id
    foreignField: 'parent', //外键,关联模型Category的parent键
    justOne: false, // 只查询一条数据
    ref: 'Category' // 关联的模型
})

// 3.13 分类关联新闻标题
schema.virtual('newsList', {
    localField: '_id', // 内键,schema对应的模型Category的_id
    foreignField: 'categories', //外键,关联模型Article的categories键
    justOne: false, // 只查询一条数据
    ref: 'Article' // 关联的模型
})


// 导出Category模型,哪里需要用,哪里引入,引入到 routes/admin/index.js
module.exports = mongoose.model('Category', schema)
    // server\routes\web\index.js
    // 新增 新闻列表接口,用于前端调用。以分类为主题,关联新闻
    router.get('/news/list', async (req, res) => {
        // //3.13 调出子分类,顺便调出子分类里的新闻,用populate关联,用lean展示出来,但存在问题不能查询单独的分类数量
        // const parent = await Category.findOne({
        //     name: '新闻资讯'
        // }).populate({
        //     path: 'children',
        //     populate: {
        //         path: 'newsList'
        //     }
        // }).lean()
        // 3.13 另一种方式,聚合查询,可以同时查询多次,聚合参数叫聚合管道
        const parent = await Category.findOne({
            name: '新闻资讯'
        })
        const cats = await Category.aggregate([
            // 条件查询:字段 = 上级分类,找到分类,这一步与where查询没有太大区别
            { $match: { parent: parent._id } },
            // 类似与关系数据库的关联联接,左关联联接left join
            // 定义模型是,第三个参数collection省略了,它表示集合的名字,省略后默认是模型的复数小写。
            // 从哪个集合,本地键,外键、as给起个名字
            { $lookup: { from: 'articles', localField: '_id', foreignField: 'categories', as: 'newsList' } },
            // 定义要几个.添加\修改字段,特殊操作符slice 
            //  每一个分类只要5个
            { $addFields: { newsList: { $slice: ['$newsList', 5] } } }
        ]
        )
        const subCats = cats.map(v => v._id)
        // 热门分类,是独立于四个分类,新增的,不限制分类,条件是 子分类是那些 in操作符会筛选出字段值等于制定数组中任何值的文档
        cats.unshift({
            name: '热门',
            newsList: await Artice.find().where({
                categories: { $in: subCats }
                // 关联 categories字段,把_id拓展为名称
            }).populate('categories').limit(5).lean()
        })
        // 把newsList上增加catergoryName,方便前端显示
        cats.map(cat => {
            cat.newsList.map(news => {
                news.categoryName = cat.name === '热门' ? news.categories[0].name : cat.name
                return news
            })
            return cat
        })

        res.send(cats)
    })

3.14 web页首页新闻资讯界面展示

前端安装 axios 配置http.js 偷个懒,和admin一致,注意的是请求地址为

 baseURL: 'http://localhost:3000/web/api'
// web端安装日期时间格式化工具
npm install dayjs --save
// 单行文字,多余的省略掉
// text overflow
.text-ellipsis {
    display: inline-block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

完整代码如下

// web\src\views\Home.vue
<template>
  <div class="home">
    <swiper ref="mySwiper" :options="swiperOptions">
      <swiper-slide>
        <img
          class="w-100"
          src="../assets/images/201.jpeg"
          alt=""
          sizes=""
          srcset=""
        />
      </swiper-slide>
      <swiper-slide>
        <img
          class="w-100"
          src="../assets/images/202.jpeg"
          alt=""
          sizes=""
          srcset=""
        />
      </swiper-slide>
      <swiper-slide>
        <img
          class="w-100"
          src="../assets/images/203.jpeg"
          alt=""
          sizes=""
          srcset=""
        />
      </swiper-slide>
      <swiper-slide>
        <img
          class="w-100"
          src="../assets/images/204.jpeg"
          alt=""
          sizes=""
          srcset=""
        />
      </swiper-slide>
      <div
        class="swiper-pagination pagination-home text-right px-3 pb-2"
        slot="pagination"
      ></div>
    </swiper>
    <!-- end of swiper -->
    <!-- 图标导航 -->
    <!-- 外部容器text-center pt-3 内部容易mb-3 这样能统一样式 -->
    <transition name="fade" mode="out-in">
      <div
        class="nav-icons bg-white mt-3 d-flex flex-wrap text-center pt-3 text-grey-1"
      >
        <!-- <div class="nav-icons bg-white mt-3 text-center pt-3 text-grey-1"> -->
        <!-- <div class="nav-item mb-3" v-for="n in 10" :key="n">
        <i class="sprite sprite-news"></i>
        <div class="py-2">爆料站</div>
      </div> -->
        <!-- 处理底部的收起按钮,需要把d-flex flex-wrap 放在子集的div上,跟它平级写个收起的div -->

        <div class="d-flex flex-wrap" :class="{ toggleActive: isCollapse }">
          <a href class="nav-item my-3">
            <i class="sprite sprite-news"></i>
            <div>爆料站</div>
          </a>
          <a href class="nav-item my-3">
            <i class="sprite sprite-practice"></i>
            <div>故事站</div>
          </a>
          <a href class="nav-item my-3">
            <i class="sprite sprite-affair"></i>
            <div>周边商城</div>
          </a>
          <a href class="nav-item my-3 border-none">
            <i class="sprite sprite-mall"></i>
            <div>体验服</div>
          </a>
          <a href class="nav-item my-2">
            <i class="sprite sprite-start"></i>
            <div>新人专区</div>
          </a>
          <a href class="nav-item my-2">
            <i class="sprite sprite-honour"></i>
            <div>荣耀·传承</div>
          </a>
          <a href class="nav-item my-2">
            <i class="sprite sprite-community"></i>
            <div>同人社区</div>
          </a>
          <a href class="nav-item my-2 border-none">
            <i class="sprite sprite-base"></i>
            <div>王者营地</div>
          </a>
          <a href class="nav-item my-2">
            <i class="sprite sprite-echart"></i>
            <div>公众号</div>
          </a>
          <a href class="nav-item my-2">
            <i class="sprite sprite-edition"></i>
            <div>版本介绍</div>
          </a>
        </div>
        <!-- <div class="d-flex flex-wrap"> -->
        <div
          class="bg-light py-2 fs-sm;"
          style="width: 100%"
          @click="switchActive"
        >
          <i
            class="sprite sprite-arrow mr-1"
            :style="{ transform: isCollapse ? 'rotate(180deg)' : '' }"
          ></i>
          <span class="retract">{{ isCollapse ? '收起' : '展开' }}</span>
        </div>
      </div>
    </transition>
    <!-- end of 图标模块 -->
    <!-- begin of 字体图标 -->
    <!-- <i class="iconfont icon-news text-primary"></i> -->
    <!-- end of 字体图标 -->
    <!-- begin of 新闻资讯卡片 -->
    <div class="card mt-3 p-3 bg-white">
      <div class="card-header d-flex ai-center pb-3">
        <i class="iconfont icon-caidananniudianji" style="color: deeppink"></i>
        <div class="fs-xl flex-1 px-2">新闻资讯</div>
        <i class="iconfont icon-menu"></i>
      </div>
      <div class="card-body pt-3">
        <div class="nav jc-between">
          <div class="nav-item active">
            <div class="nav-link">热门</div>
          </div>
          <div class="nav-item">
            <div class="nav-link">新闻</div>
          </div>
          <div class="nav-item">
            <div class="nav-link">新闻</div>
          </div>
          <div class="nav-item">
            <div class="nav-link">新闻</div>
          </div>
          <div class="nav-item">
            <div class="nav-link">新闻</div>
          </div>
        </div>
        <div class="pt-3">
          <swiper>
            <swiper-slide v-for="m in 5" :key="m"
              ><div class="py-2" v-for="n in 5" :key="n">
                <span>[新闻]</span>
                <span>|</span>
                <span>春和景明柳垂莺娇,峡谷好礼随春报到</span>
                <span>06/12</span>
              </div></swiper-slide
            >
          </swiper>
        </div>
      </div>
    </div>
    <a-card icon="caidananniudianji" title="新闻资讯-全局组件">
      <!-- 应用插槽展示数据 -->
      <div class="nav jc-between">
        <div class="nav-item active">
          <div class="nav-link">热门</div>
        </div>
        <div class="nav-item">
          <div class="nav-link">新闻</div>
        </div>
        <div class="nav-item">
          <div class="nav-link">新闻</div>
        </div>
        <div class="nav-item">
          <div class="nav-link">新闻</div>
        </div>
        <div class="nav-item">
          <div class="nav-link">新闻</div>
        </div>
      </div>
      <div class="pt-3">
        <swiper>
          <swiper-slide v-for="m in 5" :key="m"
            ><div class="py-2" v-for="n in 5" :key="n">
              <span>[新闻]</span>
              <span>|</span>
              <span>春和景明柳垂莺娇,峡谷好礼随春报到</span>
              <span>06/12</span>
            </div></swiper-slide
          >
        </swiper>
      </div>
    </a-card>
    <!-- 封装好的高级组件 -->
    <m-list-card
      icon="caidananniudianji"
      title="新闻资讯-ListCard组件"
      :categories="newsCats"
    >
      <!-- 在父组件里,不通过循环,直接拿到子组件里的具名slot的数据,
      这样的好处是 子组件的内容可以由父组件决定怎么展示 -->
      <template #items="{ category }">
        <div
          class="py-2 fs-lg d-flex"
          v-for="(item, index) in category.newsList"
          :key="index"
        >
          <span class="text-info" v-text="`[${item.categoryName}]`"
            >[新闻]</span
          >
          <span class="px-1">|</span>
          <span
            class="flex-1 text-dark-1 text-ellipsis pr-2"
            v-text="item.title"
            >春和景明柳垂莺娇,峡谷好礼随春报到</span
          >
          <span class="text-grey-1 fs-sm">{{ item.createdAt | date }}</span>
        </div>
      </template>
      <!-- <template v-slot:heros="{ category }"></template> -->
    </m-list-card>
    <m-card icon="caidananniudianji" title="新闻资讯-局部组件"></m-card>
    <m-card icon="caidananniudianji" title="英雄列表"></m-card>
    <m-card icon="caidananniudianji" title="精彩视频"></m-card>
    <m-card icon="caidananniudianji" title="图文攻略"></m-card>
    <!-- end of 新闻资讯卡片 -->
  </div>
</template>

<script>
  import dayjs from 'dayjs';
  // @ is an alias to /src
  import Card from '../components/Card';
  export default {
    filters: {
      date(val) {
        return dayjs(val).format('MM/DD');
      },
    },
    name: 'Home',
    components: { 'm-card': Card },
    data() {
      return {
        swiperOptions: {
          slidesPerView: 1,
          autoplay: {
            disableOnInteraction: false,
            delay: 2000,
          },
          pagination: {
            el: '.swiper-pagination',
            clickable: true,
          },
        },
        isCollapse: true,
        // 定义ListCard组件的数据结构
        // newsList的简单写法,新建数组5填充1,再map循环替换成对象
        newsCats: [
          {
            _id: 1,
            name: '热门',
            newsList: new Array(5).fill(1).map((v) => ({
              _id: v + 201,
              categoryName: '赛事',
              title: '景明柳垂莺娇,峡谷好礼随春报到',
              date: '06/01',
            })),
          },
          {
            _id: 2,
            name: '新闻',
            newsList: new Array(5).fill(2).map((v) => ({
              _id: v + 202,
              categoryName: '赛事',
              title: '景明柳垂莺娇,峡谷好礼随春报到',
              date: '06/01',
            })),
          },
        ],
        // 后台数据
        // newsCats: [],
        herosCats: [],
      };
    },
    created() {
      this.fetchNewsCats();
    },
    mounted() {
      console.log('Current Swiper instance object', this.swiper);
      //   this.swiper.slideTo(3, 1000, false);
    },
    computed: {
      swiper() {
        return this.$refs.mySwiper.$swiper;
      },
    },
    methods: {
      async fetchNewsCats() {
        const res = await this.$http.get('news/list');
        this.newsCats = res.data;
      },
      switchActive() {
        this.isCollapse = !this.isCollapse;
      },
    },
  };
</script>
<style lang='scss' scope>
  @import '../style/variables.scss';
  //  重新定义一个class,便于单独管理各个页面的swipers
  .pagination-home {
    .swiper-pagination-bullet {
      display: inline-block;
      opacity: 1;
      border-radius: 0.1538rem;
      background-color: map-get($colors, 'white');
      //   background-color: #ffffff;
      &.swiper-pagination-bullet-active {
        background: map-get($colors, 'info');
      }
    }
  }
  .nav-icons {
    border-top: 1px solid $border-color;
    border-bottom: 1px solid $border-color;
    .nav-item {
      width: 25%;
      // 取消右侧的边框
      border-right: 1px solid $border-color;
      &:nth-child(4n) {
        border-right: none;
      }
    }
  }
  .toggleActive {
    height: 60px;
    overflow: hidden;
  }
  .fade-enter {
    opacity: 0;
  }
  .fade-leave {
    opacity: 1;
  }
  .fade-leave-active,
  .fade-enter-active {
    transition: opacity 0.9s;
  }
</style>

3.15 web首页英雄列表提取官方数据

// 简化版
$$('.hero-nav > li').map(( li,i)=>{return {heros:$$('li',$$('.hero-list')[i]).map(el=>{return {name:$$('h3',el)[0].innerHTML}}),name:li.innerText}})
// 获取img链接,并转成json数据
JSON.stringify($$('.hero-nav > li').map(( li,i)=>{return {heros:$$('li',$$('.hero-list')[i]).map(el=>{return {name:$$('h3',el)[0].innerHTML,avatar:$$('img',el)[0].src}}),name:li.innerText}}))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值