From b37e12b7a75b783eb353e2020633c54ae2f85e0b Mon Sep 17 00:00:00 2001 From: "pikachu1995@126.com" Date: Mon, 22 Dec 2025 14:38:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=95=86=E5=93=81=E5=8F=82=E6=95=B0):=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8F=82=E6=95=B0=E7=AE=A1=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=B9=B6=E4=BC=98=E5=8C=96=E7=9B=B8=E5=85=B3=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增参数编辑页面,支持参数名称、必填、索引等属性配置 - 优化参数列表页面,增加搜索和分页功能 - 重构商品发布页面的参数展示逻辑 - 移除商品模板相关功能 - 优化品牌管理页面的分类关联功能 - 调整商品详情页参数展示样式 --- .../goodsDetail/ShowGoodsDetail.vue | 43 +- manager/src/api/goods.js | 39 +- manager/src/router/router.js | 6 + .../src/views/goods/goods-manage/brand.vue | 263 ++++++--- .../src/views/goods/goods-manage/category.vue | 113 ++-- .../goods/goods-manage/parameter-edit.vue | 442 ++++++++++++++ .../views/goods/goods-manage/parameter.vue | 540 ++++++------------ .../goods-seller/goodsOperationFirst.vue | 88 +-- .../goods/goods-seller/goodsOperationSec.vue | 309 ++++------ 9 files changed, 1065 insertions(+), 778 deletions(-) create mode 100644 manager/src/views/goods/goods-manage/parameter-edit.vue diff --git a/buyer/src/components/goodsDetail/ShowGoodsDetail.vue b/buyer/src/components/goodsDetail/ShowGoodsDetail.vue index c4a1a6d3..1a172247 100644 --- a/buyer/src/components/goodsDetail/ShowGoodsDetail.vue +++ b/buyer/src/components/goodsDetail/ShowGoodsDetail.vue @@ -87,14 +87,12 @@ - @@ -44,22 +79,35 @@ import { updateBrand, disableBrand, delBrand, + getCategoryTree, + getBrandCategoryListData, + saveBrandCategory, } from "@/api/goods"; -import uploadPicInput from "@/components/lili/upload-pic-input"; +import ossManage from "@/views/sys/oss-manage/ossManage"; import {regular} from "@/utils"; export default { name: "brand", components: { - uploadPicInput + ossManage }, data() { return { + defaultPic: require("@/assets/default.png"), loading: true, // 表单加载状态 modalType: 0, // 添加或编辑标识 modalVisible: false, // 添加或编辑显示 modalTitle: "", // 添加或编辑标题 + picModelFlag: false, // 图片选择器 + categoryModalVisible: false, + categoryModalTitle: "关联分类", + categoryTreeLoading: false, + categoryTreeData: [], + categoryTreeKey: 0, + categorySubmitLoading: false, + currentBrandId: "", + selectedCategoryIds: [], searchForm: { // 搜索框初始化对象 pageNumber: 1, // 当前页数 @@ -118,11 +166,24 @@ export default { key: "deleteFlag", align: "left", render: (h, params) => { - if (params.row.deleteFlag == 0) { - return h("Tag", {props: {color: "green",},}, "启用"); - } else if (params.row.deleteFlag == 1) { - return h("Tag", {props: {color: "volcano",},}, "禁用"); - } + return h( + "i-switch", + { + props: { + value: params.row.deleteFlag == 0, + size: "large", + }, + on: { + "on-change": (checked) => { + this.handleStatusSwitchChange(params.row, checked); + }, + }, + }, + [ + h("span", { slot: "open" }, "启用"), + h("span", { slot: "close" }, "禁用"), + ] + ); }, filters: [ { @@ -146,48 +207,10 @@ export default { { title: "操作", key: "action", - width: 180, + width: 210, align: "center", fixed: "right", render: (h, params) => { - let enableOrDisable = ""; - if (params.row.deleteFlag == 0) { - enableOrDisable = h( - "a", - { - style: { - color: "#2d8cf0", - cursor: "pointer", - textDecoration: "none", - marginRight: "5px", - }, - on: { - click: () => { - this.disable(params.row); - }, - }, - }, - "禁用" - ); - } else { - enableOrDisable = h( - "a", - { - style: { - color: "#2d8cf0", - cursor: "pointer", - textDecoration: "none", - marginRight: "5px", - }, - on: { - click: () => { - this.enable(params.row); - }, - }, - }, - "启用" - ); - } return h("div", [ h( "a", @@ -196,7 +219,6 @@ export default { color: "#2d8cf0", cursor: "pointer", textDecoration: "none", - marginRight: "5px", }, on: { click: () => { @@ -211,7 +233,22 @@ export default { { style: { margin: "0 8px", color: "#dcdee2" } }, "|" ), - enableOrDisable, + h( + "a", + { + style: { + color: "#2d8cf0", + cursor: "pointer", + textDecoration: "none", + }, + on: { + click: () => { + this.openCategoryModal(params.row); + }, + }, + }, + "关联分类" + ), h( "span", { style: { margin: "0 8px", color: "#dcdee2" } }, @@ -242,6 +279,100 @@ export default { }; }, methods: { + openLogoPicker() { + this.$refs.ossManage.selectImage = true; + this.picModelFlag = true; + }, + callbackSelected(val) { + this.picModelFlag = false; + this.form.logo = val.url; + }, + buildCategoryTreeNodes(list, selectedSet) { + if (!Array.isArray(list) || list.length === 0) return []; + return list.map((item) => { + const children = this.buildCategoryTreeNodes(item.children || [], selectedSet); + return { + id: item.id, + title: item.name, + expand: true, + checked: selectedSet.has(item.id), + children, + }; + }); + }, + async openCategoryModal(row) { + this.currentBrandId = row.id; + this.categoryModalTitle = "关联分类 - " + (row.name || ""); + this.categoryModalVisible = true; + this.categoryTreeLoading = true; + this.categoryTreeKey += 1; + this.categoryTreeData = []; + this.selectedCategoryIds = []; + try { + const [treeRes, bindRes] = await Promise.all([ + getCategoryTree(), + getBrandCategoryListData(row.id), + ]); + const selectedIds = Array.isArray(bindRes?.result) + ? bindRes.result + .map((x) => (typeof x === "string" ? x : x && x.id)) + .filter(Boolean) + : []; + this.selectedCategoryIds = selectedIds; + const selectedSet = new Set(selectedIds); + this.categoryTreeData = treeRes && treeRes.success + ? this.buildCategoryTreeNodes(treeRes.result || [], selectedSet) + : []; + } finally { + this.categoryTreeLoading = false; + } + }, + onCategoryTreeCheckChange(checkedNodes) { + if (!Array.isArray(checkedNodes)) { + this.selectedCategoryIds = []; + return; + } + this.selectedCategoryIds = checkedNodes + .map((node) => node && node.id) + .filter(Boolean); + }, + submitBrandCategory() { + if (!this.currentBrandId) return; + this.categorySubmitLoading = true; + saveBrandCategory( + this.currentBrandId, + (this.selectedCategoryIds || []).map((id) => String(id)) + ).then((res) => { + this.categorySubmitLoading = false; + if (res && res.success) { + this.$Message.success("操作成功"); + this.categoryModalVisible = false; + return; + } + }).catch(() => { + this.categorySubmitLoading = false; + }); + }, + handleStatusSwitchChange(row, checked) { + const disable = !checked; + this.$Modal.confirm({ + title: disable ? "确认禁用" : "确认启用", + content: "您确认要" + (disable ? "禁用" : "启用") + "品牌 " + row.name + " ?", + loading: true, + onOk: () => { + disableBrand(row.id, { disable }).then((res) => { + this.$Modal.remove(); + if (res.success) { + this.$Message.success("操作成功"); + } + this.getDataList(); + }); + }, + onCancel: () => { + this.$nextTick(() => this.$forceUpdate()); + }, + }); + }, // 删除品牌 async delBrand(id) { let res = await delBrand(id); @@ -337,40 +468,6 @@ export default { this.form = data; this.modalVisible = true; }, - // 启用品牌 - enable(v) { - this.$Modal.confirm({ - title: "确认启用", - content: "您确认要启用品牌 " + v.name + " ?", - loading: true, - onOk: () => { - disableBrand(v.id, {disable: false}).then((res) => { - this.$Modal.remove(); - if (res.success) { - this.$Message.success("操作成功"); - this.getDataList(); - } - }); - }, - }); - }, - // 禁用 - disable(v) { - this.$Modal.confirm({ - title: "确认禁用", - content: "您确认要禁用品牌 " + v.name + " ?", - loading: true, - onOk: () => { - disableBrand(v.id, {disable: true}).then((res) => { - this.$Modal.remove(); - if (res.success) { - this.$Message.success("操作成功"); - this.getDataList(); - } - }); - }, - }); - }, }, mounted() { this.init(); diff --git a/manager/src/views/goods/goods-manage/category.vue b/manager/src/views/goods/goods-manage/category.vue index f8e94911..41421ee9 100644 --- a/manager/src/views/goods/goods-manage/category.vue +++ b/manager/src/views/goods/goods-manage/category.vue @@ -14,32 +14,14 @@ :columns="columns" > @@ -249,6 +236,52 @@ export default { }; }, methods: { + normalizeCategoryTree(list) { + if (!Array.isArray(list) || list.length === 0) return; + list.forEach((item) => { + if (!item || typeof item !== "object") return; + if (item.deleteFlag === 0) item.deleteFlag = false; + else if (item.deleteFlag === 1) item.deleteFlag = true; + else item.deleteFlag = !!item.deleteFlag; + if (Array.isArray(item.children) && item.children.length) { + this.normalizeCategoryTree(item.children); + } + }); + }, + onStatusSwitchChange(row, nextDeleteFlag) { + const previousDeleteFlag = !nextDeleteFlag; + const isClosing = nextDeleteFlag === true; + this.$Modal.confirm({ + title: isClosing ? "确认关闭" : "确认开启", + content: + "您是否要" + + (isClosing ? "关闭" : "开启") + + "当前分类 " + + row.name + + " 及其子分类?", + loading: true, + okText: "是", + cancelText: "否", + onOk: () => { + this.$set(row, "_statusLoading", true); + disableCategory(row.id, { enableOperations: isClosing ? true : 0 }).then( + (res) => { + this.$Modal.remove(); + this.$set(row, "_statusLoading", false); + if (res && res.success) { + this.$Message.success("操作成功"); + this.getAllList(0); + return; + } + row.deleteFlag = previousDeleteFlag; + } + ); + }, + onCancel: () => { + row.deleteFlag = previousDeleteFlag; + }, + }); + }, // 初始化数据 init() { this.getAllList(0); @@ -411,6 +444,7 @@ export default { child._loading = false; child.children = []; }); + this.normalizeCategoryTree(val.children); // 模拟加载 setTimeout(() => { callback(val.children); @@ -419,6 +453,7 @@ export default { }); } else { this.deepCategoryChildren(item.id, this.categoryList); + this.normalizeCategoryTree(this.checkedCategoryChildren); setTimeout(() => { callback(this.checkedCategoryChildren); }, 100); @@ -446,6 +481,7 @@ export default { this.loading = false; if (res.success) { localStorage.setItem("category", JSON.stringify(res.result)); + this.normalizeCategoryTree(res.result); this.categoryList = JSON.parse(JSON.stringify(res.result)); this.tableData = res.result.map((item) => { if(this.recordLevel[0] && item.id === this.recordLevel[0]) { @@ -532,9 +568,18 @@ export default { cursor: pointer; text-decoration: none; } -.ops-sep { - display: inline-block; - margin: 0 8px; +.ops { + display: flex; + flex-wrap: wrap; +} +.ops-link + .ops-link { + margin-left: 16px; + position: relative; +} +.ops-link + .ops-link::before { + content: "|"; + position: absolute; + left: -10px; color: #dcdee2; } diff --git a/manager/src/views/goods/goods-manage/parameter-edit.vue b/manager/src/views/goods/goods-manage/parameter-edit.vue new file mode 100644 index 00000000..70bb57e6 --- /dev/null +++ b/manager/src/views/goods/goods-manage/parameter-edit.vue @@ -0,0 +1,442 @@ + + + + + diff --git a/manager/src/views/goods/goods-manage/parameter.vue b/manager/src/views/goods/goods-manage/parameter.vue index 3a332d4c..83bb602f 100644 --- a/manager/src/views/goods/goods-manage/parameter.vue +++ b/manager/src/views/goods/goods-manage/parameter.vue @@ -1,385 +1,202 @@ - + diff --git a/seller/src/views/goods/goods-seller/goodsOperationFirst.vue b/seller/src/views/goods/goods-seller/goodsOperationFirst.vue index 2151671c..98e08db9 100644 --- a/seller/src/views/goods/goods-seller/goodsOperationFirst.vue +++ b/seller/src/views/goods/goods-seller/goodsOperationFirst.vue @@ -2,7 +2,7 @@
-
+
-
-

返回

-
- -
- -
-

{{ item.goodsName }}

-

{{ item.sellingPoint || "" }}

-
-
-
-
-
@@ -79,9 +60,6 @@ > {{ category[1].name }} > {{ category[2].name }}

-
-

商品交易信息

-
- + + + + - - - - 推荐 - - - 不推荐 - - - -
-
- - - {{ paramsGroup.groupName }} -

- - - -

-
-
@@ -442,7 +413,6 @@ - @@ -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) => {