产品管理是商城管理系统的核心模块之一,它直接关系到商品的流通效率、库存管理和销售策略。通过高效的产品管理,商城能够实时掌握商品信息,优化库存结构,提高销售业绩。
新增数据库表
拷贝shop_cate修改为shop_goods表
修改表结构
其中包括一个自增ID字段,及三个时间类型的字段,其余的字段按照需求增加,这里title,remark,cate_id,price,line_price,amount,sellamount,skus,sku_type等个字段。其中我们用sku_type来区分是单规格还是多规格字段。
生成API
输入你安装的PHP时的域名/super/index.html,登录进去找到代码生成器界面。
新增页面
可以直接复制分类页面然后修改页面页面标识、页面名称、字体图标.
修改API
结合表shop_goods表,那么我们在后台API属性设置即为shop/goods即可。
修改表格字段
表单编辑字段
由于商品信息比较多,我们采用选项卡的方式来设计表单,基本信息、规格库存、商品详情。基本基本信息拖入单行文本字段进编辑表单设计区,输入字段标识、字段标识标题。
产品分类
产品分类字段比较特符,数据来源产品分类表的数据。这时要采用API把分类数据加载出来。
规格库存
规格库存用来设计价格等。其中规格类型我们用单选框组件,然后用FLEX组件把所有单规格输入组件包裹起来,增加显示判断,只有但单规格的时候才显示。
点击保存源码至本地
保存源码至本地后打开页面即可访问商品分类管理界面。
进入页面
启动环境进进入产品管理,在右上角可以设计不同主题效果。
这时增删改查已经完成。
数据查询
输入单选文本,字段名称改为title_like支持模板查询。PHP更多扩展查询参照此地址:PHP查询用法扩展-可视化设计工具
至此我们的产品管理增删改查已经完成
切换源码类型
低代码快速可视化完成商品增删改查代码生成器,大家根据自己喜欢可以切换选项式或组合式代码。
vue选项式代码
<template> <div class="container"> <div class="el-card is-always-shadow diygw-col-24"> <div class="el-card__body"> <div class="mb8"> <el-form class="flex flex-direction-row flex-wrap" :model="queryParams" :rules="queryParamsRules" ref="queryForm" label-width="80px"> <div class="flex diygw-col-0"> <el-form-item class="diygw-el-rate" prop="title_like" label="标题"> <el-input type="text" placeholder="请输入标题查询" v-model="queryParams.title_like"> </el-input> </el-form-item> </div> <div class="flex diygw-col-0"> <el-button type="primary" @click="handleQuery"> <SvgIcon name="ele-Search" /> 搜索 </el-button> <el-button @click="resetQuery"> <SvgIcon name="ele-Refresh" /> 重置 </el-button> </div> </el-form> </div> <div class="mb8"> <el-button type="primary" plain @click="onOpenAddModule"> <SvgIcon name="ele-Plus" />新增 </el-button> <el-button type="danger" plain :disabled="multiple" @click="onTabelRowDel"> <SvgIcon name="ele-Delete" />删除 </el-button> </div> <el-table @selection-change="handleSelectionChange" v-loading="loading" :data="tableData" stripe border current-row-key="id" empty-text="没有数据" style="width: 100%"> <el-table-column type="selection" width="55" align="center"></el-table-column> <el-table-column label="置顶" prop="title" align="center"> <template #default="scope"> <el-tag style="cursor: pointer" v-if="scope.row.sortnum > 0" type="error" @click="updatenumApi(scope.row)"> 取消置顶 </el-tag><el-tag style="cursor: pointer" @click="ordernumApi(scope.row)" v-else> 置顶 </el-tag> </template> </el-table-column> <el-table-column label="商品图片" prop="img" align="center"> <template #default="scope"> <diy-img style="width: 80px; height: 80px" :src="scope.row.img" fit="contain"></diy-img> </template> </el-table-column> <el-table-column label="商品名称" prop="title" align="center"> </el-table-column> <el-table-column label="商品价格" prop="price" align="center"> </el-table-column> <el-table-column label="总销量" prop="sellamonut" align="center"> </el-table-column> <el-table-column label="库存量" prop="amount" align="center"> </el-table-column> <el-table-column label="备注" prop="remark" align="center"> </el-table-column> <el-table-column label="数据状态" prop="status" align="center"> <template #default="scope"> <el-tag v-if="scope.row.status == 1"> 上架 </el-tag><el-tag v-else> 下架 </el-tag> </template> </el-table-column> <el-table-column label="操作" align="center" fixed="right" width="180"> <template #default="scope"> <el-button type="primary" plain size="small" @click="onOpenEditModule(scope.row)"> <SvgIcon name="ele-Edit" />修改 </el-button> <el-button v-if="scope.row.id != 0" type="danger" plain size="small" @click="onTabelRowDel(scope.row)"> <SvgIcon name="ele-Delete" />删除 </el-button> </template> </el-table-column> </el-table> <!-- 分页设置--> <div v-show="total > 0"><el-divider></el-divider> <el-pagination background :total="total" :current-page="queryParams.pageNum" :page-size="queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" @current-change="handleCurrentChange" /></div> </div> <!-- 添加或修改参数配置对话框 --> <el-dialog :fullscreen="isFullscreen" width="1000px" v-model="isShowDialog" destroy-on-close title="商品" draggable center> <template #header="{ close, titleId, titleClass }"> <h4 :id="titleId" :class="titleClass">商品</h4> <el-tooltip v-if="!isFullscreen" content="最大化" placement="bottom"> <el-button class="diygw-max-icon el-dialog__headerbtn"> <el-icon @click="isFullscreen = !isFullscreen"> <FullScreen /> </el-icon> </el-button> </el-tooltip> <el-tooltip v-if="isFullscreen" content="返回" placement="bottom"> <el-button class="diygw-max-icon el-dialog__headerbtn"> <el-icon @click="isFullscreen = !isFullscreen"> <Remove /> </el-icon> </el-button> </el-tooltip> </template> <el-form class="flex flex-direction-row flex-wrap" :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="90px"> <div class="flex diygw-col-24"> <el-tabs v-model="tabsTab" tab-position="top" @tab-click="handleClick"> <el-tab-pane label="基本信息" name="1"> <div class="flex flex-direction-row flex-wrap"> <div class="flex diygw-col-24"> <el-form-item :rules="[ { required: true, trigger: 'blur', message: '商品名称不能为空' } ]" class="diygw-el-rate" prop="title" label="商品名称" > <el-input type="text" placeholder="请输入商品名称" v-model="editForm.title"> </el-input> </el-form-item> </div> <div class="flex diygw-col-24"> <el-form-item prop="cateId" class="diygw-el-rate" label="商品分类"> <el-select clear-icon="CircleClose" suffix-icon="ArrowUp" placeholder="请选择" v-model="editForm.cateId" style="width: 100% !important"> <el-option v-for="item in cates.rows" :key="item.id" :label="item.title" :value="item.id" /> </el-select> </el-form-item> </div> <div class="flex diygw-col-24"> <el-form-item :rules="[ { required: true, trigger: 'blur', message: '商品主图不能为空' } ]" prop="img" class="diygw-el-rate" label="商品主图" > <diy-uploadinput v-model="editForm.img" title="商品主图"></diy-uploadinput> </el-form-item> </div> <div class="flex diygw-col-24"> <el-form-item prop="imgs" class="diygw-el-rate" label="商品轮播图"> <diy-photo v-model="editForm.imgs"></diy-photo> </el-form-item> </div> <div class="flex diygw-col-24"> <el-form-item prop="status" class="diygw-el-rate" label="商品状态"> <el-radio-group v-model="editForm.status"> <el-radio v-for="item in editFormData.statusDatas" :key="item.value" :label="item.value"> {{ item.label }} </el-radio> </el-radio-group> </el-form-item> </div> <div class="flex diygw-col-24"> <el-form-item prop="remark" class="diygw-el-rate" label="商品描述"> <el-input type="textarea" rows="3" placeholder="请输入描述" v-model="editForm.remark"> </el-input> </el-form-item> </div> </div> </el-tab-pane> <el-tab-pane label="规格库存" name="2"> <div class="flex flex-direction-row flex-wrap"> <div class="flex diygw-col-24"> <el-form-item prop="skuType" class="diygw-el-rate" label="规格类型"> <el-radio-group v-model="editForm.skuType"> <el-radio v-for="item in editFormData.skuTypeDatas" :key="item.value" :label="item.value"> {{ item.label }} </el-radio> </el-radio-group> </el-form-item> </div> <div v-if="editForm.skuType == 1" class="flex flex-wrap diygw-col-24 flex-direction-column"> <div class="flex diygw-col-24"> <el-form-item :rules="[ { type: 'number', required: true, trigger: 'blur', message: '请输入数字' } ]" prop="price" class="diygw-el-rate" label="商品价格" > <el-input-number min="0" max="100" step="1" controls controls-position="right" placeholder="输入商品价格" v-model="editForm.price"> </el-input-number> <div class="diygw-help-text">商品的实际购买金额,最低0.01</div> </el-form-item> </div> <div class="flex diygw-col-24"> <el-form-item :rules="[ { type: 'number', required: true, trigger: 'blur', message: '请输入数字' } ]" prop="linePrice" class="diygw-el-rate" label="切线价格" > <el-input-number min="0" max="100" step="1" controls controls-position="right" placeholder="输入切线价格" v-model="editForm.linePrice"> </el-input-number> <div class="diygw-help-text">划线价仅用于商品页展示</div> </el-form-item> </div> <div class="flex diygw-col-24"> <el-form-item :rules="[ { type: 'number', required: true, trigger: 'blur', message: '请输入数字' } ]" prop="amount" class="diygw-el-rate" label="库存数量" > <el-input-number min="0" max="100" step="1" controls controls-position="right" placeholder="输入库存数量" v-model="editForm.amount"> </el-input-number> <div class="diygw-help-text">商品的实际库存数量,为0时用户无法下单</div> </el-form-item> </div> </div> <div v-else class="flex diygw-col-24"> <el-form-item prop="skus" label="多规格"> <diy-sku :columns="editFormData.skusColumns" v-model:skus="editForm.skus.skus" v-model:specs="editForm.skus.specs"></diy-sku> </el-form-item> </div> </div> </el-tab-pane> <el-tab-pane label="商品详情" name="3"> <div class="flex flex-direction-row flex-wrap"> <div class="flex diygw-col-24"> <el-form-item :rules="[ { required: true, trigger: 'blur', message: '商品详情不能为空哟' } ]" prop="content" class="diygw-el-rate" label="商品详情" > <diy-editor v-model="editForm.content"></diy-editor> </el-form-item> </div> </div> </el-tab-pane> </el-tabs> </div> </el-form> <template #footer> <div class="dialog-footer flex justify-center"><el-button @click="closeDialog"> 取 消 </el-button> <el-button type="primary" @click="onSubmit" :loading="saveloading"> 保 存 </el-button></div> </template> </el-dialog> </div> <div class="clearfix"></div> </div> </template> <script> import { useRouter, useRoute } from 'vue-router'; import { storeToRefs } from 'pinia'; import { useUserInfo } from '@/stores/userInfo'; import { ElMessageBox, ElMessage } from 'element-plus'; import { deepClone, changeRowToForm } from '@/utils/other'; import { addData, updateData, delData, listData } from '@/api'; import DiyUploadinput from '@/components/upload/uploadinput.vue'; import DiyPhoto from '@/components/upload/photo.vue'; import DiySku from '@/components/sku/index.vue'; import DiyEditor from '@/components/editor/index.vue'; export default { components: { DiyUploadinput, DiyPhoto, DiySku, DiyEditor }, data() { return { globalOption: {}, userInfo: {}, ordernum: { code: 200, msg: '设置成功' }, updatenum: { code: 200, msg: '修改成功', data: { id: '1', sortnum: '0' } }, cates: { rows: [ { id: 0, title: '', remark: null, img: '', sortnum: null, status: '', userId: 0, createTime: '', updateTime: '', deleteTime: null } ], total: 0, code: 0, msg: '' }, // 遮罩层 loading: true, // 选中数组 ids: [], // 非单个禁用 single: true, // 非多个禁用 multiple: true, // 弹出层标题 title: '', // 总条数 total: 0, tableData: [], // 是否显示弹出层 isFullscreen: false, isShowDialog: false, saveloading: false, tabsTab: '1', editFormInit: {}, queryParamsInit: {}, editFormData: { statusDatas: [ { value: '1', label: '上架' }, { value: '2', label: '下架' } ], skuTypeDatas: [ { value: '1', label: '单规格' }, { value: '2', label: '多规格' } ], skusColumns: [ { title: '图片地址', id: 'thumb', type: 'img' }, { title: '价格', id: 'price', type: 'number' }, { title: '划线价格', id: 'linePrice', type: 'number' }, { title: '库存', id: 'amount', type: 'number' }, { title: '备注', id: 'sku', type: 'text' } ] }, queryParams: { pageNum: 1, pageSize: 10, title_like: '' }, queryParamsRules: {}, editForm: { id: undefined, title: '', cateId: '', img: '', imgs: [], status: '1', remark: '', skuType: '1', price: '', linePrice: '', amount: '', skus: { skus: [], specs: [] }, content: '' }, editFormRules: {} }; }, methods: { getParamData(e, row) { row = row || {}; let dataset = e.currentTarget ? e.currentTarget.dataset : e; if (row) { dataset = Object.assign(dataset, row); } return dataset; }, navigateTo(e, row) { let dataset = this.getParamData(e, row); let { type } = dataset; if (type == 'page' || type == 'inner' || type == 'href') { this.router.push({ path: dataset.url, query: dataset }); } else if (typeof this[type] == 'function') { this[type](dataset); } else { ElMessage.error('待实现点击事件'); } }, async init() { await this.catesApi(); }, // 置顶 API请求方法 async ordernumApi(param) { param = param || {}; param = this.getParamData(param); let http_url = '/shop/goods/sortnum'; let http_data = {}; let http_header = {}; http_data.id = param.id; let flag = await this.confirmFunction({ title: '确定置顶吗' }); if (!flag) { ElMessage.error('你已取消'); return; } let ordernum = await this.$http.post(http_url, http_data, http_header, 'json'); this.ordernum = ordernum; this.resetQuery(); }, // 设置为0 API请求方法 async updatenumApi(param) { param = param || {}; param = this.getParamData(param); let http_url = '/shop/goods/update'; let http_data = {}; let http_header = {}; http_data.id = param.id; http_data.sortnum = 0; let flag = await this.confirmFunction({ title: '确定取消置顶吗' }); if (!flag) { ElMessage.error('你已取消'); return; } let updatenum = await this.$http.post(http_url, http_data, http_header, 'json'); this.updatenum = updatenum; this.resetQuery(); }, // 分类 API请求方法 async catesApi(param) { param = param || {}; param = this.getParamData(param); let http_url = '/shop/cate/all'; let http_data = {}; let http_header = {}; let cates = await this.$http.post(http_url, http_data, http_header, 'json'); this.cates = cates; }, // confirm 自定义方法 async confirmFunction(param) { param = param || {}; let thiz = this; let title = param && (param.title || param.title == 0) ? param.title : thiz.title || ''; return new Promise((resolve) => { ElMessageBox({ message: title, title: '警告', showCancelButton: true, confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }) .then(() => { resolve(true); }) .catch(() => { resolve(false); }); }); }, // 打开弹窗 openDialog(row) { if (row.id && row.id != undefined && row.id != 0) { this.editForm = changeRowToForm(row, this.editForm); } else { this.initForm(); } this.isShowDialog = true; this.saveloading = false; }, // 关闭弹窗 closeDialog(row) { this.mittBus.emit('onEditAdmintableModule', row); this.isShowDialog = false; }, // 保存 onSubmit() { const formWrap = this.$refs.editFormRef; if (!formWrap) return; formWrap.validate((valid, fields) => { if (valid) { this.saveloading = true; if (this.editForm.skuType == 2 && this.editForm.skus && this.editForm.skus.skus.length == 0) { ElMessage.error('请设置多规格'); return; } if (this.editForm.id != undefined && this.editForm.id != 0) { updateData('/shop/goods', this.editForm) .then(() => { ElMessage.success('修改成功'); this.saveloading = false; this.closeDialog(this.editForm); // 关闭弹窗 }) .finally(() => { this.saveloading = false; }); } else { addData('/shop/goods', this.editForm) .then(() => { ElMessage.success('新增成功'); this.saveloading = false; this.closeDialog(this.editForm); // 关闭弹窗 }) .finally(() => { this.saveloading = false; }); } } else { let message = ''; if (fields && Object.keys(fields).length > 0) { let field = fields[Object.keys(fields)[0]]; let messages = field.map((item) => { return item.message; }); message = messages.join(','); } ElMessage.error('验证未通过!' + message); } }); }, // 表单初始化,方法:`resetFields()` 无法使用 initForm() { this.editForm = deepClone(this.editFormInit); }, /** 查询商品列表 */ handleQuery() { this.loading = true; listData('/shop/goods', this.queryParams).then((response) => { this.tableData = response.rows; this.total = response.total; this.loading = false; }); }, /** 重置按钮操作 */ resetQuery() { this.queryParams = deepClone(this.queryParamsInit); this.handleQuery(); }, // 打开新增商品弹窗 onOpenAddModule(row) { row = []; this.title = '添加商品'; this.openDialog(row); }, // 打开编辑商品弹窗 onOpenEditModule(row) { this.title = '修改商品'; this.initForm(); this.openDialog(row); }, /** 删除按钮操作 */ onTabelRowDel(row) { let thiz = this; const id = row.id || this.ids; ElMessageBox({ message: '是否确认删除选中的商品?', title: '警告', showCancelButton: true, confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(function () { return delData('/shop/goods', id).then(() => { thiz.handleQuery(); ElMessage.success('删除成功'); }); }); }, // 多选框选中数据 handleSelectionChange(selection) { this.ids = selection.map((item) => item.id); this.single = selection.length != 1; this.multiple = !selection.length; }, //分页页面大小发生变化 handleSizeChange(val) { this.queryParams.pageSize = val; this.handleQuery(); }, //当前页码发生变化 handleCurrentChange(val) { this.queryParams.pageNum = val; this.handleQuery(); } }, async mounted() { this.router = useRouter(); this.globalOption = useRoute().query; const stores = useUserInfo(); const { userInfos } = storeToRefs(stores); this.userInfo = userInfos; await this.init(); this.editFormInit = deepClone(this.editForm); this.queryParamsInit = deepClone(this.queryParams); this.handleQuery(); this.mittBus.on('onEditAdmintableModule', () => { this.handleQuery(); }); }, beforeUnmount() { this.mittBus.off('onEditAdmintableModule'); } }; </script> <style lang="scss" scoped> .container { font-size: 12px; } </style>
vue组合式代码
<template> <div class="container"> <div class="el-card is-always-shadow diygw-col-24"> <div class="el-card__body"> <div class="mb8"> <el-form class="flex flex-direction-row flex-wrap" :model="state.queryParams" :rules="queryParamsRules" ref="queryForm" label-width="80px"> <div class="flex diygw-col-0"> <el-form-item class="diygw-el-rate" prop="title_like" label="标题"> <el-input type="text" placeholder="请输入标题查询" v-model="queryParams.title_like"> </el-input> </el-form-item> </div> <div class="flex diygw-col-0"> <el-button type="primary" @click="handleQuery"> <SvgIcon name="ele-Search" /> 搜索 </el-button> <el-button @click="resetQuery"> <SvgIcon name="ele-Refresh" /> 重置 </el-button> </div> </el-form> </div> <div class="mb8"> <el-button type="primary" plain @click="onOpenAddModule"> <SvgIcon name="ele-Plus" />新增 </el-button> <el-button type="danger" plain :disabled="state.multiple" @click="onTabelRowDel"> <SvgIcon name="ele-Delete" />删除 </el-button> </div> <el-table @selection-change="handleSelectionChange" v-loading="state.loading" :data="state.tableData" stripe border current-row-key="id" empty-text="没有数据" style="width: 100%"> <el-table-column type="selection" width="55" align="center"></el-table-column> <el-table-column label="置顶" prop="title" align="center"> <template #default="scope"> <el-tag style="cursor: pointer" v-if="scope.row.sortnum > 0" type="error" @click="updatenumApi(scope.row)"> 取消置顶 </el-tag><el-tag style="cursor: pointer" @click="ordernumApi(scope.row)" v-else> 置顶 </el-tag> </template> </el-table-column> <el-table-column label="商品图片" prop="img" align="center"> <template #default="scope"> <diy-img style="width: 80px; height: 80px" :src="scope.row.img" fit="contain"></diy-img> </template> </el-table-column> <el-table-column label="商品名称" prop="title" align="center"> </el-table-column> <el-table-column label="商品价格" prop="price" align="center"> </el-table-column> <el-table-column label="总销量" prop="sellamonut" align="center"> </el-table-column> <el-table-column label="库存量" prop="amount" align="center"> </el-table-column> <el-table-column label="备注" prop="remark" align="center"> </el-table-column> <el-table-column label="数据状态" prop="status" align="center"> <template #default="scope"> <el-tag v-if="scope.row.status == 1"> 上架 </el-tag><el-tag v-else> 下架 </el-tag> </template> </el-table-column> <el-table-column label="操作" align="center" fixed="right" width="180"> <template #default="scope"> <el-button type="primary" plain size="small" @click="onOpenEditModule(scope.row)"> <SvgIcon name="ele-Edit" />修改 </el-button> <el-button v-if="scope.row.id != 0" type="danger" plain size="small" @click="onTabelRowDel(scope.row)"> <SvgIcon name="ele-Delete" />删除 </el-button> </template> </el-table-column> </el-table> <!-- 分页设置--> <div v-show="state.total > 0"><el-divider></el-divider> <el-pagination background :total="state.total" :current-page="state.queryParams.pageNum" :page-size="state.queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" @current-change="handleCurrentChange" /></div> </div> <!-- 添加或修改参数配置对话框 --> <el-dialog :fullscreen="isFullscreen" width="1000px" v-model="isShowDialog" destroy-on-close title="商品" draggable center> <template #header="{ close, titleId, titleClass }"> <h4 :id="titleId" :class="titleClass">商品</h4> <el-tooltip v-if="!isFullscreen" content="最大化" placement="bottom"> <el-button class="diygw-max-icon el-dialog__headerbtn"> <el-icon @click="isFullscreen = !isFullscreen"> <FullScreen /> </el-icon> </el-button> </el-tooltip> <el-tooltip v-if="isFullscreen" content="返回" placement="bottom"> <el-button class="diygw-max-icon el-dialog__headerbtn"> <el-icon @click="isFullscreen = !isFullscreen"> <Remove /> </el-icon> </el-button> </el-tooltip> </template> <el-form class="flex flex-direction-row flex-wrap" :model="state.editForm" :rules="state.editFormRules" ref="editFormRef" label-width="90px"> <div class="flex diygw-col-24"> <el-tabs v-model="tabsTab" tab-position="top" @tab-click="handleClick"> <el-tab-pane label="基本信息" name="1"> <div class="flex flex-direction-row flex-wrap"> <div class="flex diygw-col-24"> <el-form-item :rules="[ { required: true, trigger: 'blur', message: '商品名称不能为空' } ]" class="diygw-el-rate" prop="title" label="商品名称" > <el-input type="text" placeholder="请输入商品名称" v-model="editForm.title"> </el-input> </el-form-item> </div> <div class="flex diygw-col-24"> <el-form-item prop="cateId" class="diygw-el-rate" label="商品分类"> <el-select clear-icon="CircleClose" suffix-icon="ArrowUp" placeholder="请选择" v-model="editForm.cateId" style="width: 100% !important"> <el-option v-for="item in cates.rows" :key="item.id" :label="item.title" :value="item.id" /> </el-select> </el-form-item> </div> <div class="flex diygw-col-24"> <el-form-item :rules="[ { required: true, trigger: 'blur', message: '商品主图不能为空' } ]" prop="img" class="diygw-el-rate" label="商品主图" > <diy-uploadinput v-model="editForm.img" title="商品主图"></diy-uploadinput> </el-form-item> </div> <div class="flex diygw-col-24"> <el-form-item prop="imgs" class="diygw-el-rate" label="商品轮播图"> <diy-photo v-model="editForm.imgs"></diy-photo> </el-form-item> </div> <div class="flex diygw-col-24"> <el-form-item prop="status" class="diygw-el-rate" label="商品状态"> <el-radio-group v-model="editForm.status"> <el-radio v-for="item in editFormData.statusDatas" :key="item.value" :label="item.value"> {{ item.label }} </el-radio> </el-radio-group> </el-form-item> </div> <div class="flex diygw-col-24"> <el-form-item prop="remark" class="diygw-el-rate" label="商品描述"> <el-input type="textarea" rows="3" placeholder="请输入描述" v-model="editForm.remark"> </el-input> </el-form-item> </div> </div> </el-tab-pane> <el-tab-pane label="规格库存" name="2"> <div class="flex flex-direction-row flex-wrap"> <div class="flex diygw-col-24"> <el-form-item prop="skuType" class="diygw-el-rate" label="规格类型"> <el-radio-group v-model="editForm.skuType"> <el-radio v-for="item in editFormData.skuTypeDatas" :key="item.value" :label="item.value"> {{ item.label }} </el-radio> </el-radio-group> </el-form-item> </div> <div v-if="editForm.skuType == 1" class="flex flex-wrap diygw-col-24 flex-direction-column"> <div class="flex diygw-col-24"> <el-form-item :rules="[ { type: 'number', required: true, trigger: 'blur', message: '请输入数字' } ]" prop="price" class="diygw-el-rate" label="商品价格" > <el-input-number min="0" max="100" step="1" controls controls-position="right" placeholder="输入商品价格" v-model="editForm.price"> </el-input-number> <div class="diygw-help-text">商品的实际购买金额,最低0.01</div> </el-form-item> </div> <div class="flex diygw-col-24"> <el-form-item :rules="[ { type: 'number', required: true, trigger: 'blur', message: '请输入数字' } ]" prop="linePrice" class="diygw-el-rate" label="切线价格" > <el-input-number min="0" max="100" step="1" controls controls-position="right" placeholder="输入切线价格" v-model="editForm.linePrice"> </el-input-number> <div class="diygw-help-text">划线价仅用于商品页展示</div> </el-form-item> </div> <div class="flex diygw-col-24"> <el-form-item :rules="[ { type: 'number', required: true, trigger: 'blur', message: '请输入数字' } ]" prop="amount" class="diygw-el-rate" label="库存数量" > <el-input-number min="0" max="100" step="1" controls controls-position="right" placeholder="输入库存数量" v-model="editForm.amount"> </el-input-number> <div class="diygw-help-text">商品的实际库存数量,为0时用户无法下单</div> </el-form-item> </div> </div> <div v-else class="flex diygw-col-24"> <el-form-item prop="skus" label="多规格"> <diy-sku :columns="editFormData.skusColumns" v-model:skus="editForm.skus.skus" v-model:specs="editForm.skus.specs"></diy-sku> </el-form-item> </div> </div> </el-tab-pane> <el-tab-pane label="商品详情" name="3"> <div class="flex flex-direction-row flex-wrap"> <div class="flex diygw-col-24"> <el-form-item :rules="[ { required: true, trigger: 'blur', message: '商品详情不能为空哟' } ]" prop="content" class="diygw-el-rate" label="商品详情" > <diy-editor v-model="editForm.content"></diy-editor> </el-form-item> </div> </div> </el-tab-pane> </el-tabs> </div> </el-form> <template #footer> <div class="dialog-footer flex justify-center"><el-button @click="closeDialog"> 取 消 </el-button> <el-button type="primary" @click="onSubmit" :loading="state.saveloading"> 保 存 </el-button></div> </template> </el-dialog> </div> <div class="clearfix"></div> </div> </template> <script setup name="goodsgoods"> import { ElMessageBox, ElMessage } from 'element-plus'; import { ref, toRefs, reactive, onMounted, getCurrentInstance, onUnmounted, unref } from 'vue'; import { deepClone, changeRowToForm } from '@/utils/other'; import { addData, updateData, delData, listData } from '@/api'; import { useRouter, useRoute } from 'vue-router'; import { storeToRefs } from 'pinia'; import { useUserInfo } from '@/stores/userInfo'; import DiyUploadinput from '@/components/upload/uploadinput.vue'; import DiyPhoto from '@/components/upload/photo.vue'; import DiySku from '@/components/sku/index.vue'; import DiyEditor from '@/components/editor/index.vue'; const stores = useUserInfo(); const { userInfos } = storeToRefs(stores); const { proxy } = getCurrentInstance(); const router = useRouter(); const route = useRoute(); const globalOption = ref(route.query); const getParamData = (e, row) => { row = row || {}; let dataset = e.currentTarget ? e.currentTarget.dataset : e; if (row) { dataset = Object.assign(dataset, row); } return dataset; }; const navigateTo = (e, row) => { let dataset = getParamData(e, row); let { type } = dataset; if ((type == 'page' || type == 'inner' || type == 'href') && dataset.url) { router.push({ path: (dataset.url.startsWith('/') ? '' : '/') + dataset.url, query: dataset }); } else { ElMessage.error('待实现点击事件'); } }; const state = reactive({ userInfo: userInfos.value, ordernum: { code: 200, msg: '设置成功' }, updatenum: { code: 200, msg: '修改成功', data: { id: '1', sortnum: '0' } }, cates: { rows: [ { id: 0, title: '', remark: null, img: '', sortnum: null, status: '', userId: 0, createTime: '', updateTime: '', deleteTime: null } ], total: 0, code: 0, msg: '' }, // 遮罩层 loading: true, // 选中数组 ids: [], // 非单个禁用 single: true, // 非多个禁用 multiple: true, // 弹出层标题 title: '', // 总条数 total: 0, tableData: [], // 是否显示弹出层 isFullscreen: false, isShowDialog: false, saveloading: false, tabsTab: '1', editFormData: { statusDatas: [ { value: '1', label: '上架' }, { value: '2', label: '下架' } ], skuTypeDatas: [ { value: '1', label: '单规格' }, { value: '2', label: '多规格' } ], skusColumns: [ { title: '图片地址', id: 'thumb', type: 'img' }, { title: '价格', id: 'price', type: 'number' }, { title: '划线价格', id: 'linePrice', type: 'number' }, { title: '库存', id: 'amount', type: 'number' }, { title: '备注', id: 'sku', type: 'text' } ] }, queryParams: { pageNum: 1, pageSize: 10, title_like: '' }, queryParamsRules: {}, editForm: { id: undefined, title: '', cateId: '', img: '', imgs: [], status: '1', remark: '', skuType: '1', price: '', linePrice: '', amount: '', skus: { skus: [], specs: [] }, content: '' }, editFormRules: {} }); const { userInfo, cates, tabsTab, editFormData, queryParams, multiple, ordernum, tableData, updatenum, loading, title, single, total, isShowDialog, editForm, ids, saveloading, isFullscreen } = toRefs(state); // 置顶 API请求方法 const ordernumApi = async (param) => { param = param || {}; param = getParamData(param); let http_url = '/shop/goods/sortnum'; let http_data = {}; let http_header = {}; http_data.id = param.id; let flag = await confirmFunction({ title: '确定置顶吗' }); if (!flag) { ElMessage.error('你已取消'); return; } let ordernum = await proxy.$http.post(http_url, http_data, http_header, 'json'); state.ordernum = ordernum; state.resetQuery(); }; // 设置为0 API请求方法 const updatenumApi = async (param) => { param = param || {}; param = getParamData(param); let http_url = '/shop/goods/update'; let http_data = {}; let http_header = {}; http_data.id = param.id; http_data.sortnum = 0; let flag = await confirmFunction({ title: '确定取消置顶吗' }); if (!flag) { ElMessage.error('你已取消'); return; } let updatenum = await proxy.$http.post(http_url, http_data, http_header, 'json'); state.updatenum = updatenum; state.resetQuery(); }; // 分类 API请求方法 const catesApi = async (param) => { param = param || {}; param = getParamData(param); let http_url = '/shop/cate/all'; let http_data = {}; let http_header = {}; let cates = await proxy.$http.post(http_url, http_data, http_header, 'json'); state.cates = cates; }; //confirm 自定义方法 const confirmFunction = async (param) => { let title = param && (param.title || param.title == 0) ? param.title : state.title || ''; return new Promise((resolve) => { ElMessageBox({ message: title, title: '警告', showCancelButton: true, confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }) .then(() => { resolve(true); }) .catch(() => { resolve(false); }); }); }; const editFormRef = ref(null); const editFormInit = deepClone(state.editForm); // 打开弹窗 const openDialog = (row) => { if (row.id && row.id != undefined && row.id != 0) { state.editForm = changeRowToForm(row, state.editForm); } else { initForm(); } state.isShowDialog = true; state.saveloading = false; }; // 关闭弹窗 const closeDialog = (row) => { proxy.mittBus.emit('onEditAdmintableModule', row); state.isShowDialog = false; }; // 保存 const onSubmit = () => { const formWrap = unref(editFormRef); if (!formWrap) return; formWrap.validate((valid, fields) => { if (valid) { state.saveloading = true; if (editForm.skuType == 2 && editForm.skus && editForm.skus.skus.length == 0) { ElMessage.error('请设置多规格'); return; } if (state.editForm.id != undefined && state.editForm.id != 0) { updateData('/shop/goods', state.editForm) .then(() => { ElMessage.success('修改成功'); state.saveloading = false; closeDialog(state.editForm); // 关闭弹窗 }) .finally(() => { state.saveloading = false; }); } else { addData('/shop/goods', state.editForm) .then(() => { ElMessage.success('新增成功'); state.saveloading = false; closeDialog(state.editForm); // 关闭弹窗 }) .finally(() => { state.saveloading = false; }); } } else { let message = ''; if (fields && Object.keys(fields).length > 0) { let field = fields[Object.keys(fields)[0]]; let messages = field.map((item) => { return item.message; }); message = messages.join(','); } ElMessage.error('验证未通过!' + message); } }); }; // 表单初始化,方法:`resetFields()` 无法使用 const initForm = () => { state.editForm = deepClone(editFormInit); }; const queryParamsInit = deepClone(state.queryParams); /** 查询商品列表 */ const handleQuery = () => { state.loading = true; listData('/shop/goods', state.queryParams).then((response) => { state.tableData = response.rows; state.total = response.total; state.loading = false; }); }; /** 重置按钮操作 */ const resetQuery = () => { state.queryParams = deepClone(queryParamsInit); handleQuery(); }; // 打开新增商品弹窗 const onOpenAddModule = (row) => { row = []; state.title = '添加商品'; initForm(); openDialog(row); }; // 打开编辑商品弹窗 const onOpenEditModule = (row) => { state.title = '修改商品'; openDialog(row); }; /** 删除按钮操作 */ const onTabelRowDel = (row) => { const id = row.id || state.ids; ElMessageBox({ message: '是否确认删除选中的商品?', title: '警告', showCancelButton: true, confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(function () { return delData('/shop/goods', id).then(() => { handleQuery(); ElMessage.success('删除成功'); }); }); }; // 多选框选中数据 const handleSelectionChange = (selection) => { state.ids = selection.map((item) => item.id); state.single = selection.length != 1; state.multiple = !selection.length; }; //分页页面大小发生变化 const handleSizeChange = (val) => { state.queryParams.pageSize = val; handleQuery(); }; //当前页码发生变化 const handleCurrentChange = (val) => { state.queryParams.pageNum = val; handleQuery(); }; const init = async () => { await catesApi(); }; // 页面加载时 onMounted(async () => { await init(); handleQuery(); proxy.mittBus.on('onEditAdmintableModule', () => { handleQuery(); }); }); // 页面卸载时 onUnmounted(() => { proxy.mittBus.off('onEditAdmintableModule'); }); </script> <style lang="scss" scoped> .container { font-size: 12px; } </style>
扩展阅读
产品录入:
管理员可以通过产品管理模块录入新的产品信息,包括商品名称、价格、描述、分类、品牌、图片等。
录入时需要进行信息的校验和合法性验证,确保数据的准确性和完整性。
产品编辑:
管理员可以对已有产品的信息进行编辑和更新,如修改商品名称、价格、描述等。
编辑界面应友好且操作便捷,以便管理员轻松完成更新。
产品上架与下架:
管理员可以灵活地将产品上架或下架。
上架后,用户可以在商城中浏览和购买该产品;下架后,用户将无法再找到该产品进行购买。
上架和下架操作应提供明确的提示和确认机制,避免误操作。
库存管理:
产品管理模块应提供库存管理功能,管理员可以查看和管理各个产品的库存情况。
包括库存数量的增加、减少、库存警戒值的设置等。
当库存数量低于警戒值时,系统应自动提醒管理员进行补货或下架操作。