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
a132f07d
authored
Sep 28, 2024
by
puhui999
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【功能完善】商城: 完善积分商城装修
parent
1f8e419f
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
930 additions
and
11 deletions
+930
-11
src/api/mall/promotion/point/index.ts
+15
-2
src/components/AppLinkInput/data.ts
+8
-0
src/components/DiyEditor/components/mobile/PromotionPoint/config.ts
+96
-0
src/components/DiyEditor/components/mobile/PromotionPoint/index.vue
+203
-0
src/components/DiyEditor/components/mobile/PromotionPoint/property.vue
+154
-0
src/views/mall/promotion/point/activity/index.vue
+0
-9
src/views/mall/promotion/point/components/PointShowcase.vue
+154
-0
src/views/mall/promotion/point/components/PointTableSelect.vue
+300
-0
No files found.
src/api/mall/promotion/point/index.ts
View file @
a132f07d
import
request
from
'@/config/axios'
import
{
Sku
,
Spu
}
from
'@/api/mall/product/spu'
import
{
Sku
,
Spu
}
from
'@/api/mall/product/spu'
// 积分商城活动 VO
// 积分商城活动 VO
export
interface
PointActivityVO
{
id
:
number
// 积分商城活动编号
spuId
:
number
// 积分商城活动商品
status
:
number
// 活动状态
stock
:
number
// 积分商城活动库存
totalStock
:
number
// 积分商城活动总库存
remark
?:
string
// 备注
sort
:
number
// 排序
createTime
:
string
// 创建时间
...
...
@@ -17,7 +19,6 @@ export interface PointActivityVO {
marketPrice
:
number
// 商品市场价,单位:分
//======================= 显示所需兑换积分最少的 sku 信息 =======================
maxCount
:
number
// 可兑换数量
point
:
number
// 兑换积分
price
:
number
// 兑换金额,单位:分
}
...
...
@@ -44,6 +45,13 @@ export interface SpuExtension extends Spu {
skus
:
SkuExtension
[]
// 重写类型
}
export
interface
SpuExtension0
extends
Spu
{
pointStock
:
number
// 积分商城活动库存
pointTotalStock
:
number
// 积分商城活动总库存
point
:
number
// 兑换积分
pointPrice
:
number
// 兑换金额,单位:分
}
// 积分商城活动 API
export
const
PointActivityApi
=
{
// 查询积分商城活动分页
...
...
@@ -56,6 +64,11 @@ export const PointActivityApi = {
return
await
request
.
get
({
url
:
`/promotion/point-activity/get?id=`
+
id
})
},
// 查询积分商城活动列表,基于活动编号数组
getPointActivityListByIds
:
async
(
ids
:
number
[])
=>
{
return
request
.
get
({
url
:
`/promotion/point-activity/list-by-ids?ids=
${
ids
}
`
})
},
// 新增积分商城活动
createPointActivity
:
async
(
data
:
PointActivityVO
)
=>
{
return
await
request
.
post
({
url
:
`/promotion/point-activity/create`
,
data
})
...
...
src/components/AppLinkInput/data.ts
View file @
a132f07d
...
...
@@ -5,6 +5,7 @@ export interface AppLinkGroup {
// 链接列表
links
:
AppLink
[]
}
// APP 链接
export
interface
AppLink
{
// 链接名称
...
...
@@ -21,6 +22,8 @@ export const enum APP_LINK_TYPE_ENUM {
ACTIVITY_COMBINATION
,
// 秒杀活动
ACTIVITY_SECKILL
,
// 积分商城活动
ACTIVITY_POINT
,
// 文章详情
ARTICLE_DETAIL
,
// 优惠券详情
...
...
@@ -131,6 +134,11 @@ export const APP_LINK_GROUP_LIST = [
type
:
APP_LINK_TYPE_ENUM
.
ACTIVITY_SECKILL
},
{
name
:
'积分商城活动'
,
path
:
'/pages/activity/point/list'
,
type
:
APP_LINK_TYPE_ENUM
.
ACTIVITY_POINT
},
{
name
:
'签到中心'
,
path
:
'/pages/app/sign'
},
...
...
src/components/DiyEditor/components/mobile/PromotionPoint/config.ts
0 → 100644
View file @
a132f07d
import
{
ComponentStyle
,
DiyComponent
}
from
'@/components/DiyEditor/util'
/** 积分商城属性 */
export
interface
PromotionPointProperty
{
// 布局类型:单列 | 三列
layoutType
:
'oneColBigImg'
|
'oneColSmallImg'
|
'twoCol'
// 商品字段
fields
:
{
// 商品名称
name
:
PromotionPointFieldProperty
// 商品简介
introduction
:
PromotionPointFieldProperty
// 商品价格
price
:
PromotionPointFieldProperty
// 市场价
marketPrice
:
PromotionPointFieldProperty
// 商品销量
salesCount
:
PromotionPointFieldProperty
// 商品库存
stock
:
PromotionPointFieldProperty
}
// 角标
badge
:
{
// 是否显示
show
:
boolean
// 角标图片
imgUrl
:
string
}
// 按钮
btnBuy
:
{
// 类型:文字 | 图片
type
:
'text'
|
'img'
// 文字
text
:
string
// 文字按钮:背景渐变起始颜色
bgBeginColor
:
string
// 文字按钮:背景渐变结束颜色
bgEndColor
:
string
// 图片按钮:图片地址
imgUrl
:
string
}
// 上圆角
borderRadiusTop
:
number
// 下圆角
borderRadiusBottom
:
number
// 间距
space
:
number
// 秒杀活动编号
activityIds
:
number
[]
// 组件样式
style
:
ComponentStyle
}
// 商品字段
export
interface
PromotionPointFieldProperty
{
// 是否显示
show
:
boolean
// 颜色
color
:
string
}
// 定义组件
export
const
component
=
{
id
:
'PromotionPoint'
,
name
:
'积分商城'
,
icon
:
'ep:present'
,
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
:
'立即兑换'
,
bgBeginColor
:
'#FF6000'
,
bgEndColor
:
'#FE832A'
,
imgUrl
:
''
},
borderRadiusTop
:
8
,
borderRadiusBottom
:
8
,
space
:
8
,
style
:
{
bgType
:
'color'
,
bgColor
:
''
,
marginLeft
:
8
,
marginRight
:
8
,
marginBottom
:
8
}
as
ComponentStyle
}
}
as
DiyComponent
<
PromotionPointProperty
>
src/components/DiyEditor/components/mobile/PromotionPoint/index.vue
0 → 100644
View file @
a132f07d
<
template
>
<div
ref=
"containerRef"
:class=
"`box-content min-h-30px w-full flex flex-row flex-wrap`"
>
<div
v-for=
"(spu, index) in spuList"
:key=
"index"
:style=
"
{
...calculateSpace(index),
...calculateWidth(),
borderTopLeftRadius: `${property.borderRadiusTop}px`,
borderTopRightRadius: `${property.borderRadiusTop}px`,
borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
borderBottomRightRadius: `${property.borderRadiusBottom}px`
}"
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
>
<!-- 角标 -->
<div
v-if=
"property.badge.show"
class=
"absolute left-0 top-0 z-1 items-center justify-center"
>
<el-image
:src=
"property.badge.imgUrl"
class=
"h-26px w-38px"
fit=
"cover"
/>
</div>
<!-- 商品封面图 -->
<div
:class=
"[
'h-140px',
{
'w-full': property.layoutType !== 'oneColSmallImg',
'w-140px': property.layoutType === 'oneColSmallImg'
}
]"
>
<el-image
:src=
"spu.picUrl"
class=
"h-full w-full"
fit=
"cover"
/>
</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"
:style=
"
{ color: property.fields.introduction.color }"
class="truncate text-12px"
>
{{
spu
.
introduction
}}
</div>
<div>
<!-- 积分 -->
<span
v-if=
"property.fields.price.show"
:style=
"
{ color: property.fields.price.color }"
class="text-16px"
>
{{
spu
.
point
}}
积分
{{
!
spu
.
pointPrice
||
spu
.
pointPrice
===
0
?
''
:
`+${fenToYuan(spu.pointPrice)
}
元`
}}
<
/span
>
<!--
市场价
-->
<
span
v
-
if
=
"property.fields.marketPrice.show && spu.marketPrice"
:
style
=
"
{
color
:
property
.
fields
.
marketPrice
.
color
}
"
class="
ml
-
4
px
text
-
10
px
line
-
through
"
>
¥
{{
fenToYuan
(
spu
.
marketPrice
)
}}
<
/span
>
<
/div
>
<
div
class
=
"text-12px"
>
<!--
销量
-->
<
span
v
-
if
=
"property.fields.salesCount.show"
:
style
=
"
{
color
:
property
.
fields
.
salesCount
.
color
}"
>
已兑
{{
(
spu
.
pointTotalStock
||
0
)
-
(
spu
.
pointStock
||
0
)
}}
件
<
/span
>
<!--
库存
-->
<
span
v
-
if
=
"property.fields.stock.show"
:
style
=
"
{
color
:
property
.
fields
.
stock
.
color
}"
>
库存
{{
spu
.
pointTotalStock
||
0
}}
<
/span
>
<
/div
>
<
/div
>
<!--
购买按钮
-->
<
div
class
=
"absolute bottom-8px right-8px"
>
<!--
文字按钮
-->
<
span
v
-
if
=
"property.btnBuy.type === 'text'"
:
style
=
"
{
background
:
`linear-gradient(to right, ${property.btnBuy.bgBeginColor
}
, ${property.btnBuy.bgEndColor
}
`
}
"
class="
rounded
-
full
p
-
x
-
12
px
p
-
y
-
4
px
text
-
12
px
text
-
white
"
>
{{
property
.
btnBuy
.
text
}}
<
/span
>
<!--
图片按钮
-->
<
el
-
image
v
-
else
:
src
=
"property.btnBuy.imgUrl"
class
=
"h-28px w-28px rounded-full"
fit
=
"cover"
/>
<
/div
>
<
/div
>
<
/div
>
<
/template
>
<
script
lang
=
"ts"
setup
>
import
{
PromotionPointProperty
}
from
'./config'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
{
PointActivityApi
,
PointActivityVO
,
SpuExtension0
}
from
'@/api/mall/promotion/point'
import
{
fenToYuan
}
from
'@/utils'
/** 积分商城卡片 */
defineOptions
({
name
:
'PromotionPoint'
}
)
// 定义属性
const
props
=
defineProps
<
{
property
:
PromotionPointProperty
}
>
()
// 商品列表
const
spuList
=
ref
<
SpuExtension0
[]
>
([])
const
spuIdList
=
ref
<
number
[]
>
([])
const
pointActivityList
=
ref
<
PointActivityVO
[]
>
([])
watch
(
()
=>
props
.
property
.
activityIds
,
async
()
=>
{
try
{
// 新添加的积分商城组件,是没有活动ID的
const
activityIds
=
props
.
property
.
activityIds
// 检查活动ID的有效性
if
(
Array
.
isArray
(
activityIds
)
&&
activityIds
.
length
>
0
)
{
// 获取积分商城活动详情列表
pointActivityList
.
value
=
await
PointActivityApi
.
getPointActivityListByIds
(
activityIds
)
// 获取积分商城活动的 SPU 详情列表
spuList
.
value
=
[]
spuIdList
.
value
=
pointActivityList
.
value
.
map
((
activity
)
=>
activity
.
spuId
)
if
(
spuIdList
.
value
.
length
>
0
)
{
spuList
.
value
=
await
ProductSpuApi
.
getSpuDetailList
(
spuIdList
.
value
)
}
// 更新 SPU 的最低兑换积分和所需兑换金额
pointActivityList
.
value
.
forEach
((
activity
)
=>
{
// 匹配spuId
const
spu
=
spuList
.
value
.
find
((
spu
)
=>
spu
.
id
===
activity
.
spuId
)
if
(
spu
)
{
spu
.
pointStock
=
activity
.
stock
spu
.
pointTotalStock
=
activity
.
totalStock
spu
.
point
=
activity
.
point
spu
.
pointPrice
=
activity
.
price
}
}
)
}
}
catch
(
error
)
{
console
.
error
(
'获取积分商城活动细节或 SPU 细节时出错:'
,
error
)
}
}
,
{
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
lang
=
"scss"
scoped
><
/style
>
src/components/DiyEditor/components/mobile/PromotionPoint/property.vue
0 → 100644
View file @
a132f07d
<
template
>
<ComponentContainerProperty
v-model=
"formData.style"
>
<el-form
:model=
"formData"
label-width=
"80px"
>
<el-card
class=
"property-group"
header=
"积分商城活动"
shadow=
"never"
>
<PointShowcase
v-model=
"formData.activityIds"
/>
</el-card>
<el-card
class=
"property-group"
header=
"商品样式"
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
value=
"oneColBigImg"
>
<Icon
icon=
"fluent:text-column-one-24-filled"
/>
</el-radio-button>
</el-tooltip>
<el-tooltip
class=
"item"
content=
"单列小图"
placement=
"bottom"
>
<el-radio-button
value=
"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
value=
"twoCol"
>
<Icon
icon=
"fluent:text-column-two-24-filled"
/>
</el-radio-button>
</el-tooltip>
<!--
<el-tooltip
class=
"item"
content=
"三列"
placement=
"bottom"
>
<el-radio-button
value=
"threeCol"
>
<Icon
icon=
"fluent:text-column-three-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
class=
"property-group"
header=
"角标"
shadow=
"never"
>
<el-form-item
label=
"角标"
prop=
"badge.show"
>
<el-switch
v-model=
"formData.badge.show"
/>
</el-form-item>
<el-form-item
v-if=
"formData.badge.show"
label=
"角标"
prop=
"badge.imgUrl"
>
<UploadImg
v-model=
"formData.badge.imgUrl"
height=
"44px"
width=
"72px"
>
<template
#
tip
>
建议尺寸:36 * 22
</
template
>
</UploadImg>
</el-form-item>
</el-card>
<el-card
class=
"property-group"
header=
"按钮"
shadow=
"never"
>
<el-form-item
label=
"按钮类型"
prop=
"btnBuy.type"
>
<el-radio-group
v-model=
"formData.btnBuy.type"
>
<el-radio-button
value=
"text"
>
文字
</el-radio-button>
<el-radio-button
value=
"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
class=
"property-group"
header=
"商品样式"
shadow=
"never"
>
<el-form-item
label=
"上圆角"
prop=
"borderRadiusTop"
>
<el-slider
v-model=
"formData.borderRadiusTop"
:max=
"100"
:min=
"0"
:show-input-controls=
"false"
input-size=
"small"
show-input
/>
</el-form-item>
<el-form-item
label=
"下圆角"
prop=
"borderRadiusBottom"
>
<el-slider
v-model=
"formData.borderRadiusBottom"
:max=
"100"
:min=
"0"
:show-input-controls=
"false"
input-size=
"small"
show-input
/>
</el-form-item>
<el-form-item
label=
"间隔"
prop=
"space"
>
<el-slider
v-model=
"formData.space"
:max=
"100"
:min=
"0"
:show-input-controls=
"false"
input-size=
"small"
show-input
/>
</el-form-item>
</el-card>
</el-form>
</ComponentContainerProperty>
</template>
<
script
lang=
"ts"
setup
>
import
{
PromotionPointProperty
}
from
'./config'
import
{
usePropertyForm
}
from
'@/components/DiyEditor/util'
import
PointShowcase
from
'@/views/mall/promotion/point/components/PointShowcase.vue'
// 秒杀属性面板
defineOptions
({
name
:
'PromotionPointProperty'
})
const
props
=
defineProps
<
{
modelValue
:
PromotionPointProperty
}
>
()
const
emit
=
defineEmits
([
'update:modelValue'
])
const
{
formData
}
=
usePropertyForm
(
props
.
modelValue
,
emit
)
</
script
>
<
style
lang=
"scss"
scoped
></
style
>
src/views/mall/promotion/point/activity/index.vue
View file @
a132f07d
...
...
@@ -10,15 +10,6 @@
class=
"-mb-15px"
label-width=
"68px"
>
<el-form-item
label=
"活动名称"
prop=
"name"
>
<el-input
v-model=
"queryParams.name"
class=
"!w-240px"
clearable
placeholder=
"请输入活动名称"
@
keyup
.
enter=
"handleQuery"
/>
</el-form-item>
<el-form-item
label=
"活动状态"
prop=
"status"
>
<el-select
v-model=
"queryParams.status"
...
...
src/views/mall/promotion/point/components/PointShowcase.vue
0 → 100644
View file @
a132f07d
<
template
>
<div
class=
"flex flex-wrap items-center gap-8px"
>
<div
v-for=
"(pointActivity, index) in pointActivityList"
:key=
"pointActivity.id"
class=
"select-box spu-pic"
>
<el-tooltip
:content=
"pointActivity.name"
>
<div
class=
"relative h-full w-full"
>
<el-image
:src=
"pointActivity.picUrl"
class=
"h-full w-full"
/>
<Icon
v-show=
"!disabled"
class=
"del-icon"
icon=
"ep:circle-close-filled"
@
click=
"handleRemoveActivity(index)"
/>
</div>
</el-tooltip>
</div>
<el-tooltip
v-if=
"canAdd"
content=
"选择活动"
>
<div
class=
"select-box"
@
click=
"openSeckillActivityTableSelect"
>
<Icon
icon=
"ep:plus"
/>
</div>
</el-tooltip>
</div>
<!-- 拼团活动选择对话框(表格形式) -->
<PointTableSelect
ref=
"pointActivityTableSelectRef"
:multiple=
"limit != 1"
@
change=
"handleActivitySelected"
/>
</
template
>
<
script
lang=
"ts"
setup
>
import
PointTableSelect
from
'./PointTableSelect.vue'
import
{
PointActivityApi
,
PointActivityVO
}
from
'@/api/mall/promotion/point'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
oneOfType
}
from
'vue-types'
import
{
isArray
}
from
'@/utils/is'
// 活动橱窗,一般用于装修时使用
// 提供功能:展示活动列表、添加活动、删除活动
defineOptions
({
name
:
'PointShowcase'
})
const
props
=
defineProps
({
modelValue
:
oneOfType
<
number
|
Array
<
number
>>
([
Number
,
Array
]).
isRequired
,
// 限制数量:默认不限制
limit
:
propTypes
.
number
.
def
(
Number
.
MAX_VALUE
),
disabled
:
propTypes
.
bool
.
def
(
false
)
})
// 计算是否可以添加
const
canAdd
=
computed
(()
=>
{
// 情况一:禁用时不可以添加
if
(
props
.
disabled
)
return
false
// 情况二:未指定限制数量时,可以添加
if
(
!
props
.
limit
)
return
true
// 情况三:检查已添加数量是否小于限制数量
return
pointActivityList
.
value
.
length
<
props
.
limit
})
// 拼团活动列表
const
pointActivityList
=
ref
<
PointActivityVO
[]
>
([])
watch
(
()
=>
props
.
modelValue
,
async
()
=>
{
const
ids
=
isArray
(
props
.
modelValue
)
?
// 情况一:多选
props
.
modelValue
:
// 情况二:单选
props
.
modelValue
?
[
props
.
modelValue
]
:
[]
// 不需要返显
if
(
ids
.
length
===
0
)
{
pointActivityList
.
value
=
[]
return
}
// 只有活动发生变化之后,才会查询活动
if
(
pointActivityList
.
value
.
length
===
0
||
pointActivityList
.
value
.
some
((
pointActivity
)
=>
!
ids
.
includes
(
pointActivity
.
id
!
))
)
{
pointActivityList
.
value
=
await
PointActivityApi
.
getPointActivityListByIds
(
ids
)
}
},
{
immediate
:
true
}
)
/** 活动表格选择对话框 */
const
pointActivityTableSelectRef
=
ref
()
// 打开对话框
const
openSeckillActivityTableSelect
=
()
=>
{
pointActivityTableSelectRef
.
value
.
open
(
pointActivityList
.
value
)
}
/**
* 选择活动后触发
* @param activityList 选中的活动列表
*/
const
handleActivitySelected
=
(
activityList
:
PointActivityVO
|
PointActivityVO
[])
=>
{
pointActivityList
.
value
=
isArray
(
activityList
)
?
activityList
:
[
activityList
]
emitActivityChange
()
}
/**
* 删除活动
* @param index 活动索引
*/
const
handleRemoveActivity
=
(
index
:
number
)
=>
{
pointActivityList
.
value
.
splice
(
index
,
1
)
emitActivityChange
()
}
const
emit
=
defineEmits
([
'update:modelValue'
,
'change'
])
const
emitActivityChange
=
()
=>
{
if
(
props
.
limit
===
1
)
{
const
pointActivity
=
pointActivityList
.
value
.
length
>
0
?
pointActivityList
.
value
[
0
]
:
null
emit
(
'update:modelValue'
,
pointActivity
?.
id
||
0
)
emit
(
'change'
,
pointActivity
)
}
else
{
emit
(
'update:modelValue'
,
pointActivityList
.
value
.
map
((
pointActivity
)
=>
pointActivity
.
id
)
)
emit
(
'change'
,
pointActivityList
.
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
;
cursor
:
pointer
;
}
.spu-pic
{
position
:
relative
;
}
.del-icon
{
position
:
absolute
;
top
:
-10px
;
right
:
-10px
;
z-index
:
1
;
width
:
20px
!important
;
height
:
20px
!important
;
}
</
style
>
src/views/mall/promotion/point/components/PointTableSelect.vue
0 → 100644
View file @
a132f07d
<
template
>
<Dialog
v-model=
"dialogVisible"
:appendToBody=
"true"
title=
"选择活动"
width=
"70%"
>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
ref=
"queryFormRef"
:inline=
"true"
:model=
"queryParams"
class=
"-mb-15px"
label-width=
"68px"
>
<el-form-item
label=
"活动状态"
prop=
"status"
>
<el-select
v-model=
"queryParams.status"
class=
"!w-240px"
clearable
placeholder=
"请选择活动状态"
>
<el-option
v-for=
"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key=
"dict.value"
:label=
"dict.label"
:value=
"dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button
@
click=
"handleQuery"
>
<Icon
class=
"mr-5px"
icon=
"ep:search"
/>
搜索
</el-button>
<el-button
@
click=
"resetQuery"
>
<Icon
class=
"mr-5px"
icon=
"ep:refresh"
/>
重置
</el-button>
</el-form-item>
</el-form>
<el-table
v-loading=
"loading"
:data=
"list"
show-overflow-tooltip
>
<!-- 1. 多选模式(不能使用type="selection",Element会忽略Header插槽) -->
<el-table-column
v-if=
"multiple"
width=
"55"
>
<template
#
header
>
<el-checkbox
v-model=
"isCheckAll"
:indeterminate=
"isIndeterminate"
@
change=
"handleCheckAll"
/>
</
template
>
<
template
#
default=
"{ row }"
>
<el-checkbox
v-model=
"checkedStatus[row.id]"
@
change=
"(checked: boolean) => handleCheckOne(checked, row, true)"
/>
</
template
>
</el-table-column>
<!-- 2. 单选模式 -->
<el-table-column
v-else
label=
"#"
width=
"55"
>
<
template
#
default=
"{ row }"
>
<el-radio
v-model=
"selectedActivityId"
:value=
"row.id"
@
change=
"handleSingleSelected(row)"
>
<!-- 空格不能省略,是为了让单选框不显示label,如果不指定label不会有选中的效果 -->
</el-radio>
</
template
>
</el-table-column>
<el-table-column
label=
"活动编号"
min-width=
"80"
prop=
"id"
/>
<el-table-column
label=
"商品图片"
min-width=
"80"
prop=
"spuName"
>
<
template
#
default=
"scope"
>
<el-image
:preview-src-list=
"[scope.row.picUrl]"
:src=
"scope.row.picUrl"
class=
"h-40px w-40px"
preview-teleported
/>
</
template
>
</el-table-column>
<el-table-column
label=
"商品标题"
min-width=
"300"
prop=
"spuName"
/>
<el-table-column
:formatter=
"fenToYuanFormat"
label=
"原价"
min-width=
"100"
prop=
"marketPrice"
/>
<el-table-column
label=
"原价"
min-width=
"100"
prop=
"marketPrice"
/>
<el-table-column
align=
"center"
label=
"活动状态"
min-width=
"100"
prop=
"status"
>
<
template
#
default=
"scope"
>
<dict-tag
:type=
"DICT_TYPE.COMMON_STATUS"
:value=
"scope.row.status"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"库存"
min-width=
"80"
prop=
"stock"
/>
<el-table-column
align=
"center"
label=
"总库存"
min-width=
"80"
prop=
"totalStock"
/>
<el-table-column
align=
"center"
label=
"已兑换数量"
min-width=
"100"
prop=
"redeemedQuantity"
>
<
template
#
default=
"{ row }"
>
{{
getRedeemedQuantity
(
row
)
}}
</
template
>
</el-table-column>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"创建时间"
prop=
"createTime"
width=
"180px"
/>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit=
"queryParams.pageSize"
v-model:page=
"queryParams.pageNo"
:total=
"total"
@
pagination=
"getList"
/>
</ContentWrap>
<
template
v-if=
"multiple"
#
footer
>
<el-button
type=
"primary"
@
click=
"handleEmitChange"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</Dialog>
</template>
<
script
lang=
"ts"
setup
>
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
CHANGE_EVENT
}
from
'element-plus'
import
{
PointActivityApi
,
PointActivityVO
}
from
'@/api/mall/promotion/point'
import
{
fenToYuanFormat
}
from
'@/utils/formatter'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
dateFormatter
}
from
'@/utils/formatTime'
/**
* 活动表格选择对话框
* 1. 单选模式:
* 1.1 点击表格左侧的单选框时,结束选择,并关闭对话框
* 1.2 再次打开时,保持选中状态
* 2. 多选模式:
* 2.1 点击表格左侧的多选框时,记录选中的活动
* 2.2 切换分页时,保持活动的选中状态
* 2.3 点击右下角的确定按钮时,结束选择,关闭对话框
* 2.4 再次打开时,保持选中状态
*/
defineOptions
({
name
:
'PointTableSelect'
})
defineProps
({
// 多选模式
multiple
:
propTypes
.
bool
.
def
(
false
)
})
// 列表的总页数
const
total
=
ref
(
0
)
// 列表的数据
const
list
=
ref
<
PointActivityVO
[]
>
([])
// 列表的加载中
const
loading
=
ref
(
false
)
// 弹窗的是否展示
const
dialogVisible
=
ref
(
false
)
// 查询参数
const
queryParams
=
ref
({
pageNo
:
1
,
pageSize
:
10
,
name
:
null
,
status
:
undefined
})
const
getRedeemedQuantity
=
computed
(()
=>
(
row
:
any
)
=>
(
row
.
totalStock
||
0
)
-
(
row
.
stock
||
0
))
// 获得商品已兑换数量
/** 打开弹窗 */
const
open
=
(
pointList
?:
PointActivityVO
[])
=>
{
// 重置
checkedActivities
.
value
=
[]
checkedStatus
.
value
=
{}
isCheckAll
.
value
=
false
isIndeterminate
.
value
=
false
// 处理已选中
if
(
pointList
&&
pointList
.
length
>
0
)
{
checkedActivities
.
value
=
[...
pointList
]
checkedStatus
.
value
=
Object
.
fromEntries
(
pointList
.
map
((
activityVO
)
=>
[
activityVO
.
id
,
true
]))
}
dialogVisible
.
value
=
true
resetQuery
()
}
// 提供 open 方法,用于打开弹窗
defineExpose
({
open
})
/** 查询列表 */
const
getList
=
async
()
=>
{
loading
.
value
=
true
try
{
const
data
=
await
PointActivityApi
.
getPointActivityPage
(
queryParams
.
value
)
list
.
value
=
data
.
list
total
.
value
=
data
.
total
// checkbox绑定undefined会有问题,需要给一个bool值
list
.
value
.
forEach
(
(
activityVO
)
=>
(
checkedStatus
.
value
[
activityVO
.
id
]
=
checkedStatus
.
value
[
activityVO
.
id
]
||
false
)
)
// 计算全选框状态
calculateIsCheckAll
()
}
finally
{
loading
.
value
=
false
}
}
/** 搜索按钮操作 */
const
handleQuery
=
()
=>
{
queryParams
.
value
.
pageNo
=
1
getList
()
}
/** 重置按钮操作 */
const
resetQuery
=
()
=>
{
queryParams
.
value
=
{
pageNo
:
1
,
pageSize
:
10
,
name
:
null
,
status
:
undefined
}
getList
()
}
// 是否全选
const
isCheckAll
=
ref
(
false
)
// 全选框是否处于中间状态:不是全部选中 && 任意一个选中
const
isIndeterminate
=
ref
(
false
)
// 选中的活动
const
checkedActivities
=
ref
<
PointActivityVO
[]
>
([])
// 选中状态:key为活动ID,value为是否选中
const
checkedStatus
=
ref
<
Record
<
string
,
boolean
>>
({})
// 选中的活动 activityId
const
selectedActivityId
=
ref
()
/** 单选中时触发 */
const
handleSingleSelected
=
(
pointActivityVO
:
PointActivityVO
)
=>
{
emits
(
CHANGE_EVENT
,
pointActivityVO
)
// 关闭弹窗
dialogVisible
.
value
=
false
// 记住上次选择的ID
selectedActivityId
.
value
=
pointActivityVO
.
id
}
/** 多选完成 */
const
handleEmitChange
=
()
=>
{
// 关闭弹窗
dialogVisible
.
value
=
false
emits
(
CHANGE_EVENT
,
[...
checkedActivities
.
value
])
}
/** 确认选择时的触发事件 */
const
emits
=
defineEmits
<
{
(
e
:
CHANGE_EVENT
,
v
:
PointActivityVO
|
PointActivityVO
[]
|
any
):
void
}
>
()
/** 全选/全不选 */
const
handleCheckAll
=
(
checked
:
boolean
)
=>
{
isCheckAll
.
value
=
checked
isIndeterminate
.
value
=
false
list
.
value
.
forEach
((
pointActivity
)
=>
handleCheckOne
(
checked
,
pointActivity
,
false
))
}
/**
* 选中一行
* @param checked 是否选中
* @param pointActivity 活动
* @param isCalcCheckAll 是否计算全选
*/
const
handleCheckOne
=
(
checked
:
boolean
,
pointActivity
:
PointActivityVO
,
isCalcCheckAll
:
boolean
)
=>
{
if
(
checked
)
{
checkedActivities
.
value
.
push
(
pointActivity
)
checkedStatus
.
value
[
pointActivity
.
id
]
=
true
}
else
{
const
index
=
findCheckedIndex
(
pointActivity
)
if
(
index
>
-
1
)
{
checkedActivities
.
value
.
splice
(
index
,
1
)
checkedStatus
.
value
[
pointActivity
.
id
]
=
false
isCheckAll
.
value
=
false
}
}
// 计算全选框状态
if
(
isCalcCheckAll
)
{
calculateIsCheckAll
()
}
}
// 查找活动在已选中活动列表中的索引
const
findCheckedIndex
=
(
activityVO
:
PointActivityVO
)
=>
checkedActivities
.
value
.
findIndex
((
item
)
=>
item
.
id
===
activityVO
.
id
)
// 计算全选框状态
const
calculateIsCheckAll
=
()
=>
{
isCheckAll
.
value
=
list
.
value
.
every
((
activityVO
)
=>
checkedStatus
.
value
[
activityVO
.
id
])
// 计算中间状态:不是全部选中 && 任意一个选中
isIndeterminate
.
value
=
!
isCheckAll
.
value
&&
list
.
value
.
some
((
activityVO
)
=>
checkedStatus
.
value
[
activityVO
.
id
])
}
</
script
>
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