产品管理是商城管理系统的核心模块之一,它直接关系到商品的流通效率、库存管理和销售策略。通过高效的产品管理,商城能够实时掌握商品信息,优化库存结构,提高销售业绩。
新增数据库表
拷贝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>
扩展阅读
-
产品录入:
- 管理员可以通过产品管理模块录入新的产品信息,包括商品名称、价格、描述、分类、品牌、图片等。
- 录入时需要进行信息的校验和合法性验证,确保数据的准确性和完整性。
-
产品编辑:
- 管理员可以对已有产品的信息进行编辑和更新,如修改商品名称、价格、描述等。
- 编辑界面应友好且操作便捷,以便管理员轻松完成更新。
-
产品上架与下架:
- 管理员可以灵活地将产品上架或下架。
- 上架后,用户可以在商城中浏览和购买该产品;下架后,用户将无法再找到该产品进行购买。
- 上架和下架操作应提供明确的提示和确认机制,避免误操作。
-
库存管理:
- 产品管理模块应提供库存管理功能,管理员可以查看和管理各个产品的库存情况。
- 包括库存数量的增加、减少、库存警戒值的设置等。
- 当库存数量低于警戒值时,系统应自动提醒管理员进行补货或下架操作。