@@ -150,6 +150,8 @@ const PRODUCT_SEEDS = [
const storeMap = new Map < string , ProductExtensionStoreState > ( ) ;
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 ( p arams. 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 = parseUrlP arams( 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 < string , number > ( ) ;
for ( const item of items ) {
if ( ! item || typeof item !== 'object' ) continue ;
const current = item as Record < string , unknown > ;
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 < string , number > ( ) ;
for ( const item of items ) {
if ( ! item || typeof item !== 'object' ) continue ;
const current = item as Record < string , unknown > ;
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(?:\?|$)/ ,