Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
b4a17725
authored
Nov 17, 2023
by
owen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
营销:适配商城装修组件【商品卡片】
parent
49ebadd7
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
538 additions
and
1 deletions
+538
-1
src/components/DiyEditor/components/mobile/MagicCube/config.ts
+1
-1
src/components/DiyEditor/components/mobile/ProductCard/config.ts
+97
-0
src/components/DiyEditor/components/mobile/ProductCard/index.vue
+165
-0
src/components/DiyEditor/components/mobile/ProductCard/property.vue
+149
-0
src/views/mall/product/spu/components/SpuShowcase.vue
+126
-0
No files found.
src/components/DiyEditor/components/mobile/MagicCube/config.ts
View file @
b4a17725
...
...
@@ -33,7 +33,7 @@ export interface MagicCubeItemProperty {
export
const
component
=
{
id
:
'MagicCube'
,
name
:
'广告魔方'
,
icon
:
'
fluent:puzzle-cube-piece-20-filled
'
,
icon
:
'
bi:columns
'
,
property
:
{
borderRadiusTop
:
0
,
borderRadiusBottom
:
0
,
...
...
src/components/DiyEditor/components/mobile/ProductCard/config.ts
0 → 100644
View file @
b4a17725
import
{
ComponentStyle
,
DiyComponent
}
from
'@/components/DiyEditor/util'
/** 商品卡片属性 */
export
interface
ProductCardProperty
{
// 布局类型:单列大图 | 单列小图 | 双列
layoutType
:
'oneColBigImg'
|
'oneColSmallImg'
|
'twoCol'
// 商品字段
fields
:
{
// 商品名称
name
:
ProductCardFieldProperty
// 商品简介
introduction
:
ProductCardFieldProperty
// 商品价格
price
:
ProductCardFieldProperty
// 商品市场价
marketPrice
:
ProductCardFieldProperty
// 商品销量
salesCount
:
ProductCardFieldProperty
// 商品库存
stock
:
ProductCardFieldProperty
}
// 角标
badge
:
{
// 是否显示
show
:
boolean
// 角标图片
imgUrl
:
string
}
// 按钮
btnBuy
:
{
// 类型:文字 | 图片
type
:
'text'
|
'img'
// 文字
text
:
string
// 文字按钮:背景渐变起始颜色
bgBeginColor
:
string
// 文字按钮:背景渐变结束颜色
bgEndColor
:
string
// 图片按钮:图片地址
imgUrl
:
string
}
// 上圆角
borderRadiusTop
:
number
// 下圆角
borderRadiusBottom
:
number
// 间距
space
:
number
// 商品编号列表
spuIds
:
number
[]
// 组件样式
style
:
ComponentStyle
}
// 商品字段
export
interface
ProductCardFieldProperty
{
// 是否显示
show
:
boolean
// 颜色
color
:
string
}
// 定义组件
export
const
component
=
{
id
:
'ProductCard'
,
name
:
'商品卡片'
,
icon
:
'system-uicons:carousel'
,
property
:
{
layoutType
:
'oneColBigImg'
,
fields
:
{
name
:
{
show
:
true
,
color
:
'#000'
},
introduction
:
{
show
:
true
,
color
:
'#999'
},
price
:
{
show
:
true
,
color
:
'#ff3000'
},
marketPrice
:
{
show
:
true
,
color
:
'#c4c4c4'
},
salesCount
:
{
show
:
true
,
color
:
'#c4c4c4'
},
stock
:
{
show
:
false
,
color
:
'#c4c4c4'
}
},
badge
:
{
show
:
false
,
imgUrl
:
''
},
btnBuy
:
{
type
:
'text'
,
text
:
'立即购买'
,
// todo: @owen 根据主题色配置
bgBeginColor
:
'#FF6000'
,
bgEndColor
:
'#FE832A'
,
imgUrl
:
''
},
borderRadiusTop
:
8
,
borderRadiusBottom
:
8
,
space
:
8
,
spuIds
:
[],
style
:
{
bgType
:
'color'
,
bgColor
:
''
,
marginLeft
:
8
,
marginRight
:
8
,
marginBottom
:
8
}
as
ComponentStyle
}
}
as
DiyComponent
<
ProductCardProperty
>
src/components/DiyEditor/components/mobile/ProductCard/index.vue
0 → 100644
View file @
b4a17725
<
template
>
<div
:class=
"`box-content min-h-30px w-full flex flex-row flex-wrap`"
ref=
"containerRef"
>
<div
class=
"relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
:style=
"
{
...calculateSpace(index),
...calculateWidth(),
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>
<!-- 商品封面图 -->
<div
:class=
"[
'h-140px',
{
'w-full': property.layoutType !== 'oneColSmallImg',
'w-140px': property.layoutType === 'oneColSmallImg'
}
]"
>
<el-image
fit=
"cover"
class=
"h-full w-full"
:src=
"spu.picUrl"
/>
</div>
<div
:class=
"[
' flex flex-col gap-8px p-8px box-border',
{
'w-full': property.layoutType !== 'oneColSmallImg',
'w-[calc(100%-140px-16px)]': property.layoutType === 'oneColSmallImg'
}
]"
>
<!-- 商品名称 -->
<div
v-if=
"property.fields.name.show"
:class=
"[
'text-14px ',
{
truncate: property.layoutType !== 'oneColSmallImg',
'overflow-ellipsis line-clamp-2': property.layoutType === 'oneColSmallImg'
}
]"
:style="{ color: property.fields.name.color }"
>
{{
spu
.
name
}}
</div>
<!-- 商品简介 -->
<div
v-if=
"property.fields.introduction.show"
class=
"truncate text-12px"
:style=
"
{ color: property.fields.introduction.color }"
>
{{
spu
.
introduction
}}
</div>
<div>
<!-- 价格 -->
<span
v-if=
"property.fields.price.show"
class=
"text-16px"
:style=
"
{ color: property.fields.price.color }"
>
¥
{{
spu
.
price
}}
</span>
<!-- 市场价 -->
<span
v-if=
"property.fields.marketPrice.show && spu.marketPrice"
class=
"ml-4px text-10px line-through"
:style=
"
{ color: property.fields.marketPrice.color }"
>¥
{{
spu
.
marketPrice
}}
</span
>
</div>
<div
class=
"text-12px"
>
<!-- 销量 -->
<span
v-if=
"property.fields.salesCount.show"
:style=
"
{ color: property.fields.salesCount.color }"
>
已售
{{
(
spu
.
salesCount
||
0
)
+
(
spu
.
virtualSalesCount
||
0
)
}}
件
</span>
<!-- 库存 -->
<span
v-if=
"property.fields.stock.show"
:style=
"
{ color: property.fields.stock.color }">
库存
{{
spu
.
stock
||
0
}}
</span>
</div>
</div>
<!-- 购买按钮 -->
<div
class=
"absolute bottom-8px right-8px"
>
<!-- 文字按钮 -->
<span
v-if=
"property.btnBuy.type === 'text'"
class=
"rounded-full p-x-12px p-y-4px text-12px text-white"
:style=
"
{
background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`
}"
>
{{
property
.
btnBuy
.
text
}}
</span>
<!-- 图片按钮 -->
<el-image
v-else
class=
"h-28px w-28px rounded-full"
fit=
"cover"
:src=
"property.btnBuy.imgUrl"
/>
</div>
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
ProductCardProperty
}
from
'./config'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
/** 商品卡片 */
defineOptions
({
name
:
'ProductCard'
})
// 定义属性
const
props
=
defineProps
<
{
property
:
ProductCardProperty
}
>
()
// 商品列表
const
spuList
=
ref
<
ProductSpuApi
.
Spu
[]
>
([])
watch
(
()
=>
props
.
property
.
spuIds
,
async
()
=>
{
spuList
.
value
=
await
ProductSpuApi
.
getSpuDetailList
(
props
.
property
.
spuIds
)
},
{
immediate
:
true
,
deep
:
true
}
)
/**
* 计算商品的间距
* @param index 商品索引
*/
const
calculateSpace
=
(
index
:
number
)
=>
{
// 商品的列数
const
columns
=
props
.
property
.
layoutType
===
'twoCol'
?
2
:
1
// 第一列没有左边距
const
marginLeft
=
index
%
columns
===
0
?
'0'
:
props
.
property
.
space
+
'px'
// 第一行没有上边距
const
marginTop
=
index
<
columns
?
'0'
:
props
.
property
.
space
+
'px'
return
{
marginLeft
,
marginTop
}
}
// 容器
const
containerRef
=
ref
()
// 计算商品的宽度
const
calculateWidth
=
()
=>
{
let
width
=
'100%'
// 双列时每列的宽度为:(总宽度 - 间距)/ 2
if
(
props
.
property
.
layoutType
===
'twoCol'
)
{
width
=
`
${(
containerRef
.
value
.
offsetWidth
-
props
.
property
.
space
)
/
2
}
px`
}
return
{
width
}
}
</
script
>
<
style
scoped
lang=
"scss"
></
style
>
src/components/DiyEditor/components/mobile/ProductCard/property.vue
0 → 100644
View file @
b4a17725
<
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=
"oneColBigImg"
>
<Icon
icon=
"fluent:text-column-one-24-filled"
/>
</el-radio-button>
</el-tooltip>
<el-tooltip
class=
"item"
content=
"单列小图"
placement=
"bottom"
>
<el-radio-button
label=
"oneColSmallImg"
>
<Icon
icon=
"fluent:text-column-two-left-24-filled"
/>
</el-radio-button>
</el-tooltip>
<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-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.introduction.show"
>
<div
class=
"flex gap-8px"
>
<ColorInput
v-model=
"formData.fields.introduction.color"
/>
<el-checkbox
v-model=
"formData.fields.introduction.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-form-item
label=
"市场价"
prop=
"fields.marketPrice.show"
>
<div
class=
"flex gap-8px"
>
<ColorInput
v-model=
"formData.fields.marketPrice.color"
/>
<el-checkbox
v-model=
"formData.fields.marketPrice.show"
/>
</div>
</el-form-item>
<el-form-item
label=
"商品销量"
prop=
"fields.salesCount.show"
>
<div
class=
"flex gap-8px"
>
<ColorInput
v-model=
"formData.fields.salesCount.color"
/>
<el-checkbox
v-model=
"formData.fields.salesCount.show"
/>
</div>
</el-form-item>
<el-form-item
label=
"商品库存"
prop=
"fields.stock.show"
>
<div
class=
"flex gap-8px"
>
<ColorInput
v-model=
"formData.fields.stock.color"
/>
<el-checkbox
v-model=
"formData.fields.stock.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=
"btnBuy.type"
>
<el-radio-group
v-model=
"formData.btnBuy.type"
>
<el-radio-button
label=
"text"
>
文字
</el-radio-button>
<el-radio-button
label=
"img"
>
图片
</el-radio-button>
</el-radio-group>
</el-form-item>
<
template
v-if=
"formData.btnBuy.type === 'text'"
>
<el-form-item
label=
"按钮文字"
prop=
"btnBuy.text"
>
<el-input
v-model=
"formData.btnBuy.text"
/>
</el-form-item>
<el-form-item
label=
"左侧背景"
prop=
"btnBuy.bgBeginColor"
>
<ColorInput
v-model=
"formData.btnBuy.bgBeginColor"
/>
</el-form-item>
<el-form-item
label=
"右侧背景"
prop=
"btnBuy.bgEndColor"
>
<ColorInput
v-model=
"formData.btnBuy.bgEndColor"
/>
</el-form-item>
</
template
>
<
template
v-else
>
<el-form-item
label=
"图片"
prop=
"btnBuy.imgUrl"
>
<UploadImg
v-model=
"formData.btnBuy.imgUrl"
height=
"56px"
width=
"56px"
>
<template
#
tip
>
建议尺寸:56 * 56
</
template
>
</UploadImg>
</el-form-item>
</template>
</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
{
ProductCardProperty
}
from
'./config'
import
{
usePropertyForm
}
from
'@/components/DiyEditor/util'
import
SpuShowcase
from
'@/views/mall/product/spu/components/SpuShowcase.vue'
// 商品卡片属性面板
defineOptions
({
name
:
'ProductCardProperty'
})
const
props
=
defineProps
<
{
modelValue
:
ProductCardProperty
}
>
()
const
emit
=
defineEmits
([
'update:modelValue'
])
const
{
formData
}
=
usePropertyForm
(
props
.
modelValue
,
emit
)
</
script
>
<
style
scoped
lang=
"scss"
></
style
>
src/views/mall/product/spu/components/SpuShowcase.vue
0 → 100644
View file @
b4a17725
<
template
>
<div
class=
"flex flex-wrap items-center gap-8px"
>
<div
v-for=
"(spu, index) in productSpus"
:key=
"spu.id"
class=
"select-box spu-pic"
>
<el-tooltip
:content=
"spu.name"
>
<div
class=
"relative h-full w-full"
>
<el-image
:src=
"spu.picUrl"
class=
"h-full w-full"
/>
<Icon
v-show=
"!disabled"
class=
"del-icon"
icon=
"ep:circle-close-filled"
@
click=
"handleRemoveSpu(index)"
/>
</div>
</el-tooltip>
</div>
<el-tooltip
content=
"选择商品"
>
<div
v-show=
"!disabled"
v-if=
"!limit || limit
<
=
productSpus
.
length
"
class=
"select-box"
@
click=
"openSpuTableSelect"
>
<Icon
icon=
"ep:plus"
/>
</div>
</el-tooltip>
</div>
<!-- 商品选择对话框(表格形式) -->
<SpuTableSelect
ref=
"spuTableSelectRef"
multiple
@
change=
"handleSpuSelected"
/>
</
template
>
<
script
lang=
"ts"
setup
>
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
SpuTableSelect
from
'@/views/mall/product/spu/components/SpuTableSelect.vue'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
array
}
from
'vue-types'
// 商品橱窗,一般用于与商品建立关系时使用
// 提供功能:展示商品列表、添加商品、移除商品
defineOptions
({
name
:
'SpuShowcase'
})
const
props
=
defineProps
({
modelValue
:
array
<
number
>
().
def
([]).
isRequired
,
// 限制数量:默认不限制
limit
:
propTypes
.
number
.
def
(
0
),
disabled
:
propTypes
.
bool
.
def
(
false
)
})
// 商品列表
const
productSpus
=
ref
<
ProductSpuApi
.
Spu
[]
>
([])
watch
(
()
=>
props
.
modelValue
,
async
()
=>
{
if
(
props
.
modelValue
.
length
===
0
)
{
productSpus
.
value
=
[]
return
}
// 只有商品发生变化之后,才去查询商品
if
(
productSpus
.
value
.
length
===
0
||
productSpus
.
value
.
some
((
spu
)
=>
!
props
.
modelValue
.
includes
(
spu
.
id
))
)
{
debugger
productSpus
.
value
=
await
ProductSpuApi
.
getSpuDetailList
(
props
.
modelValue
)
}
},
{
immediate
:
true
}
)
/** 商品表格选择对话框 */
const
spuTableSelectRef
=
ref
()
// 打开对话框
const
openSpuTableSelect
=
()
=>
{
spuTableSelectRef
.
value
.
open
(
productSpus
.
value
)
}
/**
* 选择商品后触发
* @param spus 选中的商品列表
*/
const
handleSpuSelected
=
(
spus
:
ProductSpuApi
.
Spu
[])
=>
{
productSpus
.
value
=
spus
emitSpuChange
()
}
/**
* 删除商品
* @param index 商品索引
*/
const
handleRemoveSpu
=
(
index
:
number
)
=>
{
productSpus
.
value
.
splice
(
index
,
1
)
emitSpuChange
()
}
const
emit
=
defineEmits
([
'update:modelValue'
,
'change'
])
const
emitSpuChange
=
()
=>
{
emit
(
'update:modelValue'
,
productSpus
.
value
.
map
((
spu
)
=>
spu
.
id
)
)
emit
(
'change'
,
productSpus
.
value
)
}
</
script
>
<
style
lang=
"scss"
scoped
>
.select-box
{
display
:
flex
;
width
:
60px
;
height
:
60px
;
border
:
1px
dashed
var
(
--el-border-color-darker
);
border-radius
:
8px
;
align-items
:
center
;
justify-content
:
center
;
}
.spu-pic
{
position
:
relative
;
}
.del-icon
{
position
:
absolute
;
top
:
-10px
;
right
:
-10px
;
z-index
:
1
;
width
:
20px
!important
;
height
:
20px
!important
;
}
</
style
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment