4.商品查詢
4.1.效果預覽
接下來,我們實作商品管理的頁面,先看下我們要實作的效果:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICMyYTMvw1dvwlMvwlM3VWaWV2Zh1Wa-cmbw5iZi9Gd4VGe50mevwlMzgTM1UjNtUGall3LcVmdhNXLwRHdo9CXt92YucWbpRWdvx2Yx5yazF2Lc9CX6MHc0RHaiojIsJye.png)
可以看出整體是一個table,然後有新增按鈕。是不是跟昨天寫品牌管理很像?
模闆代碼在分别在Goods.vue
4.2.從0開始
接下來,我們自己來實作一下,建立兩個元件:MyGoods.vue和MyGoodsForm.vue
内容先随意:
<template>
<v-card>
MyGoods
</v-card>
</template>
<script>
export default {
name: "my-goods",
data() {
return {
}
}
}
</script>
<style scoped>
</style>
複制
然後修改menu.js,建立一個菜單:
修改router/index.js,添加一個路由:
預覽一下:
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>
複制
主要的改動點:
- 頁面的
中的屬性綁定修改。items指向goodsList,totalItems指向totalGoodsv-data-table
- 頁面渲染的字段名修改:字段改成商品的SPU字段:id、title,cname(商品分類名稱),bname(品牌名稱)
- data屬性修改了以下屬性:
- goodsList:目前頁商品資料
- totalGoods:商品總數
- headers:頭資訊,需要修改頭顯示名稱
- oldGoods:準備要修改的商品
- 加載資料的函數:getDataFromServer,請求的路徑進行了修改,另外去除了跟排序相關的查詢。SPU查詢不排序
- 新增商品的事件函數:清除了一些資料查詢接口,隻保留彈窗
檢視效果:
因為沒有編寫查詢功能,表格一直處于loading狀态。
接下來看彈窗:
4.3.2.上下架狀态按鈕
另外,似乎頁面少了對上下架商品的過濾,在原始效果圖中是有的:
這在Vuetify中是一組按鈕,我們檢視幫助文檔:
檢視執行個體得到以下資訊:
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屬性,寫到
public class SpuBo extends Spu { String cname;// 商品分類名稱 String bname;// 品牌名稱 // 略 。。 }ly-item-interface
-
編寫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.測試
重新整理頁面,檢視效果:
基本與預覽的效果一緻,OK!