feat(商品参数): 新增参数管理功能并优化相关页面

- 新增参数编辑页面,支持参数名称、必填、索引等属性配置
- 优化参数列表页面,增加搜索和分页功能
- 重构商品发布页面的参数展示逻辑
- 移除商品模板相关功能
- 优化品牌管理页面的分类关联功能
- 调整商品详情页参数展示样式
This commit is contained in:
pikachu1995@126.com
2025-12-22 14:38:52 +08:00
parent 8a0f5ff120
commit b37e12b7a7
9 changed files with 1065 additions and 778 deletions

View File

@@ -2,7 +2,7 @@
<div>
<!-- 选择商品类型 -->
<Modal v-model="selectGoodsType" width="550" :closable="false">
<div class="goods-type-list" v-if="!showGoodsTemplates">
<div class="goods-type-list">
<div
class="goods-type-item"
:class="{ 'active-goods-type': item.check }"
@@ -17,25 +17,6 @@
</div>
</div>
</div>
<div v-else class="goods-type-list">
<h2 @click="showGoodsTemplates = !showGoodsTemplates">返回</h2>
<div class="goods-list-box">
<Scroll :on-reach-bottom="handleReachBottom">
<div
class="goods-type-item template-item"
@click="handleClickGoodsTemplate(item)"
v-for="(item, tempIndex) in goodsTemplates"
:key="tempIndex"
>
<img :src="item.thumbnail" />
<div>
<h2>{{ item.goodsName }}</h2>
<p>{{ item.sellingPoint || "" }}</p>
</div>
</div>
</Scroll>
</div>
</div>
</Modal>
<!-- 商品分类 -->
<div class="content-goods-publish">
@@ -79,9 +60,6 @@
<span v-show="category[1].name">> {{ category[1].name }}</span>
<span v-show="category[2].name">> {{ category[2].name }}</span>
</p>
<template v-if="selectedTemplate.goodsName">
<Divider>已选商品模版:{{ selectedTemplate.goodsName }}</Divider>
</template>
</div>
<!-- 底部按钮 -->
<div class="footer">
@@ -97,10 +75,7 @@ import * as API_GOODS from "@/api/goods";
export default {
data() {
return {
selectedTemplate: {}, // 已选模板
selectGoodsType: false, // 展示选择商品分类modal
goodsTemplates: [], // 商品模板列表
showGoodsTemplates: false, //是否显示选择商品模板
goodsTypeWay: [
{
title: "实物商品",
@@ -116,12 +91,6 @@ export default {
type: "VIRTUAL_GOODS",
check: false,
},
{
title: "商品模板导入",
img: require("@/assets/goodsTypeTpl.png"),
desc: "商品模板,一键导入",
check: false,
},
],
// 商品分类选择数组
category: [
@@ -137,29 +106,9 @@ export default {
categoryListLevel2: [],
/** 3级分类列表*/
categoryListLevel3: [],
searchParams: {
saveType: "TEMPLATE",
sort: "create_time",
order: "desc",
pageSize: 10,
pageNumber: 1,
},
templateTotal: 0,
};
},
methods: {
// 商品模版触底加载
handleReachBottom() {
setTimeout(() => {
if (
this.searchParams.pageNumber * this.searchParams.pageSize <=
this.templateTotal
) {
this.searchParams.pageNumber++;
this.GET_GoodsTemplate();
}
}, 1000);
},
// 点击商品类型
handleClickGoodsType(val) {
this.goodsTypeWay.map((item) => {
@@ -167,28 +116,7 @@ export default {
});
val.check = !val.check;
if (!val.type) {
this.GET_GoodsTemplate();
this.showGoodsTemplates = true;
} else {
this.goodsType = val.type;
this.selectedTemplate = {};
}
},
// 点击商品模板
handleClickGoodsTemplate(val) {
this.selectedTemplate = val;
this.selectGoodsType = false;
this.$emit("change", { tempId: val.id });
},
// 获取商品模板
GET_GoodsTemplate() {
API_GOODS.getDraftGoodsListData(this.searchParams).then((res) => {
if (res.success) {
this.goodsTemplates.push(...res.result.records);
this.templateTotal = res.result.total;
}
});
this.goodsType = val.type;
},
/** 选择商城商品分类 */
handleSelectCategory(row, index, level) {
@@ -223,7 +151,7 @@ export default {
// 下一步
next() {
window.scrollTo(0, 0);
if (!this.goodsType && !this.selectedTemplate.goodsName) {
if (!this.goodsType) {
this.$Message.error("请选择商品类型");
return;
}
@@ -238,12 +166,7 @@ export default {
category: this.category,
goodsType: this.goodsType,
};
if (this.selectedTemplate.id) {
params.tempId = this.selectedTemplate.id;
this.$emit("change", params);
} else {
this.$emit("change", params);
}
this.$emit("change", params);
}
},
},
@@ -254,7 +177,4 @@ export default {
</script>
<style lang="scss" scoped>
@import "./addGoods.scss";
::v-deep .ivu-scroll-container {
height: 450px !important;
}
</style>

View File

@@ -32,10 +32,8 @@
<Button class="refresh-icon" icon="md-refresh" shape="circle" type="text"
@click="refresh('brand')"></Button>
</FormItem>
</div>
<h4>商品交易信息</h4>
<div class="form-item-view">
<FormItem class="form-item-view-el" label="计量单位" prop="goodsUnit">
<FormItem class="form-item-view-el" label="计量单位" prop="goodsUnit">
<Select v-model="baseInfoForm.goodsUnit" style="width: 100px">
<Option v-for="(item, index) in goodsUnitList" :key="index" :value="item">{{ item }}
</Option>
@@ -97,7 +95,18 @@
</div>
</div>
</FormItem>
<FormItem class="form-item-view-el" label="商品发布" prop="release">
<RadioGroup v-model="baseInfoForm.release" button-style="solid" type="button">
<Radio :label="1" title="上架">
<span>上架</span>
</Radio>
<Radio :label="0" title="下架">
<span>下架</span>
</Radio>
</RadioGroup>
</FormItem>
</div>
<h4>商品规格及图片</h4>
<div class="form-item-view">
<FormItem class="form-item-view-el required" label="主图" prop="goodsGalleryFiles">
@@ -380,56 +389,18 @@
<span slot="append">kg</span></Input>
</FormItem>
</div>
<h4>其他信息</h4>
<h4>参数信息</h4>
<div class="form-item-view">
<FormItem class="form-item-view-el" label="商品发布" prop="release">
<RadioGroup v-model="baseInfoForm.release" button-style="solid" type="button">
<Radio :label="1" title="立即发布">
<span>立即发布</span>
</Radio>
<Radio :label="0" title="放入仓库">
<span>放入仓库</span>
</Radio>
</RadioGroup>
<FormItem v-for="(paramsItem, paramsIndex) in goodsParams" :key="paramsItem.id || paramsIndex"
:label="`${paramsItem.paramName}`"
:rules="{ required: !!paramsItem.required, message: '参数不能为空', trigger: 'change' }">
<Select v-model="paramsItem.paramValue" clearable placeholder="请选择" style="width: 200px"
@on-change="(val) => selectParams(paramsItem, val)">
<Option v-for="option in getParamOptions(paramsItem.options)" :key="option" :label="option"
:value="option">
</Option>
</Select>
</FormItem>
<FormItem class="form-item-view-el" label="商品推荐" prop="skuList">
<RadioGroup v-model="baseInfoForm.recommend" button-style="solid" type="button">
<Radio :label="1" title="推荐">
<span>推荐</span>
</Radio>
<Radio :label="0" title="不推荐">
<span>不推荐</span>
</Radio>
</RadioGroup>
</FormItem>
</div>
<div class="form-item-view-bottom">
<Collapse v-for="(paramsGroup, groupIndex) in goodsParams" :key="paramsGroup.groupName"
v-model="params_panel" :title="paramsGroup.groupName" class="mb_10" style="text-align: left">
<Panel :name="paramsGroup.groupName">
{{ paramsGroup.groupName }}
<p slot="content">
<FormItem v-for="(paramsItem, paramsIndex) in paramsGroup.params" :key="paramsIndex"
:label="`${paramsItem.paramName}`"
:rules="{ required: paramsItem.required, message: '参数不能为空', trigger: 'blur' }">
<Select v-model="paramsItem.paramValue" clearable placeholder="请选择" style="width: 200px"
@on-change="
selectParams(
paramsGroup,
groupIndex,
paramsItem,
paramsIndex,
paramsItem.paramValue
)
">
<Option v-for="option in paramsItem.options.split(',')" :key="option" :label="option"
:value="option">
</Option>
</Select>
</FormItem>
</p>
</Panel>
</Collapse>
</div>
</div>
</div>
@@ -442,7 +413,6 @@
<Button :loading="submitLoading" type="primary" @click="save">
{{ this.$route.query.id ? "保存" : "保存商品" }}
</Button>
<Button type="primary" @click="saveToDraft">保存为模版</Button>
</ButtonGroup>
</div>
@@ -519,7 +489,7 @@ export default {
previewImage: '', // 预览图片地址
global: 0,
accessToken: "", //令牌token
goodsParams: "",
goodsParams: [],
categoryId: "", // 商品分类第三级id
//提交状态
submitLoading: false,
@@ -763,54 +733,41 @@ export default {
mouseLeave() {
// this.showContent = false
},
/**
* 选择参数
* @paramsGroup 参数分组
* @groupIndex 参数分组下标
* @params 参数选项
* @paramIndex 参数下标值
* @value 参数选项值
*/
selectParams(paramsGroup, groupIndex, params, paramsIndex, value) {
if (!this.baseInfoForm.goodsParamsDTOList[groupIndex]) {
this.baseInfoForm.goodsParamsDTOList[groupIndex] = {
groupId: "",
groupName: "",
goodsParamsItemDTOList: [],
};
getParamOptions(options) {
if (!options) return [];
return String(options)
.split(",")
.map((i) => i.trim())
.filter((i) => i);
},
selectParams(params, value) {
if (!Array.isArray(this.baseInfoForm.goodsParamsDTOList)) {
this.$set(this.baseInfoForm, "goodsParamsDTOList", []);
}
//赋予分组id、分组名称
this.baseInfoForm.goodsParamsDTOList[groupIndex].groupId =
paramsGroup.groupId;
this.baseInfoForm.goodsParamsDTOList[groupIndex].groupName =
paramsGroup.groupName;
const list = this.baseInfoForm.goodsParamsDTOList;
const paramId = params && params.id ? String(params.id) : "";
const index = list.findIndex((i) => String(i.paramId) === paramId);
//参数详细为空,则赋予
if (
!this.baseInfoForm.goodsParamsDTOList[groupIndex]
.goodsParamsItemDTOList[paramsIndex]
) {
this.baseInfoForm.goodsParamsDTOList[groupIndex].goodsParamsItemDTOList[
paramsIndex
] = {
paramName: "",
paramValue: "",
isIndex: "",
// required: "",
paramId: "",
sort: "",
};
if (!value && value !== 0) {
if (index >= 0) {
list.splice(index, 1);
}
return;
}
this.baseInfoForm.goodsParamsDTOList[groupIndex].goodsParamsItemDTOList[
paramsIndex
] = {
const newItem = {
paramId,
paramName: params.paramName,
paramValue: value,
isIndex: params.isIndex,
// required: params.required,
paramId: params.id,
sort: params.sort,
isIndex: params.isIndex || 0,
required: params.required || 0,
sort: params.sort || 0,
};
if (index >= 0) {
this.$set(list, index, newItem);
} else {
list.push(newItem);
}
},
// 编辑sku图片
editSkuPicture(row) {
@@ -1191,40 +1148,75 @@ export default {
/** 根据当前分类id查询商品应包含的参数 */
GET_GoodsParams() {
this.goodsParams = []
this.goodsParams = [];
this.params_panel = [];
API_GOODS.getCategoryParamsListDataSeller(this.categoryId).then(
(response) => {
if (!response || response.length <= 0) {
return;
}
this.goodsParams = response;
//展开选项卡
this.goodsParams.forEach((item) => {
this.params_panel.push(item.groupName);
});
if (this.baseInfoForm.goodsParamsDTOList) {
// 已选值集合
const paramsArr = [];
this.baseInfoForm.goodsParamsDTOList.forEach((group) => {
group.goodsParamsItemDTOList.forEach((param) => {
param.groupId = group.groupId;
paramsArr.push(param);
});
});
// 循环参数分组
this.goodsParams.forEach((paramsGroup) => {
paramsGroup.params.forEach((param) => {
paramsArr.forEach((arr) => {
if (param.paramName === arr.paramName) {
param.paramValue = arr.paramValue;
}
});
});
});
} else {
if (!Array.isArray(this.baseInfoForm.goodsParamsDTOList)) {
this.baseInfoForm.goodsParamsDTOList = [];
}
const mergedSelected = new Map();
const selectedList = [];
this.baseInfoForm.goodsParamsDTOList.forEach((item) => {
if (!item) return;
if (Array.isArray(item.goodsParamsItemDTOList)) {
selectedList.push(...item.goodsParamsItemDTOList);
} else if (item.paramId || item.paramName) {
selectedList.push(item);
}
});
selectedList.forEach((param) => {
if (!param) return;
const key = param.paramId ? String(param.paramId) : param.paramName;
if (!key) return;
mergedSelected.set(key, param);
});
this.baseInfoForm.goodsParamsDTOList = Array.from(mergedSelected.values()).map((p) => {
return {
paramId: p.paramId ? String(p.paramId) : "",
paramName: p.paramName,
paramValue: p.paramValue,
isIndex: p.isIndex || 0,
required: p.required || 0,
sort: p.sort || 0,
};
});
const findSelectedValue = (param) => {
if (!param) return undefined;
const byId = mergedSelected.get(String(param.id));
if (byId) return byId.paramValue;
const byName = mergedSelected.get(param.paramName);
if (byName) return byName.paramValue;
return undefined;
};
const isGrouped = response[0] && Array.isArray(response[0].params);
const flatParams = isGrouped
? response.reduce((acc, g) => {
if (g && Array.isArray(g.params)) {
acc.push(...g.params);
}
return acc;
}, [])
: response;
this.goodsParams = flatParams
.map((p) => {
const selectedValue = findSelectedValue(p);
return {
...p,
paramValue:
selectedValue !== undefined ? selectedValue : p.paramValue,
};
})
.sort((a, b) => Number(a.sort || 0) - Number(b.sort || 0));
}
);
},
@@ -1935,18 +1927,16 @@ export default {
return;
}
let checkFlag = false;
this.goodsParams.forEach(group => {
group.params.forEach(param => {
if (param.required) {
const check = this.baseInfoForm.goodsParamsDTOList.some(paramsGroup =>
paramsGroup.goodsParamsItemDTOList.some(paramsItem => paramsItem.paramId === param.id)
);
if (!check) {
checkFlag = !check;
}
}
})
})
this.goodsParams.forEach((param) => {
if (!param || !param.required) return;
const check = this.baseInfoForm.goodsParamsDTOList.some((paramsItem) => {
if (String(paramsItem.paramId) !== String(param.id)) return false;
return !!paramsItem.paramValue;
});
if (!check) {
checkFlag = true;
}
});
if (checkFlag) {
this.$Message.error("存在未填写的参数项");
return;
@@ -2078,57 +2068,6 @@ export default {
}
});
},
/** 保存为模板 */
saveToDraft() {
this.baseInfoForm.skuList = this.skuTableData;
if (this.baseInfoForm.goodsGalleryFiles.length > 0) {
this.baseInfoForm.goodsGalleryList =
this.baseInfoForm.goodsGalleryFiles.map((i) => i);
}
this.baseInfoForm.categoryName = [];
this.baseInfoForm.saveType = "TEMPLATE";
if (this.$route.query.draftId) {
this.baseInfoForm.id = this.$route.query.draftId;
this.$Modal.confirm({
title: "当前模板已存在",
content: "当前模板已存在保存为新模板或替换原模板",
okText: "保存新模板",
cancelText: "替换旧模板",
closable: true,
onOk: () => {
delete this.baseInfoForm.id;
this.SAVE_DRAFT_GOODS();
},
onCancel: () => {
this.SAVE_DRAFT_GOODS();
},
});
return;
}
this.$Modal.confirm({
title: "保存模板",
content: "是否确定保存",
okText: "保存",
closable: true,
onOk: () => {
this.SAVE_DRAFT_GOODS();
},
});
},
SAVE_DRAFT_GOODS() {
if (this.baseInfoForm.salesModel === "WHOLESALE") {
this.baseInfoForm.wholesaleList = this.wholesaleData;
}
// 保存模板
API_GOODS.saveDraftGoods(this.baseInfoForm).then((res) => {
if (res.success) {
this.$Message.info("保存成功");
this.$router.push({ name: "template-goods" });
}
});
},
GET_ShipTemplate(type) {
// 获取物流模板
API_Shop.getShipTemplate().then((res) => {