material drag and drop cdk
这个组件动态真嘟很丝滑~
drop ( event: any ) {
this . loading = true ;
if ( event. previousContainer !== event. container) {
const clone: IMenu = cloneDeep ( event. previousContainer. data[ event. previousIndex] ) ;
event. container. data. splice ( event. currentIndex, 0 , clone) ;
} else {
if ( event. previousIndex == this . selectedComponentIndex) this . selectedComponentIndex = event. currentIndex;
moveItemInArray (
event. container. data,
event. previousIndex,
event. currentIndex
) ;
}
if ( event. previousContainer. data) {
this . menu = this . menu. filter ( ( f) => ! f. temp) ;
}
}
exited ( event: any ) {
const currentIdx = event. container. data. findIndex (
( f) => f. id === event. item. data. id
) ;
this . menu. splice ( currentIdx + 1 , 0 , {
... event. item. data,
temp: true ,
} ) ;
}
部分drop 逻辑
export class PageDesignComponent {
}
menu : Array< IMenu> = [
{ type : 'carousel' , label : '轮播图' , id : 1 } ,
{ type : 'productGroup' , label : '商品组' , id : 2 , limit : 4 } ,
{ type : 'defaultVoucher' , label : '优惠券' , id : 3 } ,
{ type : 'popularProduct' , label : '热销商品' , id : 4 , limit : 4 } ,
{ type : 'newProduct' , label : '新品推荐' , id : 5 , limit : 4 } ,
] ;
drop ( event : any) {
this . loading = true ;
if ( event. previousContainer !== event. container) {
const clone : IMenu = cloneDeep ( event. previousContainer. data[ event. previousIndex] ) ;
event. container. data. splice ( event. currentIndex, 0 , clone) ;
this . pageConfig[ event. currentIndex] = { data : [ { id : null , url : '' , img : null } , { id : null , url : '' , img : null } ] , type : PageComponentType. CAROUSEL , ... clone } ;
if ( this . pageConfig[ event. currentIndex] . type == PageComponentType. CAROUSEL ) {
} else if ( this . pageConfig[ event. currentIndex] . type == PageComponentType. PRODUCT_GROUP ) {
this . pageConfig[ event. currentIndex] = {
name : clone. label,
type : PageComponentType. PRODUCT_GROUP ,
composition : ArticleCompositionType. COLUMN ,
data : [ { id : null , name : '此处显示商品名称' , url : '' , img : null , price : 99 } ,
{ id : null , name : '此处显示商品名称' , url : '' , img : null , price : 99 } ,
{ id : null , name : '此处显示商品名称' , url : '' , img : null , price : 99 } ,
{ id : null , name : '此处显示商品名称' , url : '' , img : null , price : 99 } ] ,
... clone
} ;
} else if ( this . pageConfig[ event. currentIndex] . type == PageComponentType. POPULAR_PRODUCT || this . pageConfig[ event. currentIndex] . type == PageComponentType. NEW_PRODUCT ) {
this . pageConfig[ event. currentIndex] = {
name : clone. label,
type : this . pageConfig[ event. currentIndex] . type ,
composition : ArticleCompositionType. COLUMN ,
data : [ ] ,
... clone
} ;
( new ProductGroupDataInitInitializer ( this . newProductPage, this . popularProductPage, this . groupProductPage) )
. initComponentData ( this . pageConfig[ event. currentIndex] ) ;
} else if ( this . pageConfig[ event. currentIndex] . type == PageComponentType. VOUCHE_RGROUP ) {
this . pageConfig[ event. currentIndex] = {
name : clone. label,
type : PageComponentType. VOUCHE_RGROUP ,
data : [ {
id : 301073 ,
name : "折扣券 折" ,
typeId : 100 ,
offerAmt : 20 ,
minPurchaseAmt : 100 ,
senarios : 1 ,
totalQty : 32 ,
userTakeTotalQty : 1 ,
validDay : 10
} ,
{
id : 301073 ,
name : "折扣券 折" ,
typeId : 100 ,
offerAmt : 20 ,
minPurchaseAmt : 100 ,
senarios : 1 ,
totalQty : 32 ,
userTakeTotalQty : 1 ,
validDay : 10
} ,
{
id : 301073 ,
name : "折扣券 折" ,
typeId : 100 ,
offerAmt : 20 ,
minPurchaseAmt : 100 ,
senarios : 1 ,
totalQty : 32 ,
userTakeTotalQty : 1 ,
validDay : 10
} ] , ... clone
}
} else if ( this . pageConfig[ event. currentIndex] . type == PageComponentType. DEFAULT_VOUCHER ) {
const config = {
name : clone. label,
type : PageComponentType. DEFAULT_VOUCHER ,
data : [ ... this . vouchers] , ... clone
}
this . pageConfig[ event. currentIndex] = config;
}
} else {
if ( event. previousIndex == this . selectedComponentIndex) this . selectedComponentIndex = event. currentIndex;
moveItemInArray (
event. container. data,
event. previousIndex,
event. currentIndex
) ;
}
this . loading = false ;
this . selectedComponentEvent ( event. currentIndex)
setTimeout ( ( ) => {
this . changeDetectorRef. detectChanges ( ) ;
if ( event. previousContainer. data) {
this . menu = this . menu. filter ( ( f ) => ! f. temp) ;
}
} ) ;
}
exited ( event : any) {
const currentIdx = event. container. data. findIndex (
( f ) => f. id === event. item. data. id
) ;
this . menu. splice ( currentIdx + 1 , 0 , {
... event. item. data,
temp : true ,
} ) ;
}
entered ( ) {
this . menu = this . menu. filter ( ( f ) => ! f. temp) ;
}
html 使用ng-template 实现多种组件样式
< div class = " page-design d-flex flex-row gap-2 p-3" >
< div class = " p-3 border" >
< h3 class = " px-1 border-start border-primary border-4" > 组件库</ h3>
< div class = " menu-list" cdkDropList #menuList = " cdkDropList" [cdkDropListData] = " menu" cdkDropListSortingDisabled
[cdkDropListConnectedTo] = " [tableList]" (cdkDropListDropped) = " drop($event)" (cdkDropListExited) = " exited($event)"
(cdkDropListEntered) = " entered()" >
< div>
< div class = " example-box d-flex jutsfy-content-center align-items-center" *ngFor = " let item of menu" cdkDrag [cdkDragData] = " item" >
< div class = " example-custom-placeholder d-flex flex-row justify-content-center gap-2 " style = " border: dotted 3px #999;" *cdkDragPlaceholder >
< i *ngIf = " item.type== 'carousel'" class = " bi bi-images fs-4 align-self-center" > </ i>
< i *ngIf = " item.type== 'productGroup'" class = " bi bi-grid-fill fs-4 align-self-center" > </ i>
< i *ngIf = " item.type== 'defaultVoucher'" class = " bi bi-ticket-perforated-fill fs-4 align-self-center" > </ i>
< i *ngIf = " item.type== 'popularProduct'" class = " bi bi-bag-heart-fill fs-4 align-self-center" > </ i>
< i *ngIf = " item.type== 'newProduct'" class = " bi bi-star-fill fs-4 align-self-center" > </ i>
< div class = " align-self-center" > {{ item.label }}</ div>
</ div>
< div class = " d-flex flex-column gap-2 " >
< i *ngIf = " item.type== 'carousel'" class = " bi bi-images fs-4 align-self-center" > </ i>
< i *ngIf = " item.type== 'productGroup'" class = " bi bi-grid-fill fs-4 align-self-center" > </ i>
< i *ngIf = " item.type== 'defaultVoucher'" class = " bi bi-ticket-perforated-fill fs-4 align-self-center" > </ i>
< i *ngIf = " item.type== 'popularProduct'" class = " bi bi-bag-heart-fill fs-4 align-self-center" > </ i>
< i *ngIf = " item.type== 'newProduct'" class = " bi bi-star-fill fs-4 align-self-center" > </ i>
< a> {{ item.label }}</ a>
</ div>
</ div>
< div class = " p-2 border-top flex-fill d-flex flex-row jutsfy-content-center " >
< div class = " btn btn-primary " (click) = " save()" > 保存</ div>
</ div>
</ div>
</ div>
</ div>
< div class = " page-center d-flex flex-column mx-3" >
< nz-spin nzTip = " 加载中..." [nzSpinning] = " loading" >
< div class = " page-center d-flex flex-column flex-fill" cdkDropList #tableList = " cdkDropList" [cdkDropListData] = " pageConfig" (cdkDropListDropped) = " drop($event)" >
< app-header> </ app-header>
< div class = " component-list d-flex flex-column " style = " overflow-y : auto; " >
< div class = " page-item-box d-flex flex-column p-2" [class.selectedComponentItem] = " idx == selectedComponentIndex" ng-mouseover = " itemMouseover()" (click) = " selectedComponentEvent(idx)" *ngFor = " let item of pageConfig; let idx = index" cdkDrag >
< div class = " page-item-box example-custom-placeholder d-flex flex-row justify-content-center gap-2" *cdkDragPlaceholder >
< i *ngIf = " item.type== 'carousel'" class = " bi bi-images fs-4 align-self-center" > </ i>
< i *ngIf = " item.type== 'productGroup'" class = " bi bi-grid-fill fs-4 align-self-center" > </ i>
< i *ngIf = " item.type== 'defaultVoucher'" class = " bi bi-ticket-perforated-fill fs-4 align-self-center" > </ i>
< i *ngIf = " item.type== 'popularProduct'" class = " bi bi-bag-heart-fill fs-4 align-self-center" > </ i>
< i *ngIf = " item.type== 'newProduct'" class = " bi bi-star-fill fs-4 align-self-center" > </ i>
< div class = " align-self-center" > {{ item.label }}</ div>
</ div>
< ng-container *ngIf = " item.type == 'carousel'" >
< ng-container *ngTemplateOutlet = " carousel;context:{ $implicit: item?.data }" > </ ng-container>
</ ng-container>
< ng-container *ngIf = " item.type == 'productGroup'" >
< div class = " d-flex flex-row justify-content-between p-1" >
< div class = " d-flex " > {{item?.name }}</ div>
< div class = " d-flex" > 查看更多</ div>
</ div>
< div class = " row gx-2 gy-2" >
< ng-container *ngFor = " let data of item?.data;let i = index" >
< ng-container *ngIf = " i < item.limit" >
< ng-container *ngIf = " item.composition == 'row' " >
< ng-container *ngTemplateOutlet = " productCardRowTpl;context:{ $implicit: data ,index:i}" > </ ng-container>
</ ng-container>
< ng-container *ngIf = " item.composition == 'col' " >
< ng-container *ngTemplateOutlet = " productCardColumnTpl;context:{ $implicit: data ,index:i}" > </ ng-container>
</ ng-container>
</ ng-container>
</ ng-container>
</ div>
</ ng-container>
< ng-container *ngIf = " item.type == 'voucherGroup'" >
< ng-container *ngIf = " item?.data?.length>0" >
< div>
{{item.name}}
</ div>
< ng-container *ngTemplateOutlet = " voucherGroup;context:{ $implicit: item.data}" > </ ng-container>
</ ng-container>
</ ng-container>
< ng-container *ngIf = " item.type == 'defaultVoucher'" >
< ng-container *ngIf = " item?.data?.length>0" >
< div>
{{item.name}}
</ div>
< ng-container *ngTemplateOutlet = " voucherGroup;context:{ $implicit: item.data}" > </ ng-container>
</ ng-container>
</ ng-container>
< ng-container *ngIf = " item.type == 'newProduct'" >
< ng-container *ngTemplateOutlet = " newProduct;context:{ $implicit: item}" > </ ng-container>
</ ng-container>
< ng-container *ngIf = " item.type == 'popularProduct'" >
< ng-container *ngTemplateOutlet = " popularProduct;context:{ $implicit: item}" > </ ng-container>
</ ng-container>
< ng-template #listitem >
< p> {{ idx }}--{{ item.type }}</ p>
< div class = " desc" >
< span> menuID:{{ item.id }}</ span>
</ div>
</ ng-template>
< div *ngIf = " idx == selectedComponentIndex" class = " d-flex flex-row gap-2 component-action-box" >
< div class = " bg-primary px-1" >
< p class = " text-white" (click) = " copyComponent($event)" > 复制</ p>
</ div>
< div class = " bg-secondary px-1" >
< p class = " text-white" (click) = " deleteComponent($event)" > 删除</ p>
</ div>
</ div>
</ div>
</ div>
</ div>
</ nz-spin>
</ div>
< div class = " setting p-2 d-flex flex-column flex-fill border" >
< h3 class = " px-1 border-start border-primary border-4" > {{selectedComponent?.label}}</ h3>
< div *ngIf = " selectedComponentIndex != null" class = " p-2 d-flex flex-column gap-1 p-2 bg-white" >
< ng-container *ngIf = " selectedComponent.type == 'carousel'" >
< ng-container *ngFor = " let item of pageConfig[selectedComponentIndex]?.data;let i = index" >
< ng-container *ngTemplateOutlet = " carouselPictureSetting;context:{ $implicit: item ,index:i}" > </ ng-container>
</ ng-container>
< div class = " p-1 bg-white " > < div (click) = " addCarouselPicture()" class = " border setting-item p-1 d-flex flex-row justify-content-start align-items-center" > < i class = " bi bi-plus-lg fs-5" > </ i> 添加图片</ div> </ div>
</ ng-container>
< ng-container *ngIf = " selectedComponent.type == 'productGroup'" >
< ng-container *ngTemplateOutlet = " prodcutSettingTpl;" > </ ng-container>
< div class = " d-flex flex-row gap-2 flex-wrap p-2" >
< div style = " position : relative; " *ngFor = " let item of pageConfig[selectedComponentIndex]?.data;let i = index" >
< img style = " width : 100px; height : 100px; object-fit : cover; " (error) = " setDefaultPic($event)" src = " {{getImgUrl(item.img)}}" />
< div style = " position : absolute; top : -1px; right : -1px; " > < i (click) = " deleteComponentDataByIndex(i)" class = " text-light-emphasis bi bi-x-circle-fill fs-6" > </ i> </ div>
</ div>
</ div>
< div class = " p-1 bg-white " > < div (click) = " selectProduct()" class = " border setting-item p-1 d-flex flex-row justify-content-start align-items-center" > < i class = " bi bi-plus-lg fs-5" > </ i> 选择商品</ div> </ div>
</ ng-container>
< ng-container *ngIf = " selectedComponent.type == 'voucherGroup'" >
< ng-container *ngTemplateOutlet = " prodcutSettingTpl;" > </ ng-container>
< ng-container *ngTemplateOutlet = " voucherGroupSetting;context:{ $implicit: selectedComponent.data}" > </ ng-container>
< div class = " p-1 bg-white " >
< div (click) = " addVoucher()"
class = " border setting-item p-1 d-flex flex-row justify-content-start align-items-center" > < i
class = " bi bi-plus-lg fs-5" > </ i> 选择优惠券</ div>
</ div>
</ ng-container>
< ng-container *ngIf = " selectedComponent.type == 'defaultVoucher'" >
< ng-container *ngTemplateOutlet = " prodcutSettingTpl;" > </ ng-container>
< ng-container *ngTemplateOutlet = " voucherGroupSetting;context:{ $implicit: selectedComponent.data}" > </ ng-container>
</ ng-container>
< ng-container *ngIf = " selectedComponent.type == 'popularProduct'" >
< ng-container *ngTemplateOutlet = " prodcutSettingTpl;" > </ ng-container>
</ ng-container>
< ng-container *ngIf = " selectedComponent.type == 'newProduct'" >
< ng-container *ngTemplateOutlet = " prodcutSettingTpl;" > </ ng-container>
</ ng-container>
< ng-container *ngIf = " selectedComponent.type == 'groupon'" >
< ng-container *ngTemplateOutlet = " prodcutSettingTpl;" > </ ng-container>
</ ng-container>
</ div>
</ div>
</ div>
初始化数据时为了用了设计模式的策略模式,减少了代码冗余
class ProductGroupDataInitInitializer {
dataPages = { } ;
constructor (
private newProductPage: PageArticleMallEntity,
private popularProductPage: PageArticleMallEntity,
private groupProductPage: PageArticleMallEntity,
) {
this . dataPages = {
newProduct: this . newProductPage?. records,
popularProduct: this . popularProductPage?. records,
groupon: this . groupProductPage?. records
} ;
}
initComponentData ( component: ComoponentInterface) {
const data = this . dataPages[ component. type] ;
if ( data && data. length > 0 ) {
let i = 0 ;
for ( let article of data) {
if ( article != null ) {
component. data[ i++ ] = { articleId: article. articleId, name: article. name, url: '' , img: article?. img, price: article. price }
}
}
} else {
this . setDefaultTemplate ( component) ;
}
}
private setDefaultTemplate ( component: ComoponentInterface) {
component. data = [ { id: null , name: '此处显示商品名称' , url: '' , img: null , price: 99 } ,
{ id: null , name: '此处显示商品名称' , url: '' , img: null , price: 99 } ,
{ id: null , name: '此处显示商品名称' , url: '' , img: null , price: 99 } ,
{ id: null , name: '此处显示商品名称' , url: '' , img: null , price: 99 } ]
}
}
initData ( channel: ChannelDetailEntity) {
const timeZone= Intl. DateTimeFormat ( ) . resolvedOptions ( ) . timeZone ;
forkJoin ( [
this . mallArticleService. getFeaturedProduct ( channel. channelId, "new" , 1 , 8 ) . pipe ( catchError ( ( err) => { console . error ( err) ; return of ( null ) } ) ) ,
this . mallArticleService. getFeaturedProduct ( channel. channelId, "popular" , 1 , 8 ) . pipe ( catchError ( ( err) => { console . error ( err) ; return of ( null ) } ) ) ,
this . voucherService. getVouchersAvailableToTake ( - 1 , format ( new Date ( ) , 'yyyyMMdd' ) , timeZone) . pipe ( catchError ( ( err) => { console . error ( err) ; return of ( null ) } ) )
] ) . subscribe (
( [ newProductPage, popularProductPage, vouchers] : [ PageArticleMallEntity, PageArticleMallEntity, VoucherSchemeEntity[ ] ] ) => {
if ( newProductPage != null )
this . newProductPage = newProductPage;
if ( popularProductPage != null )
this . popularProductPage = popularProductPage;
if ( vouchers != null )
this . vouchers = vouchers;
const id = Number. parseInt ( this . route. snapshot. paramMap. get ( 'id' ) ) ;
if ( id > 0 ) {
this . mallPageService. getPage ( id) . subscribe (
res => {
if ( res != null ) {
this . webPageConfig = res;
this . pageConfig = JSON . parse ( res. content) ;
for ( let component of this . pageConfig) {
if ( component. type == PageComponentType. DEFAULT_VOUCHER ) {
component. data = [ ... this . vouchers] ;
} else if ( component. type == PageComponentType. NEW_PRODUCT ) {
( new ProductGroupDataInitInitializer ( this . newProductPage, this . popularProductPage, this . groupProductPage) )
. initComponentData ( component) ;
}
}
}
}
) ;
}
}
) ;
}