营销:适配商城装修组件【商品栏】
							parent
							
								
									ebb19cfe8c
								
							
						
					
					
						commit
						3198688eb5
					
				|  | @ -0,0 +1,64 @@ | |||
| import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util' | ||||
| 
 | ||||
| /** 商品卡片属性 */ | ||||
| export interface ProductListProperty { | ||||
|   // 布局类型:双列 | 三列 | 水平滑动
 | ||||
|   layoutType: 'twoCol' | 'threeCol' | 'horizSwiper' | ||||
|   // 商品字段
 | ||||
|   fields: { | ||||
|     // 商品名称
 | ||||
|     name: ProductListFieldProperty | ||||
|     // 商品价格
 | ||||
|     price: ProductListFieldProperty | ||||
|   } | ||||
|   // 角标
 | ||||
|   badge: { | ||||
|     // 是否显示
 | ||||
|     show: boolean | ||||
|     // 角标图片
 | ||||
|     imgUrl: string | ||||
|   } | ||||
|   // 上圆角
 | ||||
|   borderRadiusTop: number | ||||
|   // 下圆角
 | ||||
|   borderRadiusBottom: number | ||||
|   // 间距
 | ||||
|   space: number | ||||
|   // 商品编号列表
 | ||||
|   spuIds: number[] | ||||
|   // 组件样式
 | ||||
|   style: ComponentStyle | ||||
| } | ||||
| // 商品字段
 | ||||
| export interface ProductListFieldProperty { | ||||
|   // 是否显示
 | ||||
|   show: boolean | ||||
|   // 颜色
 | ||||
|   color: string | ||||
| } | ||||
| 
 | ||||
| // 定义组件
 | ||||
| export const component = { | ||||
|   id: 'ProductList', | ||||
|   name: '商品栏', | ||||
|   icon: 'system-uicons:carousel', | ||||
|   property: { | ||||
|     layoutType: 'twoCol', | ||||
|     fields: { | ||||
|       name: { show: true, color: '#000' }, | ||||
|       price: { show: true, color: '#ff3000' } | ||||
|     }, | ||||
|     badge: { show: false, imgUrl: '' }, | ||||
|     borderRadiusTop: 8, | ||||
|     borderRadiusBottom: 8, | ||||
|     space: 8, | ||||
|     spuIds: [], | ||||
|     style: { | ||||
|       bgType: 'color', | ||||
|       bgColor: '', | ||||
|       marginLeft: 8, | ||||
|       marginRight: 8, | ||||
|       marginBottom: 8 | ||||
|     } as ComponentStyle | ||||
|   } | ||||
| } as DiyComponent<ProductListProperty> | ||||
|  | @ -0,0 +1,128 @@ | |||
| <template> | ||||
|   <el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef"> | ||||
|    <!-- 商品网格 --> | ||||
|     <div | ||||
|       class="grid overflow-x-auto" | ||||
|       :style="{ | ||||
|         gridGap: `${property.space}px`, | ||||
|         gridTemplateColumns, | ||||
|         width: scrollbarWidth, | ||||
|        }" | ||||
|     > | ||||
|       <!-- 商品 --> | ||||
|       <div | ||||
|         class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white" | ||||
|         :style="{ | ||||
|           borderTopLeftRadius: `${property.borderRadiusTop}px`, | ||||
|           borderTopRightRadius: `${property.borderRadiusTop}px`, | ||||
|           borderBottomLeftRadius: `${property.borderRadiusBottom}px`, | ||||
|           borderBottomRightRadius: `${property.borderRadiusBottom}px` | ||||
|         }" | ||||
|         v-for="(spu, index) in spuList" | ||||
|         :key="index" | ||||
|       > | ||||
|         <!-- 角标 --> | ||||
|         <div | ||||
|           v-if="property.badge.show" | ||||
|           class="absolute left-0 top-0 z-1 items-center justify-center" | ||||
|         > | ||||
|           <el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" /> | ||||
|         </div> | ||||
|         <!-- 商品封面图 --> | ||||
|         <el-image fit="cover" :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" /> | ||||
|         <div | ||||
|           :class="[ | ||||
|             'flex flex-col gap-8px p-8px box-border', | ||||
|             { | ||||
|               'w-[calc(100%-64px)]': columns === 2, | ||||
|               'w-full': columns === 3 | ||||
|             } | ||||
|           ]" | ||||
|         > | ||||
|           <!-- 商品名称 --> | ||||
|           <div | ||||
|             v-if="property.fields.name.show" | ||||
|             class="truncate text-12px" | ||||
|             :style="{ color: property.fields.name.color }" | ||||
|           > | ||||
|             {{ spu.name }} | ||||
|           </div> | ||||
|           <div> | ||||
|             <!-- 商品价格 --> | ||||
|             <span | ||||
|               v-if="property.fields.price.show" | ||||
|               class="text-12px" | ||||
|               :style="{ color: property.fields.price.color }" | ||||
|             > | ||||
|               ¥{{ spu.price }} | ||||
|             </span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </el-scrollbar> | ||||
| </template> | ||||
| <script setup lang="ts"> | ||||
| import { ProductListProperty } from "./config" | ||||
| import * as ProductSpuApi from "@/api/mall/product/spu" | ||||
| 
 | ||||
| /** 商品卡片 */ | ||||
| defineOptions({ name: "ProductList" }) | ||||
| // 定义属性 | ||||
| const props = defineProps<{ property: ProductListProperty }>() | ||||
| // 商品列表 | ||||
| const spuList = ref<ProductSpuApi.Spu[]>([]) | ||||
| watch( | ||||
|   () => props.property.spuIds, | ||||
|   async () => { | ||||
|     spuList.value = await ProductSpuApi.getSpuDetailList(props.property.spuIds) | ||||
|   }, | ||||
|   { | ||||
|     immediate: true, | ||||
|     deep: true | ||||
|   } | ||||
| ) | ||||
| // 手机宽度 | ||||
| const phoneWidth = ref(375) | ||||
| // 容器 | ||||
| const containerRef = ref() | ||||
| // 商品的列数 | ||||
| const columns = ref(2) | ||||
| // 滚动条宽度 | ||||
| const scrollbarWidth = ref("100%") | ||||
| // 商品图大小 | ||||
| const imageSize = ref("0") | ||||
| // 商品网络列数 | ||||
| const gridTemplateColumns = ref("") | ||||
| // 计算布局参数 | ||||
| watch( | ||||
|   () => [props.property, phoneWidth, spuList.value.length], | ||||
|   () => { | ||||
|     // 计算列数 | ||||
|     columns.value = props.property.layoutType === "twoCol" ? 2 : 3 | ||||
|     // 提取手机宽度 | ||||
|     // 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数 | ||||
|     const productWidth = (phoneWidth.value - props.property.space * (columns.value - 1)) / columns.value | ||||
|     // 商品图布局:2列时,左右布局 3列时,上下布局 | ||||
|     imageSize.value = columns.value === 2 ? "64px" : `${productWidth}px` | ||||
|     // 根据布局类型,计算行数、列数 | ||||
|     if (props.property.layoutType === "horizSwiper") { | ||||
|       // 单行显示 | ||||
|       gridTemplateColumns.value = `repeat(auto-fill, ${productWidth}px)` | ||||
|       // 显示滚动条 | ||||
|       scrollbarWidth.value = `${productWidth * spuList.value.length + props.property.space * (spuList.value.length - 1)}px` | ||||
|     } else { | ||||
|       // 指定列数 | ||||
|       gridTemplateColumns.value = `repeat(${columns.value}, auto)` | ||||
|       // 不滚动 | ||||
|       scrollbarWidth.value = "100%" | ||||
|     } | ||||
|   }, | ||||
|   { immediate: true, deep: true } | ||||
| ) | ||||
| onMounted(() => { | ||||
|   phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375; | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="scss"></style> | ||||
|  | @ -0,0 +1,99 @@ | |||
| <template> | ||||
|   <ComponentContainerProperty v-model="formData.style"> | ||||
|     <el-form label-width="80px" :model="formData"> | ||||
|       <el-card header="商品列表" class="property-group" shadow="never"> | ||||
|         <SpuShowcase v-model="formData.spuIds" /> | ||||
|       </el-card> | ||||
|       <el-card header="商品样式" class="property-group" shadow="never"> | ||||
|         <el-form-item label="布局" prop="type"> | ||||
|           <el-radio-group v-model="formData.layoutType"> | ||||
|             <el-tooltip class="item" content="双列" placement="bottom"> | ||||
|               <el-radio-button label="twoCol"> | ||||
|                 <Icon icon="fluent:text-column-two-24-filled" /> | ||||
|               </el-radio-button> | ||||
|             </el-tooltip> | ||||
|             <el-tooltip class="item" content="三列" placement="bottom"> | ||||
|               <el-radio-button label="threeCol"> | ||||
|                 <Icon icon="fluent:text-column-three-24-filled" /> | ||||
|               </el-radio-button> | ||||
|             </el-tooltip> | ||||
|             <el-tooltip class="item" content="水平滑动" placement="bottom"> | ||||
|               <el-radio-button label="horizSwiper"> | ||||
|                 <Icon icon="system-uicons:carousel" /> | ||||
|               </el-radio-button> | ||||
|             </el-tooltip> | ||||
|           </el-radio-group> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="商品名称" prop="fields.name.show"> | ||||
|           <div class="flex gap-8px"> | ||||
|             <ColorInput v-model="formData.fields.name.color" /> | ||||
|             <el-checkbox v-model="formData.fields.name.show" /> | ||||
|           </div> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="商品价格" prop="fields.price.show"> | ||||
|           <div class="flex gap-8px"> | ||||
|             <ColorInput v-model="formData.fields.price.color" /> | ||||
|             <el-checkbox v-model="formData.fields.price.show" /> | ||||
|           </div> | ||||
|         </el-form-item> | ||||
|       </el-card> | ||||
|       <el-card header="角标" class="property-group" shadow="never"> | ||||
|         <el-form-item label="角标" prop="badge.show"> | ||||
|           <el-switch v-model="formData.badge.show" /> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show"> | ||||
|           <UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px"> | ||||
|             <template #tip> 建议尺寸:36 * 22 </template> | ||||
|           </UploadImg> | ||||
|         </el-form-item> | ||||
|       </el-card> | ||||
|       <el-card header="商品样式" class="property-group" shadow="never"> | ||||
|         <el-form-item label="上圆角" prop="borderRadiusTop"> | ||||
|           <el-slider | ||||
|             v-model="formData.borderRadiusTop" | ||||
|             :max="100" | ||||
|             :min="0" | ||||
|             show-input | ||||
|             input-size="small" | ||||
|             :show-input-controls="false" | ||||
|           /> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="下圆角" prop="borderRadiusBottom"> | ||||
|           <el-slider | ||||
|             v-model="formData.borderRadiusBottom" | ||||
|             :max="100" | ||||
|             :min="0" | ||||
|             show-input | ||||
|             input-size="small" | ||||
|             :show-input-controls="false" | ||||
|           /> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="间隔" prop="space"> | ||||
|           <el-slider | ||||
|             v-model="formData.space" | ||||
|             :max="100" | ||||
|             :min="0" | ||||
|             show-input | ||||
|             input-size="small" | ||||
|             :show-input-controls="false" | ||||
|           /> | ||||
|         </el-form-item> | ||||
|       </el-card> | ||||
|     </el-form> | ||||
|   </ComponentContainerProperty> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
| import { ProductListProperty } from './config' | ||||
| import { usePropertyForm } from '@/components/DiyEditor/util' | ||||
| import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue' | ||||
| 
 | ||||
| // 商品卡片属性面板 | ||||
| defineOptions({ name: 'ProductListProperty' }) | ||||
| 
 | ||||
| const props = defineProps<{ modelValue: ProductListProperty }>() | ||||
| const emit = defineEmits(['update:modelValue']) | ||||
| const { formData } = usePropertyForm(props.modelValue, emit) | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="scss"></style> | ||||
|  | @ -107,7 +107,7 @@ export const PAGE_LIBS = [ | |||
|     extended: true, | ||||
|     components: ['ImageBar', 'Carousel', 'TitleBar', 'VideoPlayer', 'Divider', 'MagicCube'] | ||||
|   }, | ||||
|   { name: '商品组件', extended: true, components: ['ProductCard'] }, | ||||
|   { name: '商品组件', extended: true, components: ['ProductCard', 'ProductList'] }, | ||||
|   { | ||||
|     name: '会员组件', | ||||
|     extended: true, | ||||
|  |  | |||
|  | @ -59,7 +59,6 @@ watch( | |||
|       productSpus.value.length === 0 || | ||||
|       productSpus.value.some((spu) => !props.modelValue.includes(spu.id)) | ||||
|     ) { | ||||
|       debugger | ||||
|       productSpus.value = await ProductSpuApi.getSpuDetailList(props.modelValue) | ||||
|     } | ||||
|   }, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 owen
						owen