【愚公系列】《循序渐进Vue.js 3.x前端开发实践》070-商业项目:电商后台管理系统实战(商品管理模块的开发)

举报
愚公搬代码 发表于 2025/03/30 17:34:51 2025/03/30
【摘要】 标题详情作者简介愚公搬代码头衔华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。近期荣誉2022年度博客之星TOP2,2023年度博客之星TOP2,2022年华为云十佳博主,2023年华为云十佳博主,2024年华为云十佳...
标题 详情
作者简介 愚公搬代码
头衔 华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。
近期荣誉 2022年度博客之星TOP2,2023年度博客之星TOP2,2022年华为云十佳博主,2023年华为云十佳博主,2024年华为云十佳博主等。
博客内容 .NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
欢迎 👍点赞、✍评论、⭐收藏

🚀前言

在电商平台中,商品管理模块是连接商家与消费者的重要桥梁。它不仅承担着商品信息的录入、修改与删除等基本功能,还涉及到库存管理、价格调整、上下架操作等多项关键任务。一个高效的商品管理模块能够帮助商家轻松管理各种商品,提高销售效率,并确保用户获得准确的信息。

本文将围绕电商后台管理系统中的商品管理模块的开发展开,带你深入了解如何构建一个功能全面、操作便捷的商品管理系统。我们将从需求分析开始,逐步实现商品的增删改查、分类管理、库存监控等功能。

🚀一、商品管理模块的开发

有了订单管理模块的开发经验,相信你再来编写商品管理模块将会非常容易。商品管理模块与订单管理模块的开发过程基本类似,先布局页面,再将获取到的数据绑定到页面上,最后处理用户交互即可。相比订单管理模块,商品管理模块的新增商品功能略显复杂。

🔎1.商品管理列表页

🦋1.1 创建组件文件

首先,在项目的 components 文件夹下新建一个 goods 子文件夹,创建两个 Vue 组件文件,分别命名为 Goods.vueAddGoods .vue。本节先只做商品列表页的开发,对于 AddGoods .vue 文件可以先不做处理。

🦋1.2 注册路由

Router.js 文件中注册新创建的这两个组件。首先引入组件:

import Goods from '../components/goods/Goods.vue';
import AddGoods from '../components/goods/AddGoods.vue';

home 路由下的 children 中新增两个子路由:

// 0是普通商品,1是秒杀商品,2是今日推荐
{
  path: 'goods/:type',
  component: Goods,
  name: 'Goods'
},
{
  path: 'addGoods/:type',
  // 0是普通商品,1是秒杀商品,2是今日推荐
  component: AddGoods,
  name: 'AddGoods'
}

🦋1.3 Mock 数据

Mock.js 文件中新增一个获取商品数据的方法,代码如下:

// 模拟获取商品数据
// type: 商品类型。0为普通商品,1为秒杀商品,2为今日推荐
getGoods(type) {
  let array = [];
  for (let i = 0; i < mockjs.Random.integer(5, 10); i++) {
    array.push(mockjs.mock({
      'name': (type == 0 ? '普通商品' : type == 1 ? '秒杀商品' : '今日推荐') + i,
      'img': mockjs.Random.dataImage('60x100', '商品示例图'),
      'price': mockjs.Random.integer(20, 500) + '元',
      'sellCount': mockjs.Random.integer(10, 100),
      'count': mockjs.Random.integer(10, 100),
      'back': mockjs.Random.integer(10, 100),
      'backPrice': mockjs.Random.integer(0, 5000) + '元',
      'owner': mockjs.Random.cname(),
      'time': mockjs.Random.datetime('yyyy-MM-dd A HH:mm:ss'),
      'state': mockjs.Random.boolean()
    }));
  }
  return array;
}

🦋1.4 商品列表页的代码(Goods.vue

以下是 Goods.vue 的完整代码:

<template>
  <div class="content-container" direction="vertical">
    <!-- input -->
    <div>
      <el-container class="content-row">
        <div class="input-tip">商品名称:</div>
        <div class="input-field">
          <el-input v-model="queryParams.name"></el-input>
        </div>
        <div class="input-tip">商品编号:</div>
        <div class="input-field">
          <el-input v-model="queryParams.id"></el-input>
        </div>
        <div class="input-tip">商品分类:</div>
        <div class="input-field">
          <el-select style="width:150px;" v-model="queryParams.category" placeholder="请选择分类">
            <el-option v-for="item in categorys" :key="item" :label="item" :value="item"></el-option>
          </el-select>
        </div>
      </el-container>
      <el-container class="content-row">
        <div class="input-tip">是否上架:</div>
        <div class="input-field">
          <el-select v-model="sellModeString" style="width: 150px;">
            <el-option key="0" label="否" value="0"></el-option>
            <el-option key="1" label="是" value="1"></el-option>
            <el-option key="2" label="全部" value="2"></el-option>
          </el-select>
        </div>
        <div class="input-tip">是否过期:</div>
        <div class="input-field">
          <el-select v-model="expModeString" style="width:150px;">
            <el-option key="0" label="否" value="0"></el-option>
            <el-option key="1" label="是" value="1"></el-option>
            <el-option key="2" label="全部" value="2"></el-option>
          </el-select>
        </div>
      </el-container>
    </div>
    <!-- button -->
    <div class="content-row">
      <el-container>
        <el-button type="primary" @click="requestData">检索</el-button>
        <el-button type="primary" @click="clear">显示全部</el-button>
        <el-button type="success" @click="addGood">新增商品</el-button>
      </el-container>
    </div>
    <!-- list -->
    <div>
      <el-table :data="goodsData" tooltip-effect="dark" style="width:100%">
        <el-table-column label="商品" width="100">
          <template #default="scope">
            <div style="text-align:center">
              <el-image :src="scope.row.img" style="width: 60px; height: 100px"></el-image>
            </div>
            <div style="text-align:center">{{ scope.row.name }}</div>
          </template>
        </el-table-column>
        <el-table-column label="价格" width="100" prop="price"></el-table-column>
        <el-table-column label="销量" width="100" prop="sellCount"></el-table-column>
        <el-table-column label="库存" width="100" prop="count"></el-table-column>
        <el-table-column label="退款数量" width="100" prop="back"></el-table-column>
        <el-table-column label="退款金额" width="100" prop="backPrice"></el-table-column>
        <el-table-column label="操作" width="100" prop="name">
          <template #default="scope">
            <el-button @click="operate(scope.row)" :type="scope.row.state ? 'danger' : 'success'">
              {{ scope.row.state ? '下架' : '上架' }}
            </el-button>
          </template>
        </el-table-column>
        <el-table-column label="管理员" width="100" prop="owner"></el-table-column>
        <el-table-column label="更新时间" width="200" prop="time"></el-table-column>
      </el-table>
    </div>
  </div>
</template>

<script setup>
import Mock from '../../mock/Mock'
import { ref, computed, onMounted } from 'vue'
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'

const route = useRoute()
const router = useRouter()
const goodsData = ref([])

// 模拟分类数据
const categorys = ref([
  "全部",
  "男装",
  "女装"
])

const queryParams = ref({
  name: "",
  id: "",
  category: "",
  sellMode: 2,  // 0否,1是,2全部
  expMode: 2   // 0否,1是,2全部
})

const sellModeString = computed({
  get() {
    if (queryParams.value.sellMode === 2) {
      return '全部'
    }
    return queryParams.value.sellMode === 0 ? '否' : '是'
  },
  set(val) {
    queryParams.value.sellMode = val
  }
})

const expModeString = computed({
  get() {
    if (queryParams.value.expMode === 2) {
      return '全部'
    }
    return queryParams.value.expMode === 0 ? '否' : '是'
  },
  set(val) {
    queryParams.value.expMode = val
  }
})

// 组件挂载时获取数据
onMounted(() => {
  goodsData.value = Mock.getGoods(route.params.type)
})

// 路由更新时刷新数据
onBeforeRouteUpdate((to) => {
  goodsData.value = Mock.getGoods(to.params.type)
})

function requestData() {
  ElMessage({
    message: '筛选请求参数: ' + JSON.stringify(queryParams.value),
    type: 'success'
  })
  goodsData.value = Mock.getGoods(route.params.type)
}

// 进行上架、下架操作
function operate(item) {
  item.state = !item.state
}

// 清空筛选项
function clear() {
  queryParams.value = {
    name: "",
    id: "",
    category: "",
    sellMode: 2,
    expMode: 2,
  }
  goodsData.value = Mock.getGoods(route.params.type)
}

// 新增商品
function addGood() {
  router.push({ name: 'AddGoods', params: { type: route.params.type } })
}
</script>

🦋1.5 运行和测试

运行当前工程,访问商品列表页面,检查页面中的交互元素是否能正确执行对应的方法。

在这里插入图片描述

🔎2.新建商品之基础配置

在商品管理列表页中,有一个“新增商品”按钮。点击此按钮后,会跳转到“新建商品”页面。在此页面中,需要对商品的诸多属性进行设置。为了方便管理和用户体验,可以使用 el-tab 组件将商品设置分成几个模块,如基础设置、价格库存设置和商品详情设置等。每一个设置模块都可以封装成一个独立的组件。

🦋2.1 文件结构

首先,在工程的 goods 文件夹下新建以下文件:

  • GoodsBaseSetting.vue
  • GoodsPriceSetting.vue
  • GoodsDetailSetting.vue

AddGoods.vue 中引入这3个子组件。

🦋2.2 AddGoods.vue

以下是 AddGoods.vue 的代码:

<script setup>
import { ref } from 'vue';
import BaseSetting from './GoodsBaseSetting.vue';
import PriceSetting from './GoodsPriceSetting.vue';
import DetailSetting from './GoodsDetailSetting.vue';

const activeTab = ref("1");

function handleClick(idx) {
  // 处理Tab点击事件
}
</script>

<template>
  <div class="content-container" direction="vertical">
    <el-tabs v-model="activeTab" type="card" @tab-click="handleClick">
      <el-tab-pane label="基础设置" name="1">
        <BaseSetting></BaseSetting>
      </el-tab-pane>
      <el-tab-pane label="价格库存" name="2">
        <PriceSetting></PriceSetting>
      </el-tab-pane>
      <el-tab-pane label="商品详情" name="3">
        <DetailSetting></DetailSetting>
      </el-tab-pane>
    </el-tabs>
  </div>
</template>

🦋2.3 GoodsBaseSetting.vue

接下来编写 GoodsBaseSetting.vue 组件的代码:

<script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';

const router = useRouter();
const queryParams = ref({
  name: "",
  desc: "",
  timeRange: "",
  category: 0,
});

function cancel() {
  router.go(-1);
}

function submit() {
  ElMessage({
    type: 'success',
    message: '设置商品基本属性: ' + JSON.stringify(queryParams.value),
  });
}
</script>

<template>
  <div>
    <el-container class="content-row">
      <div class="input-tip">商品名称:</div>
      <div class="input-field">
        <el-input v-model="queryParams.name"></el-input>
      </div>
    </el-container>

    <el-container class="content-row">
      <div class="input-tip">商品简介:</div>
      <div class="input-field">
        <el-input type="textarea" :rows="3" v-model="queryParams.desc"></el-input>
      </div>
    </el-container>

    <el-container class="content-row">
      <div class="input-tip">商品封面:</div>
      <el-upload :auto-upload="false" :limit="1" list-type="picture-card">
        <el-icon><Plus/></el-icon>
      </el-upload>
    </el-container>

    <el-container class="content-row">
      <div class="input-tip">列表图片:</div>
      <el-upload :auto-upload="false" :limit="5" list-type="picture-card">
        <el-icon><Plus/></el-icon>
      </el-upload>
    </el-container>

    <el-container class="content-row">
      <div class="input-tip">上架日期:</div>
      <div class="input-field">
        <el-date-picker type="daterange" range-separator="至"
          start-placeholder="开始日期" end-placeholder="结束日期"
          v-model="queryParams.timeRange">
        </el-date-picker>
      </div>
    </el-container>

    <el-container class="content-row">
      <div class="input-tip">商品分类:</div>
      <div class="input-field">
        <el-select style="width: 150px;" v-model="queryParams.category">
          <el-option key="0" label="男装" :value="0"></el-option>
          <el-option key="1" label="男鞋" :value="1"></el-option>
          <el-option key="2" label="围巾" :value="2"></el-option>
        </el-select>
      </div>
      <div style="margin-top:6px">
        <el-button type="primary" size="small" round>添加分类</el-button>
      </div>
    </el-container>

    <el-container class="content-row">
      <el-button type="success" plain @click="submit">提交</el-button>
      <div style="margin-left:40px"></div>
      <el-button type="warning" plain @click="cancel">取消</el-button>
    </el-container>
  </div>
</template>

在这里插入图片描述

🔎3.新建商品之价格和库存配置

价格和库存配置模块相对简单,只需要布局一些输入框来接收用户的输入配置即可。在 GoodsPriceSetting.vue 中编写如下代码。

🦋3.1 文件结构

确保在 goods 文件夹下已经新建了 GoodsPriceSetting.vue 文件。

🦋3.2 GoodsPriceSetting.vue

以下是 GoodsPriceSetting.vue 的代码:

<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
const router = useRouter()
const queryParams = ref({
    maketPrice:0,
    showPrice:0,
    coin:0,
    price:0,
    limit:0,
    count:0,
    sellCount:0,
    viewCount:0
})
function cancel(){
    router.go(-1);
}
function submit() {
    ElMessage({
        type:'success',
        message:'设置价格与库存:' + JSON.stringify(queryParams.value)
    })
}
</script>
<template>
    <div>
        <div class="title">
            <div style="line-height:35px;margin-left:20px">价格设置</div>
        </div>
        <el-container class="content-row">
            <div class="input-tip">市场价:</div>
            <div class="input-field">
                <el-input v-model="queryParams.maketPrice"></el-input>
            </div>
        </el-container>
        <el-container class="content-row">
            <div class="input-tip">展示价:</div>
            <div class="input-field">
                <el-input v-model="queryParams.showPrice"></el-input>
            </div>
        </el-container>
        <el-container class="content-row">
            <div class="input-tip">积分数:</div>
            <div class="input-field">
                <el-input v-model="queryParams.coin"></el-input>
            </div>
        </el-container>
        <el-container class="content-row">
            <div class="input-tip">成本价:</div>
            <div class="input-field">
                <el-input v-model="queryParams.price"></el-input>
            </div>
        </el-container>
        <el-container class="content-row">
            <div class="input-tip">限购数:</div>
            <div class="input-field">
                <el-input v-model="queryParams.limit"></el-input>
            </div>
        </el-container>
        <div class="title">
            <div style="line-height:35px;margin-left:20px">库存设置</div>
        </div>
        <el-container class="content-row">
            <div class="input-tip">库存数量:</div>
            <div class="input-field">
                <el-input v-model="queryParams.count"></el-input>
            </div>
        </el-container>
        <el-container class="content-row">
            <div class="input-tip">基础销量:</div>
            <div class="input-field">
                <el-input v-model="queryParams.sellCount"></el-input>
            </div>
        </el-container>
        <el-container class="content-row">
            <div class="input-tip">浏览数量:</div>
            <div class="input-field">
                <el-input v-model="queryParams.viewCount"></el-input>
            </div>
        </el-container>
        <el-container class="content-row">
            <el-button type="success" plain @click="submit">提交</el-button>
            <div style="margin-left:40px"></div>
            <el-button type="warning" plain @click="cancel">取消</el-button>
        </el-container>
    </div>
</template>
<style scoped>
.title {
    background-color:#e1e1e1;
    height: 35px;
    margin-bottom: 15px;
}
</style>

在这里插入图片描述

🦋3.3 代码讲解

☀️3.3.1 <script setup>

  • import { ref } from 'vue'; 引入 ref 函数来创建响应式数据。
  • import { useRouter } from 'vue-router'; 引入 useRouter 钩子函数来使用路由功能。
  • import { ElMessage } from 'element-plus'; 引入 ElMessage 来显示消息提示。

const queryParams = ref({...}) 定义了一个响应式对象 queryParams,用于存储用户输入的商品价格和库存信息。

function cancel() {...} 定义了取消按钮的点击事件处理函数,点击后返回上一页。

function submit() {...} 定义了提交按钮的点击事件处理函数,点击后显示一个成功消息。

☀️3.3.2 <template>

  • 使用 el-container 组件来布局输入框,每个 el-container 包含一个标签和一个输入框。
  • v-model 双向绑定 queryParams 中对应的属性和输入框的值。

☀️3.3.3 <style scoped>

  • .title 设置标题的样式。
  • .content-row 设置每一行内容之间的间距。
  • .input-tip 设置输入框标签的样式。
  • .input-field 设置输入框的样式。

🔎4.新建商品之详情设置

商品详情的定制性通常较强,通常在商品上架添加时进行定制化设置。商品详情编辑推荐使用富文本编辑器,这类编辑器能够将富文本内容转换为HTML格式,操作简便。尽管富文本编辑器需要支持多种样式的文本、图片和超链接等功能,其实现可能较为复杂,但幸运的是,互联网上提供了许多优秀的富文本编辑器插件,它们可以直接使用,避免了重复开发的工作。

🦋4.1 安装wangEditor富文本编辑器插件

首先,在项目工程目录下执行以下命令来安装wangEditor富文本编辑器插件:

npm install wangeditor --save

安装成功后,在goods文件夹下新建一个名为GoodsEdit.vue的文件,在其中编写如下代码:

<script setup>
import E from "wangeditor"
import { ref, onMounted, getCurrentInstance } from 'vue'
// 用来获取当前组件实例
const instance = getCurrentInstance()
// 定义组件事件,内容改变时调用
const emit = defineEmits(['contentChange'])
// 编辑器对象的引用
let editor = null
const editorContent = ref('')
onMounted(()=>{
    editor = new E(instance.proxy.$refs.editorElem);
    // 编辑器的事件,每次改变会获取其html内容
    editor.config.onchange = contentChange
    editor.config.menus = [
      // 菜单配置
      'head', // 标题
      'bold', // 粗体
      'fontSize', // 字号
      'fontName', // 字体
      'italic', // 斜体
      'underline', // 下划线
      'strikeThrough', // 删除线
      'foreColor', // 文字颜色
      'backColor', // 背景颜色
      'link', // 插入链接
      'list', // 列表
      'justify', // 对齐方式
      'quote', // 引用
      'emoticon', // 表情
      'image', // 插入图片
      'table', // 表格
      'code', // 插入代码
      'undo', // 撤销
      'redo' // 重复
    ];
    editor.create(); // 创建富文本实例
})
function contentChange(html) {
    editorContent.value = html;
    emit('contentChange', editorContent.value); 
}

</script>
<template>
  <div id="wangeditor">
    <div ref="editorElem" style="text-align:left;"></div>
  </div>
</template>

上述代码有详细的注释,通过一些简单的配置即可使用此富文本组件。

🦋4.2 修改GoodsDetailSetting.vue文件

下面是修改后的GoodsDetailSetting.vue文件的代码:

<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import GoodsEdit from './GoodsEdit.vue'
const router = useRouter()
const content = ref("")
// 富文本组件内容变化的回调
function contentChange(c) {
    content.value = c;
}
function cancel() {
    router.go(-1)
}
function submit() {
    ElMessage({
        type:'success',
        message:'设置详情HTML:' + content.value
    })
}
</script>
<template>
    <div style="margin-bottom:20px">
        <goods-edit @contentChange="contentChange"></goods-edit>
    </div>
    <el-container class="content-row">
        <el-button type="success" plain @click="submit">提交</el-button>
        <div style="margin-left:40px"></div>
        <el-button type="warning" plain @click="cancel">取消</el-button>
    </el-container>
</template>

运行代码,商品详情编辑页如图所示。
在这里插入图片描述

🔎5.添加商品分类

商品分类管理模块的实现相对简单,通过一个列表展示已有的分类,并提供删除和新增分类的功能。在 goods 文件夹下新建一个名为 GoodsCategory.vue 的文件,编写如下示例代码:

🦋5.1 GoodsCategory.vue

<script setup>
import { ref } from 'vue'
import { ElMessageBox } from 'element-plus'

const categoryList = ref([
    {id:1231, name:"男装", manager:"管理员用户01"},
    {id:1131, name:"男鞋", manager:"管理员用户01"},
    {id:1031, name:"帽子", manager:"管理员用户01"}
])

function deleteCategory(index) {
    categoryList.value.splice(index,1)
}

function addCategory() {
    ElMessageBox.prompt('请输入分类名','新增分类',{
        confirmButtonText: '确定',
        cancelButtonText: '取消',
    }).then(({value})=>{
        categoryList.value.push({
            id:1000,
            name:value,
            manager:"管理员用户01"
        })
    });
}
</script>
<template>
    <div class="content-container" direction="vertical">
        <el-container class="content-row">
            <el-button type="primary" @click="addCategory">添加分类</el-button>
        </el-container>
        <div>
            <el-table :data="categoryList" tooltip-effect="dark" style="width: 100%">
                <el-table-column label="分类ID" width="100" prop="id"></el-table-column>
                <el-table-column label="分类名称" width="100" prop="name"></el-table-column>
                <el-table-column label="分类负责人" width="500" prop="manager"></el-table-column>
                <el-table-column label="操作" width="200" prop="time">
                    <template #default="scope">
                        <el-button size="small" @click="deleteCategory(scope.$index)">删除</el-button>
                    </template>
                </el-table-column>
            </el-table>
        </div>
    </div>
</template>

🦋5.1 代码解析

  1. 模板部分 (<template>):

    • el-button 用于触发新增分类的按钮。
    • el-table 用于展示分类列表,包含分类ID、分类名称、分类负责人和操作列。
    • el-table-column 定义表格的列结构。
    • 删除按钮使用 @click 事件绑定 deleteCategory 方法。
  2. 脚本部分 (<script setup>):

    • 使用 ref 定义 categoryList,存储分类数据。
    • deleteCategory 方法用于删除指定索引的分类。
    • addCategory 方法使用 ElMessageBox.prompt 弹出对话框,获取用户输入的分类名,并将新分类添加到 categoryList
  3. 样式部分 (<style scoped>):

    • 定义了一些基本的样式,用于调整布局和间距。

🦋5.2 路由配置

不要忘记在 Router.js 中补充对应的路由配置,确保可以从其他页面跳转到商品分类管理页面。

示例路由配置:

import { createRouter, createWebHistory } from 'vue-router';
import GoodsCategory from './components/goods/GoodsCategory.vue';

const routes = [
  // 其他路由配置
  {
     path:'category',
     component:GoodsCategory,
     name:"GoodsCategory"
  }
];

const router = createRouter({
  history: createWebHistory(),
  routes
});

export default router;

运行上面的代码后,你应该会看到一个商品分类管理页面,能够展示已有的分类,并提供删除和新增分类的功能。效果如图所示。

在这里插入图片描述

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。