From 6a17ffebe9bd85f67e2e2948280c308d380a3cad Mon Sep 17 00:00:00 2001 From: "pikachu1995@126.com" Date: Wed, 6 May 2026 17:15:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=BE=AE=E4=BF=A1=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E5=8F=B7):=20=E6=96=B0=E5=A2=9E=E7=B1=BB=E7=9B=AE=E5=85=B3?= =?UTF-8?q?=E8=81=94=E4=B8=8E=E5=95=86=E5=93=81=E5=88=97=E8=A1=A8=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在类目管理页面增加"关联分类"功能,支持将微信小店类目与平台类目进行映射 - 新增删除类目申请功能,允许删除审核中的申请 - 商品管理页面增加Tab切换,分别展示微信小店商品和可关联平台商品 - 类目申请页面优化资质信息显示逻辑,仅需资质的类目才显示JSON输入框 - 调整API导入顺序并新增相关接口:删除类目申请、更新类目映射、查询已关联类目、查询可关联商品 --- manager/src/api/index.js | 22 +- .../promotions/wxchannels/category-apply.vue | 36 +- .../views/promotions/wxchannels/category.vue | 315 +++++++++++++++--- .../src/views/promotions/wxchannels/goods.vue | 250 ++++++++++---- 4 files changed, 512 insertions(+), 111 deletions(-) diff --git a/manager/src/api/index.js b/manager/src/api/index.js index 9e4d2492..09210bfa 100644 --- a/manager/src/api/index.js +++ b/manager/src/api/index.js @@ -3,10 +3,10 @@ import { getRequest, postRequest, putRequest, + putRequestWithNoForm, deleteRequest, importRequest, getRequestWithNoToken, - putRequestWithNoForm, postRequestWithNoTokenData, postRequestWithNoForm, managerUrl @@ -388,6 +388,21 @@ export const applyWxChannelsCategory = (params) => { return postRequestWithNoForm(`/wxchannels/category/apply`, params); }; +// 删除微信小店类目申请 +export const deleteWxChannelsCategoryApply = (id) => { + return deleteRequest(`/wxchannels/category/${id}`); +}; + +// 编辑微信小店类目映射 +export const updateWxChannelsCategoryMapping = (id, params) => { + return putRequestWithNoForm(`/wxchannels/category/${id}`, params); +}; + +// 查询已关联平台类目 +export const getWxChannelsCategoryMapped = (params) => { + return getRequest(`/wxchannels/category/mapped`, params); +}; + // 微信小店类目审核单详情 export const getWxChannelsCategoryFlowDetail = (params) => { return getRequest(`/wxchannels/category/flow/detail`, params); @@ -403,6 +418,11 @@ export const getWxChannelsGoodsPage = (params) => { return getRequest(`/wxchannels/goods`, params); }; +// 微信视频号可关联平台商品分页 +export const getWxChannelsLinkableGoodsPage = (params) => { + return getRequest(`/wxchannels/goods/linkable`, params); +}; + // 微信视频号订单分页 export const getWxChannelsOrderPage = (params) => { return getRequest(`/wxchannels/order`, params); diff --git a/manager/src/views/promotions/wxchannels/category-apply.vue b/manager/src/views/promotions/wxchannels/category-apply.vue index 893ec02f..ca448378 100644 --- a/manager/src/views/promotions/wxchannels/category-apply.vue +++ b/manager/src/views/promotions/wxchannels/category-apply.vue @@ -42,7 +42,7 @@ - + + + 当前类目无需提交资质信息 + @@ -64,6 +67,13 @@ import { getWxChannelsCategoryDetail, applyWxChannelsCategory } from "@/api/inde export default { name: "wxchannels-category-apply", + computed: { + needQualification() { + const list = this.detailData && this.detailData.productQuaList; + if (!Array.isArray(list) || list.length === 0) return false; + return list.some((item) => item && (item.mandatory === true || item.needToApply === true)); + }, + }, data() { return { loading: false, @@ -125,15 +135,17 @@ export default { }, async submitApply() { let licenseGroupList = []; - try { - licenseGroupList = JSON.parse(this.applyForm.licenseGroupJson || "[]"); - } catch (e) { - this.$Message.error("证照组JSON格式错误"); - return; - } - if (!Array.isArray(licenseGroupList) || licenseGroupList.length === 0) { - this.$Message.warning("licenseGroupList不能为空"); - return; + if (this.needQualification) { + try { + licenseGroupList = JSON.parse(this.applyForm.licenseGroupJson || "[]"); + } catch (e) { + this.$Message.error("证照组JSON格式错误"); + return; + } + if (!Array.isArray(licenseGroupList) || licenseGroupList.length === 0) { + this.$Message.warning("licenseGroupList不能为空"); + return; + } } const brandIds = (this.applyForm.brandIdsText || "") .split(",") @@ -159,8 +171,10 @@ export default { const payload = { // 按一级->二级->三级顺序传递完整分类链 catsV2, - licenseGroupList, }; + if (this.needQualification) { + payload.licenseGroupList = licenseGroupList; + } if (brandIds.length > 0) { payload.brandIds = brandIds; } diff --git a/manager/src/views/promotions/wxchannels/category.vue b/manager/src/views/promotions/wxchannels/category.vue index ab894005..ff4626f8 100644 --- a/manager/src/views/promotions/wxchannels/category.vue +++ b/manager/src/views/promotions/wxchannels/category.vue @@ -1,25 +1,6 @@ @@ -107,19 +130,48 @@ import { getWxChannelsCategoryFlowDetail, getWxChannelsCategoryDetail, initWxChannelsCategory, + deleteWxChannelsCategoryApply, + updateWxChannelsCategoryMapping, + getWxChannelsCategoryMapped, } from "@/api/index"; +import { getCategoryTree } from "@/api/goods"; + +const toStringArray = (arr) => { + if (!Array.isArray(arr)) return []; + return arr.map((x) => String(x)).filter((x) => x.length > 0); +}; + +const buildCategoryIdNameMap = (list, map) => { + if (!Array.isArray(list) || list.length === 0) return; + list.forEach((item) => { + if (!item) return; + const id = item.id !== undefined && item.id !== null ? String(item.id) : ""; + if (id) map[id] = item.name; + buildCategoryIdNameMap(item.children || [], map); + }); +}; export default { name: "wxchannels-category", data() { return { - innerTab: "third", + innerTab: "apply", thirdLoading: false, initLoading: false, applyLoading: false, thirdData: [], applyData: [], applyTotal: 0, + bindModalVisible: false, + bindModalTitle: "关联分类", + bindSubmitting: false, + bindTargetRow: null, + selectedCategoryIds: [], + categoryTreeLoading: false, + categoryTreeData: [], + categoryTreeSource: [], + categoryIdNameMap: {}, + categoryTreeKey: 0, // 保存用户当前选择的完整类目链(一级 -> 二级 -> 三级) fullCategoryPath: [], detailModalVisible: false, @@ -127,7 +179,7 @@ export default { detailData: null, detailRawJson: "", applyQuery: { - status: "", + status: "APPROVED", pageNumber: 1, pageSize: 20, }, @@ -169,7 +221,8 @@ export default { align: "center", render: (h, params) => { const row = params.row || {}; - if (Number(row.level) !== 3) { + // 仅最底层(无下级)展示申请按钮 + if (row.hasChildren === true) { return h("span", "-"); } return h( @@ -184,9 +237,33 @@ export default { }, ], applyColumns: [ - { title: "审核单ID", key: "auditId", minWidth: 120, tooltip: true }, - { title: "类目ID", key: "catId", minWidth: 120, tooltip: true }, - { title: "类目名称", key: "catName", minWidth: 180, tooltip: true }, + { + title: "类目ID", + key: "wxCategoryId", + minWidth: 120, + tooltip: true, + render: (h, params) => { + const row = params.row || {}; + return h("span", row.wxCategoryId || row.catId || "-"); + }, + }, + { + title: "类目名称", + key: "wxCategoryName", + minWidth: 180, + tooltip: true, + render: (h, params) => { + const row = params.row || {}; + return h("span", row.wxCategoryName || row.catName || "-"); + }, + }, + { + title: "已关联平台分类", + key: "mappedPlatformCategoryNames", + minWidth: 220, + tooltip: true, + render: (h, params) => h("span", this.formatMappedPlatformCategoryNames(params.row)), + }, { title: "状态", key: "status", @@ -206,31 +283,31 @@ export default { { title: "操作", key: "action", - width: 180, + width: 120, render: (h, params) => { const row = params.row || {}; - const actions = []; - actions.push( - h( + if (row.status === "APPROVED") { + return h( "Button", { props: { type: "text", size: "small" }, - on: { click: () => this.showCategoryDetail(row.catId) }, + on: { click: () => this.openBindCategoryModal(row) }, }, - "类目详情" - ) - ); - actions.push( - h( + "关联分类" + ); + } + if (row.status === "PENDING") { + return h( "Button", { props: { type: "text", size: "small" }, - on: { click: () => this.showFlowDetail(row.auditId) }, + style: { color: "#ed4014" }, + on: { click: () => this.handleDeleteApply(row) }, }, - "审核单详情" - ) - ); - return h("div", actions); + "删除" + ); + } + return h("span", "-"); }, }, ], @@ -257,11 +334,17 @@ export default { innerTab(val) { if (val === "apply" && this.applyData.length === 0) { this.loadApplyPage(); + } else if (val === "third" && this.thirdData.length === 0) { + this.loadThirdCategories(); } }, }, mounted() { - this.loadThirdCategories(); + if (this.innerTab === "apply") { + this.loadApplyPage(); + } else { + this.loadThirdCategories(); + } }, methods: { boolText(val) { @@ -436,6 +519,156 @@ export default { this.detailModalVisible = true; } }, + 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(String(item.id)), + children, + }; + }); + }, + parsePlatformCategoryIds(row) { + if (!row) return []; + const raw = + row.platformCategoryIds ?? + row.platform_category_ids ?? + row.platformCategoryId ?? + row.platform_category_id; + if (Array.isArray(raw)) return toStringArray(raw); + if (typeof raw === "string") { + const text = raw.trim(); + if (!text) return []; + try { + const parsed = JSON.parse(text); + if (Array.isArray(parsed)) return toStringArray(parsed); + } catch (e) { + // ignore + } + return text + .split(",") + .map((x) => x.trim()) + .filter((x) => x.length > 0); + } + if (raw !== undefined && raw !== null && String(raw)) return [String(raw)]; + return []; + }, + formatMappedPlatformCategoryNames(row) { + if (!row) return "-"; + const raw = row.mappedPlatformCategoryNames ?? row.platformCategoryNames ?? row.platform_category_names; + if (Array.isArray(raw)) { + const names = raw.map((x) => String(x || "").trim()).filter((x) => x.length > 0); + return names.length > 0 ? names.join(",") : "-"; + } + if (typeof raw === "string") { + const text = raw.trim(); + if (!text) return "-"; + try { + const parsed = JSON.parse(text); + if (Array.isArray(parsed)) { + const names = parsed.map((x) => String(x || "").trim()).filter((x) => x.length > 0); + return names.length > 0 ? names.join(",") : "-"; + } + } catch (e) { + // ignore + } + return text; + } + return "-"; + }, + async fetchMappedCategoryIds(id, row) { + if (!id) return this.parsePlatformCategoryIds(row); + try { + const res = await getWxChannelsCategoryMapped({ id }); + if (res && res.success) { + const list = Array.isArray(res.result) ? res.result : []; + return toStringArray( + list + .map((item) => item && (item.platformCategoryId || item.platform_category_id)) + .filter((x) => x !== undefined && x !== null && String(x)), + ); + } + } catch (e) { + // ignore and fallback + } + return this.parsePlatformCategoryIds(row); + }, + async openBindCategoryModal(row) { + this.bindTargetRow = row || null; + this.bindModalVisible = true; + this.categoryTreeLoading = true; + this.categoryTreeKey += 1; + try { + const id = row && row.id; + this.selectedCategoryIds = await this.fetchMappedCategoryIds(id, row); + if (!Array.isArray(this.categoryTreeSource) || this.categoryTreeSource.length === 0) { + const res = await getCategoryTree(); + this.categoryTreeSource = res && res.success ? res.result || [] : []; + const map = {}; + buildCategoryIdNameMap(this.categoryTreeSource, map); + this.categoryIdNameMap = map; + } + const selectedSet = new Set(toStringArray(this.selectedCategoryIds)); + this.categoryTreeData = this.buildCategoryTreeNodes(this.categoryTreeSource || [], selectedSet); + } finally { + this.categoryTreeLoading = false; + } + }, + onBindCategoryCheckChange(checkedNodes) { + if (!Array.isArray(checkedNodes)) { + this.selectedCategoryIds = []; + return; + } + this.selectedCategoryIds = toStringArray(checkedNodes.map((node) => node && node.id).filter(Boolean)); + }, + async submitBindCategory() { + const row = this.bindTargetRow || {}; + const id = row.id; + if (!id) { + this.$Message.warning("缺少申请ID"); + return; + } + const platformCategoryIds = toStringArray(this.selectedCategoryIds); + const platformCategoryNames = platformCategoryIds + .map((x) => this.categoryIdNameMap[x] || "") + .filter((x) => x.length > 0); + this.bindSubmitting = true; + try { + const res = await updateWxChannelsCategoryMapping(id, { + platformCategoryIds, + platformCategoryNames, + }); + if (res && res.success) { + this.$Message.success("关联分类成功"); + this.bindModalVisible = false; + this.loadApplyPage(); + } + } finally { + this.bindSubmitting = false; + } + }, + handleDeleteApply(row) { + const id = row && row.id; + if (!id) { + this.$Message.warning("缺少申请ID"); + return; + } + this.$Modal.confirm({ + title: "确认删除", + content: "确认删除该类目申请吗?", + onOk: async () => { + const res = await deleteWxChannelsCategoryApply(id); + if (res && res.success) { + this.$Message.success("删除成功"); + this.loadApplyPage(); + } + }, + }); + }, }, }; diff --git a/manager/src/views/promotions/wxchannels/goods.vue b/manager/src/views/promotions/wxchannels/goods.vue index 2e8384fa..b6bb8303 100644 --- a/manager/src/views/promotions/wxchannels/goods.vue +++ b/manager/src/views/promotions/wxchannels/goods.vue @@ -1,61 +1,114 @@