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
51e79f29
authored
Jun 25, 2023
by
puhui999
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: 封装 SpuSelect SpuAndSkuList 为商品活动商品选择商品编辑通用组件
parent
76ccc54a
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
229 additions
and
177 deletions
+229
-177
src/api/mall/product/spu.ts
+7
-11
src/api/mall/promotion/seckill/seckillActivity.ts
+4
-2
src/views/mall/product/spu/components/BasicInfoForm.vue
+1
-4
src/views/mall/product/spu/components/SkuList.vue
+34
-11
src/views/mall/product/spu/components/index.ts
+13
-2
src/views/mall/promotion/components/SpuAndSkuList.vue
+34
-72
src/views/mall/promotion/components/SpuSelect.vue
+25
-38
src/views/mall/promotion/components/index.ts
+13
-0
src/views/mall/promotion/seckill/activity/SeckillActivityForm.vue
+67
-13
src/views/mall/promotion/seckill/activity/components/index.ts
+0
-4
src/views/mall/promotion/seckill/activity/index.vue
+16
-3
src/views/mall/promotion/seckill/activity/seckillActivity.data.ts
+15
-17
No files found.
src/api/mall/product/spu.ts
View file @
51e79f29
...
@@ -47,17 +47,13 @@ export interface Spu {
...
@@ -47,17 +47,13 @@ export interface Spu {
recommendBest
?:
boolean
// 是否精品
recommendBest
?:
boolean
// 是否精品
recommendNew
?:
boolean
// 是否新品
recommendNew
?:
boolean
// 是否新品
recommendGood
?:
boolean
// 是否优品
recommendGood
?:
boolean
// 是否优品
}
price
?:
number
// 商品价格
salesCount
?:
number
// 商品销量
// TODO @puhui999: SpuRespVO 合并到 SPU 里?前端少点 VO 类哈;
marketPrice
?:
number
// 市场价
export
interface
SpuRespVO
extends
Spu
{
costPrice
?:
number
// 成本价
price
:
number
stock
?:
number
// 商品库存
salesCount
:
number
createTime
?:
Date
// 商品创建时间
marketPrice
:
number
status
?:
number
// 商品状态
costPrice
:
number
stock
:
number
createTime
:
Date
status
:
number
}
}
// 获得 Spu 列表
// 获得 Spu 列表
...
...
src/api/mall/promotion/seckill/seckillActivity.ts
View file @
51e79f29
import
request
from
'@/config/axios'
import
request
from
'@/config/axios'
import
{
Sku
,
Spu
RespVO
}
from
'@/api/mall/product/spu'
import
{
Sku
,
Spu
}
from
'@/api/mall/product/spu'
export
interface
SeckillActivityVO
{
export
interface
SeckillActivityVO
{
id
:
number
id
:
number
...
@@ -21,6 +21,7 @@ export interface SeckillActivityVO {
...
@@ -21,6 +21,7 @@ export interface SeckillActivityVO {
products
:
SeckillProductVO
[]
products
:
SeckillProductVO
[]
}
}
// 秒杀活动所需属性
export
interface
SeckillProductVO
{
export
interface
SeckillProductVO
{
spuId
:
number
spuId
:
number
skuId
:
number
skuId
:
number
...
@@ -28,11 +29,12 @@ export interface SeckillProductVO {
...
@@ -28,11 +29,12 @@ export interface SeckillProductVO {
stock
:
number
stock
:
number
}
}
// 扩展 Sku 配置
type
SkuExtension
=
Sku
&
{
type
SkuExtension
=
Sku
&
{
productConfig
:
SeckillProductVO
productConfig
:
SeckillProductVO
}
}
export
interface
SpuExtension
extends
Spu
RespVO
{
export
interface
SpuExtension
extends
Spu
{
skus
:
SkuExtension
[]
// 重写类型
skus
:
SkuExtension
[]
// 重写类型
}
}
...
...
src/views/mall/product/spu/components/BasicInfoForm.vue
View file @
51e79f29
...
@@ -274,10 +274,7 @@ watch(
...
@@ -274,10 +274,7 @@ watch(
const
emit
=
defineEmits
([
'update:activeName'
])
const
emit
=
defineEmits
([
'update:activeName'
])
const
validate
=
async
()
=>
{
const
validate
=
async
()
=>
{
// 校验 sku
// 校验 sku
if
(
!
skuListRef
.
value
.
validateSku
())
{
skuListRef
.
value
.
validateSku
()
message
.
warning
(
'商品相关价格不能低于 0.01 元!!'
)
throw
new
Error
(
'商品相关价格不能低于 0.01 元!!'
)
}
// 校验表单
// 校验表单
if
(
!
productSpuBasicInfoRef
)
return
if
(
!
productSpuBasicInfoRef
)
return
return
await
unref
(
productSpuBasicInfoRef
).
validate
((
valid
)
=>
{
return
await
unref
(
productSpuBasicInfoRef
).
validate
((
valid
)
=>
{
...
...
src/views/mall/product/spu/components/SkuList.vue
View file @
51e79f29
...
@@ -259,8 +259,10 @@ import { UploadImg } from '@/components/UploadFile'
...
@@ -259,8 +259,10 @@ import { UploadImg } from '@/components/UploadFile'
import
type
{
Property
,
Sku
,
Spu
}
from
'@/api/mall/product/spu'
import
type
{
Property
,
Sku
,
Spu
}
from
'@/api/mall/product/spu'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
RuleConfig
}
from
'@/views/mall/product/spu/components/index'
import
{
RuleConfig
}
from
'@/views/mall/product/spu/components/index'
import
{
Properties
}
from
'./index'
defineOptions
({
name
:
'SkuList'
})
defineOptions
({
name
:
'SkuList'
})
const
message
=
useMessage
()
// 消息弹窗
const
props
=
defineProps
({
const
props
=
defineProps
({
propFormData
:
{
propFormData
:
{
...
@@ -268,7 +270,7 @@ const props = defineProps({
...
@@ -268,7 +270,7 @@ const props = defineProps({
default
:
()
=>
{}
default
:
()
=>
{}
},
},
propertyList
:
{
propertyList
:
{
type
:
Array
,
type
:
Array
as
PropType
<
Properties
[]
>
,
default
:
()
=>
[]
default
:
()
=>
[]
},
},
ruleConfig
:
{
ruleConfig
:
{
...
@@ -323,26 +325,47 @@ const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表
...
@@ -323,26 +325,47 @@ const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表
/**
/**
* 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
* 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
*/
*/
const
validateSku
=
()
:
boolean
=>
{
const
validateSku
=
()
=>
{
const
checks
=
[
'price'
,
'marketPrice'
,
'costPrice'
]
const
checks
=
[
'price'
,
'marketPrice'
,
'costPrice'
]
let
warningInfo
=
'请检查商品各行相关属性配置,'
let
validate
=
true
// 默认通过
let
validate
=
true
// 默认通过
for
(
const
sku
of
formData
.
value
!
.
skus
)
{
for
(
const
sku
of
formData
.
value
!
.
skus
!
)
{
// 作为活动组件的校验
// 作为活动组件的校验
if
(
props
.
isActivityComponent
)
{
if
(
props
.
isActivityComponent
)
{
for
(
const
rule
of
props
.
ruleConfig
)
{
for
(
const
rule
of
props
.
ruleConfig
)
{
if
(
sku
[
rule
.
name
]
<
rule
.
geValue
)
{
const
arg
=
getValue
(
sku
,
rule
.
name
)
if
(
!
rule
.
rule
(
arg
))
{
validate
=
false
// 只要有一个不通过则直接不通过
validate
=
false
// 只要有一个不通过则直接不通过
warningInfo
+=
rule
.
message
break
break
}
}
}
}
}
else
{
}
else
{
if
(
checks
.
some
((
check
)
=>
sku
[
check
]
<
0.01
))
{
if
(
checks
.
some
((
check
)
=>
sku
[
check
]
<
0.01
))
{
validate
=
false
// 只要有一个不通过则直接不通过
validate
=
false
// 只要有一个不通过则直接不通过
warningInfo
=
'商品相关价格不能低于 0.01 元!!'
break
break
}
}
}
}
// 只要有一个不通过则结束后续的校验
if
(
!
validate
)
{
message
.
warning
(
warningInfo
)
throw
new
Error
(
warningInfo
)
}
}
}
const
getValue
=
(
obj
,
arg
)
=>
{
const
keys
=
arg
.
split
(
'.'
)
let
value
=
obj
for
(
const
key
of
keys
)
{
if
(
value
&&
typeof
value
===
'object'
&&
key
in
value
)
{
value
=
value
[
key
]
}
else
{
value
=
undefined
break
}
}
}
return
val
idat
e
return
val
u
e
}
}
const
emit
=
defineEmits
<
{
const
emit
=
defineEmits
<
{
...
@@ -417,13 +440,13 @@ const generateTableData = (propertyList: any[]) => {
...
@@ -417,13 +440,13 @@ const generateTableData = (propertyList: any[]) => {
* 生成 skus 前置校验
* 生成 skus 前置校验
*/
*/
const
validateData
=
(
propertyList
:
any
[])
=>
{
const
validateData
=
(
propertyList
:
any
[])
=>
{
const
skuPropertyIds
=
[]
const
skuPropertyIds
:
number
[]
=
[]
formData
.
value
!
.
skus
!
.
forEach
((
sku
)
=>
formData
.
value
!
.
skus
!
.
forEach
((
sku
)
=>
sku
.
properties
sku
.
properties
?.
map
((
property
)
=>
property
.
propertyId
)
?.
map
((
property
)
=>
property
.
propertyId
)
.
forEach
((
propertyId
)
=>
{
?
.
forEach
((
propertyId
)
=>
{
if
(
skuPropertyIds
.
indexOf
(
propertyId
)
===
-
1
)
{
if
(
skuPropertyIds
.
indexOf
(
propertyId
!
)
===
-
1
)
{
skuPropertyIds
.
push
(
propertyId
)
skuPropertyIds
.
push
(
propertyId
!
)
}
}
})
})
)
)
...
@@ -457,7 +480,7 @@ const build = (propertyValuesList: Property[][]) => {
...
@@ -457,7 +480,7 @@ const build = (propertyValuesList: Property[][]) => {
/** 监听属性列表,生成相关参数和表头 */
/** 监听属性列表,生成相关参数和表头 */
watch
(
watch
(
()
=>
props
.
propertyList
,
()
=>
props
.
propertyList
,
(
propertyList
)
=>
{
(
propertyList
:
Properties
[]
)
=>
{
// 如果不是多规格则结束
// 如果不是多规格则结束
if
(
!
formData
.
value
!
.
specType
)
{
if
(
!
formData
.
value
!
.
specType
)
{
return
return
...
@@ -497,7 +520,7 @@ watch(
...
@@ -497,7 +520,7 @@ watch(
return
return
}
}
// 添加新属性没有属性值也不做处理
// 添加新属性没有属性值也不做处理
if
(
propertyList
.
some
((
item
)
=>
item
.
values
.
length
===
0
))
{
if
(
propertyList
.
some
((
item
)
=>
item
.
values
!
.
length
===
0
))
{
return
return
}
}
// 生成 table 数据,即 sku 列表
// 生成 table 数据,即 sku 列表
...
...
src/views/mall/product/spu/components/index.ts
View file @
51e79f29
...
@@ -14,8 +14,19 @@ interface Properties {
...
@@ -14,8 +14,19 @@ interface Properties {
}
}
interface
RuleConfig
{
interface
RuleConfig
{
name
:
string
// 需要校验的字段
// 需要校验的字段
geValue
:
number
// TODO 暂定大于一个数字
// 例:name: 'name' 则表示校验 sku.name 的值
// 例:name: 'productConfig.stock' 则表示校验 sku.productConfig.name 的值,此处 productConfig 表示我在 Sku 上扩展的属性
name
:
string
// 校验规格为一个毁掉函数,其中 arg 为需要校验的字段的值。
// 例:需要校验价格必须大于0.01
// {
// name:'price',
// rule:(arg) => arg > 0.01
// }
rule
:
(
arg
:
any
)
=>
boolean
// 校验不通过时的消息提示
message
:
string
}
}
/**
/**
...
...
src/views/mall/promotion/
seckill/activity/
components/SpuAndSkuList.vue
→
src/views/mall/promotion/components/SpuAndSkuList.vue
View file @
51e79f29
<
template
>
<
template
>
<el-table
:data=
"spuData"
>
<el-table
:data=
"spuData"
:default-expand-all=
"true"
>
<el-table-column
type=
"expand"
width=
"30"
>
<el-table-column
type=
"expand"
width=
"30"
>
<template
#
default=
"
{ row }">
<template
#
default=
"
{ row }">
<SkuList
<SkuList
...
@@ -46,90 +46,39 @@
...
@@ -46,90 +46,39 @@
<el-table-column
align=
"center"
label=
"库存"
min-width=
"90"
prop=
"stock"
/>
<el-table-column
align=
"center"
label=
"库存"
min-width=
"90"
prop=
"stock"
/>
</el-table>
</el-table>
</template>
</template>
<
script
lang=
"ts"
setup
>
<
script
generic=
"T extends Spu"
lang=
"ts"
setup
>
// TODO 后续计划重新封装作为活动商品配置通用组件;可以等其他活动做到的时候,在统一处理 SPU 选择组件哈
// TODO 后续计划重新封装作为活动商品配置通用组件;可以等其他活动做到的时候,在统一处理 SPU 选择组件哈
import
{
formatToFraction
}
from
'@/utils'
import
{
formatToFraction
}
from
'@/utils'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
{
Spu
}
from
'@/api/mall/product/spu'
import
{
SpuRespVO
}
from
'@/api/mall/product/spu'
import
{
RuleConfig
,
SkuList
}
from
'@/views/mall/product/spu/components'
import
{
import
{
SeckillProductVO
}
from
'@/api/mall/promotion/seckill/seckillActivity'
getPropertyList
,
import
{
SpuProperty
}
from
'@/views/mall/promotion/components/index'
Properties
,
RuleConfig
,
SkuList
}
from
'@/views/mall/product/spu/components'
import
{
SeckillProductVO
,
SpuExtension
}
from
'@/api/mall/promotion/seckill/seckillActivity'
defineOptions
({
name
:
'PromotionSpuAndSkuList'
})
defineOptions
({
name
:
'PromotionSpuAndSkuList'
})
const
message
=
useMessage
()
// 消息弹窗
// TODO @puhui999:是不是改成传递一个 spu 就好啦? 因为活动商品可以多选所以展示编辑的时候需要展示多个
const
props
=
defineProps
<
{
spuList
:
T
[]
ruleConfig
:
RuleConfig
[]
spuPropertyListP
:
SpuProperty
<
T
>
[]
}
>
()
// TODO @puhui999:是不是改成传递一个 spu 就好啦?
const
spuData
=
ref
<
Spu
[]
>
([])
// spu 详情数据列表
const
props
=
defineProps
({
spuList
:
{
type
:
Array
,
default
:
()
=>
[]
}
})
const
spuData
=
ref
<
SpuRespVO
[]
>
([])
// spu 详情数据列表
const
skuListRef
=
ref
()
// 商品属性列表Ref
const
skuListRef
=
ref
()
// 商品属性列表Ref
interface
spuProperty
{
spuId
:
number
spuDetail
:
SpuExtension
propertyList
:
Properties
[]
}
// TODO @puhui999:类名首字母大写哈
const
spuPropertyList
=
ref
<
spuProperty
[]
>
([])
// spuId 对应的 sku 的属性列表
const
spuPropertyList
=
ref
<
SpuProperty
<
T
>
[]
>
([])
// spuId 对应的 sku 的属性列表
/**
* 获取 SPU 详情
* @param spuIds
*/
const
getSpuDetails
=
async
(
spuIds
:
number
[])
=>
{
const
spuProperties
:
spuProperty
[]
=
[]
// TODO puhui999: 考虑后端添加通过 spuIds 批量获取
for
(
const
spuId
of
spuIds
)
{
// 获取 SPU 详情
const
res
=
(
await
ProductSpuApi
.
getSpu
(
spuId
))
as
SpuExtension
if
(
!
res
)
{
continue
}
// 初始化每个 sku 秒杀配置
res
.
skus
?.
forEach
((
sku
)
=>
{
const
config
:
SeckillProductVO
=
{
spuId
,
skuId
:
sku
.
id
!
,
stock
:
0
,
seckillPrice
:
0
}
sku
.
productConfig
=
config
})
spuProperties
.
push
({
spuId
,
spuDetail
:
res
,
propertyList
:
getPropertyList
(
res
)
})
}
spuPropertyList
.
value
=
spuProperties
}
const
ruleConfig
:
RuleConfig
[]
=
[
{
name
:
'stock'
,
geValue
:
10
},
{
name
:
'seckillPrice'
,
geValue
:
0.01
}
]
/**
/**
* 获取所有 sku 秒杀配置
* 获取所有 sku 秒杀配置
* @param extendedAttribute 在 sku 上扩展的属性,例:秒杀活动 sku 扩展属性 productConfig 请参考 seckillActivity.ts
*/
*/
const
getSkuConfigs
=
():
SeckillProductVO
[]
=>
{
const
getSkuConfigs
:
<
V
>
(
extendedAttribute
:
string
)
=>
V
[]
=
(
extendedAttribute
:
string
)
=>
{
if
(
!
skuListRef
.
value
.
validateSku
())
{
skuListRef
.
value
.
validateSku
()
// TODO 作为通用组件是需要进一步完善
message
.
warning
(
'请检查商品相关属性配置!!'
)
throw
new
Error
(
'请检查商品相关属性配置!!'
)
}
const
seckillProducts
:
SeckillProductVO
[]
=
[]
const
seckillProducts
:
SeckillProductVO
[]
=
[]
spuPropertyList
.
value
.
forEach
((
item
)
=>
{
spuPropertyList
.
value
.
forEach
((
item
)
=>
{
item
.
spuDetail
.
skus
.
forEach
((
sku
)
=>
{
item
.
spuDetail
.
skus
.
forEach
((
sku
)
=>
{
seckillProducts
.
push
(
sku
.
productConfig
)
seckillProducts
.
push
(
sku
[
extendedAttribute
]
)
})
})
})
})
return
seckillProducts
return
seckillProducts
...
@@ -152,8 +101,21 @@ watch(
...
@@ -152,8 +101,21 @@ watch(
()
=>
props
.
spuList
,
()
=>
props
.
spuList
,
(
data
)
=>
{
(
data
)
=>
{
if
(
!
data
)
return
if
(
!
data
)
return
spuData
.
value
=
data
as
SpuRespVO
[]
spuData
.
value
=
data
as
Spu
[]
getSpuDetails
(
spuData
.
value
.
map
((
spu
)
=>
spu
.
id
!
))
},
{
deep
:
true
,
immediate
:
true
}
)
/**
* 将传进来的值赋值给 skuList
*/
watch
(
()
=>
props
.
spuPropertyListP
,
(
data
)
=>
{
if
(
!
data
)
return
spuPropertyList
.
value
=
data
as
SpuProperty
<
T
>
[]
},
},
{
{
deep
:
true
,
deep
:
true
,
...
...
src/views/mall/promotion/
seckill/activity/components/SpuAndSkuSelectForm
.vue
→
src/views/mall/promotion/
components/SpuSelect
.vue
View file @
51e79f29
...
@@ -20,7 +20,6 @@
...
@@ -20,7 +20,6 @@
class=
"w-1/1"
class=
"w-1/1"
node-key=
"id"
node-key=
"id"
placeholder=
"请选择商品分类"
placeholder=
"请选择商品分类"
@
change=
"nodeClick"
/>
/>
</el-col>
</el-col>
<el-col
:span=
"6"
>
<el-col
:span=
"6"
>
...
@@ -116,13 +115,13 @@ import { ElTable } from 'element-plus'
...
@@ -116,13 +115,13 @@ import { ElTable } from 'element-plus'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
formatToFraction
}
from
'@/utils'
import
{
formatToFraction
}
from
'@/utils'
import
{
checkSelectedNode
,
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'
defineOptions
({
name
:
'PromotionSpu
AndSku
Select'
})
defineOptions
({
name
:
'PromotionSpuSelect'
})
const
props
=
defineProps
({
const
props
=
defineProps
({
// 默认不需要(不需要的情况下只返回 spu,需要的情况下返回 选中的 spu 和 sku 列表)
// 默认不需要(不需要的情况下只返回 spu,需要的情况下返回 选中的 spu 和 sku 列表)
...
@@ -161,7 +160,7 @@ const expandChange = async (row: ProductSpuApi.Spu, expandedRows: ProductSpuApi.
...
@@ -161,7 +160,7 @@ const expandChange = async (row: ProductSpuApi.Spu, expandedRows: ProductSpuApi.
return
return
}
}
// 获取 SPU 详情
// 获取 SPU 详情
const
res
=
(
await
ProductSpuApi
.
getSpu
(
row
.
id
as
number
))
as
ProductSpuApi
.
Spu
RespVO
const
res
=
(
await
ProductSpuApi
.
getSpu
(
row
.
id
as
number
))
as
ProductSpuApi
.
Spu
propertyList
.
value
=
getPropertyList
(
res
)
propertyList
.
value
=
getPropertyList
(
res
)
spuData
.
value
=
res
spuData
.
value
=
res
isExpand
.
value
=
true
isExpand
.
value
=
true
...
@@ -169,53 +168,50 @@ const expandChange = async (row: ProductSpuApi.Spu, expandedRows: ProductSpuApi.
...
@@ -169,53 +168,50 @@ const expandChange = async (row: ProductSpuApi.Spu, expandedRows: ProductSpuApi.
}
}
//============ 商品选择相关 ============
//============ 商品选择相关 ============
const
selectedSpu
=
ref
<
ProductSpuApi
.
Spu
>
()
// 选中的商品 spu 只能选择一个
const
selectedSpu
Ids
=
ref
<
number
[]
>
([])
// 选中的商品 spuIds
const
selectedSku
=
ref
<
ProductSpuApi
.
Sku
[]
>
()
// 选中的商品 sku
const
selectedSku
Ids
=
ref
<
number
[]
>
([])
// 选中的商品 skuIds
const
selectSku
=
(
val
:
ProductSpuApi
.
Sku
[])
=>
{
const
selectSku
=
(
val
:
ProductSpuApi
.
Sku
[])
=>
{
selectedSku
.
value
=
val
selectedSku
Ids
.
value
=
val
.
map
((
sku
)
=>
sku
.
id
!
)
}
}
const
selectSpu
=
(
val
:
ProductSpuApi
.
Spu
[])
=>
{
const
selectSpu
=
(
val
:
ProductSpuApi
.
Spu
[])
=>
{
// 只选择一个
selectedSpuIds
.
value
=
val
.
map
((
spu
)
=>
spu
.
id
!
)
selectedSpu
.
value
=
val
[
0
]
// // 只选择一个
// 如果大于1个
// selectedSpu.value = val[0]
if
(
val
.
length
>
1
)
{
// // 如果大于1个
// 清空选择
// if (val.length > 1) {
spuListRef
.
value
.
clearSelection
()
// // 清空选择
// 变更为最后一次选择的
// spuListRef.value.clearSelection()
spuListRef
.
value
.
toggleRowSelection
(
val
.
pop
(),
true
)
// // 变更为最后一次选择的
}
// spuListRef.value.toggleRowSelection(val.pop(), true)
// }
}
}
// 确认选择时的触发事件
// 确认选择时的触发事件
const
emits
=
defineEmits
<
{
const
emits
=
defineEmits
<
{
(
e
:
'confirm'
,
value
:
ProductSpuApi
.
Spu
,
value1
?:
ProductSpuApi
.
Sku
[]):
void
(
e
:
'confirm'
,
spuIds
:
number
[],
skuIds
?:
number
[]):
void
}
>
()
}
>
()
/**
/**
* 确认选择返回选中的 spu 和 sku (如果需要选择sku的话)
* 确认选择返回选中的 spu 和 sku (如果需要选择sku的话)
*/
*/
const
confirm
=
()
=>
{
const
confirm
=
()
=>
{
if
(
typeof
selectedSpu
.
value
===
'undefined'
)
{
if
(
selectedSpuIds
.
value
.
length
===
0
)
{
message
.
warning
(
'没有选择任何商品'
)
message
.
warning
(
'没有选择任何商品'
)
return
return
}
}
if
(
if
(
props
.
isSelectSku
&&
selectedSkuIds
.
value
.
length
===
0
)
{
(
props
.
isSelectSku
&&
typeof
selectedSku
.
value
===
'undefined'
)
||
selectedSku
.
value
?.
length
===
0
)
{
message
.
warning
(
'没有选择任何商品属性'
)
message
.
warning
(
'没有选择任何商品属性'
)
return
return
}
}
//
TODO 返回选择 sku 没测试过,后续测试完善
//
返回各自 id 列表
props
.
isSelectSku
props
.
isSelectSku
?
emits
(
'confirm'
,
selectedSpu
.
value
!
,
selectedSku
.
value
!
)
?
emits
(
'confirm'
,
selectedSpu
Ids
.
value
,
selectedSkuIds
.
value
)
:
emits
(
'confirm'
,
selectedSpu
.
value
!
)
:
emits
(
'confirm'
,
selectedSpu
Ids
.
value
)
// 关闭弹窗
// 关闭弹窗
dialogVisible
.
value
=
false
dialogVisible
.
value
=
false
}
}
// TODO @puhui999:直接叫商品选择;不用外部传入标题;
/** 打开弹窗 */
/** 打开弹窗 TODO 没做国际化 */
const
open
=
()
=>
{
const
open
=
(
title
:
string
)
=>
{
dialogTitle
.
value
=
'商品选择'
dialogTitle
.
value
=
title
dialogVisible
.
value
=
true
dialogVisible
.
value
=
true
}
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
...
@@ -261,15 +257,6 @@ const imagePreview = (imgUrl: string) => {
...
@@ -261,15 +257,6 @@ const imagePreview = (imgUrl: string) => {
const
categoryList
=
ref
()
// 分类树
const
categoryList
=
ref
()
// 分类树
// TODO @puhui999:商品搜索的时候,可以通过一级搜二级;所以这个校验可以去掉哈;也就是说,只允许挂在二级,但是一级可搜索到
// TODO @puhui999:商品搜索的时候,可以通过一级搜二级;所以这个校验可以去掉哈;也就是说,只允许挂在二级,但是一级可搜索到
/**
* 校验所选是否为二级及以下节点
*/
const
nodeClick
=
()
=>
{
if
(
!
checkSelectedNode
(
categoryList
.
value
,
queryParams
.
value
.
categoryId
))
{
queryParams
.
value
.
categoryId
=
null
message
.
warning
(
'必须选择二级及以下节点!!'
)
}
}
/** 初始化 **/
/** 初始化 **/
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
await
getList
()
await
getList
()
...
...
src/views/mall/promotion/components/index.ts
0 → 100644
View file @
51e79f29
import
SpuSelect
from
'./SpuSelect.vue'
import
SpuAndSkuList
from
'./SpuAndSkuList.vue'
import
{
Properties
}
from
'@/views/mall/product/spu/components'
type
SpuProperty
<
T
>
=
{
spuId
:
number
spuDetail
:
T
propertyList
:
Properties
[]
}
/**
* 提供商品活动商品选择通用组件
*/
export
{
SpuSelect
,
SpuAndSkuList
,
SpuProperty
}
src/views/mall/promotion/seckill/activity/SeckillActivityForm.vue
View file @
51e79f29
...
@@ -8,10 +8,15 @@
...
@@ -8,10 +8,15 @@
:schema=
"allSchemas.formSchema"
:schema=
"allSchemas.formSchema"
>
>
<!-- 先选择 -->
<!-- 先选择 -->
<template
#
spuId
>
<template
#
spuId
s
>
<el-button
@
click=
"spu
AndSkuSelectForm.open('秒杀商品选择'
)"
>
选择商品
</el-button>
<el-button
@
click=
"spu
SelectRef.open(
)"
>
选择商品
</el-button>
<!-- TODO @puhui999:默认展开 SKU 哈,毕竟 SKU 是主角,SPU 是配角 -->
<!-- TODO @puhui999:默认展开 SKU 哈,毕竟 SKU 是主角,SPU 是配角 -->
<SpuAndSkuList
ref=
"spuAndSkuListRef"
:spu-list=
"spuList"
/>
<SpuAndSkuList
ref=
"spuAndSkuListRef"
:rule-config=
"ruleConfig"
:spu-list=
"spuList"
:spu-property-list-p=
"spuPropertyList"
/>
</
template
>
</
template
>
</Form>
</Form>
<
template
#
footer
>
<
template
#
footer
>
...
@@ -19,15 +24,15 @@
...
@@ -19,15 +24,15 @@
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</
template
>
</Dialog>
</Dialog>
<!-- TODO @puhui999:这个组件是不是 SpuSelect,不需要带 sku 或者 Form 呀 -->
<SpuSelect
ref=
"spuSelectRef"
:is-select-sku=
"true"
@
confirm=
"selectSpu"
/>
<SpuAndSkuSelectForm
ref=
"spuAndSkuSelectForm"
@
confirm=
"selectSpu"
/>
</template>
</template>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
SpuAndSkuList
,
Spu
AndSkuSelectForm
}
from
'
./components'
import
{
SpuAndSkuList
,
Spu
Property
,
SpuSelect
}
from
'../.
./components'
import
{
allSchemas
,
rules
}
from
'./seckillActivity.data'
import
{
allSchemas
,
rules
}
from
'./seckillActivity.data'
import
{
Spu
}
from
'@/api/mall/product/spu'
import
*
as
SeckillActivityApi
from
'@/api/mall/promotion/seckill/seckillActivity'
import
*
as
SeckillActivityApi
from
'@/api/mall/promotion/seckill/seckillActivity'
import
{
getPropertyList
,
RuleConfig
}
from
'@/views/mall/product/spu/components'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
defineOptions
({
name
:
'PromotionSeckillActivityForm'
})
defineOptions
({
name
:
'PromotionSeckillActivityForm'
})
...
@@ -39,14 +44,26 @@ const dialogTitle = ref('') // 弹窗的标题
...
@@ -39,14 +44,26 @@ const dialogTitle = ref('') // 弹窗的标题
const
formLoading
=
ref
(
false
)
// 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const
formLoading
=
ref
(
false
)
// 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const
formType
=
ref
(
''
)
// 表单的类型:create - 新增;update - 修改
const
formType
=
ref
(
''
)
// 表单的类型:create - 新增;update - 修改
const
formRef
=
ref
()
// 表单 Ref
const
formRef
=
ref
()
// 表单 Ref
const
spu
AndSkuSelectForm
=
ref
()
// 商品和属性选择 Ref
const
spu
SelectRef
=
ref
()
// 商品和属性选择 Ref
const
spuAndSkuListRef
=
ref
()
// sku 秒杀配置组件Ref
const
spuAndSkuListRef
=
ref
()
// sku 秒杀配置组件Ref
const
ruleConfig
:
RuleConfig
[]
=
[
{
name
:
'productConfig.stock'
,
rule
:
(
arg
)
=>
arg
>
1
,
message
:
'商品秒杀库存必须大于 1 !!!'
},
{
name
:
'productConfig.seckillPrice'
,
rule
:
(
arg
)
=>
arg
>
0.01
,
message
:
'商品秒杀价格必须大于 0.01 !!!'
}
]
/** 打开弹窗 */
/** 打开弹窗 */
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
dialogVisible
.
value
=
true
dialogVisible
.
value
=
true
dialogTitle
.
value
=
t
(
'action.'
+
type
)
dialogTitle
.
value
=
t
(
'action.'
+
type
)
formType
.
value
=
type
formType
.
value
=
type
resetForm
()
// 修改时,设置数据 TODO 没测试估计有问题
// 修改时,设置数据 TODO 没测试估计有问题
if
(
id
)
{
if
(
id
)
{
formLoading
.
value
=
true
formLoading
.
value
=
true
...
@@ -60,12 +77,49 @@ const open = async (type: string, id?: number) => {
...
@@ -60,12 +77,49 @@ const open = async (type: string, id?: number) => {
}
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
const
spuList
=
ref
<
Spu
[]
>
([])
// 选择的 spu
const
spuList
=
ref
<
SeckillActivityApi
.
SpuExtension
[]
>
([])
// 选择的 spu
const
selectSpu
=
(
val
:
Spu
)
=>
{
const
spuPropertyList
=
ref
<
SpuProperty
<
SeckillActivityApi
.
SpuExtension
>
[]
>
([])
formRef
.
value
.
setValues
({
spuId
:
val
.
id
})
const
selectSpu
=
(
spuIds
:
number
[])
=>
{
spuList
.
value
=
[
val
]
formRef
.
value
.
setValues
({
spuIds
})
getSpuDetails
(
spuIds
)
}
/**
* 获取 SPU 详情
* TODO 获取 SPU 详情,放到各自活动表单来做,让 SpuAndSkuList 职责单一点
* @param spuIds
*/
const
getSpuDetails
=
async
(
spuIds
:
number
[])
=>
{
const
spuProperties
:
SpuProperty
<
SeckillActivityApi
.
SpuExtension
>
[]
=
[]
spuList
.
value
=
[]
// TODO puhui999: 考虑后端添加通过 spuIds 批量获取
for
(
const
spuId
of
spuIds
)
{
// 获取 SPU 详情
const
res
=
(
await
ProductSpuApi
.
getSpu
(
spuId
))
as
SeckillActivityApi
.
SpuExtension
if
(
!
res
)
{
continue
}
spuList
.
value
.
push
(
res
)
// 初始化每个 sku 秒杀配置
res
.
skus
?.
forEach
((
sku
)
=>
{
const
config
:
SeckillActivityApi
.
SeckillProductVO
=
{
spuId
,
skuId
:
sku
.
id
!
,
stock
:
0
,
seckillPrice
:
0
}
sku
.
productConfig
=
config
})
spuProperties
.
push
({
spuId
,
spuDetail
:
res
,
propertyList
:
getPropertyList
(
res
)
})
}
spuPropertyList
.
value
=
spuProperties
}
}
/** 重置表单 */
const
resetForm
=
()
=>
{
spuList
.
value
=
[]
spuPropertyList
.
value
=
[]
formRef
.
value
.
getElFormRef
().
resetFields
()
}
/** 提交表单 */
/** 提交表单 */
const
emit
=
defineEmits
([
'success'
])
// 定义 success 事件,用于操作成功后的回调
const
emit
=
defineEmits
([
'success'
])
// 定义 success 事件,用于操作成功后的回调
const
submitForm
=
async
()
=>
{
const
submitForm
=
async
()
=>
{
...
...
src/views/mall/promotion/seckill/activity/components/index.ts
deleted
100644 → 0
View file @
76ccc54a
import
SpuAndSkuSelectForm
from
'./SpuAndSkuSelectForm.vue'
import
SpuAndSkuList
from
'./SpuAndSkuList.vue'
export
{
SpuAndSkuSelectForm
,
SpuAndSkuList
}
src/views/mall/promotion/seckill/activity/index.vue
View file @
51e79f29
...
@@ -29,6 +29,11 @@
...
@@ -29,6 +29,11 @@
total: tableObject.total
total: tableObject.total
}"
}"
>
>
<
template
#
configIds=
"{ row }"
>
<el-tag
v-for=
"(name, index) in convertSeckillConfigNames(row)"
:key=
"index"
class=
"mr-5px"
>
{{
name
}}
</el-tag>
</
template
>
<
template
#
action=
"{ row }"
>
<
template
#
action=
"{ row }"
>
<el-button
<el-button
v-hasPermi=
"['promotion:seckill-activity:update']"
v-hasPermi=
"['promotion:seckill-activity:update']"
...
@@ -55,6 +60,7 @@
...
@@ -55,6 +60,7 @@
</template>
</template>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
allSchemas
}
from
'./seckillActivity.data'
import
{
allSchemas
}
from
'./seckillActivity.data'
import
{
getListAllSimple
}
from
'@/api/mall/promotion/seckill/seckillConfig'
import
*
as
SeckillActivityApi
from
'@/api/mall/promotion/seckill/seckillActivity'
import
*
as
SeckillActivityApi
from
'@/api/mall/promotion/seckill/seckillActivity'
import
SeckillActivityForm
from
'./SeckillActivityForm.vue'
import
SeckillActivityForm
from
'./SeckillActivityForm.vue'
...
@@ -80,9 +86,16 @@ const openForm = (type: string, id?: number) => {
...
@@ -80,9 +86,16 @@ const openForm = (type: string, id?: number) => {
const
handleDelete
=
(
id
:
number
)
=>
{
const
handleDelete
=
(
id
:
number
)
=>
{
tableMethods
.
delList
(
id
,
false
)
tableMethods
.
delList
(
id
,
false
)
}
}
const
seckillConfigAllSimple
=
ref
([])
// 时段配置精简列表
const
convertSeckillConfigNames
=
computed
(
()
=>
(
row
)
=>
seckillConfigAllSimple
.
value
?.
filter
((
item
)
=>
row
.
configIds
.
includes
(
item
.
id
))
?.
map
((
config
)
=>
config
.
name
)
)
/** 初始化 **/
/** 初始化 **/
onMounted
(()
=>
{
onMounted
(
async
()
=>
{
getList
()
await
getList
()
seckillConfigAllSimple
.
value
=
await
getListAllSimple
()
})
})
</
script
>
</
script
>
src/views/mall/promotion/seckill/activity/seckillActivity.data.ts
View file @
51e79f29
...
@@ -40,8 +40,7 @@ const crudSchemas = reactive<CrudSchema[]>([
...
@@ -40,8 +40,7 @@ const crudSchemas = reactive<CrudSchema[]>([
component
:
'DatePicker'
,
component
:
'DatePicker'
,
componentProps
:
{
componentProps
:
{
valueFormat
:
'YYYY-MM-DD'
,
valueFormat
:
'YYYY-MM-DD'
,
type
:
'daterange'
,
type
:
'daterange'
defaultTime
:
[
new
Date
(
'1 00:00:00'
),
new
Date
(
'1 23:59:59'
)]
}
}
},
},
form
:
{
form
:
{
...
@@ -52,7 +51,7 @@ const crudSchemas = reactive<CrudSchema[]>([
...
@@ -52,7 +51,7 @@ const crudSchemas = reactive<CrudSchema[]>([
}
}
},
},
table
:
{
table
:
{
width
:
30
0
width
:
12
0
}
}
},
},
{
{
...
@@ -64,8 +63,7 @@ const crudSchemas = reactive<CrudSchema[]>([
...
@@ -64,8 +63,7 @@ const crudSchemas = reactive<CrudSchema[]>([
component
:
'DatePicker'
,
component
:
'DatePicker'
,
componentProps
:
{
componentProps
:
{
valueFormat
:
'YYYY-MM-DD'
,
valueFormat
:
'YYYY-MM-DD'
,
type
:
'daterange'
,
type
:
'daterange'
defaultTime
:
[
new
Date
(
'1 00:00:00'
),
new
Date
(
'1 23:59:59'
)]
}
}
},
},
form
:
{
form
:
{
...
@@ -76,11 +74,11 @@ const crudSchemas = reactive<CrudSchema[]>([
...
@@ -76,11 +74,11 @@ const crudSchemas = reactive<CrudSchema[]>([
}
}
},
},
table
:
{
table
:
{
width
:
30
0
width
:
12
0
}
}
},
},
{
{
label
:
'秒杀时段'
,
// todo @PUHUI999: 在列表界面,格式化不对
label
:
'秒杀时段'
,
field
:
'configIds'
,
field
:
'configIds'
,
form
:
{
form
:
{
component
:
'Select'
,
component
:
'Select'
,
...
@@ -106,7 +104,7 @@ const crudSchemas = reactive<CrudSchema[]>([
...
@@ -106,7 +104,7 @@ const crudSchemas = reactive<CrudSchema[]>([
value
:
0
value
:
0
},
},
table
:
{
table
:
{
width
:
30
0
width
:
12
0
}
}
},
},
{
{
...
@@ -118,7 +116,7 @@ const crudSchemas = reactive<CrudSchema[]>([
...
@@ -118,7 +116,7 @@ const crudSchemas = reactive<CrudSchema[]>([
value
:
0
value
:
0
},
},
table
:
{
table
:
{
width
:
30
0
width
:
12
0
}
}
},
},
{
{
...
@@ -130,7 +128,7 @@ const crudSchemas = reactive<CrudSchema[]>([
...
@@ -130,7 +128,7 @@ const crudSchemas = reactive<CrudSchema[]>([
value
:
0
value
:
0
},
},
table
:
{
table
:
{
width
:
30
0
width
:
12
0
}
}
},
},
{
{
...
@@ -141,7 +139,7 @@ const crudSchemas = reactive<CrudSchema[]>([
...
@@ -141,7 +139,7 @@ const crudSchemas = reactive<CrudSchema[]>([
value
:
0
value
:
0
},
},
table
:
{
table
:
{
width
:
30
0
width
:
12
0
}
}
},
},
{
{
...
@@ -152,7 +150,7 @@ const crudSchemas = reactive<CrudSchema[]>([
...
@@ -152,7 +150,7 @@ const crudSchemas = reactive<CrudSchema[]>([
value
:
0
value
:
0
},
},
table
:
{
table
:
{
width
:
30
0
width
:
12
0
}
}
},
},
{
{
...
@@ -164,7 +162,7 @@ const crudSchemas = reactive<CrudSchema[]>([
...
@@ -164,7 +162,7 @@ const crudSchemas = reactive<CrudSchema[]>([
value
:
0
value
:
0
},
},
table
:
{
table
:
{
width
:
30
0
width
:
12
0
}
}
},
},
{
{
...
@@ -175,12 +173,12 @@ const crudSchemas = reactive<CrudSchema[]>([
...
@@ -175,12 +173,12 @@ const crudSchemas = reactive<CrudSchema[]>([
value
:
0
value
:
0
},
},
table
:
{
table
:
{
width
:
30
0
width
:
12
0
}
}
},
},
{
{
label
:
'秒杀活动商品'
,
// TODO @puhui999:格式化的商品不对;
label
:
'秒杀活动商品'
,
// TODO @puhui999:格式化的商品不对;
field
:
'spuId'
,
field
:
'spuId
s
'
,
form
:
{
form
:
{
colProps
:
{
colProps
:
{
span
:
24
span
:
24
...
@@ -204,7 +202,7 @@ const crudSchemas = reactive<CrudSchema[]>([
...
@@ -204,7 +202,7 @@ const crudSchemas = reactive<CrudSchema[]>([
},
},
isForm
:
false
,
isForm
:
false
,
table
:
{
table
:
{
width
:
30
0
width
:
12
0
}
}
},
},
{
{
...
@@ -215,7 +213,7 @@ const crudSchemas = reactive<CrudSchema[]>([
...
@@ -215,7 +213,7 @@ const crudSchemas = reactive<CrudSchema[]>([
value
:
0
value
:
0
},
},
table
:
{
table
:
{
width
:
30
0
width
:
8
0
}
}
},
},
{
{
...
...
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