Vue智慧商城案例-首页+搜索分类页

306 阅读2分钟

首页

联想截图_20240201202818.png

image.png

1. 首页的静态结构+动态渲染

  1. 最开始的静态结构准备在 home.vue 中
<template>
  <div class="home">
    <!-- 导航条 -->
    <van-nav-bar title="智慧商城" fixed />

    <!-- 搜索框 -->
    <van-search
      readonly
      shape="round"
      background="#f1f1f2"
      placeholder="请在此输入搜索关键词"
      @click="$router.push('/search')"
    />

    <!-- 轮播图 -->
    <van-swipe class="my-swipe" :autoplay="3000" indicator-color="white">
      <van-swipe-item>
        <img src="@/assets/banner1.jpg" alt="">
      </van-swipe-item>
      <van-swipe-item>
        <img src="@/assets/banner2.jpg" alt="">
      </van-swipe-item>
      <van-swipe-item>
        <img src="@/assets/banner3.jpg" alt="">
      </van-swipe-item>
    </van-swipe>

    <!-- 导航 -->
    <van-grid column-num="5" icon-size="40">
      <van-grid-item
        v-for="item in 10" :key="item"
        icon="http://cba.itlike.com/public/uploads/10001/20230320/58a7c1f62df4cb1eb47fe83ff0e566e6.png"
        text="新品首发"
        @click="$router.push('/category')"
      />
    </van-grid>

    <!-- 主会场 -->
    <div class="main">
      <img src="@/assets/main.png" alt="">
    </div>

    <!-- 猜你喜欢 -->
    <div class="guess">
      <p class="guess-title">—— 猜你喜欢 ——</p>

      <div class="goods-list">
        <GoodsItem v-for="item in 10" :key="item"></GoodsItem>
      </div>
    </div>
  </div>
</template>

<script>
import GoodsItem from '@/components/GoodsItem.vue'
export default {
  name: 'HomeIndex',
  components: {
    GoodsItem
  }
}
</script>

<style lang="less" scoped>
// 主题 padding
.home {
  padding-top: 100px;
  padding-bottom: 50px;
}

// 导航条样式定制
.van-nav-bar {
  z-index: 999;
  background-color: #c21401;
  ::v-deep .van-nav-bar__title {
    color: #fff;
  }
}

// 搜索框样式定制
.van-search {
  position: fixed;
  width: 100%;
  top: 46px;
  z-index: 999;
}

// 分类导航部分
.my-swipe .van-swipe-item {
  height: 185px;
  color: #fff;
  font-size: 20px;
  text-align: center;
  background-color: #39a9ed;
}
.my-swipe .van-swipe-item img {
  width: 100%;
  height: 185px;
}

// 主会场
.main img {
  display: block;
  width: 100%;
}

// 猜你喜欢
.guess .guess-title {
  height: 40px;
  line-height: 40px;
  text-align: center;
}

// 商品样式
.goods-list {
  background-color: #f6f6f6;
}
</style>

  1. 下面的商品封装成了小组件,所以我们还需要在components里再写一个组件

image.png

<template>
    <div class="goods-item" @click="$router.push('/prodetail')">
      <div class="left">
        <img src="@/assets/product.jpg" alt="" />
      </div>
      <div class="right">
        <p class="tit text-ellipsis-2">
          三星手机 SAMSUNG Galaxy S23 8GB+256GB 超视觉夜拍系统 超清夜景 悠雾紫
          5G手机 游戏拍照旗舰机s23
        </p>
        <p class="count">已售104件</p>
        <p class="price">
          <span class="new">¥3999.00</span>
          <span class="old">¥6699.00</span>
        </p>
      </div>
    </div>
  </template>

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

  <style lang="less" scoped>
  .goods-item {
    height: 148px;
    margin-bottom: 6px;
    padding: 10px;
    background-color: #fff;
    display: flex;
    .left {
      width: 127px;
      img {
        display: block;
        width: 100%;
      }
    }
    .right {
      flex: 1;
      font-size: 14px;
      line-height: 1.3;
      padding: 10px;
      display: flex;
      flex-direction: column;
      justify-content: space-evenly;

      .count {
        color: #999;
        font-size: 12px;
      }
      .price {
        color: #999;
        font-size: 16px;
        .new {
          color: #f03c3c;
          margin-right: 10px;
        }
        .old {
          text-decoration: line-through;
          font-size: 12px;
        }
      }
    }
  }
  </style>

  1. 补充一下组件(utils/vant-ui.js
import { Search, Swipe, SwipeItem, Grid, GridItem, Toast, Tabbar, TabbarItem, NavBar } from 'vant'

Vue.use(Search)
Vue.use(Swipe)
Vue.use(SwipeItem)
Vue.use(Grid)
Vue.use(GridItem)

2. 封装接口

在api中新建一个 home.js ,把接口封装好

import request from '@/utils/request'

// 获取首页数据
export const getHomeData = () => {
  // get传参可以用params
  return request.get('/page/detail', {
    params: {
      pageId: 0
    }
  })
}

3. home.vue页面调用

<script>
import GoodsItem from '@/components/GoodsItem.vue'
// 调用接口
import { getHomeData } from '@/api/home'
export default {
  name: 'HomeIndex',
  components: {
    GoodsItem
  },
  // 调用接口
  async created () {
    const res = await getHomeData()
    console.log(res)
  }
}
</script>

4. 动态渲染

应该是1、4、6(我标错了) image.png

 data () {
    return {
      bannerList: [], // 轮播
      navList: [], // 导航
      proList: []// 商品
    }
  },
  async created () {
    // const res = await getHomeData()
    // console.log(res)
    // 把原来的res解构,因为我们只需要data里数据↓
    const { data: { pageData } } = await getHomeData()
    this.bannerList = pageData.item[1].data
    this.navList = pageData.item[4].data
    this.proList = pageData.item[6].data
  }
  • 然后接着上面页面渲染html里也渲染一下
  • 这三个里面,下面主要说一下商品的渲染,用到的父传子
  1. 首先console.log(this.proList),在控制台看一下详细的信息

image.png

home.vue

父传子

<div class="goods-list">
        <!-- :item="item",父传子 -->
        <GoodsItem v-for="items in proList" :key="items.goods_id" :items="items"></GoodsItem>
      </div>

GoodsItem.vue

props: {
    items: {
      type: Object,
      // default给了一个默认值
      default: () => {
        return {}
      }
    }
  }
}

搜索历史管理

image.png

静态结构

<template>
  <div class="search">
    <van-nav-bar title="商品搜索" left-arrow @click-left="$router.go(-1)" />

    <van-search show-action placeholder="请输入搜索关键词" clearable>
      <template #action>
        <div>搜索</div>
      </template>
    </van-search>

    <!-- 搜索历史 -->
    <div class="search-history">
      <div class="title">
        <span>最近搜索</span>
        <van-icon name="delete-o" size="16" />
      </div>
      <div class="list">
        <div class="list-item" @click="$router.push('/searchlist')">炒锅</div>
        <div class="list-item" @click="$router.push('/searchlist')">电视</div>
        <div class="list-item" @click="$router.push('/searchlist')">冰箱</div>
        <div class="list-item" @click="$router.push('/searchlist')">手机</div>
      </div>
    </div>
  </div>
</template>

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

<style lang="less" scoped>
.search {
  .searchBtn {
    background-color: #fa2209;
    color: #fff;
  }
  ::v-deep .van-search__action {
    background-color: #c21401;
    color: #fff;
    padding: 0 20px;
    border-radius: 0 5px 5px 0;
    margin-right: 10px;
  }
  ::v-deep .van-icon-arrow-left {
    color: #333;
  }
  .title {
    height: 40px;
    line-height: 40px;
    font-size: 14px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 15px;
  }
  .list {
    display: flex;
    justify-content: flex-start;
    flex-wrap: wrap;
    padding: 0 10px;
    gap: 5%;
  }
  .list-item {
    width: 30%;
    text-align: center;
    padding: 7px;
    line-height: 15px;
    border-radius: 50px;
    background: #fff;
    font-size: 13px;
    border: 1px solid #efefef;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    margin-bottom: 10px;
  }
}
</style>

实现

  1. 点击搜索,添加历史/点击下面历史记录,实现删除与追加
  1. 点击清空图标,可以清空历史记录
export default {
  name: 'SearchIndex',
  data () {
    return {
      search: '', // 输入框的内容
      history: ['手机', '杂志', '平板']// 历史记录
    }
  },
  methods: {
    goSearch (key) {
      // indexOf()方法返回数组中第一次出现给定元素的下标,如果不存在则返回-1
      const index = this.history.indexOf(key)
      if (index !== -1) {
        // 说明存在相同的项,那么要做的就是把这个原有的移除,再在前面unshift
        // splice(从哪开始,删除几个)
        this.history.splice(index, 1)
      }
      this.history.unshift(key)
    },
    clear () {
      this.history = []
    }
  }
}
  1. 搜索历史持久化,刷新完历史不丢失

历史持久化的本质就是你history数组的持久化

utils/storage.js

const HISTORY_KEY = 'hm_history_list'

// 获取搜索历史
export const getHistoryList = () => {
  const result = localStorage.getItem(HISTORY_KEY)
  return result ? JSON.parse(result) : []
}
// 设置搜索历史
export const setHistoryList = (arr) => {
  localStorage.setItem(HISTORY_KEY, JSON.stringify(arr))
}

页面直接调用:

image.png

搜索列表

目标:实现搜索列表页静态结构,封装接口,完成搜索列表页的渲染

image.png

静态结构

<template>
  <div class="search">
    <van-nav-bar fixed title="商品列表" left-arrow @click-left="$router.go(-1)" />

    <van-search
      readonly
      shape="round"
      background="#ffffff"
      value="手机"
      show-action
      @click="$router.push('/search')"
    >
      <template #action>
        <van-icon class="tool" name="apps-o" />
      </template>
    </van-search>

    <!-- 排序选项按钮 -->
    <div class="sort-btns">
      <div class="sort-item">综合</div>
      <div class="sort-item">销量</div>
      <div class="sort-item">价格 </div>
    </div>

    <div class="goods-list">
      <GoodsItem v-for="item in 10" :key="item"></GoodsItem>
    </div>
  </div>
</template>

<script>
import GoodsItem from '@/components/GoodsItem.vue'
export default {
  name: 'ListIndex',
  components: {
    GoodsItem
  }
}
</script>

<style lang="less" scoped>
.search {
  padding-top: 46px;
  ::v-deep .van-icon-arrow-left {
    color: #333;
  }
  .tool {
    font-size: 24px;
    height: 40px;
    line-height: 40px;
  }

  .sort-btns {
    display: flex;
    height: 36px;
    line-height: 36px;
    .sort-item {
      text-align: center;
      flex: 1;
      font-size: 16px;
    }
  }
}

// 商品样式
.goods-list {
  background-color: #f6f6f6;
}
</style>

实现(实现地址栏能获取搜索信息,并在搜索框渲染出来)

  1. 封装接口

image.png

import request from '@/utils/request'

// 获取搜索商品列表的数据
export const getProList = (obj) => {
  const { categoryId, goodsName, page } = obj
  return request.get('/goods/list', {
    params: {
      categoryId,
      goodsName,
      page
    }
  })
}

// 获取商品详情数据
export const getProDetail = (goodsId) => {
  return request.get('/goods/detail', {
    params: {
      goodsId
    }
  })
}

// 获取商品评价
export const getProComments = (goodsId, limit) => {
  return request.get('/comment/listRows', {
    params: {
      goodsId,
      limit
    }
  })
}

  1. 获取参数

:value="querySearch || '搜索商品'" image.png

 computed:{
    // 获取地址栏的搜索关键字
    querySearch () {
      return this.$route.query.search
    }
  }
}
  1. 调用接口(发请求)

image.png

data () {
    return {
      page: 1,
      proList: []
    }
  },
  async created () {
    const res = await getProList({
      goodsName: this.querySearch,
      page: this.page
    })
    console.log(res)
  }

这里打印了一下res后可以得到下图,所以可知,我们可以解构再进行渲染了 image.png

   <div class="goods-list">
      <GoodsItem v-for="items in proList" :key="items.goods_id" :items="items"></GoodsItem>
    </div>
    
    
 async created () {
    const { data: { list } } = await getProList({
      goodsName: this.querySearch,
      page: this.page
    })
    this.proList = list.data
  }

分类页

image.png
  1. 封装接口,api/category.js
import request from '@/utils/request'

// 获取分类数据
export const getCategoryData = () => {
  return request.get('/category/list')
}
  1. category的静态结构
<template>
  <div class="category">
    <!-- 分类 -->
    <van-nav-bar title="全部分类" fixed />

    <!-- 搜索框 -->
    <van-search
      readonly
      shape="round"
      background="#f1f1f2"
      placeholder="请输入搜索关键词"
      @click="$router.push('/search')"
    />

    <!-- 分类列表 -->
    <div class="list-box">
      <div class="left">
        <ul>
          <li v-for="(item, index) in list" :key="item.category_id">
            <a :class="{ active: index === activeIndex }" @click="activeIndex = index" href="javascript:;">{{ item.name }}</a>
          </li>
        </ul>
      </div>
      <div class="right">
        <div @click="$router.push(`/searchlist?categoryId=${item.category_id}`)" v-for="item in list[activeIndex]?.children" :key="item.category_id" class="cate-goods">
          <img :src="item.image?.external_url" alt="">
          <p>{{ item.name }}</p>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { getCategoryData } from '@/api/category'
export default {
  name: 'CategoryIndex',
  created () {
    this.getCategoryList()
  },
  data () {
    return {
      list: [],
      activeIndex: 0
    }
  },
  methods: {
    async getCategoryList () {
      const { data: { list } } = await getCategoryData()
      this.list = list
    }
  }
}
</script>

<style lang="less" scoped>
// 主题 padding
.category {
  padding-top: 100px;
  padding-bottom: 50px;
  height: 100vh;
  .list-box {
    height: 100%;
    display: flex;
    .left {
      width: 85px;
      height: 100%;
      background-color: #f3f3f3;
      overflow: auto;
      a {
        display: block;
        height: 45px;
        line-height: 45px;
        text-align: center;
        color: #444444;
        font-size: 12px;
        &.active {
          color: #fb442f;
          background-color: #fff;
        }
      }
    }
    .right {
      flex: 1;
      height: 100%;
      background-color: #ffffff;
      display: flex;
      flex-wrap: wrap;
      justify-content: flex-start;
      align-content: flex-start;
      padding: 10px 0;
      overflow: auto;

      .cate-goods {
        width: 33.3%;
        margin-bottom: 10px;
        img {
          width: 70px;
          height: 70px;
          display: block;
          margin: 5px auto;
        }
        p {
          text-align: center;
          font-size: 12px;
        }
      }
    }
  }
}

// 导航条样式定制
.van-nav-bar {
  z-index: 999;
}

// 搜索框样式定制
.van-search {
  position: fixed;
  width: 100%;
  top: 46px;
  z-index: 999;
}
</style>

  1. 给分类页传id

联想截图_20240202114415.png image.png

 // 请求时要携带参数,不然在分类页不管点击哪个,都只会得到所有商品总列表
categoryId: this.$route.query.categoryId,