@@ -150,6 +150,8 @@ const PRODUCT_SEEDS = [
const storeMap = new Map < string , ProductExtensionStoreState > ( ) ;
const storeMap = new Map < string , ProductExtensionStoreState > ( ) ;
let idSeed = 10 _000 ;
let idSeed = 10 _000 ;
const ENABLE_PRODUCT_CATEGORY_MOCK =
import . meta . env . VITE_MOCK_PRODUCT_CATEGORY === 'true' ;
function parseUrlParams ( url : string ) {
function parseUrlParams ( url : string ) {
const parsed = new URL ( url , 'http://localhost' ) ;
const parsed = new URL ( url , 'http://localhost' ) ;
@@ -592,287 +594,291 @@ function ensureStoreState(storeId = '') {
return state ;
return state ;
}
}
Mock . mock (
// 分类管理与商品选择器默认走真实 TenantApi, 需手动开启才启用 mock。
/\/product\/category\/manage\/list(?:\?|$)/ ,
if ( ENABLE_PRODUCT_CATEGORY_MOCK ) {
'get' ,
Mock . mock (
( options : MockRequestOptions ) = > {
/\/product\/category\/manage\/list(?:\?|$)/ ,
const params = parseUrlParams ( options . url ) ;
'get' ,
const storeId = normalizeText ( params . storeId ) ;
( options : MockRequestOptions ) = > {
const keyword = normalizeText ( p arams. keyword ) . toLowerCase ( ) ;
const params = parseUrlP arams( options . url ) ;
const status = normalizeText ( params . status ) ;
const storeId = normalizeText ( params . storeId ) ;
const state = ensureStoreState ( storeId ) ;
const keyword = normalizeText ( params . keyword ) . toLowerCase ( ) ;
const status = normalizeText ( params . status ) ;
const state = ensureStoreState ( storeId ) ;
const list = state . categories
const list = state . categories
. toSorted ( ( a , b ) = > a . sort - b . sort )
. toSorted ( ( a , b ) = > a . sort - b . sort )
. filter ( ( item ) = > {
. filter ( ( item ) = > {
if ( status && item . status !== status ) return false ;
if ( status && item . status !== status ) return false ;
if ( ! keyword ) return true ;
if ( ! keyword ) return true ;
return (
return (
item . name . toLowerCase ( ) . includes ( keyword ) ||
item . name . toLowerCase ( ) . includes ( keyword ) ||
item . description . toLowerCase ( ) . includes ( keyword )
item . description . toLowerCase ( ) . includes ( keyword )
) ;
) ;
} )
} )
. map ( ( item ) = > toCategoryManageItem ( state , item ) ) ;
. map ( ( item ) = > toCategoryManageItem ( state , item ) ) ;
return { code : 200 , data : list } ;
return { code : 200 , data : list } ;
} ,
} ,
) ;
) ;
Mock . mock (
Mock . mock (
/\/product\/category\/manage\/save/ ,
/\/product\/category\/manage\/save/ ,
'post' ,
'post' ,
( options : MockRequestOptions ) = > {
( options : MockRequestOptions ) = > {
const body = parseBody ( options ) ;
const body = parseBody ( options ) ;
const storeId = normalizeText ( body . storeId ) ;
const storeId = normalizeText ( body . storeId ) ;
const id = normalizeText ( body . id ) ;
const id = normalizeText ( body . id ) ;
const name = normalizeText ( body . name ) ;
const name = normalizeText ( body . name ) ;
if ( ! storeId || ! name ) {
if ( ! storeId || ! name ) {
return { code : 400 , data : null , message : '参数不完整' } ;
return { code : 400 , data : null , message : '参数不完整' } ;
}
}
const state = ensureStoreState ( storeId ) ;
const state = ensureStoreState ( storeId ) ;
const duplicate = state . categories . find (
const duplicate = state . categories . find (
( item ) = > item . name === name && item . id !== id ,
( item ) = > item . name === name && item . id !== id ,
) ;
) ;
if ( duplicate ) {
if ( duplicate ) {
return { code : 400 , data : null , message : '分类名称已存在' } ;
return { code : 400 , data : null , message : '分类名称已存在' } ;
}
}
const currentSortMax =
const currentSortMax =
state . categories . reduce ( ( max , item ) = > Math . max ( max , item . sort ) , 0 ) + 1 ;
state . categories . reduce ( ( max , item ) = > Math . max ( max , item . sort ) , 0 ) +
const existingIndex = state . categories . findIndex ( ( item ) = > item . id === id ) ;
1 ;
const existingIndex = state . categories . findIndex ( ( item ) = > item . id === id ) ;
const next =
const next =
existingIndex === - 1
existingIndex === - 1
? {
? {
id : createId ( 'cat' , storeId ) ,
id : createId ( 'cat' , storeId ) ,
name ,
name ,
description : normalizeText ( body . description ) ,
description : normalizeText ( body . description ) ,
icon : normalizeText ( body . icon , 'lucide:folder' ) ,
icon : normalizeText ( body . icon , 'lucide:folder' ) ,
sort : normalizeInt ( body . sort , currentSortMax , 1 ) ,
sort : normalizeInt ( body . sort , currentSortMax , 1 ) ,
status : normalizeSwitchStatus ( body . status , 'enabled' ) ,
status : normalizeSwitchStatus ( body . status , 'enabled' ) ,
channels : normalizeChannels ( body . channels , [ 'wm' ] ) ,
channels : normalizeChannels ( body . channels , [ 'wm' ] ) ,
}
}
: {
: {
. . . state . categories [ existingIndex ] ,
. . . state . categories [ existingIndex ] ,
name ,
name ,
description : normalizeText (
description : normalizeText (
body . description ,
body . description ,
state . categories [ existingIndex ] . description ,
state . categories [ existingIndex ] . description ,
) ,
) ,
icon : normalizeText (
icon : normalizeText (
body . icon ,
body . icon ,
state . categories [ existingIndex ] . icon ,
state . categories [ existingIndex ] . icon ,
) ,
) ,
sort : normalizeInt (
sort : normalizeInt (
body . sort ,
body . sort ,
state . categories [ existingIndex ] . sort ,
state . categories [ existingIndex ] . sort ,
1 ,
1 ,
) ,
) ,
status : normalizeSwitchStatus (
status : normalizeSwitchStatus (
body . status ,
body . status ,
state . categories [ existingIndex ] . status ,
state . categories [ existingIndex ] . status ,
) ,
) ,
channels : normalizeChannels (
channels : normalizeChannels (
body . channels ,
body . channels ,
state . categories [ existingIndex ] . channels ,
state . categories [ existingIndex ] . channels ,
) ,
) ,
} ;
} ;
if ( existingIndex === - 1 ) {
if ( existingIndex === - 1 ) {
state . categories . push ( next ) ;
state . categories . push ( next ) ;
} else {
} else {
state . categories . splice ( existingIndex , 1 , next ) ;
state . categories . splice ( existingIndex , 1 , next ) ;
}
}
return { code : 200 , data : toCategoryManageItem ( state , next ) } ;
return { code : 200 , data : toCategoryManageItem ( state , next ) } ;
} ,
} ,
) ;
) ;
Mock . mock (
Mock . mock (
/\/product\/category\/manage\/delete/ ,
/\/product\/category\/manage\/delete/ ,
'post' ,
'post' ,
( options : MockRequestOptions ) = > {
( options : MockRequestOptions ) = > {
const body = parseBody ( options ) ;
const body = parseBody ( options ) ;
const storeId = normalizeText ( body . storeId ) ;
const storeId = normalizeText ( body . storeId ) ;
const categoryId = normalizeText ( body . categoryId ) ;
const categoryId = normalizeText ( body . categoryId ) ;
if ( ! storeId || ! categoryId ) {
if ( ! storeId || ! categoryId ) {
return { code : 400 , data : null , message : '参数不完整' } ;
return { code : 400 , data : null , message : '参数不完整' } ;
}
}
const state = ensureStoreState ( storeId ) ;
const state = ensureStoreState ( storeId ) ;
const hasProducts = state . products . some (
const hasProducts = state . products . some (
( item ) = > item . categoryId === categoryId ,
( item ) = > item . categoryId === categoryId ,
) ;
) ;
if ( hasProducts ) {
if ( hasProducts ) {
return { code : 400 , data : null , message : '分类下仍有商品,不能删除' } ;
return { code : 400 , data : null , message : '分类下仍有商品,不能删除' } ;
}
}
if ( state . categories . length <= 1 ) {
if ( state . categories . length <= 1 ) {
return { code : 400 , data : null , message : '至少保留一个分类' } ;
return { code : 400 , data : null , message : '至少保留一个分类' } ;
}
}
state . categories = state . categories . filter (
state . categories = state . categories . filter (
( item ) = > item . id !== categoryId ,
( item ) = > item . id !== categoryId ,
) ;
) ;
return { code : 200 , data : null } ;
return { code : 200 , data : null } ;
} ,
} ,
) ;
) ;
Mock . mock (
Mock . mock (
/\/product\/category\/manage\/status/ ,
/\/product\/category\/manage\/status/ ,
'post' ,
'post' ,
( options : MockRequestOptions ) = > {
( options : MockRequestOptions ) = > {
const body = parseBody ( options ) ;
const body = parseBody ( options ) ;
const storeId = normalizeText ( body . storeId ) ;
const storeId = normalizeText ( body . storeId ) ;
const categoryId = normalizeText ( body . categoryId ) ;
const categoryId = normalizeText ( body . categoryId ) ;
if ( ! storeId || ! categoryId ) {
if ( ! storeId || ! categoryId ) {
return { code : 400 , data : null , message : '参数不完整' } ;
return { code : 400 , data : null , message : '参数不完整' } ;
}
}
const state = ensureStoreState ( storeId ) ;
const state = ensureStoreState ( storeId ) ;
const target = state . categories . find ( ( item ) = > item . id === categoryId ) ;
const target = state . categories . find ( ( item ) = > item . id === categoryId ) ;
if ( ! target ) return { code : 404 , data : null , message : '分类不存在' } ;
if ( ! target ) return { code : 404 , data : null , message : '分类不存在' } ;
target . status = normalizeSwitchStatus ( body . status , target . status ) ;
target . status = normalizeSwitchStatus ( body . status , target . status ) ;
return { code : 200 , data : toCategoryManageItem ( state , target ) } ;
return { code : 200 , data : toCategoryManageItem ( state , target ) } ;
} ,
} ,
) ;
) ;
Mock . mock (
Mock . mock (
/\/product\/category\/manage\/sort/ ,
/\/product\/category\/manage\/sort/ ,
'post' ,
'post' ,
( options : MockRequestOptions ) = > {
( options : MockRequestOptions ) = > {
const body = parseBody ( options ) ;
const body = parseBody ( options ) ;
const storeId = normalizeText ( body . storeId ) ;
const storeId = normalizeText ( body . storeId ) ;
const items = Array . isArray ( body . items ) ? body . items : [ ] ;
const items = Array . isArray ( body . items ) ? body . items : [ ] ;
if ( ! storeId ) return { code : 400 , data : null , message : '参数不完整' } ;
if ( ! storeId ) return { code : 400 , data : null , message : '参数不完整' } ;
const sortMap = new Map < string , number > ( ) ;
const sortMap = new Map < string , number > ( ) ;
for ( const item of items ) {
for ( const item of items ) {
if ( ! item || typeof item !== 'object' ) continue ;
if ( ! item || typeof item !== 'object' ) continue ;
const current = item as Record < string , unknown > ;
const current = item as Record < string , unknown > ;
const id = normalizeText ( current . categoryId ) ;
const id = normalizeText ( current . categoryId ) ;
const sort = normalizeInt ( current . sort , 0 , 1 ) ;
const sort = normalizeInt ( current . sort , 0 , 1 ) ;
if ( ! id || sort <= 0 ) continue ;
if ( ! id || sort <= 0 ) continue ;
sortMap . set ( id , sort ) ;
sortMap . set ( id , sort ) ;
}
}
const state = ensureStoreState ( storeId ) ;
const state = ensureStoreState ( storeId ) ;
state . categories = state . categories
state . categories = state . categories
. map ( ( item ) = > ( {
. map ( ( item ) = > ( {
. . . item ,
. . . item ,
sort : sortMap.get ( item . id ) ? ? item . sort ,
sort : sortMap.get ( item . id ) ? ? item . sort ,
} ) )
} ) )
. toSorted ( ( a , b ) = > a . sort - b . sort )
. toSorted ( ( a , b ) = > a . sort - b . sort )
. map ( ( item , index ) = > ( { . . . item , sort : index + 1 } ) ) ;
. map ( ( item , index ) = > ( { . . . item , sort : index + 1 } ) ) ;
return {
return {
code : 200 ,
code : 200 ,
data : state.categories.map ( ( item ) = > toCategoryManageItem ( state , item ) ) ,
data : state.categories.map ( ( item ) = > toCategoryManageItem ( state , item ) ) ,
} ;
} ;
} ,
} ,
) ;
) ;
Mock . mock (
Mock . mock (
/\/product\/category\/manage\/products\/bind/ ,
/\/product\/category\/manage\/products\/bind/ ,
'post' ,
'post' ,
( options : MockRequestOptions ) = > {
( options : MockRequestOptions ) = > {
const body = parseBody ( options ) ;
const body = parseBody ( options ) ;
const storeId = normalizeText ( body . storeId ) ;
const storeId = normalizeText ( body . storeId ) ;
const categoryId = normalizeText ( body . categoryId ) ;
const categoryId = normalizeText ( body . categoryId ) ;
const productIds = normalizeIdList ( body . productIds ) ;
const productIds = normalizeIdList ( body . productIds ) ;
if ( ! storeId || ! categoryId || productIds . length === 0 ) {
if ( ! storeId || ! categoryId || productIds . length === 0 ) {
return { code : 400 , data : null , message : '参数不完整' } ;
return { code : 400 , data : null , message : '参数不完整' } ;
}
}
const state = ensureStoreState ( storeId ) ;
const state = ensureStoreState ( storeId ) ;
if ( ! hasCategory ( state , categoryId ) ) {
if ( ! hasCategory ( state , categoryId ) ) {
return { code : 404 , data : null , message : '分类不存在' } ;
return { code : 404 , data : null , message : '分类不存在' } ;
}
}
const idSet = new Set ( productIds ) ;
const idSet = new Set ( productIds ) ;
let successCount = 0 ;
let successCount = 0 ;
for ( const item of state . products ) {
for ( const item of state . products ) {
if ( ! idSet . has ( item . id ) ) continue ;
if ( ! idSet . has ( item . id ) ) continue ;
item . categoryId = categoryId ;
item . categoryId = categoryId ;
successCount += 1 ;
successCount += 1 ;
}
}
return {
return {
code : 200 ,
code : 200 ,
data : {
data : {
totalCount : productIds.length ,
totalCount : productIds.length ,
successCount ,
successCount ,
failedCount : Math.max ( productIds . length - successCount , 0 ) ,
failedCount : Math.max ( productIds . length - successCount , 0 ) ,
} ,
} ,
} ;
} ;
} ,
} ,
) ;
) ;
Mock . mock (
Mock . mock (
/\/product\/category\/manage\/products\/unbind/ ,
/\/product\/category\/manage\/products\/unbind/ ,
'post' ,
'post' ,
( options : MockRequestOptions ) = > {
( options : MockRequestOptions ) = > {
const body = parseBody ( options ) ;
const body = parseBody ( options ) ;
const storeId = normalizeText ( body . storeId ) ;
const storeId = normalizeText ( body . storeId ) ;
const categoryId = normalizeText ( body . categoryId ) ;
const categoryId = normalizeText ( body . categoryId ) ;
const productId = normalizeText ( body . productId ) ;
const productId = normalizeText ( body . productId ) ;
if ( ! storeId || ! categoryId || ! productId ) {
if ( ! storeId || ! categoryId || ! productId ) {
return { code : 400 , data : null , message : '参数不完整' } ;
return { code : 400 , data : null , message : '参数不完整' } ;
}
}
const state = ensureStoreState ( storeId ) ;
const state = ensureStoreState ( storeId ) ;
const fallbackCategory = state . categories . find (
const fallbackCategory = state . categories . find (
( item ) = > item . id !== categoryId ,
( item ) = > item . id !== categoryId ,
) ;
) ;
if ( ! fallbackCategory ) {
if ( ! fallbackCategory ) {
return { code : 400 , data : null , message : '无可用目标分类' } ;
return { code : 400 , data : null , message : '无可用目标分类' } ;
}
}
const target = state . products . find ( ( item ) = > item . id === productId ) ;
const target = state . products . find ( ( item ) = > item . id === productId ) ;
if ( ! target ) return { code : 404 , data : null , message : '商品不存在' } ;
if ( ! target ) return { code : 404 , data : null , message : '商品不存在' } ;
target . categoryId = fallbackCategory . id ;
target . categoryId = fallbackCategory . id ;
return { code : 200 , data : target } ;
return { code : 200 , data : target } ;
} ,
} ,
) ;
) ;
Mock . mock (
Mock . mock (
/\/product\/picker\/list(?:\?|$)/ ,
/\/product\/picker\/list(?:\?|$)/ ,
'get' ,
'get' ,
( options : MockRequestOptions ) = > {
( options : MockRequestOptions ) = > {
const params = parseUrlParams ( options . url ) ;
const params = parseUrlParams ( options . url ) ;
const storeId = normalizeText ( params . storeId ) ;
const storeId = normalizeText ( params . storeId ) ;
const keyword = normalizeText ( params . keyword ) . toLowerCase ( ) ;
const keyword = normalizeText ( params . keyword ) . toLowerCase ( ) ;
const categoryId = normalizeText ( params . categoryId ) ;
const categoryId = normalizeText ( params . categoryId ) ;
const limit = Math . max (
const limit = Math . max (
1 ,
1 ,
Math . min ( 500 , normalizeInt ( params . limit , 200 , 1 ) ) ,
Math . min ( 500 , normalizeInt ( params . limit , 200 , 1 ) ) ,
) ;
) ;
const state = ensureStoreState ( storeId ) ;
const state = ensureStoreState ( storeId ) ;
const list = state . products
const list = state . products
. filter ( ( item ) = > {
. filter ( ( item ) = > {
if ( categoryId && item . categoryId !== categoryId ) return false ;
if ( categoryId && item . categoryId !== categoryId ) return false ;
if ( ! keyword ) return true ;
if ( ! keyword ) return true ;
return (
return (
item . name . toLowerCase ( ) . includes ( keyword ) ||
item . name . toLowerCase ( ) . includes ( keyword ) ||
item . spuCode . toLowerCase ( ) . includes ( keyword )
item . spuCode . toLowerCase ( ) . includes ( keyword )
) ;
) ;
} )
} )
. slice ( 0 , limit )
. slice ( 0 , limit )
. map ( ( item ) = > ( {
. map ( ( item ) = > ( {
id : item.id ,
id : item.id ,
name : item.name ,
name : item.name ,
spuCode : item.spuCode ,
spuCode : item.spuCode ,
categoryId : item.categoryId ,
categoryId : item.categoryId ,
categoryName : getCategoryName ( state , item . categoryId ) ,
categoryName : getCategoryName ( state , item . categoryId ) ,
price : item.price ,
price : item.price ,
status : item.status ,
status : item.status ,
} ) ) ;
} ) ) ;
return { code : 200 , data : list } ;
return { code : 200 , data : list } ;
} ,
} ,
) ;
) ;
}
Mock . mock (
Mock . mock (
/\/product\/spec\/list(?:\?|$)/ ,
/\/product\/spec\/list(?:\?|$)/ ,