Bootstrap

Vue3+TypeScript项目(SKU管理模块)

一、SKU模块静态页面

src\views\product\sku\index.vue

<template>
  <el-card>
    <el-table border style="margin: 10px 0px">
      <el-table-column type="index" label="序号" width="80px"></el-table-column>
      <el-table-column
        label="名称"
        width="80px"
        show-overflow-tooltip
      ></el-table-column>
      <el-table-column
        label="描述"
        width="300px"
        show-overflow-tooltip
      ></el-table-column>
      <el-table-column label="图片" width="300px"></el-table-column>
      <el-table-column label="重量" width="300px"></el-table-column>
      <el-table-column label="价格" width="300px"></el-table-column>
      <el-table-column
        label="操作"
        width="300px"
        fixed="right"
      ></el-table-column>
    </el-table>
    <el-pagination
      v-model:current-page="pageNo"
      v-model:page-size="pageSize"
      :page-sizes="[10, 20, 30, 40]"
      :background="true"
      layout="prev, pager, next, jumper, ->,sizes,total "
      :total="400"
    />
  </el-card>
</template>

二、获取展示数据

2.1API

src\api\product\sku\index.ts

//SKU模块接口管理
import request from '@/utils/request'
import type { SkuResponseData} from './type'
//枚举地址
enum API {
  //获取已有的商品的数据-SKU
  SKU_URL = 'http://114.115.179.162:8022/prod-api/admin/product/list/',
}
//获取商品SKU的接口
export const reqSkuList = (page: number, limit: number) => {
  return request.get<any, SkuResponseData>(API.SKU_URL + `${page}/${limit}`)
}

2.2 ts类型

src\api\product\sku\type.ts

export interface ResponseData {
    code: number
    message: string
    ok: boolean
  }
  //定义SKU对象的ts类型
  export interface Attr {
    id?: number
    attrId: number | string //平台属性的ID
    valueId: number | string //属性值的ID
  }
  export interface saleArr {
    id?: number
    saleAttrId: number | string //属性ID
    saleAttrValueId: number | string //属性值的ID
  }
  export interface SkuData {
    category3Id?: string | number //三级分类的ID
    spuId?: string | number //已有的SPU的ID
    tmId?: string | number //SPU品牌的ID
    skuName?: string //sku名字
    price?: string | number //sku价格
    weight?: string | number //sku重量
    skuDesc?: string //sku的描述
    skuAttrValueList?: Attr[]
    skuSaleAttrValueList?: saleArr[]
    skuDefaultImg?: string //sku图片地址
    isSale?: number //控制商品的上架与下架
    id?: number
  }
  
  
  //获取SKU接口返回的数据ts类型
  export interface SkuResponseData extends ResponseData {
    data: {
      records: SkuData[]
      total: number
      size: number
      current: number
      orders: []
      optimizeCountSql: boolean
      hitCount: boolean
      countId: null
      maxLimit: null
      searchCount: boolean
      pages: number
    }
  }

 2.3 组件获取数据

<script setup lang="ts">
import { ref, onMounted } from "vue";
import { reqSkuList } from "@/api/product/sku/index.ts";
import type { SkuResponseData, SkuData } from "@/api/product/sku/type.ts";
//当前页码
let pageNo = ref<number>(1);
//每一页展示的数据
let pageSize = ref<number>(10);
//存储已有品牌数据总数
let total = ref<number>(0);
//存储已有sku的数据
let skuArr = ref<SkuData[]>([]);
const getHasSku = async (pager = 1) => {
  //修改当前页码
  pageNo.value = pager;
  let result: SkuResponseData = await reqSkuList(pageNo.value, pageSize.value);
  console.log(result);
  if (result.code == 200) {
    skuArr.value = result.data.records;
    total.value = result.data.total;
  }
};
//组件挂载完毕钩子---发一次请求,获取第一页、一页三个已有品牌数据
onMounted(() => {
  getHasSku();
});
//当下拉菜单发生变化的时候触发此方法
//这个自定义事件,分页器组件会将下拉菜单选中数据返回
const sizeChange = () => {
  //当前每一页的数据量发生变化的时候,当前页码归1
  getHasSku();
};
</script>

2.4 展示数据 

<el-table border style="margin: 10px 0px" :data="skuArr">
      <el-table-column type="index" label="序号" width="80px"></el-table-column>
      <el-table-column
        prop="skuName"
        label="名称"
        width="80px"
        show-overflow-tooltip
      ></el-table-column>
      <el-table-column
        prop="skuDesc"
        label="描述"
        width="300px"
        show-overflow-tooltip
      ></el-table-column>
      <el-table-column label="图片" width="300px">
        <template #="{ row, $index }">
          <img
            :src="row.skuDefaultImg"
            alt=""
            style="width: 100px; height: 100px"
          />
        </template>
      </el-table-column>
      <el-table-column
        label="重量"
        width="300px"
        prop="weight"
      ></el-table-column>
      <el-table-column
        label="价格"
        width="300px"
        prop="price"
      ></el-table-column>
      <el-table-column label="操作" width="300px" fixed="right">
        <el-button type="primary" size="small" icon="Top"></el-button>
        <el-button type="primary" size="small" icon="Edit"></el-button>
        <el-button type="primary" size="small" icon="InfoFilled"></el-button>
        <el-button type="primary" size="small" icon="Delete"></el-button>
      </el-table-column>
    </el-table>

2.5分页器

//当下拉菜单发生变化的时候触发此方法
//这个自定义事件,分页器组件会将下拉菜单选中数据返回
const sizeChange = () => {
  //当前每一页的数据量发生变化的时候,当前页码归1
  getHasSku();
};

 注意:在这里切换页码和切换每页数据条数的回调不同是因为:它们都能对函数注入数据,切换页码注入的是点击的页码数,因此我们可以直接使用getHasSku作为他的回调。切换每页数据条数注入的是切换的页码条数,我们希望切换后跳转到第一页,因此使用handler,间接调用getHasSku。

三、上架下架按钮 

3.1 API

//上架
SALE_URL = '/admin/product/onSale/',
//下架的接口
CANCELSALE_URL = '/admin/product/cancelSale/',
    //已有商品上架的请求
export const reqSaleSku = (skuId: number) => {
  return request.get<any, any>(API.SALE_URL + skuId)
}
//下架的请求
export const reqCancelSale = (skuId: number) => {
  return request.get<any, any>(API.CANCELSALE_URL + skuId)
}

 由于没有返回数据,所以没有ts类型

3.2 上架下架回调 

流程:发请求->更新页面 

//商品的上架与下架的操作
const updateSale = async (row: SkuData) => {
  //如果当前商品的isSale==1,说明当前商品是上架的额状态->更新为下架
  //否则else情况与上面情况相反
  if (row.isSale == 1) {
    //下架操作
    await reqCancelSale(row.id as number)
    //提示信息
    ElMessage({ type: 'success', message: '下架成功' })
    //发请求获取当前更新完毕的全部已有的SKU
    getHasSku(pageNo.value)
  } else {
    //下架操作
    await reqSaleSku(row.id as number)
    //提示信息
    ElMessage({ type: 'success', message: '上架成功' })
    //发请求获取当前更新完毕的全部已有的SKU
    getHasSku(pageNo.value)
  }
}

 3.3 更新按钮

更新按钮这里没有业务。个人觉得是因为SKU的编写在SPU已经做完了。防止业务逻辑混乱

//更新已有的SKU
const updateSku = ()=>{
  ElMessage({
    type:'success',
    message:'keson正在努力开发中~~~'
  })
}

 四、商品详情静态搭建

4.1Drawer 抽屉

描述:呼出一个临时的侧边栏, 可以从多个方向呼出

 

//控制抽屉显示与隐藏的字段
let drawer = ref<boolean>(false);


....



//查看商品详情按钮的回调
const findSku = async (row: SkuData) => {
  //显示抽屉
  drawer.value = true;
  //获取已有的商品详情数据
  let result:SkuInfoData =await reqSkuInfo(row.id as number)
  console.log(result)
  //存储已有的SKU
  skuInfo.value = result.data
};

 4.2 Layout 布局

通过基础的 24 分栏,迅速简便地创建布局。

src\views\product\sku\index.vue

<el-drawer v-model="drawer" >
    <template #header>
      <h4>商品详情</h4>
    </template>
    <template #default>
      <el-row style="margin: 10px 0">
        <el-col :span="6">名称:</el-col>
        <el-col :span="18">{{ skuInfo.skuName }}</el-col>
      </el-row>
      <el-row style="margin: 10px 0">
        <el-col :span="6">描述:</el-col>
        <el-col :span="18">{{ skuInfo.skuDesc }}</el-col>
      </el-row>
      <el-row style="margin: 10px 0">
        <el-col :span="6">价格(元):</el-col>
        <el-col :span="18">{{ skuInfo.price}}</el-col>
      </el-row>
      <el-row style="margin: 10px 0">
        <el-col :span="6">平台属性:</el-col>
        <el-col :span="18">
          <el-tag style="margin: 2px" v-for="item in skuInfo.skuAttrValueList" :key="item.id">{{ item.attrName }}</el-tag>
        </el-col>
      </el-row>
      <el-row style="margin: 10px 0">
        <el-col :span="6">销售属性:</el-col>
        <el-col :span="18">
          <el-tag type="danger" style="margin: 2px" v-for="item in skuInfo.skuSaleAttrValueList" :key="item.id">{{ item.saleAttrName }}</el-tag>
        </el-col>
      </el-row>
      <el-row style="margin: 10px 0">
        <el-col :span="6">商品图片:</el-col>
        <el-col :span="18">
          <el-carousel :interval="4000" type="card" height="200px" >
            <el-carousel-item v-for="item in skuInfo.skuImageList" :key="item.id">
              <img :src="item.imgUrl" alt="" style="width: 100%;height:100%">
            </el-carousel-item>
          </el-carousel>
        </el-col>
      </el-row>
    </template>
   
  </el-drawer>

轮播图 carousel

注意:把对应的style也复制过来

4.3 商品详情展示业务 

API   src\api\product\sku\index.ts

//获取商品详情的接口
  SKUINFO_URL = '/admin/product/getSkuInfo/',
//获取商品详情的接口
export const reqSkuInfo = (skuId: number) => {
  return request.get<any, SkuInfoData>(API.SKUINFO_URL + skuId)
}

ts类型

//获取SKU商品详情接口的ts类型
export interface SkuInfoData extends ResponseData {
  data: SkuData
}

4.4 发请求&&存储数据

let skuInfo = ref<any>({})
//查看商品详情按钮的回调
const findSku = async (row: SkuData) => {
  //抽屉展示出来
  drawer.value = true
  //获取已有商品详情数据
  let result: SkuInfoData = await reqSkuInfo(row.id as number)
  //存储已有的SKU
  skuInfo.value = result.data
}

4.5 展示数据(销售属性为例)

五、 删除SKU

API

 //删除SKU的接口
    DELETESKU_URL = 'http://114.115.179.162:8022/prod-api/admin/product/deleteSku/',


//删除SKU的接口
  export const reqRemoveSku = (skuId: number) =>{
   return request.delete<any, any>(API.DELETESKU_URL + skuId)
  }

//删除SKU的回调
const deleSku = async(row:SkuData)=>{
 let result:any = await reqRemoveSku(row.id as number)
 if(result.code == 200){
  ElMessage({
    type:'success',
    message:'删除成功'
  })
  getHasSku()
 }else{
  ElMessage({
    type:'error',
    message:'删除失败'
  })
 }
}

 六、结论

全部代码

src\views\product\sku\index.vue

<template>
  <el-card>
    <el-table border :data="skuArr">
      <el-table-column
        type="index"
        align="center"
        width="80px"
        label="序号"
      ></el-table-column>
      <el-table-column
        show-overflow-tooltip
        width="150px"
        label="名称"
        prop="skuName"
      ></el-table-column>
      <el-table-column
        show-overflow-tooltip
        width="150px"
        label="描述"
        prop="skuDesc"
      ></el-table-column>
      <el-table-column width="150px" label="图片">
        <!-- img需要用插槽 -->
        <template #="{ row, $index }">
          <img :src="row.skuDefaultImg" alt="" style="width: 80px; height: 80px" />
        </template>
      </el-table-column>
      <el-table-column width="150px" label="重量" prop="weight"></el-table-column>
      <el-table-column width="150px" label="价格" prop="price"></el-table-column>
      <el-table-column width="250px" label="操作" fixed="right">
        <template #="{ row, $index }">
          <el-button
            type="default"
            size="small"
            :icon="row.isSale == 1 ? 'Bottom' : 'Top'"
            @click="updateSale(row)"
          ></el-button>
          <el-button
            type="primary"
            size="small"
            icon="Edit"
            @click="updateSku"
          ></el-button>
          <el-button
            type="info"
            size="small"
            icon="InfoFilled"
            @click="findSku(row)"
          ></el-button>
          <el-button type="danger" size="small" icon="Delete" @click="deleSku(row)"></el-button>
        </template>
      </el-table-column>
    </el-table>
    <el-pagination
      v-model:current-page="pageNo"
      v-model:page-size="pageSize"
      :page-sizes="[10, 20, 30, 40]"
      :background="true"
      layout=" prev, pager, next, jumper,->, sizes,total"
      :total="total"
      @size-change="sizeChange"
      @current-change="getHasSku"
    />
  </el-card>
  <el-drawer v-model="drawer" >
    <template #header>
      <h4>商品详情</h4>
    </template>
    <template #default>
      <el-row style="margin: 10px 0">
        <el-col :span="6">名称:</el-col>
        <el-col :span="18">{{ skuInfo.skuName }}</el-col>
      </el-row>
      <el-row style="margin: 10px 0">
        <el-col :span="6">描述:</el-col>
        <el-col :span="18">{{ skuInfo.skuDesc }}</el-col>
      </el-row>
      <el-row style="margin: 10px 0">
        <el-col :span="6">价格(元):</el-col>
        <el-col :span="18">{{ skuInfo.price}}</el-col>
      </el-row>
      <el-row style="margin: 10px 0">
        <el-col :span="6">平台属性:</el-col>
        <el-col :span="18">
          <el-tag style="margin: 2px" v-for="item in skuInfo.skuAttrValueList" :key="item.id">{{ item.attrName }}</el-tag>
        </el-col>
      </el-row>
      <el-row style="margin: 10px 0">
        <el-col :span="6">销售属性:</el-col>
        <el-col :span="18">
          <el-tag type="danger" style="margin: 2px" v-for="item in skuInfo.skuSaleAttrValueList" :key="item.id">{{ item.saleAttrName }}</el-tag>
        </el-col>
      </el-row>
      <el-row style="margin: 10px 0">
        <el-col :span="6">商品图片:</el-col>
        <el-col :span="18">
          <el-carousel :interval="4000" type="card" height="200px" >
            <el-carousel-item v-for="item in skuInfo.skuImageList" :key="item.id">
              <img :src="item.imgUrl" alt="" style="width: 100%;height:100%">
            </el-carousel-item>
          </el-carousel>
        </el-col>
      </el-row>
    </template>
   
  </el-drawer>
</template>

<script setup lang="ts">
import { ref, onMounted } from "vue";
import { reqSkuList, reqCancelSale, reqSaleSku,reqSkuInfo,reqRemoveSku } from "@/api/product/sku/index.ts";
import type { SkuResponseData, SkuData ,SkuInfoData} from "@/api/product/sku/type.ts";
import { ElMessage } from "element-plus";
//当前页码
let pageNo = ref<number>(1);
//每一页展示的数据
let pageSize = ref<number>(10);
//存储已有品牌数据总数
let total = ref<number>(0);
//存储已有sku的数据
let skuArr = ref<SkuData[]>([]);
//控制抽屉显示与隐藏的字段
let drawer = ref<boolean>(false);
//存储商品详情信息
let skuInfo = ref<any>({})
const getHasSku = async (pager = 1) => {
  //修改当前页码
  pageNo.value = pager;
  let result: SkuResponseData = await reqSkuList(pageNo.value, pageSize.value);
  console.log(result);
  if (result.code == 200) {
    skuArr.value = result.data.records;
    total.value = result.data.total;
  }
};
//组件挂载完毕钩子---发一次请求,获取第一页、一页三个已有品牌数据
onMounted(() => {
  getHasSku();
});
//当下拉菜单发生变化的时候触发此方法
//这个自定义事件,分页器组件会将下拉菜单选中数据返回
const sizeChange = () => {
  //当前每一页的数据量发生变化的时候,当前页码归1
  getHasSku();
};

//商品的上架与下架的操作
const updateSale = async (row: SkuData) => {
  //如果当前商品的isSale==1,说明当前商品是上架的额状态->更新为下架
  //否则else情况与上面情况相反
  if (row.isSale == 1) {
    //下架操作
    await reqCancelSale(row.id as number);
    //提示信息
    ElMessage({ type: "success", message: "下架成功" });
    //发请求获取当前更新完毕的全部已有的SKU
    getHasSku(pageNo.value);
  } else {
    //下架操作
    await reqSaleSku(row.id as number);
    //提示信息
    ElMessage({ type: "success", message: "上架成功" });
    //发请求获取当前更新完毕的全部已有的SKU
    getHasSku(pageNo.value);
  }
};

//更新已有的SKU
const updateSku = () => {
  ElMessage({
    type: "success",
    message: "keson正在努力开发中~~~",
  });
};

//查看商品详情按钮的回调
const findSku = async (row: SkuData) => {
  //显示抽屉
  drawer.value = true;
  //获取已有的商品详情数据
  let result:SkuInfoData =await reqSkuInfo(row.id as number)
  console.log(result)
  //存储已有的SKU
  skuInfo.value = result.data
};

//删除SKU的回调
const deleSku = async(row:SkuData)=>{
 let result:any = await reqRemoveSku(row.id as number)
 if(result.code == 200){
  ElMessage({
    type:'success',
    message:'删除成功'
  })
  getHasSku()
 }else{
  ElMessage({
    type:'error',
    message:'删除失败'
  })
 }
}
</script>

<style scoped lang="scss">
.el-card {
  margin: 20px 0;
}
.el-table {
  margin: 10px 0;
}
.el-carousel__item img {
  color: #475669;
  opacity: 0.75;
  line-height: 200px;
  margin: 0;
  text-align: center;
}

.el-carousel__item:nth-child(2n) {
  background-color: #99a9bf;
}

.el-carousel__item:nth-child(2n + 1) {
  background-color: #d3dce6;
}
</style>

这模块的思路其实都比较简单。无外乎API(type),组件内发请求拿数据、将数据放到模板中。再加上一个对仓库的处理。

这部分真正的难点也是最值得学习的点在于

1:type的书写

2:对数据结构的理解(可以将请求回来的数据放到正确的位置上)

3:element-plus组件的使用。

;