From 6145eec825a8c3f5726dc1f516a6f30a56434bda Mon Sep 17 00:00:00 2001 From: MSuMshk <2039814060@qq.com> Date: Fri, 20 Feb 2026 18:46:11 +0800 Subject: [PATCH] chore: force category management to use real tenant APIs --- apps/web-antd/.env.development | 3 + apps/web-antd/src/mock/index.ts | 4 +- apps/web-antd/src/mock/product-extensions.ts | 512 ++++++++++--------- apps/web-antd/src/mock/product.ts | 32 +- 4 files changed, 282 insertions(+), 269 deletions(-) diff --git a/apps/web-antd/.env.development b/apps/web-antd/.env.development index 39fd1b2..91dadc9 100644 --- a/apps/web-antd/.env.development +++ b/apps/web-antd/.env.development @@ -12,6 +12,9 @@ VITE_TENANT_ID=806357433394921472 # 是否开启 Nitro Mock服务,true 为开启,false 为关闭 VITE_NITRO_MOCK=false +# 是否开启商品分类管理 mock(默认 false,分类管理强制走真实 API) +VITE_MOCK_PRODUCT_CATEGORY=false + # 是否打开 devtools,true 为打开,false 为关闭 VITE_DEVTOOLS=false diff --git a/apps/web-antd/src/mock/index.ts b/apps/web-antd/src/mock/index.ts index 33e93c0..4a4966f 100644 --- a/apps/web-antd/src/mock/index.ts +++ b/apps/web-antd/src/mock/index.ts @@ -1,6 +1,6 @@ // Mock 数据入口,仅在开发环境下使用 -// 门店模块已切换真实 TenantApi,此处仅保留其他业务的 mock。 +// 门店模块与商品分类管理已切换真实 TenantApi,此处仅保留其他业务 mock。 import './product'; import './product-extensions'; -console.warn('[Mock] 非门店模块 Mock 数据已启用'); +console.warn('[Mock] 已启用非门店/非分类管理 Mock 数据(分类管理强制走真实 API)'); diff --git a/apps/web-antd/src/mock/product-extensions.ts b/apps/web-antd/src/mock/product-extensions.ts index d9e1b44..c622fdc 100644 --- a/apps/web-antd/src/mock/product-extensions.ts +++ b/apps/web-antd/src/mock/product-extensions.ts @@ -150,6 +150,8 @@ const PRODUCT_SEEDS = [ const storeMap = new Map(); let idSeed = 10_000; +const ENABLE_PRODUCT_CATEGORY_MOCK = + import.meta.env.VITE_MOCK_PRODUCT_CATEGORY === 'true'; function parseUrlParams(url: string) { const parsed = new URL(url, 'http://localhost'); @@ -592,287 +594,291 @@ function ensureStoreState(storeId = '') { return state; } -Mock.mock( - /\/product\/category\/manage\/list(?:\?|$)/, - 'get', - (options: MockRequestOptions) => { - const params = parseUrlParams(options.url); - const storeId = normalizeText(params.storeId); - const keyword = normalizeText(params.keyword).toLowerCase(); - const status = normalizeText(params.status); - const state = ensureStoreState(storeId); +// 分类管理与商品选择器默认走真实 TenantApi,需手动开启才启用 mock。 +if (ENABLE_PRODUCT_CATEGORY_MOCK) { + Mock.mock( + /\/product\/category\/manage\/list(?:\?|$)/, + 'get', + (options: MockRequestOptions) => { + const params = parseUrlParams(options.url); + const storeId = normalizeText(params.storeId); + const keyword = normalizeText(params.keyword).toLowerCase(); + const status = normalizeText(params.status); + const state = ensureStoreState(storeId); - const list = state.categories - .toSorted((a, b) => a.sort - b.sort) - .filter((item) => { - if (status && item.status !== status) return false; - if (!keyword) return true; - return ( - item.name.toLowerCase().includes(keyword) || - item.description.toLowerCase().includes(keyword) - ); - }) - .map((item) => toCategoryManageItem(state, item)); + const list = state.categories + .toSorted((a, b) => a.sort - b.sort) + .filter((item) => { + if (status && item.status !== status) return false; + if (!keyword) return true; + return ( + item.name.toLowerCase().includes(keyword) || + item.description.toLowerCase().includes(keyword) + ); + }) + .map((item) => toCategoryManageItem(state, item)); - return { code: 200, data: list }; - }, -); + return { code: 200, data: list }; + }, + ); -Mock.mock( - /\/product\/category\/manage\/save/, - 'post', - (options: MockRequestOptions) => { - const body = parseBody(options); - const storeId = normalizeText(body.storeId); - const id = normalizeText(body.id); - const name = normalizeText(body.name); - if (!storeId || !name) { - return { code: 400, data: null, message: '参数不完整' }; - } + Mock.mock( + /\/product\/category\/manage\/save/, + 'post', + (options: MockRequestOptions) => { + const body = parseBody(options); + const storeId = normalizeText(body.storeId); + const id = normalizeText(body.id); + const name = normalizeText(body.name); + if (!storeId || !name) { + return { code: 400, data: null, message: '参数不完整' }; + } - const state = ensureStoreState(storeId); - const duplicate = state.categories.find( - (item) => item.name === name && item.id !== id, - ); - if (duplicate) { - return { code: 400, data: null, message: '分类名称已存在' }; - } + const state = ensureStoreState(storeId); + const duplicate = state.categories.find( + (item) => item.name === name && item.id !== id, + ); + if (duplicate) { + return { code: 400, data: null, message: '分类名称已存在' }; + } - const currentSortMax = - state.categories.reduce((max, item) => Math.max(max, item.sort), 0) + 1; - const existingIndex = state.categories.findIndex((item) => item.id === id); + const currentSortMax = + state.categories.reduce((max, item) => Math.max(max, item.sort), 0) + + 1; + const existingIndex = state.categories.findIndex((item) => item.id === id); - const next = - existingIndex === -1 - ? { - id: createId('cat', storeId), - name, - description: normalizeText(body.description), - icon: normalizeText(body.icon, 'lucide:folder'), - sort: normalizeInt(body.sort, currentSortMax, 1), - status: normalizeSwitchStatus(body.status, 'enabled'), - channels: normalizeChannels(body.channels, ['wm']), - } - : { - ...state.categories[existingIndex], - name, - description: normalizeText( - body.description, - state.categories[existingIndex].description, - ), - icon: normalizeText( - body.icon, - state.categories[existingIndex].icon, - ), - sort: normalizeInt( - body.sort, - state.categories[existingIndex].sort, - 1, - ), - status: normalizeSwitchStatus( - body.status, - state.categories[existingIndex].status, - ), - channels: normalizeChannels( - body.channels, - state.categories[existingIndex].channels, - ), - }; + const next = + existingIndex === -1 + ? { + id: createId('cat', storeId), + name, + description: normalizeText(body.description), + icon: normalizeText(body.icon, 'lucide:folder'), + sort: normalizeInt(body.sort, currentSortMax, 1), + status: normalizeSwitchStatus(body.status, 'enabled'), + channels: normalizeChannels(body.channels, ['wm']), + } + : { + ...state.categories[existingIndex], + name, + description: normalizeText( + body.description, + state.categories[existingIndex].description, + ), + icon: normalizeText( + body.icon, + state.categories[existingIndex].icon, + ), + sort: normalizeInt( + body.sort, + state.categories[existingIndex].sort, + 1, + ), + status: normalizeSwitchStatus( + body.status, + state.categories[existingIndex].status, + ), + channels: normalizeChannels( + body.channels, + state.categories[existingIndex].channels, + ), + }; - if (existingIndex === -1) { - state.categories.push(next); - } else { - state.categories.splice(existingIndex, 1, next); - } + if (existingIndex === -1) { + state.categories.push(next); + } else { + state.categories.splice(existingIndex, 1, next); + } - return { code: 200, data: toCategoryManageItem(state, next) }; - }, -); + return { code: 200, data: toCategoryManageItem(state, next) }; + }, + ); -Mock.mock( - /\/product\/category\/manage\/delete/, - 'post', - (options: MockRequestOptions) => { - const body = parseBody(options); - const storeId = normalizeText(body.storeId); - const categoryId = normalizeText(body.categoryId); - if (!storeId || !categoryId) { - return { code: 400, data: null, message: '参数不完整' }; - } + Mock.mock( + /\/product\/category\/manage\/delete/, + 'post', + (options: MockRequestOptions) => { + const body = parseBody(options); + const storeId = normalizeText(body.storeId); + const categoryId = normalizeText(body.categoryId); + if (!storeId || !categoryId) { + return { code: 400, data: null, message: '参数不完整' }; + } - const state = ensureStoreState(storeId); - const hasProducts = state.products.some( - (item) => item.categoryId === categoryId, - ); - if (hasProducts) { - return { code: 400, data: null, message: '分类下仍有商品,不能删除' }; - } + const state = ensureStoreState(storeId); + const hasProducts = state.products.some( + (item) => item.categoryId === categoryId, + ); + if (hasProducts) { + return { code: 400, data: null, message: '分类下仍有商品,不能删除' }; + } - if (state.categories.length <= 1) { - return { code: 400, data: null, message: '至少保留一个分类' }; - } + if (state.categories.length <= 1) { + return { code: 400, data: null, message: '至少保留一个分类' }; + } - state.categories = state.categories.filter( - (item) => item.id !== categoryId, - ); - return { code: 200, data: null }; - }, -); + state.categories = state.categories.filter( + (item) => item.id !== categoryId, + ); + return { code: 200, data: null }; + }, + ); -Mock.mock( - /\/product\/category\/manage\/status/, - 'post', - (options: MockRequestOptions) => { - const body = parseBody(options); - const storeId = normalizeText(body.storeId); - const categoryId = normalizeText(body.categoryId); - if (!storeId || !categoryId) { - return { code: 400, data: null, message: '参数不完整' }; - } - const state = ensureStoreState(storeId); - const target = state.categories.find((item) => item.id === categoryId); - if (!target) return { code: 404, data: null, message: '分类不存在' }; - target.status = normalizeSwitchStatus(body.status, target.status); - return { code: 200, data: toCategoryManageItem(state, target) }; - }, -); + Mock.mock( + /\/product\/category\/manage\/status/, + 'post', + (options: MockRequestOptions) => { + const body = parseBody(options); + const storeId = normalizeText(body.storeId); + const categoryId = normalizeText(body.categoryId); + if (!storeId || !categoryId) { + return { code: 400, data: null, message: '参数不完整' }; + } + const state = ensureStoreState(storeId); + const target = state.categories.find((item) => item.id === categoryId); + if (!target) return { code: 404, data: null, message: '分类不存在' }; + target.status = normalizeSwitchStatus(body.status, target.status); + return { code: 200, data: toCategoryManageItem(state, target) }; + }, + ); -Mock.mock( - /\/product\/category\/manage\/sort/, - 'post', - (options: MockRequestOptions) => { - const body = parseBody(options); - const storeId = normalizeText(body.storeId); - const items = Array.isArray(body.items) ? body.items : []; - if (!storeId) return { code: 400, data: null, message: '参数不完整' }; + Mock.mock( + /\/product\/category\/manage\/sort/, + 'post', + (options: MockRequestOptions) => { + const body = parseBody(options); + const storeId = normalizeText(body.storeId); + const items = Array.isArray(body.items) ? body.items : []; + if (!storeId) return { code: 400, data: null, message: '参数不完整' }; - const sortMap = new Map(); - for (const item of items) { - if (!item || typeof item !== 'object') continue; - const current = item as Record; - const id = normalizeText(current.categoryId); - const sort = normalizeInt(current.sort, 0, 1); - if (!id || sort <= 0) continue; - sortMap.set(id, sort); - } + const sortMap = new Map(); + for (const item of items) { + if (!item || typeof item !== 'object') continue; + const current = item as Record; + const id = normalizeText(current.categoryId); + const sort = normalizeInt(current.sort, 0, 1); + if (!id || sort <= 0) continue; + sortMap.set(id, sort); + } - const state = ensureStoreState(storeId); - state.categories = state.categories - .map((item) => ({ - ...item, - sort: sortMap.get(item.id) ?? item.sort, - })) - .toSorted((a, b) => a.sort - b.sort) - .map((item, index) => ({ ...item, sort: index + 1 })); + const state = ensureStoreState(storeId); + state.categories = state.categories + .map((item) => ({ + ...item, + sort: sortMap.get(item.id) ?? item.sort, + })) + .toSorted((a, b) => a.sort - b.sort) + .map((item, index) => ({ ...item, sort: index + 1 })); - return { - code: 200, - data: state.categories.map((item) => toCategoryManageItem(state, item)), - }; - }, -); + return { + code: 200, + data: state.categories.map((item) => toCategoryManageItem(state, item)), + }; + }, + ); -Mock.mock( - /\/product\/category\/manage\/products\/bind/, - 'post', - (options: MockRequestOptions) => { - const body = parseBody(options); - const storeId = normalizeText(body.storeId); - const categoryId = normalizeText(body.categoryId); - const productIds = normalizeIdList(body.productIds); - if (!storeId || !categoryId || productIds.length === 0) { - return { code: 400, data: null, message: '参数不完整' }; - } + Mock.mock( + /\/product\/category\/manage\/products\/bind/, + 'post', + (options: MockRequestOptions) => { + const body = parseBody(options); + const storeId = normalizeText(body.storeId); + const categoryId = normalizeText(body.categoryId); + const productIds = normalizeIdList(body.productIds); + if (!storeId || !categoryId || productIds.length === 0) { + return { code: 400, data: null, message: '参数不完整' }; + } - const state = ensureStoreState(storeId); - if (!hasCategory(state, categoryId)) { - return { code: 404, data: null, message: '分类不存在' }; - } + const state = ensureStoreState(storeId); + if (!hasCategory(state, categoryId)) { + return { code: 404, data: null, message: '分类不存在' }; + } - const idSet = new Set(productIds); - let successCount = 0; - for (const item of state.products) { - if (!idSet.has(item.id)) continue; - item.categoryId = categoryId; - successCount += 1; - } + const idSet = new Set(productIds); + let successCount = 0; + for (const item of state.products) { + if (!idSet.has(item.id)) continue; + item.categoryId = categoryId; + successCount += 1; + } - return { - code: 200, - data: { - totalCount: productIds.length, - successCount, - failedCount: Math.max(productIds.length - successCount, 0), - }, - }; - }, -); + return { + code: 200, + data: { + totalCount: productIds.length, + successCount, + failedCount: Math.max(productIds.length - successCount, 0), + }, + }; + }, + ); -Mock.mock( - /\/product\/category\/manage\/products\/unbind/, - 'post', - (options: MockRequestOptions) => { - const body = parseBody(options); - const storeId = normalizeText(body.storeId); - const categoryId = normalizeText(body.categoryId); - const productId = normalizeText(body.productId); - if (!storeId || !categoryId || !productId) { - return { code: 400, data: null, message: '参数不完整' }; - } + Mock.mock( + /\/product\/category\/manage\/products\/unbind/, + 'post', + (options: MockRequestOptions) => { + const body = parseBody(options); + const storeId = normalizeText(body.storeId); + const categoryId = normalizeText(body.categoryId); + const productId = normalizeText(body.productId); + if (!storeId || !categoryId || !productId) { + return { code: 400, data: null, message: '参数不完整' }; + } - const state = ensureStoreState(storeId); - const fallbackCategory = state.categories.find( - (item) => item.id !== categoryId, - ); - if (!fallbackCategory) { - return { code: 400, data: null, message: '无可用目标分类' }; - } + const state = ensureStoreState(storeId); + const fallbackCategory = state.categories.find( + (item) => item.id !== categoryId, + ); + if (!fallbackCategory) { + return { code: 400, data: null, message: '无可用目标分类' }; + } - const target = state.products.find((item) => item.id === productId); - if (!target) return { code: 404, data: null, message: '商品不存在' }; - target.categoryId = fallbackCategory.id; + const target = state.products.find((item) => item.id === productId); + if (!target) return { code: 404, data: null, message: '商品不存在' }; + target.categoryId = fallbackCategory.id; - return { code: 200, data: target }; - }, -); + return { code: 200, data: target }; + }, + ); -Mock.mock( - /\/product\/picker\/list(?:\?|$)/, - 'get', - (options: MockRequestOptions) => { - const params = parseUrlParams(options.url); - const storeId = normalizeText(params.storeId); - const keyword = normalizeText(params.keyword).toLowerCase(); - const categoryId = normalizeText(params.categoryId); - const limit = Math.max( - 1, - Math.min(500, normalizeInt(params.limit, 200, 1)), - ); - const state = ensureStoreState(storeId); + Mock.mock( + /\/product\/picker\/list(?:\?|$)/, + 'get', + (options: MockRequestOptions) => { + const params = parseUrlParams(options.url); + const storeId = normalizeText(params.storeId); + const keyword = normalizeText(params.keyword).toLowerCase(); + const categoryId = normalizeText(params.categoryId); + const limit = Math.max( + 1, + Math.min(500, normalizeInt(params.limit, 200, 1)), + ); + const state = ensureStoreState(storeId); - const list = state.products - .filter((item) => { - if (categoryId && item.categoryId !== categoryId) return false; - if (!keyword) return true; - return ( - item.name.toLowerCase().includes(keyword) || - item.spuCode.toLowerCase().includes(keyword) - ); - }) - .slice(0, limit) - .map((item) => ({ - id: item.id, - name: item.name, - spuCode: item.spuCode, - categoryId: item.categoryId, - categoryName: getCategoryName(state, item.categoryId), - price: item.price, - status: item.status, - })); + const list = state.products + .filter((item) => { + if (categoryId && item.categoryId !== categoryId) return false; + if (!keyword) return true; + return ( + item.name.toLowerCase().includes(keyword) || + item.spuCode.toLowerCase().includes(keyword) + ); + }) + .slice(0, limit) + .map((item) => ({ + id: item.id, + name: item.name, + spuCode: item.spuCode, + categoryId: item.categoryId, + categoryName: getCategoryName(state, item.categoryId), + price: item.price, + status: item.status, + })); - return { code: 200, data: list }; - }, -); + return { code: 200, data: list }; + }, + ); +} Mock.mock( /\/product\/spec\/list(?:\?|$)/, diff --git a/apps/web-antd/src/mock/product.ts b/apps/web-antd/src/mock/product.ts index 5b1c9d0..f4ef29b 100644 --- a/apps/web-antd/src/mock/product.ts +++ b/apps/web-antd/src/mock/product.ts @@ -296,6 +296,8 @@ const PRODUCT_SEEDS: ProductSeed[] = [ ]; const productStoreMap = new Map(); +const ENABLE_PRODUCT_CATEGORY_MOCK = + import.meta.env.VITE_MOCK_PRODUCT_CATEGORY === 'true'; /** 解析 URL 查询参数。 */ function parseUrlParams(url: string) { @@ -540,20 +542,22 @@ function resolveStatusByShelfMode( return fallback; } -/** 获取商品分类。 */ -Mock.mock( - /\/product\/category\/list(?:\?|$)/, - 'get', - (options: MockRequestOptions) => { - const params = parseUrlParams(options.url); - const storeId = String(params.storeId || ''); - const state = ensureStoreState(storeId); - return { - code: 200, - data: buildCategoryList(state.products), - }; - }, -); +/** 获取商品分类(默认走真实 TenantApi,需手动开启才启用 mock)。 */ +if (ENABLE_PRODUCT_CATEGORY_MOCK) { + Mock.mock( + /\/product\/category\/list(?:\?|$)/, + 'get', + (options: MockRequestOptions) => { + const params = parseUrlParams(options.url); + const storeId = String(params.storeId || ''); + const state = ensureStoreState(storeId); + return { + code: 200, + data: buildCategoryList(state.products), + }; + }, + ); +} /** 获取商品列表。 */ Mock.mock(/\/product\/list(?:\?|$)/, 'get', (options: MockRequestOptions) => {