【愚公系列】《循序渐进Vue.js 3.x前端开发实践》070-商业项目:电商后台管理系统实战(商品管理模块的开发)
标题 | 详情 |
---|---|
作者简介 | 愚公搬代码 |
头衔 | 华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,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.vue
和 AddGoods .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 代码解析
-
模板部分 (
<template>
):el-button
用于触发新增分类的按钮。el-table
用于展示分类列表,包含分类ID、分类名称、分类负责人和操作列。el-table-column
定义表格的列结构。- 删除按钮使用
@click
事件绑定deleteCategory
方法。
-
脚本部分 (
<script setup>
):- 使用
ref
定义categoryList
,存储分类数据。 deleteCategory
方法用于删除指定索引的分类。addCategory
方法使用ElMessageBox.prompt
弹出对话框,获取用户输入的分类名,并将新分类添加到categoryList
。
- 使用
-
样式部分 (
<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;
运行上面的代码后,你应该会看到一个商品分类管理页面,能够展示已有的分类,并提供删除和新增分类的功能。效果如图所示。
- 点赞
- 收藏
- 关注作者
评论(0)