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
Unverified
Commit
8f97155b
authored
Nov 24, 2023
by
芋道源码
Committed by
Gitee
Nov 24, 2023
Browse files
Options
Browse Files
Download
Plain Diff
!328 商城装修
Merge pull request !328 from 疯狂的世界/dev
parents
ebb19cfe
c3cad3f3
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
871 additions
and
200 deletions
+871
-200
src/api/mall/promotion/coupon/couponTemplate.ts
+7
-0
src/components/DiyEditor/components/mobile/CouponCard/component.tsx
+78
-0
src/components/DiyEditor/components/mobile/CouponCard/config.ts
+47
-0
src/components/DiyEditor/components/mobile/CouponCard/index.vue
+142
-0
src/components/DiyEditor/components/mobile/CouponCard/property.vue
+104
-0
src/components/DiyEditor/components/mobile/ProductCard/config.ts
+1
-1
src/components/DiyEditor/components/mobile/ProductList/config.ts
+64
-0
src/components/DiyEditor/components/mobile/ProductList/index.vue
+131
-0
src/components/DiyEditor/components/mobile/ProductList/property.vue
+99
-0
src/components/DiyEditor/util.ts
+7
-3
src/components/ShortcutDateRangePicker/index.vue
+0
-5
src/utils/formatTime.ts
+4
-1
src/views/mall/product/comment/CommentForm.vue
+5
-26
src/views/mall/product/comment/index.vue
+1
-1
src/views/mall/product/spu/components/SpuShowcase.vue
+41
-25
src/views/mall/product/spu/components/SpuTableSelect.vue
+125
-73
src/views/mall/promotion/coupon/components/CouponSelect.vue
+3
-7
src/views/mall/promotion/coupon/template/CouponTemplateForm.vue
+3
-58
src/views/mall/statistics/trade/index.vue
+9
-0
No files found.
src/api/mall/promotion/coupon/couponTemplate.ts
View file @
8f97155b
...
@@ -73,6 +73,13 @@ export function getCouponTemplatePage(params: PageParam) {
...
@@ -73,6 +73,13 @@ export function getCouponTemplatePage(params: PageParam) {
})
})
}
}
// 获得优惠劵模板分页
export
function
getCouponTemplateList
(
ids
:
number
[])
{
return
request
.
get
({
url
:
`/promotion/coupon-template/list?ids=
${
ids
}
`
})
}
// 导出优惠劵模板 Excel
// 导出优惠劵模板 Excel
export
function
exportCouponTemplateExcel
(
params
:
PageParam
)
{
export
function
exportCouponTemplateExcel
(
params
:
PageParam
)
{
return
request
.
get
({
return
request
.
get
({
...
...
src/components/DiyEditor/components/mobile/CouponCard/component.tsx
0 → 100644
View file @
8f97155b
import
*
as
CouponTemplateApi
from
'@/api/mall/promotion/coupon/couponTemplate'
import
{
CouponTemplateValidityTypeEnum
,
PromotionDiscountTypeEnum
}
from
'@/utils/constants'
import
{
floatToFixed2
}
from
'@/utils'
import
{
formatDate
}
from
'@/utils/formatTime'
// 优惠值
export
const
CouponDiscount
=
defineComponent
({
name
:
'CouponDiscount'
,
props
:
{
coupon
:
{
type
:
CouponTemplateApi
.
CouponTemplateVO
}
},
setup
(
props
)
{
const
coupon
=
props
.
coupon
as
CouponTemplateApi
.
CouponTemplateVO
// 折扣
let
value
=
coupon
.
discountPercent
+
''
let
suffix
=
' 折'
// 满减
if
(
coupon
.
discountType
===
PromotionDiscountTypeEnum
.
PRICE
.
type
)
{
value
=
floatToFixed2
(
coupon
.
discountPrice
)
suffix
=
' 元'
}
return
()
=>
(
<
div
>
<
span
class=
{
'text-20px font-bold'
}
>
{
value
}
</
span
>
<
span
>
{
suffix
}
</
span
>
</
div
>
)
}
})
// 优惠描述
export
const
CouponDiscountDesc
=
defineComponent
({
name
:
'CouponDiscountDesc'
,
props
:
{
coupon
:
{
type
:
CouponTemplateApi
.
CouponTemplateVO
}
},
setup
(
props
)
{
const
coupon
=
props
.
coupon
as
CouponTemplateApi
.
CouponTemplateVO
// 使用条件
const
useCondition
=
coupon
.
usePrice
>
0
?
`满
${
floatToFixed2
(
coupon
.
usePrice
)}
元,`
:
''
// 优惠描述
const
discountDesc
=
coupon
.
discountType
===
PromotionDiscountTypeEnum
.
PRICE
.
type
?
`减
${
floatToFixed2
(
coupon
.
discountPrice
)}
元`
:
`打
${
coupon
.
discountPercent
}
折`
return
()
=>
(
<
div
>
<
span
>
{
useCondition
}
</
span
>
<
span
>
{
discountDesc
}
</
span
>
</
div
>
)
}
})
// 有效期
export
const
CouponValidTerm
=
defineComponent
({
name
:
'CouponValidTerm'
,
props
:
{
coupon
:
{
type
:
CouponTemplateApi
.
CouponTemplateVO
}
},
setup
(
props
)
{
const
coupon
=
props
.
coupon
as
CouponTemplateApi
.
CouponTemplateVO
const
text
=
coupon
.
validityType
===
CouponTemplateValidityTypeEnum
.
DATE
.
type
?
`有效期:
${
formatDate
(
coupon
.
validStartTime
,
'YYYY-MM-DD'
)}
至
${
formatDate
(
coupon
.
validEndTime
,
'YYYY-MM-DD'
)}
`
:
`领取后第
${
coupon
.
fixedStartTerm
}
-
${
coupon
.
fixedEndTerm
}
天内可用`
return
()
=>
<
div
>
{
text
}
</
div
>
}
})
src/components/DiyEditor/components/mobile/CouponCard/config.ts
0 → 100644
View file @
8f97155b
import
{
ComponentStyle
,
DiyComponent
}
from
'@/components/DiyEditor/util'
/** 商品卡片属性 */
export
interface
CouponCardProperty
{
// 列数
columns
:
number
// 背景图
bgImg
:
string
// 文字颜色
textColor
:
string
// 按钮样式
button
:
{
// 颜色
color
:
string
// 背景颜色
bgColor
:
string
}
// 间距
space
:
number
// 优惠券编号列表
couponIds
:
number
[]
// 组件样式
style
:
ComponentStyle
}
// 定义组件
export
const
component
=
{
id
:
'CouponCard'
,
name
:
'优惠券'
,
icon
:
'ep:ticket'
,
property
:
{
columns
:
1
,
bgImg
:
''
,
textColor
:
'#E9B461'
,
button
:
{
color
:
'#434343'
,
bgColor
:
''
},
space
:
0
,
couponIds
:
[],
style
:
{
bgType
:
'color'
,
bgColor
:
''
,
marginBottom
:
8
}
as
ComponentStyle
}
}
as
DiyComponent
<
CouponCardProperty
>
src/components/DiyEditor/components/mobile/CouponCard/index.vue
0 → 100644
View file @
8f97155b
<
template
>
<el-scrollbar
class=
"z-1 min-h-30px"
wrap-class=
"w-full"
ref=
"containerRef"
>
<div
class=
"flex flex-row text-12px"
:style=
"
{
gap: `${property.space}px`,
width: scrollbarWidth
}"
>
<div
class=
"box-content"
:style=
"
{
background: property.bgImg
? `url(${property.bgImg}) 100% center / 100% 100% no-repeat`
: '#fff',
width: `${couponWidth}px`,
color: property.textColor
}"
v-for="(coupon, index) in couponList"
:key="index"
>
<!-- 布局1:1列-->
<div
v-if=
"property.columns === 1"
class=
"m-l-16px flex flex-row justify-between p-8px"
>
<div
class=
"flex flex-col justify-evenly gap-4px"
>
<!-- 优惠值 -->
<CouponDiscount
:coupon=
"coupon"
/>
<!-- 优惠描述 -->
<CouponDiscountDesc
:coupon=
"coupon"
/>
<!-- 有效期 -->
<CouponValidTerm
:coupon=
"coupon"
/>
</div>
<div
class=
"flex flex-col justify-evenly"
>
<div
class=
"rounded-20px p-x-8px p-y-2px"
:style=
"
{
color: property.button.color,
background: property.button.bgColor
}"
>
立即领取
</div>
</div>
</div>
<!-- 布局2:2列-->
<div
v-else-if=
"property.columns === 2"
class=
"m-l-16px flex flex-row justify-between p-8px"
>
<div
class=
"flex flex-col justify-evenly gap-4px"
>
<!-- 优惠值 -->
<CouponDiscount
:coupon=
"coupon"
/>
<div>
{{
coupon
.
name
}}
</div>
</div>
<div
class=
"flex flex-col"
>
<div
class=
"h-full w-20px rounded-20px p-x-2px p-y-8px text-center"
:style=
"
{
color: property.button.color,
background: property.button.bgColor
}"
>
立即领取
</div>
</div>
</div>
<!-- 布局3:3列-->
<div
v-else
class=
"flex flex-col items-center justify-around gap-4px p-4px"
>
<!-- 优惠值 -->
<CouponDiscount
:coupon=
"coupon"
/>
<div>
{{
coupon
.
name
}}
</div>
<div
class=
"rounded-20px p-x-8px p-y-2px"
:style=
"
{
color: property.button.color,
background: property.button.bgColor
}"
>
立即领取
</div>
</div>
</div>
</div>
</el-scrollbar>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
CouponCardProperty
}
from
'./config'
import
*
as
CouponTemplateApi
from
'@/api/mall/promotion/coupon/couponTemplate'
import
{
CouponDiscount
}
from
'./component'
import
{
CouponDiscountDesc
,
CouponValidTerm
}
from
'@/components/DiyEditor/components/mobile/CouponCard/component'
/** 商品卡片 */
defineOptions
({
name
:
'CouponCard'
})
// 定义属性
const
props
=
defineProps
<
{
property
:
CouponCardProperty
}
>
()
// 商品列表
const
couponList
=
ref
<
CouponTemplateApi
.
CouponTemplateVO
[]
>
([])
watch
(
()
=>
props
.
property
.
couponIds
,
async
()
=>
{
if
(
props
.
property
.
couponIds
?.
length
>
0
)
{
couponList
.
value
=
await
CouponTemplateApi
.
getCouponTemplateList
(
props
.
property
.
couponIds
)
}
},
{
immediate
:
true
,
deep
:
true
}
)
// 手机宽度
const
phoneWidth
=
ref
(
375
)
// 容器
const
containerRef
=
ref
()
// 滚动条宽度
const
scrollbarWidth
=
ref
(
'100%'
)
// 优惠券的宽度
const
couponWidth
=
ref
(
375
)
// 计算布局参数
watch
(
()
=>
[
props
.
property
,
phoneWidth
,
couponList
.
value
.
length
],
()
=>
{
// 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
couponWidth
.
value
=
(
phoneWidth
.
value
*
0.95
-
props
.
property
.
space
*
(
props
.
property
.
columns
-
1
))
/
props
.
property
.
columns
// 显示滚动条
scrollbarWidth
.
value
=
`
${
couponWidth
.
value
*
couponList
.
value
.
length
+
props
.
property
.
space
*
(
couponList
.
value
.
length
-
1
)
}
px`
},
{
immediate
:
true
,
deep
:
true
}
)
onMounted
(()
=>
{
// 提取手机宽度
phoneWidth
.
value
=
containerRef
.
value
?.
wrapRef
?.
offsetWidth
||
375
})
</
script
>
<
style
scoped
lang=
"scss"
></
style
>
src/components/DiyEditor/components/mobile/CouponCard/property.vue
0 → 100644
View file @
8f97155b
<
template
>
<ComponentContainerProperty
v-model=
"formData.style"
>
<el-form
label-width=
"80px"
:model=
"formData"
>
<el-card
header=
"优惠券列表"
class=
"property-group"
shadow=
"never"
>
<div
v-for=
"(coupon, index) in couponList"
:key=
"index"
class=
"flex items-center justify-between"
>
<el-text
size=
"large"
truncated
>
{{
coupon
.
name
}}
</el-text>
<el-text
type=
"info"
truncated
>
<span
v-if=
"coupon.usePrice > 0"
>
满
{{
floatToFixed2
(
coupon
.
usePrice
)
}}
元,
</span>
<span
v-if=
"coupon.discountType === PromotionDiscountTypeEnum.PRICE.type"
>
减
{{
floatToFixed2
(
coupon
.
discountPrice
)
}}
元
</span>
<span
v-else
>
打
{{
coupon
.
discountPercent
}}
折
</span>
</el-text>
</div>
<el-form-item
label-width=
"0"
>
<el-button
@
click=
"handleAddCoupon"
type=
"primary"
plain
class=
"m-t-8px w-full"
>
<Icon
icon=
"ep:plus"
class=
"mr-5px"
/>
添加
</el-button>
</el-form-item>
</el-card>
<el-card
header=
"优惠券样式"
class=
"property-group"
shadow=
"never"
>
<el-form-item
label=
"列数"
prop=
"type"
>
<el-radio-group
v-model=
"formData.columns"
>
<el-tooltip
class=
"item"
content=
"一列"
placement=
"bottom"
>
<el-radio-button
:label=
"1"
>
<Icon
icon=
"fluent:text-column-one-24-filled"
/>
</el-radio-button>
</el-tooltip>
<el-tooltip
class=
"item"
content=
"二列"
placement=
"bottom"
>
<el-radio-button
:label=
"2"
>
<Icon
icon=
"fluent:text-column-two-24-filled"
/>
</el-radio-button>
</el-tooltip>
<el-tooltip
class=
"item"
content=
"三列"
placement=
"bottom"
>
<el-radio-button
:label=
"3"
>
<Icon
icon=
"fluent:text-column-three-24-filled"
/>
</el-radio-button>
</el-tooltip>
</el-radio-group>
</el-form-item>
<el-form-item
label=
"背景图片"
prop=
"bgImg"
>
<UploadImg
v-model=
"formData.bgImg"
height=
"80px"
width=
"100%"
class=
"min-w-160px"
/>
</el-form-item>
<el-form-item
label=
"文字颜色"
prop=
"textColor"
>
<ColorInput
v-model=
"formData.textColor"
/>
</el-form-item>
<el-form-item
label=
"按钮背景"
prop=
"button.bgColor"
>
<ColorInput
v-model=
"formData.button.bgColor"
/>
</el-form-item>
<el-form-item
label=
"按钮文字"
prop=
"button.color"
>
<ColorInput
v-model=
"formData.button.color"
/>
</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>
<!-- 优惠券选择 -->
<CouponSelect
ref=
"couponSelectDialog"
v-model:multiple-selection=
"couponList"
/>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
CouponCardProperty
}
from
'./config'
import
{
usePropertyForm
}
from
'@/components/DiyEditor/util'
import
*
as
CouponTemplateApi
from
'@/api/mall/promotion/coupon/couponTemplate'
import
{
floatToFixed2
}
from
'@/utils'
import
{
PromotionDiscountTypeEnum
}
from
'@/utils/constants'
import
CouponSelect
from
'@/views/mall/promotion/coupon/components/CouponSelect.vue'
// 优惠券卡片属性面板
defineOptions
({
name
:
'CouponCardProperty'
})
const
props
=
defineProps
<
{
modelValue
:
CouponCardProperty
}
>
()
const
emit
=
defineEmits
([
'update:modelValue'
])
const
{
formData
}
=
usePropertyForm
(
props
.
modelValue
,
emit
)
// 优惠券列表
const
couponList
=
ref
<
CouponTemplateApi
.
CouponTemplateVO
[]
>
([])
const
couponSelectDialog
=
ref
()
// 添加优惠券
const
handleAddCoupon
=
()
=>
{
couponSelectDialog
.
value
.
open
()
}
watch
(
()
=>
couponList
.
value
,
()
=>
{
formData
.
value
.
couponIds
=
couponList
.
value
.
map
((
coupon
)
=>
coupon
.
id
)
}
)
</
script
>
<
style
scoped
lang=
"scss"
></
style
>
src/components/DiyEditor/components/mobile/ProductCard/config.ts
View file @
8f97155b
...
@@ -62,7 +62,7 @@ export interface ProductCardFieldProperty {
...
@@ -62,7 +62,7 @@ export interface ProductCardFieldProperty {
export
const
component
=
{
export
const
component
=
{
id
:
'ProductCard'
,
id
:
'ProductCard'
,
name
:
'商品卡片'
,
name
:
'商品卡片'
,
icon
:
'
system-uicons:carousel
'
,
icon
:
'
fluent:text-column-two-left-24-filled
'
,
property
:
{
property
:
{
layoutType
:
'oneColBigImg'
,
layoutType
:
'oneColBigImg'
,
fields
:
{
fields
:
{
...
...
src/components/DiyEditor/components/mobile/ProductList/config.ts
0 → 100644
View file @
8f97155b
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
:
'fluent:text-column-two-24-filled'
,
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
>
src/components/DiyEditor/components/mobile/ProductList/index.vue
0 → 100644
View file @
8f97155b
<
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
>
src/components/DiyEditor/components/mobile/ProductList/property.vue
0 → 100644
View file @
8f97155b
<
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
>
src/components/DiyEditor/util.ts
View file @
8f97155b
...
@@ -107,11 +107,15 @@ export const PAGE_LIBS = [
...
@@ -107,11 +107,15 @@ export const PAGE_LIBS = [
extended
:
true
,
extended
:
true
,
components
:
[
'ImageBar'
,
'Carousel'
,
'TitleBar'
,
'VideoPlayer'
,
'Divider'
,
'MagicCube'
]
components
:
[
'ImageBar'
,
'Carousel'
,
'TitleBar'
,
'VideoPlayer'
,
'Divider'
,
'MagicCube'
]
},
},
{
name
:
'商品组件'
,
extended
:
true
,
components
:
[
'ProductCard'
]
},
{
name
:
'商品组件'
,
extended
:
true
,
components
:
[
'ProductCard'
,
'ProductList'
]
},
{
{
name
:
'会员组件'
,
name
:
'会员组件'
,
extended
:
true
,
extended
:
true
,
components
:
[
'UserCard'
,
'
OrderCard'
,
'WalletCard'
,
'CouponCard
'
]
components
:
[
'UserCard'
,
'
UserOrder'
,
'UserWallet'
,
'UserCoupon
'
]
},
},
{
name
:
'营销组件'
,
extended
:
true
,
components
:
[
'Combination'
,
'Seckill'
,
'Point'
,
'Coupon'
]
}
{
name
:
'营销组件'
,
extended
:
true
,
components
:
[
'CombinationCard'
,
'SeckillCard'
,
'PointCard'
,
'CouponCard'
]
}
]
as
DiyComponentLibrary
[]
]
as
DiyComponentLibrary
[]
src/components/ShortcutDateRangePicker/index.vue
View file @
8f97155b
...
@@ -74,11 +74,6 @@ const emits = defineEmits<{
...
@@ -74,11 +74,6 @@ const emits = defineEmits<{
}
>
()
}
>
()
/** 触发时间范围选中事件 */
/** 触发时间范围选中事件 */
const
emitDateRangePicker
=
async
()
=>
{
const
emitDateRangePicker
=
async
()
=>
{
// 开始与截止在同一天的, 折线图出不来, 需要延长一天
if
(
DateUtil
.
isSameDay
(
times
.
value
[
0
],
times
.
value
[
1
]))
{
// 前天
times
.
value
[
0
]
=
DateUtil
.
formatDate
(
dayjs
(
times
.
value
[
0
]).
subtract
(
1
,
'd'
))
}
emits
(
'change'
,
times
.
value
)
emits
(
'change'
,
times
.
value
)
}
}
...
...
src/utils/formatTime.ts
View file @
8f97155b
...
@@ -335,5 +335,8 @@ export function getDateRange(
...
@@ -335,5 +335,8 @@ export function getDateRange(
beginDate
:
dayjs
.
ConfigType
,
beginDate
:
dayjs
.
ConfigType
,
endDate
:
dayjs
.
ConfigType
endDate
:
dayjs
.
ConfigType
):
[
string
,
string
]
{
):
[
string
,
string
]
{
return
[
dayjs
(
beginDate
).
startOf
(
'd'
).
toString
(),
dayjs
(
endDate
).
endOf
(
'd'
).
toString
()]
return
[
dayjs
(
beginDate
).
startOf
(
'd'
).
format
(
'YYYY-MM-DD HH:mm:ss'
),
dayjs
(
endDate
).
endOf
(
'd'
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
]
}
}
src/views/mall/product/comment/CommentForm.vue
View file @
8f97155b
...
@@ -8,14 +8,7 @@
...
@@ -8,14 +8,7 @@
v-loading=
"formLoading"
v-loading=
"formLoading"
>
>
<el-form-item
label=
"商品"
prop=
"spuId"
>
<el-form-item
label=
"商品"
prop=
"spuId"
>
<div
@
click=
"handleSelectSpu"
class=
"h-60px w-60px"
>
<SpuShowcase
v-model=
"formData.spuId"
:limit=
"1"
/>
<div
v-if=
"spuData && spuData.picUrl"
>
<el-image
:src=
"spuData.picUrl"
/>
</div>
<div
v-else
class=
"select-box"
>
<Icon
icon=
"ep:plus"
/>
</div>
</div>
</el-form-item>
</el-form-item>
<el-form-item
label=
"商品规格"
prop=
"skuId"
v-if=
"formData.spuId"
>
<el-form-item
label=
"商品规格"
prop=
"skuId"
v-if=
"formData.spuId"
>
<div
@
click=
"handleSelectSku"
class=
"h-60px w-60px"
>
<div
@
click=
"handleSelectSku"
class=
"h-60px w-60px"
>
...
@@ -51,12 +44,11 @@
...
@@ -51,12 +44,11 @@
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</
template
>
</Dialog>
</Dialog>
<SpuTableSelect
ref=
"spuTableSelectRef"
@
change=
"handleSpuChange"
/>
<SkuTableSelect
ref=
"skuTableSelectRef"
@
change=
"handleSkuChange"
:spu-id=
"formData.spuId"
/>
<SkuTableSelect
ref=
"skuTableSelectRef"
@
change=
"handleSkuChange"
:spu-id=
"spuData.id"
/>
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
*
as
CommentApi
from
'@/api/mall/product/comment'
import
*
as
CommentApi
from
'@/api/mall/product/comment'
import
Spu
TableSelect
from
'@/views/mall/product/spu/components/SpuTableSelect
.vue'
import
Spu
Showcase
from
'@/views/mall/product/spu/components/SpuShowcase
.vue'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
SkuTableSelect
from
'@/views/mall/product/spu/components/SkuTableSelect.vue'
import
SkuTableSelect
from
'@/views/mall/product/spu/components/SkuTableSelect.vue'
...
@@ -72,8 +64,7 @@ const formData = ref({
...
@@ -72,8 +64,7 @@ const formData = ref({
userId
:
undefined
,
userId
:
undefined
,
userNickname
:
undefined
,
userNickname
:
undefined
,
userAvatar
:
undefined
,
userAvatar
:
undefined
,
spuId
:
undefined
,
spuId
:
0
,
spuName
:
undefined
,
skuId
:
undefined
,
skuId
:
undefined
,
descriptionScores
:
5
,
descriptionScores
:
5
,
benefitScores
:
5
,
benefitScores
:
5
,
...
@@ -90,7 +81,6 @@ const formRules = reactive({
...
@@ -90,7 +81,6 @@ const formRules = reactive({
benefitScores
:
[{
required
:
true
,
message
:
'服务星级不能为空'
,
trigger
:
'blur'
}]
benefitScores
:
[{
required
:
true
,
message
:
'服务星级不能为空'
,
trigger
:
'blur'
}]
})
})
const
formRef
=
ref
()
// 表单 Ref
const
formRef
=
ref
()
// 表单 Ref
const
spuData
=
ref
<
ProductSpuApi
.
Spu
>
({})
const
skuData
=
ref
({
const
skuData
=
ref
({
id
:
-
1
,
id
:
-
1
,
name
:
''
,
name
:
''
,
...
@@ -149,8 +139,7 @@ const resetForm = () => {
...
@@ -149,8 +139,7 @@ const resetForm = () => {
userId
:
undefined
,
userId
:
undefined
,
userNickname
:
undefined
,
userNickname
:
undefined
,
userAvatar
:
undefined
,
userAvatar
:
undefined
,
spuId
:
undefined
,
spuId
:
0
,
spuName
:
undefined
,
skuId
:
undefined
,
skuId
:
undefined
,
descriptionScores
:
5
,
descriptionScores
:
5
,
benefitScores
:
5
,
benefitScores
:
5
,
...
@@ -160,16 +149,6 @@ const resetForm = () => {
...
@@ -160,16 +149,6 @@ const resetForm = () => {
formRef
.
value
?.
resetFields
()
formRef
.
value
?.
resetFields
()
}
}
/** SPU 表格选择 */
const
spuTableSelectRef
=
ref
()
const
handleSelectSpu
=
()
=>
{
spuTableSelectRef
.
value
.
open
()
}
const
handleSpuChange
=
(
spu
:
ProductSpuApi
.
Spu
)
=>
{
spuData
.
value
=
spu
formData
.
value
.
spuId
=
spu
.
id
}
/** SKU 表格选择 */
/** SKU 表格选择 */
const
skuTableSelectRef
=
ref
()
const
skuTableSelectRef
=
ref
()
const
handleSelectSku
=
()
=>
{
const
handleSelectSku
=
()
=>
{
...
...
src/views/mall/product/comment/index.vue
View file @
8f97155b
...
@@ -59,7 +59,7 @@
...
@@ -59,7 +59,7 @@
<!-- 列表 -->
<!-- 列表 -->
<ContentWrap>
<ContentWrap>
<el-table
v-loading=
"loading"
:data=
"list"
:stripe=
"true"
:show-overflow-tooltip=
"false"
>
<el-table
v-loading=
"loading"
:data=
"list"
:stripe=
"true"
:show-overflow-tooltip=
"false"
>
<el-table-column
label=
"评论编号"
align=
"center"
prop=
"id"
min-width=
"
5
0"
/>
<el-table-column
label=
"评论编号"
align=
"center"
prop=
"id"
min-width=
"
8
0"
/>
<el-table-column
label=
"商品信息"
align=
"center"
min-width=
"400"
>
<el-table-column
label=
"商品信息"
align=
"center"
min-width=
"400"
>
<template
#
default=
"scope"
>
<template
#
default=
"scope"
>
<div
class=
"row flex items-center gap-x-4px"
>
<div
class=
"row flex items-center gap-x-4px"
>
...
...
src/views/mall/product/spu/components/SpuShowcase.vue
View file @
8f97155b
...
@@ -13,54 +13,64 @@
...
@@ -13,54 +13,64 @@
</div>
</div>
</el-tooltip>
</el-tooltip>
</div>
</div>
<el-tooltip
content=
"选择商品"
>
<el-tooltip
content=
"选择商品"
v-if=
"canAdd"
>
<div
<div
class=
"select-box"
@
click=
"openSpuTableSelect"
>
v-show=
"!disabled"
v-if=
"!limit || limit
<
=
productSpus
.
length
"
class=
"select-box"
@
click=
"openSpuTableSelect"
>
<Icon
icon=
"ep:plus"
/>
<Icon
icon=
"ep:plus"
/>
</div>
</div>
</el-tooltip>
</el-tooltip>
</div>
</div>
<!-- 商品选择对话框(表格形式) -->
<!-- 商品选择对话框(表格形式) -->
<SpuTableSelect
ref=
"spuTableSelectRef"
multiple
@
change=
"handleSpuSelected"
/>
<SpuTableSelect
ref=
"spuTableSelectRef"
:multiple=
"limit != 1"
@
change=
"handleSpuSelected"
/>
</
template
>
</
template
>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
SpuTableSelect
from
'@/views/mall/product/spu/components/SpuTableSelect.vue'
import
SpuTableSelect
from
'@/views/mall/product/spu/components/SpuTableSelect.vue'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
array
}
from
'vue-types'
import
{
oneOfType
}
from
'vue-types'
import
{
isArray
}
from
'@/utils/is'
// 商品橱窗,一般用于与商品建立关系时使用
// 商品橱窗,一般用于与商品建立关系时使用
// 提供功能:展示商品列表、添加商品、移除商品
// 提供功能:展示商品列表、添加商品、移除商品
defineOptions
({
name
:
'SpuShowcase'
})
defineOptions
({
name
:
'SpuShowcase'
})
const
props
=
defineProps
({
const
props
=
defineProps
({
modelValue
:
array
<
number
>
().
def
([
]).
isRequired
,
modelValue
:
oneOfType
<
number
|
Array
<
number
>>
([
Number
,
Array
]).
isRequired
,
// 限制数量:默认不限制
// 限制数量:默认不限制
limit
:
propTypes
.
number
.
def
(
0
),
limit
:
propTypes
.
number
.
def
(
Number
.
MAX_VALUE
),
disabled
:
propTypes
.
bool
.
def
(
false
)
disabled
:
propTypes
.
bool
.
def
(
false
)
})
})
// 计算是否可以添加
const
canAdd
=
computed
(()
=>
{
// 情况一:禁用时不可以添加
if
(
props
.
disabled
)
return
false
// 情况二:未指定限制数量时,可以添加
if
(
!
props
.
limit
)
return
true
// 情况三:检查已添加数量是否小于限制数量
return
productSpus
.
value
.
length
<
props
.
limit
})
// 商品列表
// 商品列表
const
productSpus
=
ref
<
ProductSpuApi
.
Spu
[]
>
([])
const
productSpus
=
ref
<
ProductSpuApi
.
Spu
[]
>
([])
watch
(
watch
(
()
=>
props
.
modelValue
,
()
=>
props
.
modelValue
,
async
()
=>
{
async
()
=>
{
if
(
props
.
modelValue
.
length
===
0
)
{
const
ids
=
isArray
(
props
.
modelValue
)
?
// 情况一:多选
props
.
modelValue
:
// 情况二:单选
props
.
modelValue
?
[
props
.
modelValue
]
:
[]
// 不需要返显
if
(
ids
.
length
===
0
)
{
productSpus
.
value
=
[]
productSpus
.
value
=
[]
return
return
}
}
// 只有商品发生变化之后,才去查询商品
// 只有商品发生变化之后,才去查询商品
if
(
if
(
productSpus
.
value
.
length
===
0
||
productSpus
.
value
.
some
((
spu
)
=>
!
ids
.
includes
(
spu
.
id
!
)))
{
productSpus
.
value
.
length
===
0
||
productSpus
.
value
=
await
ProductSpuApi
.
getSpuDetailList
(
ids
)
productSpus
.
value
.
some
((
spu
)
=>
!
props
.
modelValue
.
includes
(
spu
.
id
))
)
{
debugger
productSpus
.
value
=
await
ProductSpuApi
.
getSpuDetailList
(
props
.
modelValue
)
}
}
},
},
{
immediate
:
true
}
{
immediate
:
true
}
...
@@ -77,8 +87,8 @@ const openSpuTableSelect = () => {
...
@@ -77,8 +87,8 @@ const openSpuTableSelect = () => {
* 选择商品后触发
* 选择商品后触发
* @param spus 选中的商品列表
* @param spus 选中的商品列表
*/
*/
const
handleSpuSelected
=
(
spus
:
ProductSpuApi
.
Spu
[])
=>
{
const
handleSpuSelected
=
(
spus
:
ProductSpuApi
.
Spu
|
ProductSpuApi
.
Spu
[])
=>
{
productSpus
.
value
=
spus
productSpus
.
value
=
isArray
(
spus
)
?
spus
:
[
spus
]
emitSpuChange
()
emitSpuChange
()
}
}
...
@@ -92,11 +102,17 @@ const handleRemoveSpu = (index: number) => {
...
@@ -92,11 +102,17 @@ const handleRemoveSpu = (index: number) => {
}
}
const
emit
=
defineEmits
([
'update:modelValue'
,
'change'
])
const
emit
=
defineEmits
([
'update:modelValue'
,
'change'
])
const
emitSpuChange
=
()
=>
{
const
emitSpuChange
=
()
=>
{
emit
(
if
(
props
.
limit
===
1
)
{
'update:modelValue'
,
const
spu
=
productSpus
.
value
.
length
>
0
?
productSpus
.
value
[
0
]
:
null
productSpus
.
value
.
map
((
spu
)
=>
spu
.
id
)
emit
(
'update:modelValue'
,
spu
?.
id
||
0
)
)
emit
(
'change'
,
spu
)
emit
(
'change'
,
productSpus
.
value
)
}
else
{
emit
(
'update:modelValue'
,
productSpus
.
value
.
map
((
spu
)
=>
spu
.
id
)
)
emit
(
'change'
,
productSpus
.
value
)
}
}
}
</
script
>
</
script
>
...
...
src/views/mall/product/spu/components/SpuTableSelect.vue
View file @
8f97155b
<
template
>
<
template
>
<Dialog
v-model=
"dialogVisible"
:appendToBody=
"true"
title=
"选择商品"
width=
"70%"
>
<Dialog
v-model=
"dialogVisible"
:appendToBody=
"true"
title=
"选择商品"
width=
"70%"
>
<ContentWrap>
<ContentWrap>
<el-row
:gutter=
"20"
class=
"mb-10px"
>
<el-form
<el-col
:span=
"6"
>
ref=
"queryFormRef"
:inline=
"true"
:model=
"queryParams"
class=
"-mb-15px"
label-width=
"68px"
>
<el-form-item
label=
"商品名称"
prop=
"name"
>
<el-input
<el-input
v-model=
"queryParams.name"
v-model=
"queryParams.name"
class=
"!w-240px"
class=
"!w-240px"
...
@@ -10,19 +16,19 @@
...
@@ -10,19 +16,19 @@
placeholder=
"请输入商品名称"
placeholder=
"请输入商品名称"
@
keyup
.
enter=
"handleQuery"
@
keyup
.
enter=
"handleQuery"
/>
/>
</el-
col
>
</el-
form-item
>
<el-
col
:span=
"6
"
>
<el-
form-item
label=
"商品分类"
prop=
"categoryId
"
>
<el-tree-select
<el-tree-select
v-model=
"queryParams.categoryId"
v-model=
"queryParams.categoryId"
:data=
"categoryTreeList"
:data=
"categoryTreeList"
:props=
"defaultProps"
:props=
"defaultProps"
check-strictly
check-strictly
class=
"
w-1/1
"
class=
"
!w-240px
"
node-key=
"id"
node-key=
"id"
placeholder=
"请选择商品分类"
placeholder=
"请选择商品分类"
/>
/>
</el-
col
>
</el-
form-item
>
<el-
col
:span=
"6
"
>
<el-
form-item
label=
"创建时间"
prop=
"createTime
"
>
<el-date-picker
<el-date-picker
v-model=
"queryParams.createTime"
v-model=
"queryParams.createTime"
:default-time=
"[new Date('1 00:00:00'), new Date('1 23:59:59')]"
:default-time=
"[new Date('1 00:00:00'), new Date('1 23:59:59')]"
...
@@ -32,8 +38,8 @@
...
@@ -32,8 +38,8 @@
type=
"daterange"
type=
"daterange"
value-format=
"YYYY-MM-DD HH:mm:ss"
value-format=
"YYYY-MM-DD HH:mm:ss"
/>
/>
</el-
col
>
</el-
form-item
>
<el-
col
:span=
"6"
>
<el-
form-item
>
<el-button
@
click=
"handleQuery"
>
<el-button
@
click=
"handleQuery"
>
<Icon
class=
"mr-5px"
icon=
"ep:search"
/>
<Icon
class=
"mr-5px"
icon=
"ep:search"
/>
搜索
搜索
...
@@ -42,30 +48,32 @@
...
@@ -42,30 +48,32 @@
<Icon
class=
"mr-5px"
icon=
"ep:refresh"
/>
<Icon
class=
"mr-5px"
icon=
"ep:refresh"
/>
重置
重置
</el-button>
</el-button>
</el-
col
>
</el-
form-item
>
</el-
row
>
</el-
form
>
<el-table
v-loading=
"loading"
:data=
"list"
show-overflow-tooltip
>
<el-table
v-loading=
"loading"
:data=
"list"
show-overflow-tooltip
>
<!--
多选模式
-->
<!--
1. 多选模式(不能使用type="selection",Element会忽略Header插槽)
-->
<el-table-column
key=
"2"
type=
"selection"
width=
"55"
v-if=
"multiple"
>
<el-table-column
width=
"55"
v-if=
"multiple"
>
<template
#
header
>
<template
#
header
>
<el-checkbox
<el-checkbox
:value=
"allChecked && checkedPageNos.indexOf(queryParams.pageNo) > -1"
v-model=
"isCheckAll"
:indeterminate=
"isIndeterminate"
@
change=
"handleCheckAll"
@
change=
"handleCheckAll"
/>
/>
</
template
>
</
template
>
<
template
#
default=
"{ row }"
>
<
template
#
default=
"{ row }"
>
<el-checkbox
<el-checkbox
:value=
"checkedSpuIds.indexOf(row.id) > -1
"
v-model=
"checkedStatus[row.id]
"
@
change=
"(checked: boolean) => handleCheckOne(checked, row)"
@
change=
"(checked: boolean) => handleCheckOne(checked, row
, true
)"
/>
/>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<!-- 单选模式 -->
<!--
2.
单选模式 -->
<el-table-column
label=
"#"
width=
"55"
v-else
>
<el-table-column
label=
"#"
width=
"55"
v-else
>
<
template
#
default=
"{ row }"
>
<
template
#
default=
"{ row }"
>
<el-radio
:label=
"row.id"
v-model=
"selectedSpuId"
@
change=
"handleSingleSelected(row)"
<el-radio
:label=
"row.id"
v-model=
"selectedSpuId"
@
change=
"handleSingleSelected(row)"
>
>
</el-radio
<!-- 空格不能省略,是为了让单选框不显示label,如果不指定label不会有选中的效果 -->
>
</el-radio>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
key=
"id"
align=
"center"
label=
"商品编号"
prop=
"id"
min-width=
"60"
/>
<el-table-column
key=
"id"
align=
"center"
label=
"商品编号"
prop=
"id"
min-width=
"60"
/>
...
@@ -102,54 +110,71 @@
...
@@ -102,54 +110,71 @@
</template>
</template>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
ElTable
}
from
'element-plus'
import
{
defaultProps
,
handleTree
}
from
'@/utils/tree'
import
{
defaultProps
,
handleTree
}
from
'@/utils/tree'
import
*
as
ProductCategoryApi
from
'@/api/mall/product/category'
import
*
as
ProductCategoryApi
from
'@/api/mall/product/category'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
CHANGE_EVENT
}
from
'element-plus'
type
Spu
=
Required
<
ProductSpuApi
.
Spu
>
type
Spu
=
Required
<
ProductSpuApi
.
Spu
>
/**
* 商品表格选择对话框
* 1. 单选模式:
* 1.1 点击表格左侧的单选框时,结束选择,并关闭对话框
* 1.2 再次打开时,保持选中状态
* 2. 多选模式:
* 2.1 点击表格左侧的多选框时,记录选中的商品
* 2.2 切换分页时,保持商品的选中的状态
* 2.3 点击右下角的确定按钮时,结束选择,关闭对话框
* 2.4 再次打开时,保持选中状态
*/
defineOptions
({
name
:
'SpuTableSelect'
})
defineOptions
({
name
:
'SpuTableSelect'
})
const
props
=
defineProps
({
defineProps
({
// 多选
// 多选
模式
multiple
:
propTypes
.
bool
.
def
(
false
)
multiple
:
propTypes
.
bool
.
def
(
false
)
})
})
const
total
=
ref
(
0
)
// 列表的总页数
// 列表的总页数
const
list
=
ref
<
Spu
[]
>
([])
// 列表的数据
const
total
=
ref
(
0
)
const
loading
=
ref
(
false
)
// 列表的加载中
// 列表的数据
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
list
=
ref
<
Spu
[]
>
([])
// 列表的加载中
const
loading
=
ref
(
false
)
// 弹窗的是否展示
const
dialogVisible
=
ref
(
false
)
// 查询参数
const
queryParams
=
ref
({
const
queryParams
=
ref
({
pageNo
:
1
,
pageNo
:
1
,
pageSize
:
10
,
pageSize
:
10
,
tabType
:
0
,
// 默认获取上架的商品
// 默认获取上架的商品
tabType
:
0
,
name
:
''
,
name
:
''
,
categoryId
:
null
,
categoryId
:
null
,
createTime
:
[]
createTime
:
[]
})
// 查询参数
})
const
selectedSpuId
=
ref
()
// 选中的商品 spuId
/** 打开弹窗 */
/** 打开弹窗 */
const
open
=
(
spus
?:
Spu
[])
=>
{
const
open
=
(
spuList
?:
Spu
[])
=>
{
if
(
spus
&&
spus
.
length
>
0
)
{
// 重置
// todo check-box不显示选中?
checkedSpus
.
value
=
[]
checkedSpus
.
value
=
[...
spus
]
checkedStatus
.
value
=
{}
checkedSpuIds
.
value
=
spus
.
map
((
spu
)
=>
spu
.
id
)
isCheckAll
.
value
=
false
}
else
{
isIndeterminate
.
value
=
false
checkedSpus
.
value
=
[]
checkedSpuIds
.
value
=
[]
// 处理已选中
if
(
spuList
&&
spuList
.
length
>
0
)
{
checkedSpus
.
value
=
[...
spuList
]
checkedStatus
.
value
=
Object
.
fromEntries
(
spuList
.
map
((
spu
)
=>
[
spu
.
id
,
true
]))
}
}
allChecked
.
value
=
false
checkedPageNos
.
value
=
[]
dialogVisible
.
value
=
true
dialogVisible
.
value
=
true
resetQuery
()
resetQuery
()
}
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
// 提供 open 方法,用于打开弹窗
defineExpose
({
open
})
/** 查询列表 */
/** 查询列表 */
const
getList
=
async
()
=>
{
const
getList
=
async
()
=>
{
...
@@ -158,6 +183,12 @@ const getList = async () => {
...
@@ -158,6 +183,12 @@ const getList = async () => {
const
data
=
await
ProductSpuApi
.
getSpuPage
(
queryParams
.
value
)
const
data
=
await
ProductSpuApi
.
getSpuPage
(
queryParams
.
value
)
list
.
value
=
data
.
list
list
.
value
=
data
.
list
total
.
value
=
data
.
total
total
.
value
=
data
.
total
// checkbox绑定undefined会有问题,需要给一个bool值
list
.
value
.
forEach
(
(
spu
)
=>
(
checkedStatus
.
value
[
spu
.
id
]
=
checkedStatus
.
value
[
spu
.
id
]
||
false
)
)
// 计算全选框状态
calculateIsCheckAll
()
}
finally
{
}
finally
{
loading
.
value
=
false
loading
.
value
=
false
}
}
...
@@ -174,7 +205,8 @@ const resetQuery = () => {
...
@@ -174,7 +205,8 @@ const resetQuery = () => {
queryParams
.
value
=
{
queryParams
.
value
=
{
pageNo
:
1
,
pageNo
:
1
,
pageSize
:
10
,
pageSize
:
10
,
tabType
:
0
,
// 默认获取上架的商品
// 默认获取上架的商品
tabType
:
0
,
name
:
''
,
name
:
''
,
categoryId
:
null
,
categoryId
:
null
,
createTime
:
[]
createTime
:
[]
...
@@ -182,65 +214,85 @@ const resetQuery = () => {
...
@@ -182,65 +214,85 @@ const resetQuery = () => {
getList
()
getList
()
}
}
const
allChecked
=
ref
(
false
)
//是否全选
// 是否全选
const
checkedPageNos
=
ref
<
number
[]
>
([])
//选中的页码
const
isCheckAll
=
ref
(
false
)
const
checkedSpuIds
=
ref
<
number
[]
>
([])
//选中的商品ID
// 全选框是否处于中间状态:不是全部选中 && 任意一个选中
const
checkedSpus
=
ref
<
Spu
[]
>
([])
//选中的商品
const
isIndeterminate
=
ref
(
false
)
// 选中的商品
const
checkedSpus
=
ref
<
Spu
[]
>
([])
// 选中状态:key为商品ID,value为是否选中
const
checkedStatus
=
ref
<
Record
<
string
,
boolean
>>
({})
// 选中的商品 spuId
const
selectedSpuId
=
ref
()
/** 单选中时触发 */
/** 单选中时触发 */
const
handleSingleSelected
=
(
row
:
Spu
)
=>
{
const
handleSingleSelected
=
(
spu
:
Spu
)
=>
{
emits
(
'change'
,
row
)
emits
(
CHANGE_EVENT
,
spu
)
// 关闭弹窗
// 关闭弹窗
dialogVisible
.
value
=
false
dialogVisible
.
value
=
false
// 记住上次选择的ID
// 记住上次选择的ID
selectedSpuId
.
value
=
row
.
id
selectedSpuId
.
value
=
spu
.
id
}
}
/** 多选完成 */
/** 多选完成 */
const
handleEmitChange
=
()
=>
{
const
handleEmitChange
=
()
=>
{
// 关闭弹窗
// 关闭弹窗
dialogVisible
.
value
=
false
dialogVisible
.
value
=
false
emits
(
'change'
,
[...
checkedSpus
.
value
])
emits
(
CHANGE_EVENT
,
[...
checkedSpus
.
value
])
}
}
/** 确认选择时的触发事件 */
/** 确认选择时的触发事件 */
const
emits
=
defineEmits
<
{
const
emits
=
defineEmits
<
{
(
e
:
'change'
,
spu
:
Spu
|
Spu
[]
|
any
):
void
change
:
[
spu
:
Spu
|
Spu
[]
|
any
]
}
>
()
}
>
()
/** 全选 */
/** 全选
/全不选
*/
const
handleCheckAll
=
(
checked
:
boolean
)
=>
{
const
handleCheckAll
=
(
checked
:
boolean
)
=>
{
debugger
isCheckAll
.
value
=
checked
console
.
log
(
'checkAll'
,
checked
)
isIndeterminate
.
value
=
false
allChecked
.
value
=
checked
const
index
=
checkedPageNos
.
value
.
indexOf
(
queryParams
.
value
.
pageNo
)
checkedPageNos
.
value
.
push
(
queryParams
.
value
.
pageNo
)
if
(
index
>
-
1
)
{
checkedPageNos
.
value
.
splice
(
index
,
1
)
}
list
.
value
.
forEach
((
item
)
=>
handleCheckOne
(
checked
,
item
))
list
.
value
.
forEach
((
spu
)
=>
handleCheckOne
(
checked
,
spu
,
false
))
}
}
/** 选中一行 */
/**
const
handleCheckOne
=
(
checked
:
boolean
,
spu
:
Spu
)
=>
{
* 选中一行
* @param checked 是否选中
* @param spu 商品
* @param isCalcCheckAll 是否计算全选
*/
const
handleCheckOne
=
(
checked
:
boolean
,
spu
:
Spu
,
isCalcCheckAll
:
boolean
)
=>
{
if
(
checked
)
{
if
(
checked
)
{
const
index
=
checkedSpuIds
.
value
.
indexOf
(
spu
.
id
)
checkedSpus
.
value
.
push
(
spu
)
if
(
index
===
-
1
)
{
checkedStatus
.
value
[
spu
.
id
]
=
true
checkedSpuIds
.
value
.
push
(
spu
.
id
)
checkedSpus
.
value
.
push
(
spu
)
}
}
else
{
}
else
{
const
index
=
checkedSpuIds
.
value
.
indexOf
(
spu
.
id
)
const
index
=
findCheckedIndex
(
spu
)
if
(
index
>
-
1
)
{
if
(
index
>
-
1
)
{
checkedSpuIds
.
value
.
splice
(
index
,
1
)
checkedSpus
.
value
.
splice
(
index
,
1
)
checkedSpus
.
value
.
splice
(
index
,
1
)
checkedStatus
.
value
[
spu
.
id
]
=
false
isCheckAll
.
value
=
false
}
}
}
}
// 计算全选框状态
if
(
isCalcCheckAll
)
{
calculateIsCheckAll
()
}
}
// 查找商品在已选中商品列表中的索引
const
findCheckedIndex
=
(
spu
:
Spu
)
=>
checkedSpus
.
value
.
findIndex
((
item
)
=>
item
.
id
===
spu
.
id
)
// 计算全选框状态
const
calculateIsCheckAll
=
()
=>
{
isCheckAll
.
value
=
list
.
value
.
every
((
spu
)
=>
checkedStatus
.
value
[
spu
.
id
])
// 计算中间状态:不是全部选中 && 任意一个选中
isIndeterminate
.
value
=
!
isCheckAll
.
value
&&
list
.
value
.
some
((
spu
)
=>
checkedStatus
.
value
[
spu
.
id
])
}
}
const
categoryList
=
ref
()
// 分类列表
// 分类列表
const
categoryTreeList
=
ref
()
// 分类树
const
categoryList
=
ref
()
// 分类树
const
categoryTreeList
=
ref
()
/** 初始化 **/
/** 初始化 **/
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
await
getList
()
await
getList
()
...
...
src/views/mall/promotion/coupon/components/CouponSelect.vue
View file @
8f97155b
...
@@ -150,15 +150,14 @@ import {
...
@@ -150,15 +150,14 @@ import {
}
from
'@/views/mall/promotion/coupon/formatter'
}
from
'@/views/mall/promotion/coupon/formatter'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
*
as
CouponTemplateApi
from
'@/api/mall/promotion/coupon/couponTemplate'
import
*
as
CouponTemplateApi
from
'@/api/mall/promotion/coupon/couponTemplate'
import
type
{
GiveCouponTemplate
}
from
'@/api/mall/product/spu'
defineOptions
({
name
:
'CouponSelect'
})
defineOptions
({
name
:
'CouponSelect'
})
defineProps
<
{
defineProps
<
{
multipleSelection
:
GiveCouponTemplate
[]
multipleSelection
:
CouponTemplateApi
.
CouponTemplateVO
[]
}
>
()
}
>
()
const
emit
=
defineEmits
<
{
const
emit
=
defineEmits
<
{
(
e
:
'update:multipleSelection'
,
v
:
GiveCouponTemplate
[])
(
e
:
'update:multipleSelection'
,
v
:
CouponTemplateApi
.
CouponTemplateVO
[])
}
>
()
}
>
()
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
dialogTitle
=
ref
(
'选择优惠卷'
)
// 弹窗的标题
const
dialogTitle
=
ref
(
'选择优惠卷'
)
// 弹窗的标题
...
@@ -210,10 +209,7 @@ const open = async () => {
...
@@ -210,10 +209,7 @@ const open = async () => {
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
const
handleSelectionChange
=
(
val
:
CouponTemplateApi
.
CouponTemplateVO
[])
=>
{
const
handleSelectionChange
=
(
val
:
CouponTemplateApi
.
CouponTemplateVO
[])
=>
{
emit
(
emit
(
'update:multipleSelection'
,
val
)
'update:multipleSelection'
,
val
.
map
((
item
)
=>
({
id
:
item
.
id
,
name
:
item
.
name
}))
)
}
}
const
submitForm
=
()
=>
{
const
submitForm
=
()
=>
{
...
...
src/views/mall/promotion/coupon/template/CouponTemplateForm.vue
View file @
8f97155b
...
@@ -26,15 +26,7 @@
...
@@ -26,15 +26,7 @@
label=
"商品"
label=
"商品"
prop=
"productSpuIds"
prop=
"productSpuIds"
>
>
<div
class=
"flex flex-wrap items-center gap-1"
>
<SpuShowcase
v-model=
"formData.productSpuIds"
/>
<div
v-for=
"(spu, index) in productSpus"
:key=
"spu.id"
class=
"select-box spu-pic"
>
<el-image
:src=
"spu.picUrl"
/>
<Icon
class=
"del-icon"
icon=
"ep:circle-close-filled"
@
click=
"handleRemoveSpu(index)"
/>
</div>
<div
class=
"select-box"
@
click=
"openSpuTableSelect"
>
<Icon
icon=
"ep:plus"
/>
</div>
</div>
</el-form-item>
</el-form-item>
<el-form-item
<el-form-item
v-if=
"formData.productScope === PromotionProductScopeEnum.CATEGORY.scope"
v-if=
"formData.productScope === PromotionProductScopeEnum.CATEGORY.scope"
...
@@ -186,18 +178,16 @@
...
@@ -186,18 +178,16 @@
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</
template
>
</Dialog>
</Dialog>
<SpuTableSelect
ref=
"spuTableSelectRef"
multiple
@
change=
"handleSpuSelected"
/>
</template>
</template>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
*
as
CouponTemplateApi
from
'@/api/mall/promotion/coupon/couponTemplate'
import
*
as
CouponTemplateApi
from
'@/api/mall/promotion/coupon/couponTemplate'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
{
import
{
CouponTemplateValidityTypeEnum
,
CouponTemplateValidityTypeEnum
,
PromotionDiscountTypeEnum
,
PromotionDiscountTypeEnum
,
PromotionProductScopeEnum
PromotionProductScopeEnum
}
from
'@/utils/constants'
}
from
'@/utils/constants'
import
Spu
TableSelect
from
'@/views/mall/product/spu/components/SpuTableSelect
.vue'
import
Spu
Showcase
from
'@/views/mall/product/spu/components/SpuShowcase
.vue'
import
ProductCategorySelect
from
'@/views/mall/product/category/components/ProductCategorySelect.vue'
import
ProductCategorySelect
from
'@/views/mall/product/category/components/ProductCategorySelect.vue'
import
{
convertToInteger
,
formatToFraction
}
from
'@/utils'
import
{
convertToInteger
,
formatToFraction
}
from
'@/utils'
...
@@ -251,7 +241,6 @@ const formRules = reactive({
...
@@ -251,7 +241,6 @@ const formRules = reactive({
productCategoryIds
:
[{
required
:
true
,
message
:
'分类不能为空'
,
trigger
:
'blur'
}]
productCategoryIds
:
[{
required
:
true
,
message
:
'分类不能为空'
,
trigger
:
'blur'
}]
})
})
const
formRef
=
ref
()
// 表单 Ref
const
formRef
=
ref
()
// 表单 Ref
const
productSpus
=
ref
<
ProductSpuApi
.
Spu
[]
>
([])
// 商品列表
/** 打开弹窗 */
/** 打开弹窗 */
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
...
@@ -354,7 +343,6 @@ const resetForm = () => {
...
@@ -354,7 +343,6 @@ const resetForm = () => {
productCategoryIds
:
[]
productCategoryIds
:
[]
}
}
formRef
.
value
?.
resetFields
()
formRef
.
value
?.
resetFields
()
productSpus
.
value
=
[]
}
}
/** 获得商品范围 */
/** 获得商品范围 */
...
@@ -363,8 +351,6 @@ const getProductScope = async () => {
...
@@ -363,8 +351,6 @@ const getProductScope = async () => {
case
PromotionProductScopeEnum
.
SPU
.
scope
:
case
PromotionProductScopeEnum
.
SPU
.
scope
:
// 设置商品编号
// 设置商品编号
formData
.
value
.
productSpuIds
=
formData
.
value
.
productScopeValues
formData
.
value
.
productSpuIds
=
formData
.
value
.
productScopeValues
// 获得商品列表
productSpus
.
value
=
await
ProductSpuApi
.
getSpuDetailList
(
formData
.
value
.
productScopeValues
)
break
break
case
PromotionProductScopeEnum
.
CATEGORY
.
scope
:
case
PromotionProductScopeEnum
.
CATEGORY
.
scope
:
await
nextTick
(()
=>
{
await
nextTick
(()
=>
{
...
@@ -397,47 +383,6 @@ function setProductScopeValues(data: CouponTemplateApi.CouponTemplateVO) {
...
@@ -397,47 +383,6 @@ function setProductScopeValues(data: CouponTemplateApi.CouponTemplateVO) {
break
break
}
}
}
}
/** 活动商品的按钮 */
const
spuTableSelectRef
=
ref
()
const
openSpuTableSelect
=
()
=>
{
spuTableSelectRef
.
value
.
open
(
productSpus
.
value
)
}
/** 选择商品后触发 */
const
handleSpuSelected
=
(
spus
:
ProductSpuApi
.
Spu
[])
=>
{
productSpus
.
value
=
spus
formData
.
value
.
productSpuIds
=
spus
.
map
((
spu
)
=>
spu
.
id
)
as
[]
}
/** 选择商品后触发 */
const
handleRemoveSpu
=
(
index
:
number
)
=>
{
productSpus
.
value
.
splice
(
index
,
1
)
formData
.
value
.
productSpuIds
.
splice
(
index
,
1
)
}
</
script
>
</
script
>
<
style
lang=
"scss"
scoped
>
<
style
lang=
"scss"
scoped
></
style
>
.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
>
src/views/mall/statistics/trade/index.vue
View file @
8f97155b
...
@@ -219,6 +219,8 @@ import { TradeSummaryRespVO, TradeTrendSummaryRespVO } from '@/api/mall/statisti
...
@@ -219,6 +219,8 @@ import { TradeSummaryRespVO, TradeTrendSummaryRespVO } from '@/api/mall/statisti
import
{
calculateRelativeRate
,
fenToYuan
}
from
'@/utils'
import
{
calculateRelativeRate
,
fenToYuan
}
from
'@/utils'
import
download
from
'@/utils/download'
import
download
from
'@/utils/download'
import
{
CardTitle
}
from
'@/components/Card'
import
{
CardTitle
}
from
'@/components/Card'
import
*
as
DateUtil
from
'@/utils/formatTime'
import
dayjs
from
'dayjs'
/** 交易统计 */
/** 交易统计 */
defineOptions
({
name
:
'TradeStatistics'
})
defineOptions
({
name
:
'TradeStatistics'
})
...
@@ -289,6 +291,13 @@ const lineChartOptions = reactive<EChartsOption>({
...
@@ -289,6 +291,13 @@ const lineChartOptions = reactive<EChartsOption>({
/** 处理交易状况查询 */
/** 处理交易状况查询 */
const
getTradeTrendData
=
async
()
=>
{
const
getTradeTrendData
=
async
()
=>
{
trendLoading
.
value
=
true
trendLoading
.
value
=
true
// 1. 处理时间: 开始与截止在同一天的, 折线图出不来, 需要延长一天
const
times
=
shortcutDateRangePicker
.
value
.
times
if
(
DateUtil
.
isSameDay
(
times
[
0
],
times
[
1
]))
{
// 前天
times
[
0
]
=
DateUtil
.
formatDate
(
dayjs
(
times
[
0
]).
subtract
(
1
,
'd'
))
}
// 查询数据
await
Promise
.
all
([
getTradeTrendSummary
(),
getTradeStatisticsList
()])
await
Promise
.
all
([
getTradeTrendSummary
(),
getTradeStatisticsList
()])
trendLoading
.
value
=
false
trendLoading
.
value
=
false
}
}
...
...
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