天天看點

商城項目-商品查詢4.商品查詢

4.商品查詢

4.1.效果預覽

接下來,我們實作商品管理的頁面,先看下我們要實作的效果:

商城項目-商品查詢4.商品查詢

可以看出整體是一個table,然後有新增按鈕。是不是跟昨天寫品牌管理很像?

模闆代碼在分别在Goods.vue

商城項目-商品查詢4.商品查詢

4.2.從0開始

接下來,我們自己來實作一下,建立兩個元件:MyGoods.vue和MyGoodsForm.vue

商城項目-商品查詢4.商品查詢

内容先随意:

<template>
  <v-card>
      MyGoods
  </v-card>
</template>

<script>

  export default {
    name: "my-goods",
    data() {
      return {
      }
    }
  }
</script>

<style scoped>

</style>           

複制

然後修改menu.js,建立一個菜單:

商城項目-商品查詢4.商品查詢

修改router/index.js,添加一個路由:

商城項目-商品查詢4.商品查詢

預覽一下:

商城項目-商品查詢4.商品查詢

4.3.頁面實作

4.3.1.頁面基本表格

商品清單頁與品牌清單頁幾乎一樣,我們可以直接去複制一份過來,然後進行一些修改。

首先,字段不一樣,商品清單也展示的SPU資訊,包含以下字段:

id:
title:标題
cname:商品分類名稱
bname:品牌名稱           

複制

完整代碼:

<template>
  <v-card>
    <v-card-title>
      <v-btn color="primary" @click="addGoods">新增商品</v-btn>
      <!--搜尋框,與search屬性關聯-->
      <v-spacer/>
      <v-text-field label="輸入關鍵字搜尋" v-model.lazy="search" append-icon="search" hide-details/>
    </v-card-title>
    <v-divider/>
    <v-data-table
      :headers="headers"
      :items="goodsList"
      :search="search"
      :pagination.sync="pagination"
      :total-items="totalGoods"
      :loading="loading"
      class="elevation-1"
    >
      <template slot="items" slot-scope="props">
        <td>{{ props.item.id }}</td>
        <td class="text-xs-center">{{ props.item.title }}</td>
        <td class="text-xs-center">{{props.item.cname}}</td>
        <td class="text-xs-center">{{ props.item.bname }}</td>
        <td class="justify-center layout">
          <v-btn color="info" @click="editGoods(props.item)">編輯</v-btn>
          <v-btn color="warning">删除</v-btn>
          <v-btn >下架</v-btn>
        </td>
      </template>
    </v-data-table>
    <!--彈出的對話框-->
    <v-dialog max-width="500" v-model="show" persistent>
      <v-card>
        <!--對話框的标題-->
        <v-toolbar dense dark color="primary">
          <v-toolbar-title>{{isEdit ? '修改' : '新增'}}商品</v-toolbar-title>
          <v-spacer/>
          <!--關閉視窗的按鈕-->
          <v-btn icon @click="closeWindow"><v-icon>close</v-icon></v-btn>
        </v-toolbar>
        <!--對話框的内容,表單-->
        <v-card-text class="px-5">
          <my-goods-form :oldGoods="oldGoods" />
        </v-card-text>
      </v-card>
    </v-dialog>
  </v-card>
</template>

<script>
  // 導入自定義的表單元件
  import MyGoodsForm from './MyGoodsForm'

  export default {
    name: "my-goods",
    data() {
      return {
        search: '', // 搜尋過濾字段
        totalGoods: 0, // 總條數
        goodsList: [], // 目前頁品牌資料
        loading: true, // 是否在加載中
        pagination: {}, // 分頁資訊
        headers: [
          {text: 'id', align: 'center', value: 'id'},
          {text: '标題', align: 'center', sortable: false, value: 'title'},
          {text: '商品分類', align: 'center', sortable: false, value: 'cname'},
          {text: '品牌', align: 'center', value: 'bname', sortable: false,},
          {text: '操作', align: 'center', sortable: false}
        ],
        show: false,// 控制對話框的顯示
        oldGoods: {}, // 即将被編輯的商品資訊
        isEdit: false, // 是否是編輯
      }
    },
    mounted() { // 渲染後執行
      // 查詢資料
      this.getDataFromServer();
    },
    watch: {
      pagination: { // 監視pagination屬性的變化
        deep: true, // deep為true,會監視pagination的屬性及屬性中的對象屬性變化
        handler() {
          // 變化後的回調函數,這裡我們再次調用getDataFromServer即可
          this.getDataFromServer();
        }
      },
      search: { // 監視搜尋字段
        handler() {
          this.getDataFromServer();
        }
      }
    },
    methods: {
      getDataFromServer() { // 從服務的加載數的方法。
        // 發起請求
        this.$http.get("/item/spu/page", {
          params: {
            key: this.search, // 搜尋條件
            page: this.pagination.page,// 目前頁
            rows: this.pagination.rowsPerPage,// 每頁大小
            sortBy: this.pagination.sortBy,// 排序字段
            desc: this.pagination.descending// 是否降序
          }
        }).then(resp => { // 這裡使用箭頭函數
          this.goodsList = resp.data.items;
          this.totalGoods = resp.data.total;
          // 完成指派後,把加載狀态指派為false
          this.loading = false;
        })
      },
      addGoods() {
        // 修改标記
        this.isEdit = false;
        // 控制彈窗可見:
        this.show = true;
        // 把oldBrand變為null
        this.oldBrand = null;
      },
      editGoods(oldGoods){
        // 修改标記
        this.isEdit = true;
        // 控制彈窗可見:
        this.show = true;
        // 擷取要編輯的brand
        this.oldGoods = oldGoods;
      },
      closeWindow(){
        // 重新加載資料
        this.getDataFromServer();
        // 關閉視窗
        this.show = false;
      }
    },
    components:{
      MyGoodsForm
    }
  }
</script>

<style scoped>

</style>           

複制

主要的改動點:

  • 頁面的

    v-data-table

    中的屬性綁定修改。items指向goodsList,totalItems指向totalGoods
  • 頁面渲染的字段名修改:字段改成商品的SPU字段:id、title,cname(商品分類名稱),bname(品牌名稱)
  • data屬性修改了以下屬性:
    • goodsList:目前頁商品資料
    • totalGoods:商品總數
    • headers:頭資訊,需要修改頭顯示名稱
    • oldGoods:準備要修改的商品
  • 加載資料的函數:getDataFromServer,請求的路徑進行了修改,另外去除了跟排序相關的查詢。SPU查詢不排序
  • 新增商品的事件函數:清除了一些資料查詢接口,隻保留彈窗

檢視效果:

商城項目-商品查詢4.商品查詢

因為沒有編寫查詢功能,表格一直處于loading狀态。

接下來看彈窗:

商城項目-商品查詢4.商品查詢

4.3.2.上下架狀态按鈕

另外,似乎頁面少了對上下架商品的過濾,在原始效果圖中是有的:

商城項目-商品查詢4.商品查詢

這在Vuetify中是一組按鈕,我們檢視幫助文檔:

商城項目-商品查詢4.商品查詢

檢視執行個體得到以下資訊:

v-btn

:一個按鈕

v-btn-toggle

:按鈕組,内部可以有多個按鈕,點選切換,有以下屬性:

  • multiple:是否支援多選,預設是false
  • value:選中的按鈕的值,如果是多選,結果是一個數組;單選,結果是點選的v-btn中的value值,是以按鈕組的每個btn都需要指定value屬性

改造頁面:

首先在data中定義一個屬性,記錄按鈕的值。

filter:{
    saleable: false, // 上架還是下架
    search: '', // 搜尋過濾字段
}           

複制

這裡我們的做法是定義一個filter屬性,内部在定義search來關聯過濾字段,saleable來關聯上下架情況。

這樣watch就必須監聽filter,而不是隻監聽search了:

filter: {// 監視搜尋字段
  handler() {
    this.getDataFromServer();
  },
  deep:true
}           

複制

另外,頁面中與search有關的所有字段都需要修改成filter.search:

<!--搜尋框,與search屬性關聯-->
<v-text-field label="輸入關鍵字搜尋" v-model.lazy="filter.search" append-icon="search" hide-details/>           

複制

然後,在頁面中添加按鈕組:

<v-flex xs3>
     狀态:
     <v-btn-toggle v-model="filter.saleable">
         <v-btn flat>
             全部
         </v-btn>
         <v-btn flat :value="true">
             上架
         </v-btn>
         <v-btn flat :value="false">
             下架
         </v-btn>
     </v-btn-toggle>
</v-flex>           

複制

最後,不要忘了在查詢時,将saleable攜帶上:

getDataFromServer() { // 從服務的加載數的方法。
    // 發起請求
    this.$http.get("/item/spu/page", {
        params: {
            key: this.filter.search, // 搜尋條件
            saleable: this.filter.saleable, // 上下架
            page: this.pagination.page,// 目前頁
            rows: this.pagination.rowsPerPage,// 每頁大小
        }
    }).then(resp => { // 這裡使用箭頭函數
        this.goodsList = resp.data.items;
        this.totalGoods = resp.data.total;
        // 完成指派後,把加載狀态指派為false
        this.loading = false;
    })
}           

複制

4.4.背景提供接口

頁面已經準備好,接下來在背景提供分頁查詢SPU的功能:

4.4.1.實體類

SPU
@Table(name = "tb_spu")
public class Spu {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long brandId;
    private Long cid1;// 1級類目
    private Long cid2;// 2級類目
    private Long cid3;// 3級類目
    private String title;// 标題
    private String subTitle;// 子标題
    private Boolean saleable;// 是否上架
    private Boolean valid;// 是否有效,邏輯删除用
    private Date createTime;// 建立時間
    private Date lastUpdateTime;// 最後修改時間
	// 省略getter和setter
}           

複制

SPU詳情
@Table(name="tb_spu_detail")
public class SpuDetail {
    @Id
    private Long spuId;// 對應的SPU的id
    private String description;// 商品描述
    private String specTemplate;// 商品特殊規格的名稱及可選值模闆
    private String specifications;// 商品的全局規格屬性
    private String packingList;// 包裝清單
    private String afterService;// 售後服務
    // 省略getter和setter
}           

複制

4.4.2.controller

先分析:

  • 請求方式:GET
  • 請求路徑:/spu/page
  • 請求參數:
    • page:目前頁
    • rows:每頁大小
    • key:過濾條件
    • saleable:上架或下架
  • 傳回結果:商品SPU的分頁資訊。
    • 要注意,頁面展示的是商品分類和品牌名稱,而資料庫中儲存的是id,怎麼辦?

      我們可以建立一個類,繼承SPU,并且拓展cname和bname屬性,寫到

      ly-item-interface

      public class SpuBo extends Spu { String cname;// 商品分類名稱 String bname;// 品牌名稱 // 略 。。 }

編寫controller代碼:

我們把與商品相關的一切業務接口都放到一起,起名為GoodsController,業務層也是這樣

@RestController
public class GoodsController {

    @Autowired
    private GoodsService goodsService;

    /**
     * 分頁查詢SPU
     * @param page
     * @param rows
     * @param key
     * @return
     */
    @GetMapping("/spu/page")
    public ResponseEntity<PageResult<SpuBo>> querySpuByPage(
            @RequestParam(value = "page", defaultValue = "1") Integer page,
            @RequestParam(value = "rows", defaultValue = "5") Integer rows,
            @RequestParam(value = "key", required = false) String key) {
        // 分頁查詢spu資訊
        PageResult<SpuBo> result = this.goodsService.querySpuByPageAndSort(page, rows, key);
        if (result == null || result.getItems().size() == 0) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        return ResponseEntity.ok(result);
    }
}           

複制

4.4.3.service

所有商品相關的業務(包括SPU和SKU)放到一個業務下:GoodsService。

@Service
public class GoodsService {

    @Autowired
    private SpuMapper spuMapper;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private BrandMapper brandMapper;

    public PageResult<SpuBo> querySpuByPageAndSort(Integer page, Integer rows, Boolean saleable, String key) {
        // 1、查詢SPU
        // 分頁,最多允許查100條
        PageHelper.startPage(page, Math.min(rows, 100));
        // 建立查詢條件
        Example example = new Example(Spu.class);
        Example.Criteria criteria = example.createCriteria();
        // 是否過濾上下架
        if (saleable != null) {
            criteria.orEqualTo("saleable", saleable);
        }
        // 是否模糊查詢
        if (StringUtils.isNotBlank(key)) {
            criteria.andLike("title", "%" + key + "%");
        }
        Page<Spu> pageInfo = (Page<Spu>) this.spuMapper.selectByExample(example);

        List<SpuBo> list = pageInfo.getResult().stream().map(spu -> {
            // 2、把spu變為 spuBo
            SpuBo spuBo = new SpuBo();
            // 屬性拷貝
            BeanUtils.copyProperties(spu, spuBo);

            // 3、查詢spu的商品分類名稱,要查三級分類
            List<String> names = this.categoryService.queryNameByIds(
                    Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
            // 将分類名稱拼接後存入
            spuBo.setCname(StringUtils.join(names, "/"));

            // 4、查詢spu的品牌名稱
            Brand brand = this.brandMapper.selectByPrimaryKey(spu.getBrandId());
            spuBo.setBname(brand.getName());
            return spuBo;
        }).collect(Collectors.toList());

        return new PageResult<>(pageInfo.getTotal(), list);
    }
}           

複制

4.4.4.mapper

public interface SpuMapper extends Mapper<Spu> {
}           

複制

4.4.5.Category中拓展查詢名稱的功能

頁面需要商品的分類名稱需要在這裡查詢,是以要額外提供查詢分類名稱的功能,

在CategoryService中添加功能:

public List<String> queryNameByIds(List<Long> ids) {
    return this.categoryMapper.selectByIdList(ids).stream().map(Category::getName).collect(Collectors.toList());
}           

複制

mapper的selectByIDList方法是來自于通用mapper。不過需要我們在mapper上繼承一個通用mapper接口:

public interface CategoryMapper extends Mapper<Category>, SelectByIdListMapper<Category, Long> { 
    // ...coding
}           

複制

4.5.測試

重新整理頁面,檢視效果:

商城項目-商品查詢4.商品查詢

基本與預覽的效果一緻,OK!