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
b41d8f06
authored
Jul 26, 2023
by
芋道源码
Committed by
Gitee
Jul 26, 2023
Browse files
Options
Browse Files
Download
Plain Diff
!184 同步最新商城实现
Merge pull request !184 from 芋道源码/dev
parents
f908c129
a003b59e
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
828 additions
and
171 deletions
+828
-171
src/api/mall/product/spu.ts
+5
-0
src/api/mall/promotion/combination/combinationactivity.ts
+63
-0
src/api/mall/promotion/seckill/seckillActivity.ts
+18
-19
src/api/mall/promotion/seckill/seckillConfig.ts
+1
-1
src/utils/formatTime.ts
+7
-0
src/views/mall/product/spu/components/SkuList.vue
+13
-8
src/views/mall/product/spu/components/index.ts
+8
-8
src/views/mall/promotion/combination/activity/CombinationActivityForm.vue
+189
-0
src/views/mall/promotion/combination/activity/combinationActivity.data.ts
+151
-0
src/views/mall/promotion/combination/activity/index.vue
+117
-0
src/views/mall/promotion/components/SpuAndSkuList.vue
+12
-25
src/views/mall/promotion/components/SpuSelect.vue
+60
-28
src/views/mall/promotion/components/index.ts
+2
-2
src/views/mall/promotion/seckill/activity/SeckillActivityForm.vue
+97
-47
src/views/mall/promotion/seckill/activity/index.vue
+30
-5
src/views/mall/promotion/seckill/activity/seckillActivity.data.ts
+15
-18
src/views/mall/promotion/seckill/config/SeckillConfigForm.vue
+14
-2
src/views/mall/promotion/seckill/config/index.vue
+20
-5
src/views/mall/promotion/seckill/config/seckillConfig.data.ts
+6
-3
No files found.
src/api/mall/product/spu.ts
View file @
b41d8f06
...
...
@@ -86,6 +86,11 @@ export const getSpu = (id: number) => {
return
request
.
get
({
url
:
`/product/spu/get-detail?id=
${
id
}
`
})
}
// 获得商品 Spu 详情列表
export
const
getSpuDetailList
=
(
ids
:
number
[])
=>
{
return
request
.
get
({
url
:
`/product/spu/list?spuIds=
${
ids
}
`
})
}
// 删除商品 Spu
export
const
deleteSpu
=
(
id
:
number
)
=>
{
return
request
.
delete
({
url
:
`/product/spu/delete?id=
${
id
}
`
})
...
...
src/api/mall/promotion/combination/combinationactivity.ts
0 → 100644
View file @
b41d8f06
import
request
from
'@/config/axios'
import
{
Sku
,
Spu
}
from
'@/api/mall/product/spu'
// TODO @puhui999: combinationActivity.ts
export
interface
CombinationActivityVO
{
id
?:
number
name
?:
string
spuId
?:
number
totalLimitCount
?:
number
singleLimitCount
?:
number
startTime
?:
Date
endTime
?:
Date
userSize
?:
number
totalNum
?:
number
successNum
?:
number
orderUserCount
?:
number
virtualGroup
?:
number
status
?:
number
limitDuration
?:
number
products
:
CombinationProductVO
[]
}
// 拼团活动所需属性
export
interface
CombinationProductVO
{
spuId
:
number
skuId
:
number
activePrice
:
number
// 拼团价格
}
// 扩展 Sku 配置
export
type
SkuExtension
=
Sku
&
{
productConfig
:
CombinationProductVO
}
export
interface
SpuExtension
extends
Spu
{
skus
:
SkuExtension
[]
// 重写类型
}
// 查询拼团活动列表
export
const
getCombinationActivityPage
=
async
(
params
)
=>
{
return
await
request
.
get
({
url
:
'/promotion/combination-activity/page'
,
params
})
}
// 查询拼团活动详情
export
const
getCombinationActivity
=
async
(
id
:
number
)
=>
{
return
await
request
.
get
({
url
:
'/promotion/combination-activity/get?id='
+
id
})
}
// 新增拼团活动
export
const
createCombinationActivity
=
async
(
data
:
CombinationActivityVO
)
=>
{
return
await
request
.
post
({
url
:
'/promotion/combination-activity/create'
,
data
})
}
// 修改拼团活动
export
const
updateCombinationActivity
=
async
(
data
:
CombinationActivityVO
)
=>
{
return
await
request
.
put
({
url
:
'/promotion/combination-activity/update'
,
data
})
}
// 删除拼团活动
export
const
deleteCombinationActivity
=
async
(
id
:
number
)
=>
{
return
await
request
.
delete
({
url
:
'/promotion/combination-activity/delete?id='
+
id
})
}
src/api/mall/promotion/seckill/seckillActivity.ts
View file @
b41d8f06
...
...
@@ -2,35 +2,34 @@ import request from '@/config/axios'
import
{
Sku
,
Spu
}
from
'@/api/mall/product/spu'
export
interface
SeckillActivityVO
{
id
:
number
spuId
s
:
number
[]
name
:
string
status
:
number
remark
:
string
startTime
:
Date
endTime
:
Date
sort
:
number
configIds
:
string
orderCount
:
number
userCount
:
number
totalPrice
:
number
totalLimitCount
:
number
singleLimitCount
:
number
stock
:
number
totalStock
:
number
products
:
SeckillProductVO
[]
id
?
:
number
spuId
?:
number
name
?
:
string
status
?
:
number
remark
?
:
string
startTime
?
:
Date
endTime
?
:
Date
sort
?
:
number
configIds
?
:
string
orderCount
?
:
number
userCount
?
:
number
totalPrice
?
:
number
totalLimitCount
?
:
number
singleLimitCount
?
:
number
stock
?
:
number
totalStock
?
:
number
products
?
:
SeckillProductVO
[]
}
// 秒杀活动所需属性
export
interface
SeckillProductVO
{
spuId
:
number
skuId
:
number
seckillPrice
:
number
stock
:
number
}
// 扩展 Sku 配置
type
SkuExtension
=
Sku
&
{
export
type
SkuExtension
=
Sku
&
{
productConfig
:
SeckillProductVO
}
...
...
src/api/mall/promotion/seckill/seckillConfig.ts
View file @
b41d8f06
...
...
@@ -5,7 +5,7 @@ export interface SeckillConfigVO {
name
:
string
startTime
:
string
endTime
:
string
picUrl
:
string
sliderPicUrls
:
string
[]
status
:
number
}
...
...
src/utils/formatTime.ts
View file @
b41d8f06
...
...
@@ -24,6 +24,13 @@ export function formatDate(date: Date, format?: string): string {
}
/**
* 获取当前的日期+时间
*/
export
function
getNowDateTime
()
{
return
dayjs
()
}
/**
* 获取当前日期是第几周
* @param dateTime 当前传入的日期值
* @returns 返回第几周数字值
...
...
src/views/mall/product/spu/components/SkuList.vue
View file @
b41d8f06
...
...
@@ -2,7 +2,7 @@
<!-- 情况一:添加/修改 -->
<el-table
v-if=
"!isDetail && !isActivityComponent"
:data=
"isBatch ? skuList : formData!.skus"
:data=
"isBatch ? skuList : formData!.skus
!
"
border
class=
"tabNumWidth"
max-height=
"500"
...
...
@@ -113,7 +113,8 @@
<!-- 情况二:详情 -->
<el-table
v-if=
"isDetail"
:data=
"formData!.skus"
ref=
"activitySkuListRef"
:data=
"formData!.skus!"
border
max-height=
"500"
size=
"small"
...
...
@@ -194,7 +195,7 @@
<!-- 情况三:作为活动组件 -->
<el-table
v-if=
"isActivityComponent"
:data=
"formData!.skus"
:data=
"formData!.skus
!
"
border
max-height=
"500"
size=
"small"
...
...
@@ -259,7 +260,8 @@ import { UploadImg } from '@/components/UploadFile'
import
type
{
Property
,
Sku
,
Spu
}
from
'@/api/mall/product/spu'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
RuleConfig
}
from
'@/views/mall/product/spu/components/index'
import
{
Properties
}
from
'./index'
import
{
PropertyAndValues
}
from
'./index'
import
{
ElTable
}
from
'element-plus'
defineOptions
({
name
:
'SkuList'
})
const
message
=
useMessage
()
// 消息弹窗
...
...
@@ -270,7 +272,7 @@ const props = defineProps({
default
:
()
=>
{}
},
propertyList
:
{
type
:
Array
as
PropType
<
Propert
i
es
[]
>
,
type
:
Array
as
PropType
<
Propert
yAndValu
es
[]
>
,
default
:
()
=>
[]
},
ruleConfig
:
{
...
...
@@ -480,7 +482,7 @@ const build = (propertyValuesList: Property[][]) => {
/** 监听属性列表,生成相关参数和表头 */
watch
(
()
=>
props
.
propertyList
,
(
propertyList
:
Propert
i
es
[])
=>
{
(
propertyList
:
Propert
yAndValu
es
[])
=>
{
// 如果不是多规格则结束
if
(
!
formData
.
value
!
.
specType
)
{
return
...
...
@@ -514,7 +516,6 @@ watch(
// name加属性项index区分属性值
tableHeaders
.
value
.
push
({
prop
:
`name
${
index
}
`
,
label
:
item
.
name
})
})
// 如果回显的 sku 属性和添加的属性一致则不处理
if
(
validateData
(
propertyList
))
{
return
...
...
@@ -531,6 +532,10 @@ watch(
immediate
:
true
}
)
const
activitySkuListRef
=
ref
<
InstanceType
<
typeof
ElTable
>>
()
const
clearSelection
=
()
=>
{
activitySkuListRef
.
value
.
clearSelection
()
}
// 暴露出生成 sku 方法,给添加属性成功时调用
defineExpose
({
generateTableData
,
validateSku
})
defineExpose
({
generateTableData
,
validateSku
,
clearSelection
})
</
script
>
src/views/mall/product/spu/components/index.ts
View file @
b41d8f06
...
...
@@ -7,11 +7,11 @@ import SkuList from './SkuList.vue'
import
{
Spu
}
from
'@/api/mall/product/spu'
// TODO @puhui999:Properties 改成 Property 更合适?
interface
Propert
i
es
{
// TODO @puhui999:Properties 改成 Property 更合适?
Property 在 Spu 中已存在避免冲突 PropertyAndValues
interface
Propert
yAndValu
es
{
id
:
number
name
:
string
values
?:
Propert
i
es
[]
values
?:
Propert
yAndValu
es
[]
}
interface
RuleConfig
{
...
...
@@ -23,7 +23,7 @@ interface RuleConfig {
// 例:需要校验价格必须大于0.01
// {
// name:'price',
// rule:(arg) => arg > 0.01
// rule:(arg
: number
) => arg > 0.01
// }
rule
:
(
arg
:
any
)
=>
boolean
// 校验不通过时的消息提示
...
...
@@ -34,11 +34,11 @@ interface RuleConfig {
* 获得商品的规格列表
*
* @param spu
* @return Property 规格列表
* @return Property
AndValues
规格列表
*/
const
getPropertyList
=
(
spu
:
Spu
):
Propert
i
es
[]
=>
{
const
getPropertyList
=
(
spu
:
Spu
):
Propert
yAndValu
es
[]
=>
{
// 直接拿返回的 skus 属性逆向生成出 propertyList
const
properties
:
Propert
i
es
[]
=
[]
const
properties
:
Propert
yAndValu
es
[]
=
[]
// 只有是多规格才处理
if
(
spu
.
specType
)
{
spu
.
skus
?.
forEach
((
sku
)
=>
{
...
...
@@ -66,6 +66,6 @@ export {
ProductPropertyAddForm
,
SkuList
,
getPropertyList
,
Propert
i
es
,
Propert
yAndValu
es
,
RuleConfig
}
src/views/mall/promotion/combination/activity/CombinationActivityForm.vue
0 → 100644
View file @
b41d8f06
<
template
>
<Dialog
v-model=
"dialogVisible"
:title=
"dialogTitle"
width=
"65%"
>
<Form
ref=
"formRef"
v-loading=
"formLoading"
:is-col=
"true"
:rules=
"rules"
:schema=
"allSchemas.formSchema"
>
<template
#
spuId
>
<el-button
@
click=
"spuSelectRef.open()"
>
选择商品
</el-button>
<SpuAndSkuList
ref=
"spuAndSkuListRef"
:rule-config=
"ruleConfig"
:spu-list=
"spuList"
:spu-property-list-p=
"spuPropertyList"
>
<el-table-column
align=
"center"
label=
"拼团价格(元)"
min-width=
"168"
>
<template
#
default=
"
{ row: sku }">
<el-input-number
v-model=
"sku.productConfig.activePrice"
:min=
"0"
:precision=
"2"
:step=
"0.1"
class=
"w-100%"
/>
</
template
>
</el-table-column>
</SpuAndSkuList>
</template>
</Form>
<
template
#
footer
>
<el-button
:disabled=
"formLoading"
type=
"primary"
@
click=
"submitForm"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</Dialog>
<SpuSelect
ref=
"spuSelectRef"
:isSelectSku=
"true"
@
confirm=
"selectSpu"
/>
</template>
<
script
lang=
"ts"
setup
>
import
*
as
CombinationActivityApi
from
'@/api/mall/promotion/combination/combinationactivity'
import
{
CombinationProductVO
}
from
'@/api/mall/promotion/combination/combinationactivity'
import
{
allSchemas
,
rules
}
from
'./combinationActivity.data'
import
{
SpuAndSkuList
,
SpuProperty
,
SpuSelect
}
from
'@/views/mall/promotion/components'
import
{
getPropertyList
,
RuleConfig
}
from
'@/views/mall/product/spu/components'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
{
convertToInteger
,
formatToFraction
}
from
'@/utils'
defineOptions
({
name
:
'PromotionCombinationActivityForm'
})
const
{
t
}
=
useI18n
()
// 国际化
const
message
=
useMessage
()
// 消息弹窗
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
dialogTitle
=
ref
(
''
)
// 弹窗的标题
const
formLoading
=
ref
(
false
)
// 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const
formType
=
ref
(
''
)
// 表单的类型:create - 新增;update - 修改
const
formRef
=
ref
()
// 表单 Ref
// ================= 商品选择相关 =================
const
spuSelectRef
=
ref
()
// 商品和属性选择 Ref
const
spuAndSkuListRef
=
ref
()
// sku 秒杀配置组件Ref
const
spuList
=
ref
<
CombinationActivityApi
.
SpuExtension
[]
>
([])
// 选择的 spu
const
spuPropertyList
=
ref
<
SpuProperty
<
CombinationActivityApi
.
SpuExtension
>
[]
>
([])
const
ruleConfig
:
RuleConfig
[]
=
[
{
name
:
'productConfig.activePrice'
,
rule
:
(
arg
)
=>
arg
>
0.01
,
message
:
'商品拼团价格不能小于0.01 !!!'
}
]
const
selectSpu
=
(
spuId
:
number
,
skuIds
:
number
[])
=>
{
formRef
.
value
.
setValues
({
spuId
})
getSpuDetails
(
spuId
,
skuIds
)
}
/**
* 获取 SPU 详情
*/
const
getSpuDetails
=
async
(
spuId
:
number
,
skuIds
:
number
[]
|
undefined
,
products
?:
CombinationProductVO
[]
)
=>
{
const
spuProperties
:
SpuProperty
<
CombinationActivityApi
.
SpuExtension
>
[]
=
[]
const
res
=
(
await
ProductSpuApi
.
getSpuDetailList
([
spuId
]))
as
CombinationActivityApi
.
SpuExtension
[]
if
(
res
.
length
==
0
)
{
return
}
spuList
.
value
=
[]
// 因为只能选择一个
const
spu
=
res
[
0
]
const
selectSkus
=
typeof
skuIds
===
'undefined'
?
spu
?.
skus
:
spu
?.
skus
?.
filter
((
sku
)
=>
skuIds
.
includes
(
sku
.
id
!
))
selectSkus
?.
forEach
((
sku
)
=>
{
let
config
:
CombinationProductVO
=
{
spuId
:
spu
.
id
!
,
skuId
:
sku
.
id
!
,
activePrice
:
0
}
if
(
typeof
products
!==
'undefined'
)
{
const
product
=
products
.
find
((
item
)
=>
item
.
skuId
===
sku
.
id
)
if
(
product
)
{
// 分转元
product
.
activePrice
=
formatToFraction
(
product
.
activePrice
)
}
config
=
product
||
config
}
sku
.
productConfig
=
config
})
spu
.
skus
=
selectSkus
as
CombinationActivityApi
.
SkuExtension
[]
spuProperties
.
push
({
spuId
:
spu
.
id
!
,
spuDetail
:
spu
,
propertyList
:
getPropertyList
(
spu
)
})
spuList
.
value
.
push
(
spu
)
spuPropertyList
.
value
=
spuProperties
}
// ================= end =================
/** 打开弹窗 */
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
dialogVisible
.
value
=
true
dialogTitle
.
value
=
t
(
'action.'
+
type
)
formType
.
value
=
type
await
resetForm
()
// 修改时,设置数据
if
(
id
)
{
formLoading
.
value
=
true
try
{
const
data
=
(
await
CombinationActivityApi
.
getCombinationActivity
(
id
))
as
CombinationActivityApi
.
CombinationActivityVO
await
getSpuDetails
(
data
.
spuId
!
,
data
.
products
?.
map
((
sku
)
=>
sku
.
skuId
),
data
.
products
)
formRef
.
value
.
setValues
(
data
)
}
finally
{
formLoading
.
value
=
false
}
}
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
/** 重置表单 */
const
resetForm
=
async
()
=>
{
spuList
.
value
=
[]
spuPropertyList
.
value
=
[]
await
nextTick
()
formRef
.
value
.
getElFormRef
().
resetFields
()
}
/** 提交表单 */
const
emit
=
defineEmits
([
'success'
])
// 定义 success 事件,用于操作成功后的回调
const
submitForm
=
async
()
=>
{
// 校验表单
if
(
!
formRef
)
return
const
valid
=
await
formRef
.
value
.
getElFormRef
().
validate
()
if
(
!
valid
)
return
// 提交请求
formLoading
.
value
=
true
try
{
const
data
=
formRef
.
value
.
formModel
as
CombinationActivityApi
.
CombinationActivityVO
const
products
=
spuAndSkuListRef
.
value
.
getSkuConfigs
(
'productConfig'
)
products
.
forEach
((
item
:
CombinationProductVO
)
=>
{
// 拼团价格元转分
item
.
activePrice
=
convertToInteger
(
item
.
activePrice
)
})
data
.
products
=
products
if
(
formType
.
value
===
'create'
)
{
await
CombinationActivityApi
.
createCombinationActivity
(
data
)
message
.
success
(
t
(
'common.createSuccess'
))
}
else
{
await
CombinationActivityApi
.
updateCombinationActivity
(
data
)
message
.
success
(
t
(
'common.updateSuccess'
))
}
dialogVisible
.
value
=
false
// 发送操作成功的事件
emit
(
'success'
)
}
finally
{
formLoading
.
value
=
false
}
}
</
script
>
src/views/mall/promotion/combination/activity/combinationActivity.data.ts
0 → 100644
View file @
b41d8f06
import
type
{
CrudSchema
}
from
'@/hooks/web/useCrudSchemas'
import
{
dateFormatter
,
getNowDateTime
}
from
'@/utils/formatTime'
// 表单校验
export
const
rules
=
reactive
({
name
:
[
required
],
totalLimitCount
:
[
required
],
singleLimitCount
:
[
required
],
startTime
:
[
required
],
endTime
:
[
required
],
userSize
:
[
required
],
totalNum
:
[
required
],
successNum
:
[
required
],
orderUserCount
:
[
required
],
virtualGroup
:
[
required
],
status
:
[
required
],
limitDuration
:
[
required
]
})
// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
const
crudSchemas
=
reactive
<
CrudSchema
[]
>
([
{
label
:
'拼团名称'
,
field
:
'name'
,
isSearch
:
true
,
isTable
:
false
,
form
:
{
colProps
:
{
span
:
24
}
}
},
{
label
:
'活动时间'
,
field
:
'activityTime'
,
formatter
:
dateFormatter
,
search
:
{
show
:
true
,
component
:
'DatePicker'
,
componentProps
:
{
valueFormat
:
'x'
,
type
:
'datetimerange'
,
rangeSeparator
:
'至'
}
},
form
:
{
component
:
'DatePicker'
,
componentProps
:
{
valueFormat
:
'x'
,
type
:
'datetimerange'
,
rangeSeparator
:
'至'
},
value
:
[
getNowDateTime
().
valueOf
(),
getNowDateTime
().
valueOf
()],
colProps
:
{
span
:
24
}
}
},
{
label
:
'参与人数'
,
field
:
'orderUserCount'
,
isSearch
:
false
,
form
:
{
component
:
'InputNumber'
,
labelMessage
:
'参与人数不能少于两人'
,
value
:
2
}
},
{
label
:
'限制时长'
,
field
:
'limitDuration'
,
isSearch
:
false
,
isTable
:
false
,
form
:
{
component
:
'InputNumber'
,
labelMessage
:
'限制时长(小时)'
,
componentProps
:
{
placeholder
:
'请输入限制时长(小时)'
}
}
},
{
label
:
'总限购数量'
,
field
:
'totalLimitCount'
,
isSearch
:
false
,
isTable
:
false
,
form
:
{
component
:
'InputNumber'
,
value
:
0
}
},
{
label
:
'单次限购数量'
,
field
:
'singleLimitCount'
,
isSearch
:
false
,
isTable
:
false
,
form
:
{
component
:
'InputNumber'
,
value
:
0
}
},
{
label
:
'购买人数'
,
field
:
'userSize'
,
isSearch
:
false
,
isForm
:
false
},
{
label
:
'开团组数'
,
field
:
'totalNum'
,
isSearch
:
false
,
isForm
:
false
},
{
label
:
'成团组数'
,
field
:
'successNum'
,
isSearch
:
false
,
isForm
:
false
},
{
label
:
'虚拟成团'
,
field
:
'virtualGroup'
,
isSearch
:
false
,
isTable
:
false
,
isForm
:
false
},
{
label
:
'活动状态'
,
field
:
'status'
,
dictType
:
DICT_TYPE
.
COMMON_STATUS
,
dictClass
:
'number'
,
isSearch
:
true
,
isForm
:
false
},
{
label
:
'拼团商品'
,
field
:
'spuId'
,
isSearch
:
false
,
form
:
{
colProps
:
{
span
:
24
}
}
},
{
label
:
'操作'
,
field
:
'action'
,
isForm
:
false
}
])
export
const
{
allSchemas
}
=
useCrudSchemas
(
crudSchemas
)
src/views/mall/promotion/combination/activity/index.vue
0 → 100644
View file @
b41d8f06
<
template
>
<!-- 搜索工作栏 -->
<ContentWrap>
<Search
:schema=
"allSchemas.searchSchema"
@
reset=
"setSearchParams"
@
search=
"setSearchParams"
>
<!-- 新增等操作按钮 -->
<template
#
actionMore
>
<el-button
v-hasPermi=
"['promotion:combination-activity:create']"
plain
type=
"primary"
@
click=
"openForm('create')"
>
<Icon
class=
"mr-5px"
icon=
"ep:plus"
/>
新增
</el-button>
</
template
>
</Search>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<Table
v-model:currentPage=
"tableObject.currentPage"
v-model:pageSize=
"tableObject.pageSize"
:columns=
"allSchemas.tableColumns"
:data=
"tableObject.tableList"
:loading=
"tableObject.loading"
:pagination=
"{
total: tableObject.total
}"
>
<
template
#
spuId=
"{ row }"
>
<el-image
:src=
"row.picUrl"
class=
"w-30px h-30px align-middle mr-5px"
@
click=
"imagePreview(row.picUrl)"
/>
<span
class=
"align-middle"
>
{{
row
.
spuName
}}
</span>
</
template
>
<
template
#
action=
"{ row }"
>
<el-button
v-hasPermi=
"['promotion:combination-activity:update']"
link
type=
"primary"
@
click=
"openForm('update', row.id)"
>
编辑
</el-button>
<el-button
v-hasPermi=
"['promotion:combination-activity:delete']"
link
type=
"danger"
@
click=
"handleDelete(row.id)"
>
删除
</el-button>
</
template
>
</Table>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<CombinationActivityForm
ref=
"formRef"
@
success=
"getList"
/>
</template>
<
script
lang=
"ts"
setup
>
import
{
allSchemas
}
from
'./combinationActivity.data'
import
*
as
CombinationActivityApi
from
'@/api/mall/promotion/combination/combinationactivity'
import
CombinationActivityForm
from
'./CombinationActivityForm.vue'
import
{
cloneDeep
}
from
'lodash-es'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
defineOptions
({
name
:
'PromotionCombinationActivity'
})
// tableObject:表格的属性对象,可获得分页大小、条数等属性
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
const
{
tableObject
,
tableMethods
}
=
useTable
({
getListApi
:
CombinationActivityApi
.
getCombinationActivityPage
,
// 分页接口
delListApi
:
CombinationActivityApi
.
deleteCombinationActivity
// 删除接口
})
// 获得表格的各种操作
const
{
getList
,
setSearchParams
}
=
tableMethods
/** 商品图预览 */
const
imagePreview
=
(
imgUrl
:
string
)
=>
{
createImageViewer
({
urlList
:
[
imgUrl
]
})
}
/** 添加/修改操作 */
const
formRef
=
ref
()
const
openForm
=
(
type
:
string
,
id
?:
number
)
=>
{
formRef
.
value
.
open
(
type
,
id
)
}
/** 删除按钮操作 */
const
handleDelete
=
(
id
:
number
)
=>
{
tableMethods
.
delList
(
id
,
false
)
}
// TODO @puhui999:要不还是使用原生的 element plus 做。感觉 crud schema 复杂界面,做起来麻烦
/** 初始化 **/
onMounted
(()
=>
{
/**
TODO
后面准备封装成一个函数来操作 tableColumns 重新排列:比如说需求是表单上商品选择是在后面的而列表展示的时候需要调到位置。
封装效果支持批量操作,给出 field 和需要插入的位置,例:[{field:'spuId',index: 1}] 效果为把 field 为 spuId 的 column 移动到第一个位置
*/
// 处理一下表格列让商品往前
const
index
=
allSchemas
.
tableColumns
.
findIndex
((
item
)
=>
item
.
field
===
'spuId'
)
const
column
=
cloneDeep
(
allSchemas
.
tableColumns
[
index
])
allSchemas
.
tableColumns
.
splice
(
index
,
1
)
// 添加到开头
allSchemas
.
tableColumns
.
unshift
(
column
)
getList
()
})
</
script
>
src/views/mall/promotion/components/SpuAndSkuList.vue
View file @
b41d8f06
<
template
>
<el-table
:data=
"spuData"
:
default-expand-all=
"true
"
>
<el-table
:data=
"spuData"
:
expand-row-keys=
"expandRowKeys"
row-key=
"id
"
>
<el-table-column
type=
"expand"
width=
"30"
>
<template
#
default=
"
{ row }">
<SkuList
...
...
@@ -10,22 +10,7 @@
:rule-config=
"ruleConfig"
>
<template
#
extension
>
<el-table-column
align=
"center"
label=
"秒杀库存"
min-width=
"168"
>
<template
#
default=
"
{ row: sku }">
<el-input-number
v-model=
"sku.productConfig.stock"
:min=
"0"
class=
"w-100%"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"秒杀价格(元)"
min-width=
"168"
>
<
template
#
default=
"{ row: sku }"
>
<el-input-number
v-model=
"sku.productConfig.seckillPrice"
:min=
"0"
:precision=
"2"
:step=
"0.1"
class=
"w-100%"
/>
</
template
>
</el-table-column>
<slot></slot>
</
template
>
</SkuList>
</template>
...
...
@@ -47,35 +32,33 @@
</el-table>
</template>
<
script
generic=
"T extends Spu"
lang=
"ts"
setup
>
// TODO 后续计划重新封装作为活动商品配置通用组件;可以等其他活动做到的时候,在统一处理 SPU 选择组件哈
import
{
formatToFraction
}
from
'@/utils'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
Spu
}
from
'@/api/mall/product/spu'
import
{
RuleConfig
,
SkuList
}
from
'@/views/mall/product/spu/components'
import
{
SeckillProductVO
}
from
'@/api/mall/promotion/seckill/seckillActivity'
import
{
SpuProperty
}
from
'@/views/mall/promotion/components/index'
defineOptions
({
name
:
'PromotionSpuAndSkuList'
})
// TODO @puhui999:是不是改成传递一个 spu 就好啦? 因为活动商品可以多选所以展示编辑的时候需要展示多个
const
props
=
defineProps
<
{
spuList
:
T
[]
spuList
:
T
[]
// TODO 为了方便兼容后续可能有需要展示多个 spu 的情况暂时保持,如果后续都是只操作一个 spu 的话则可更改为接受一个 spu 或保持
ruleConfig
:
RuleConfig
[]
spuPropertyListP
:
SpuProperty
<
T
>
[]
}
>
()
const
spuData
=
ref
<
Spu
[]
>
([])
// spu 详情数据列表
const
skuListRef
=
ref
()
// 商品属性列表Ref
const
spuPropertyList
=
ref
<
SpuProperty
<
T
>
[]
>
([])
// spuId 对应的 sku 的属性列表
const
expandRowKeys
=
ref
<
number
[]
>
()
// 控制展开行需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。
/**
* 获取所有 sku 秒杀配置
* 获取所有 sku 活动配置
*
* @param extendedAttribute 在 sku 上扩展的属性,例:秒杀活动 sku 扩展属性 productConfig 请参考 seckillActivity.ts
*/
const
getSkuConfigs
:
<
V
>
(
extendedAttribute
:
string
)
=>
V
[]
=
(
extendedAttribute
:
string
)
=>
{
const
getSkuConfigs
=
(
extendedAttribute
:
string
)
=>
{
skuListRef
.
value
.
validateSku
()
const
seckillProducts
:
SeckillProductVO
[]
=
[]
const
seckillProducts
=
[]
spuPropertyList
.
value
.
forEach
((
item
)
=>
{
item
.
spuDetail
.
skus
.
forEach
((
sku
)
=>
{
seckillProducts
.
push
(
sku
[
extendedAttribute
])
...
...
@@ -116,6 +99,10 @@ watch(
(
data
)
=>
{
if
(
!
data
)
return
spuPropertyList
.
value
=
data
as
SpuProperty
<
T
>
[]
// 解决如果之前选择的是单规格 spu 的话后面选择多规格 sku 多规格属性信息不展示的问题。解决方法:让 SkuList 组件重新渲染(行折叠会干掉包含的组件展开时会重新加载)
setTimeout
(()
=>
{
expandRowKeys
.
value
=
data
.
map
((
item
)
=>
item
.
spuId
)
},
200
)
},
{
deep
:
true
,
...
...
src/views/mall/promotion/components/SpuSelect.vue
View file @
b41d8f06
...
...
@@ -57,6 +57,7 @@
<template
#
default
>
<SkuList
v-if=
"isExpand"
ref=
"skuListRef"
:isComponent=
"true"
:isDetail=
"true"
:prop-form-data=
"spuData"
...
...
@@ -110,7 +111,7 @@
</template>
<
script
lang=
"ts"
setup
>
import
{
getPropertyList
,
Propert
i
es
,
SkuList
}
from
'@/views/mall/product/spu/components'
import
{
getPropertyList
,
Propert
yAndValu
es
,
SkuList
}
from
'@/views/mall/product/spu/components'
import
{
ElTable
}
from
'element-plus'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
...
...
@@ -143,19 +144,66 @@ const queryParams = ref({
categoryId
:
null
,
createTime
:
[]
})
// 查询参数
const
propertyList
=
ref
<
Propert
i
es
[]
>
([])
// 商品属性列表
const
propertyList
=
ref
<
Propert
yAndValu
es
[]
>
([])
// 商品属性列表
const
spuListRef
=
ref
<
InstanceType
<
typeof
ElTable
>>
()
const
spuData
=
ref
<
ProductSpuApi
.
Spu
|
{}
>
()
// 商品详情
const
skuListRef
=
ref
()
// 商品属性选择 Ref
const
spuData
=
ref
<
ProductSpuApi
.
Spu
>
()
// 商品详情
const
isExpand
=
ref
(
false
)
// 控制 SKU 列表显示
const
expandRowKeys
=
ref
<
number
[]
>
()
// 控制展开行需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。
//============ 商品选择相关 ============
const
selectedSpuId
=
ref
<
number
>
(
0
)
// 选中的商品 spuId
const
selectedSkuIds
=
ref
<
number
[]
>
([])
// 选中的商品 skuIds
const
selectSku
=
(
val
:
ProductSpuApi
.
Sku
[])
=>
{
if
(
selectedSpuId
.
value
===
0
)
{
message
.
warning
(
'请先选择商品再选择相应的规格!!!'
)
skuListRef
.
value
.
clearSelection
()
return
}
selectedSkuIds
.
value
=
val
.
map
((
sku
)
=>
sku
.
id
!
)
}
const
selectSpu
=
(
val
:
ProductSpuApi
.
Spu
[])
=>
{
if
(
val
.
length
===
0
)
{
selectedSpuId
.
value
=
0
return
}
// 只选择一个
selectedSpuId
.
value
=
val
.
map
((
spu
)
=>
spu
.
id
!
)[
0
]
// 切换选择 spu 如果有选择的 sku 则清空,确保选择的 sku 是对应的 spu 下面的
if
(
selectedSkuIds
.
value
.
length
>
0
)
{
selectedSkuIds
.
value
=
[]
}
// 如果大于1个
if
(
val
.
length
>
1
)
{
// 清空选择
spuListRef
.
value
.
clearSelection
()
// 变更为最后一次选择的
spuListRef
.
value
.
toggleRowSelection
(
val
.
pop
(),
true
)
return
}
expandChange
(
val
[
0
],
val
)
}
// 计算商品属性
const
expandChange
=
async
(
row
:
ProductSpuApi
.
Spu
,
expandedRows
:
ProductSpuApi
.
Spu
[])
=>
{
const
expandChange
=
async
(
row
:
ProductSpuApi
.
Spu
,
expandedRows
?:
ProductSpuApi
.
Spu
[])
=>
{
// 判断需要展开的 spuId === 选择的 spuId。如果选择了 A 就展开 A 的 skuList。如果选择了 A 手动展开 B 则阻断
// 目的防止误选 sku
if
(
selectedSpuId
.
value
!==
0
)
{
if
(
row
.
id
!==
selectedSpuId
.
value
)
{
message
.
warning
(
'你已选择商品请先取消'
)
expandRowKeys
.
value
=
[
selectedSpuId
.
value
]
return
}
// 如果以展开 skuList 则选择此对应的 spu 不需要重新获取渲染 skuList
if
(
isExpand
.
value
&&
spuData
.
value
?.
id
===
row
.
id
)
{
return
}
}
spuData
.
value
=
{}
propertyList
.
value
=
[]
isExpand
.
value
=
false
// 如果展开个数为 0
if
(
expandedRows
.
length
===
0
)
{
if
(
expandedRows
?.
length
===
0
)
{
// 如果展开个数为 0
expandRowKeys
.
value
=
[]
return
}
...
...
@@ -167,33 +215,15 @@ const expandChange = async (row: ProductSpuApi.Spu, expandedRows: ProductSpuApi.
expandRowKeys
.
value
=
[
row
.
id
!
]
}
//============ 商品选择相关 ============
const
selectedSpuIds
=
ref
<
number
[]
>
([])
// 选中的商品 spuIds
const
selectedSkuIds
=
ref
<
number
[]
>
([])
// 选中的商品 skuIds
const
selectSku
=
(
val
:
ProductSpuApi
.
Sku
[])
=>
{
selectedSkuIds
.
value
=
val
.
map
((
sku
)
=>
sku
.
id
!
)
}
const
selectSpu
=
(
val
:
ProductSpuApi
.
Spu
[])
=>
{
selectedSpuIds
.
value
=
val
.
map
((
spu
)
=>
spu
.
id
!
)
// // 只选择一个
// selectedSpu.value = val[0]
// // 如果大于1个
// if (val.length > 1) {
// // 清空选择
// spuListRef.value.clearSelection()
// // 变更为最后一次选择的
// spuListRef.value.toggleRowSelection(val.pop(), true)
// }
}
// 确认选择时的触发事件
const
emits
=
defineEmits
<
{
(
e
:
'confirm'
,
spuId
s
:
number
[]
,
skuIds
?:
number
[]):
void
(
e
:
'confirm'
,
spuId
:
number
,
skuIds
?:
number
[]):
void
}
>
()
/**
* 确认选择返回选中的 spu 和 sku (如果需要选择sku的话)
*/
const
confirm
=
()
=>
{
if
(
selectedSpuId
s
.
value
.
length
===
0
)
{
if
(
selectedSpuId
.
value
===
0
)
{
message
.
warning
(
'没有选择任何商品'
)
return
}
...
...
@@ -203,10 +233,12 @@ const confirm = () => {
}
// 返回各自 id 列表
props
.
isSelectSku
?
emits
(
'confirm'
,
selectedSpuId
s
.
value
,
selectedSkuIds
.
value
)
:
emits
(
'confirm'
,
selectedSpuId
s
.
value
)
?
emits
(
'confirm'
,
selectedSpuId
.
value
,
selectedSkuIds
.
value
)
:
emits
(
'confirm'
,
selectedSpuId
.
value
)
// 关闭弹窗
dialogVisible
.
value
=
false
selectedSpuId
.
value
=
0
selectedSkuIds
.
value
=
[]
}
/** 打开弹窗 */
...
...
src/views/mall/promotion/components/index.ts
View file @
b41d8f06
import
SpuSelect
from
'./SpuSelect.vue'
import
SpuAndSkuList
from
'./SpuAndSkuList.vue'
import
{
Propert
i
es
}
from
'@/views/mall/product/spu/components'
import
{
Propert
yAndValu
es
}
from
'@/views/mall/product/spu/components'
type
SpuProperty
<
T
>
=
{
spuId
:
number
spuDetail
:
T
propertyList
:
Propert
i
es
[]
propertyList
:
Propert
yAndValu
es
[]
}
/**
...
...
src/views/mall/promotion/seckill/activity/SeckillActivityForm.vue
View file @
b41d8f06
...
...
@@ -8,14 +8,31 @@
:schema=
"allSchemas.formSchema"
>
<!-- 先选择 -->
<template
#
spuId
s
>
<template
#
spuId
>
<el-button
@
click=
"spuSelectRef.open()"
>
选择商品
</el-button>
<SpuAndSkuList
ref=
"spuAndSkuListRef"
:rule-config=
"ruleConfig"
:spu-list=
"spuList"
:spu-property-list-p=
"spuPropertyList"
/>
>
<el-table-column
align=
"center"
label=
"秒杀库存"
min-width=
"168"
>
<template
#
default=
"
{ row: sku }">
<el-input-number
v-model=
"sku.productConfig.stock"
:min=
"0"
class=
"w-100%"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"秒杀价格(元)"
min-width=
"168"
>
<
template
#
default=
"{ row: sku }"
>
<el-input-number
v-model=
"sku.productConfig.seckillPrice"
:min=
"0"
:precision=
"2"
:step=
"0.1"
class=
"w-100%"
/>
</
template
>
</el-table-column>
</SpuAndSkuList>
</template>
</Form>
<
template
#
footer
>
...
...
@@ -23,15 +40,17 @@
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</Dialog>
<SpuSelect
ref=
"spuSelectRef"
@
confirm=
"selectSpu"
/>
<SpuSelect
ref=
"spuSelectRef"
:isSelectSku=
"true"
@
confirm=
"selectSpu"
/>
</template>
<
script
lang=
"ts"
setup
>
import
{
SpuAndSkuList
,
SpuProperty
,
SpuSelect
}
from
'../../components'
import
{
allSchemas
,
rules
}
from
'./seckillActivity.data'
import
*
as
SeckillActivityApi
from
'@/api/mall/promotion/seckill/seckillActivity'
import
{
getPropertyList
,
RuleConfig
}
from
'@/views/mall/product/spu/components
'
import
{
SeckillProductVO
}
from
'@/api/mall/promotion/seckill/seckillActivity
'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
{
getPropertyList
,
RuleConfig
}
from
'@/views/mall/product/spu/components'
import
{
convertToInteger
,
formatToFraction
}
from
'@/utils'
defineOptions
({
name
:
'PromotionSeckillActivityForm'
})
...
...
@@ -43,6 +62,9 @@ const dialogTitle = ref('') // 弹窗的标题
const
formLoading
=
ref
(
false
)
// 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const
formType
=
ref
(
''
)
// 表单的类型:create - 新增;update - 修改
const
formRef
=
ref
()
// 表单 Ref
// ================= 商品选择相关 =================
const
spuSelectRef
=
ref
()
// 商品和属性选择 Ref
const
spuAndSkuListRef
=
ref
()
// sku 秒杀配置组件Ref
const
ruleConfig
:
RuleConfig
[]
=
[
...
...
@@ -57,17 +79,76 @@ const ruleConfig: RuleConfig[] = [
message
:
'商品秒杀价格必须大于 0.01 !!!'
}
]
const
spuList
=
ref
<
SeckillActivityApi
.
SpuExtension
[]
>
([])
// 选择的 spu
const
spuPropertyList
=
ref
<
SpuProperty
<
SeckillActivityApi
.
SpuExtension
>
[]
>
([])
const
selectSpu
=
(
spuId
:
number
,
skuIds
:
number
[])
=>
{
formRef
.
value
.
setValues
({
spuId
})
getSpuDetails
(
spuId
,
skuIds
)
}
/**
* 获取 SPU 详情
*/
const
getSpuDetails
=
async
(
spuId
:
number
,
skuIds
:
number
[]
|
undefined
,
products
?:
SeckillProductVO
[]
)
=>
{
const
spuProperties
:
SpuProperty
<
SeckillActivityApi
.
SpuExtension
>
[]
=
[]
const
res
=
(
await
ProductSpuApi
.
getSpuDetailList
([
spuId
]))
as
SeckillActivityApi
.
SpuExtension
[]
if
(
res
.
length
==
0
)
{
return
}
spuList
.
value
=
[]
// 因为只能选择一个
const
spu
=
res
[
0
]
const
selectSkus
=
typeof
skuIds
===
'undefined'
?
spu
?.
skus
:
spu
?.
skus
?.
filter
((
sku
)
=>
skuIds
.
includes
(
sku
.
id
!
))
selectSkus
?.
forEach
((
sku
)
=>
{
let
config
:
SeckillActivityApi
.
SeckillProductVO
=
{
skuId
:
sku
.
id
!
,
stock
:
0
,
seckillPrice
:
0
}
if
(
typeof
products
!==
'undefined'
)
{
const
product
=
products
.
find
((
item
)
=>
item
.
skuId
===
sku
.
id
)
if
(
product
)
{
// 分转元
product
.
seckillPrice
=
formatToFraction
(
product
.
seckillPrice
)
}
config
=
product
||
config
}
sku
.
productConfig
=
config
})
spu
.
skus
=
selectSkus
as
SeckillActivityApi
.
SkuExtension
[]
spuProperties
.
push
({
spuId
:
spu
.
id
!
,
spuDetail
:
spu
,
propertyList
:
getPropertyList
(
spu
)
})
spuList
.
value
.
push
(
spu
)
spuPropertyList
.
value
=
spuProperties
}
// ================= end =================
/** 打开弹窗 */
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
dialogVisible
.
value
=
true
dialogTitle
.
value
=
t
(
'action.'
+
type
)
formType
.
value
=
type
resetForm
()
// 修改时,设置数据
TODO 没测试估计有问题
await
resetForm
()
// 修改时,设置数据
if
(
id
)
{
formLoading
.
value
=
true
try
{
const
data
=
await
SeckillActivityApi
.
getSeckillActivity
(
id
)
const
data
=
(
await
SeckillActivityApi
.
getSeckillActivity
(
id
))
as
SeckillActivityApi
.
SeckillActivityVO
await
getSpuDetails
(
data
.
spuId
!
,
data
.
products
?.
map
((
sku
)
=>
sku
.
skuId
),
data
.
products
)
formRef
.
value
.
setValues
(
data
)
}
finally
{
formLoading
.
value
=
false
...
...
@@ -76,47 +157,11 @@ const open = async (type: string, id?: number) => {
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
const
spuList
=
ref
<
SeckillActivityApi
.
SpuExtension
[]
>
([])
// 选择的 spu
const
spuPropertyList
=
ref
<
SpuProperty
<
SeckillActivityApi
.
SpuExtension
>
[]
>
([])
const
selectSpu
=
(
spuIds
:
number
[])
=>
{
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
=
()
=>
{
const
resetForm
=
async
()
=>
{
spuList
.
value
=
[]
spuPropertyList
.
value
=
[]
await
nextTick
()
formRef
.
value
.
getElFormRef
().
resetFields
()
}
/** 提交表单 */
...
...
@@ -130,8 +175,13 @@ const submitForm = async () => {
formLoading
.
value
=
true
try
{
const
data
=
formRef
.
value
.
formModel
as
SeckillActivityApi
.
SeckillActivityVO
data
.
spuIds
=
spuList
.
value
.
map
((
spu
)
=>
spu
.
id
!
)
data
.
products
=
spuAndSkuListRef
.
value
.
getSkuConfigs
(
'productConfig'
)
const
products
=
spuAndSkuListRef
.
value
.
getSkuConfigs
(
'productConfig'
)
products
.
forEach
((
item
:
SeckillProductVO
)
=>
{
// 秒杀价格元转分
item
.
seckillPrice
=
convertToInteger
(
item
.
seckillPrice
)
})
// 获取秒杀商品配置
data
.
products
=
products
if
(
formType
.
value
===
'create'
)
{
await
SeckillActivityApi
.
createSeckillActivity
(
data
)
message
.
success
(
t
(
'common.createSuccess'
))
...
...
src/views/mall/promotion/seckill/activity/index.vue
View file @
b41d8f06
...
...
@@ -32,6 +32,14 @@
@
expand-change=
"expandChange"
>
<
template
#
expand
>
展示活动商品和商品相关属性活动配置
</
template
>
<
template
#
spuId=
"{ row }"
>
<el-image
:src=
"row.picUrl"
class=
"w-30px h-30px align-middle mr-5px"
@
click=
"imagePreview(row.picUrl)"
/>
<span
class=
"align-middle"
>
{{
row
.
spuName
}}
</span>
</
template
>
<
template
#
configIds=
"{ row }"
>
<el-tag
v-for=
"(name, index) in convertSeckillConfigNames(row)"
:key=
"index"
class=
"mr-5px"
>
{{
name
}}
...
...
@@ -66,6 +74,8 @@ import { allSchemas } from './seckillActivity.data'
import
{
getListAllSimple
}
from
'@/api/mall/promotion/seckill/seckillConfig'
import
*
as
SeckillActivityApi
from
'@/api/mall/promotion/seckill/seckillActivity'
import
SeckillActivityForm
from
'./SeckillActivityForm.vue'
import
{
cloneDeep
}
from
'lodash-es'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
defineOptions
({
name
:
'PromotionSeckillActivity'
})
...
...
@@ -89,12 +99,16 @@ const openForm = (type: string, id?: number) => {
const
handleDelete
=
(
id
:
number
)
=>
{
tableMethods
.
delList
(
id
,
false
)
}
// TODO @puhui:是不是直接叫 configList 就好啦
const
seckillConfigAllSimple
=
ref
([])
// 时段配置精简列表
/** 商品图预览 */
const
imagePreview
=
(
imgUrl
:
string
)
=>
{
createImageViewer
({
urlList
:
[
imgUrl
]
})
}
const
configList
=
ref
([])
// 时段配置精简列表
const
convertSeckillConfigNames
=
computed
(
()
=>
(
row
)
=>
seckillConfigAllSimple
.
value
configList
.
value
?.
filter
((
item
)
=>
row
.
configIds
.
includes
(
item
.
id
))
?.
map
((
config
)
=>
config
.
name
)
)
...
...
@@ -106,7 +120,18 @@ const expandChange = (row, expandedRows) => {
/** 初始化 **/
onMounted
(
async
()
=>
{
/*
TODO
后面准备封装成一个函数来操作 tableColumns 重新排列:比如说需求是表单上商品选择是在后面的而列表展示的时候需要调到位置。
封装效果支持批量操作,给出 field 和需要插入的位置,例:[{field:'spuId',index: 1}] 效果为把 field 为 spuId 的 column 移动到第一个位置
*/
// 处理一下表格列让商品往前
const
index
=
allSchemas
.
tableColumns
.
findIndex
((
item
)
=>
item
.
field
===
'spuId'
)
const
column
=
cloneDeep
(
allSchemas
.
tableColumns
[
index
])
allSchemas
.
tableColumns
.
splice
(
index
,
1
)
// 添加到开头
allSchemas
.
tableColumns
.
unshift
(
column
)
await
getList
()
seckillConfigAllSimple
.
value
=
await
getListAllSimple
()
configList
.
value
=
await
getListAllSimple
()
})
</
script
>
src/views/mall/promotion/seckill/activity/seckillActivity.data.ts
View file @
b41d8f06
...
...
@@ -153,6 +153,17 @@ const crudSchemas = reactive<CrudSchema[]>([
}
},
{
label
:
'排序'
,
field
:
'sort'
,
form
:
{
component
:
'InputNumber'
,
value
:
0
},
table
:
{
width
:
80
}
},
{
label
:
'秒杀库存'
,
field
:
'stock'
,
isForm
:
false
,
...
...
@@ -167,18 +178,15 @@ const crudSchemas = reactive<CrudSchema[]>([
{
label
:
'秒杀总库存'
,
field
:
'totalStock'
,
form
:
{
component
:
'InputNumber'
,
value
:
0
},
isForm
:
false
,
table
:
{
width
:
120
}
},
{
label
:
'秒杀活动商品'
,
field
:
'spuId
s
'
,
isTable
:
fals
e
,
field
:
'spuId'
,
isTable
:
tru
e
,
isSearch
:
false
,
form
:
{
colProps
:
{
...
...
@@ -186,7 +194,7 @@ const crudSchemas = reactive<CrudSchema[]>([
}
},
table
:
{
width
:
2
00
width
:
3
00
}
},
{
...
...
@@ -207,17 +215,6 @@ const crudSchemas = reactive<CrudSchema[]>([
}
},
{
label
:
'排序'
,
field
:
'sort'
,
form
:
{
component
:
'InputNumber'
,
value
:
0
},
table
:
{
width
:
80
}
},
{
label
:
'状态'
,
field
:
'status'
,
dictType
:
DICT_TYPE
.
COMMON_STATUS
,
...
...
src/views/mall/promotion/seckill/config/SeckillConfigForm.vue
View file @
b41d8f06
...
...
@@ -10,6 +10,7 @@
<
script
lang=
"ts"
name=
"SeckillConfigForm"
setup
>
import
*
as
SeckillConfigApi
from
'@/api/mall/promotion/seckill/seckillConfig'
import
{
allSchemas
,
rules
}
from
'./seckillConfig.data'
import
{
cloneDeep
}
from
'lodash-es'
const
{
t
}
=
useI18n
()
// 国际化
const
message
=
useMessage
()
// 消息弹窗
...
...
@@ -30,6 +31,9 @@ const open = async (type: string, id?: number) => {
formLoading
.
value
=
true
try
{
const
data
=
await
SeckillConfigApi
.
getSeckillConfig
(
id
)
data
.
sliderPicUrls
=
data
[
'sliderPicUrls'
]?.
map
((
item
)
=>
({
url
:
item
}))
formRef
.
value
.
setValues
(
data
)
}
finally
{
formLoading
.
value
=
false
...
...
@@ -48,12 +52,20 @@ const submitForm = async () => {
// 提交请求
formLoading
.
value
=
true
try
{
// 处理轮播图列表
const
data
=
formRef
.
value
.
formModel
as
SeckillConfigApi
.
SeckillConfigVO
const
cloneData
=
cloneDeep
(
data
)
const
newSliderPicUrls
=
[]
cloneData
.
sliderPicUrls
.
forEach
((
item
)
=>
{
// 如果是前端选的图
typeof
item
===
'object'
?
newSliderPicUrls
.
push
(
item
.
url
)
:
newSliderPicUrls
.
push
(
item
)
})
cloneData
.
sliderPicUrls
=
newSliderPicUrls
if
(
formType
.
value
===
'create'
)
{
await
SeckillConfigApi
.
createSeckillConfig
(
d
ata
)
await
SeckillConfigApi
.
createSeckillConfig
(
cloneD
ata
)
message
.
success
(
t
(
'common.createSuccess'
))
}
else
{
await
SeckillConfigApi
.
updateSeckillConfig
(
d
ata
)
await
SeckillConfigApi
.
updateSeckillConfig
(
cloneD
ata
)
message
.
success
(
t
(
'common.updateSuccess'
))
}
dialogVisible
.
value
=
false
...
...
src/views/mall/promotion/seckill/config/index.vue
View file @
b41d8f06
...
...
@@ -29,8 +29,14 @@
total: tableObject.total
}"
>
<
template
#
picUrl=
"{ row }"
>
<el-image
:src=
"row.picUrl"
class=
"w-30px h-30px"
@
click=
"imagePreview(row.picUrl)"
/>
<
template
#
sliderPicUrls=
"{ row }"
>
<el-image
v-for=
"(item, index) in row.sliderPicUrls"
:key=
"index"
:src=
"item"
class=
"w-60px h-60px mr-10px"
@
click=
"imagePreview(row.sliderPicUrls)"
/>
</
template
>
<
template
#
status=
"{ row }"
>
<el-switch
...
...
@@ -70,6 +76,7 @@ import * as SeckillConfigApi from '@/api/mall/promotion/seckill/seckillConfig'
import
SeckillConfigForm
from
'./SeckillConfigForm.vue'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
CommonStatusEnum
}
from
'@/utils/constants'
import
{
isArray
}
from
'@/utils/is'
const
message
=
useMessage
()
// 消息弹窗
// tableObject:表格的属性对象,可获得分页大小、条数等属性
...
...
@@ -82,10 +89,18 @@ const { tableObject, tableMethods } = useTable({
// 获得表格的各种操作
const
{
getList
,
setSearchParams
}
=
tableMethods
/** 商品图预览 */
const
imagePreview
=
(
imgUrl
:
string
)
=>
{
/** 轮播图预览预览 */
const
imagePreview
=
(
args
)
=>
{
const
urlList
=
[]
if
(
isArray
(
args
))
{
args
.
forEach
((
item
)
=>
{
urlList
.
push
(
item
)
})
}
else
{
urlList
.
push
(
args
)
}
createImageViewer
({
urlList
:
[
imgUrl
]
urlList
})
}
...
...
src/views/mall/promotion/seckill/config/seckillConfig.data.ts
View file @
b41d8f06
...
...
@@ -46,11 +46,14 @@ const crudSchemas = reactive<CrudSchema[]>([
}
},
{
label
:
'秒杀
主
图'
,
field
:
'
picUrl
'
,
label
:
'秒杀
轮播
图'
,
field
:
'
sliderPicUrls
'
,
isSearch
:
false
,
form
:
{
component
:
'UploadImg'
component
:
'UploadImgs'
},
table
:
{
width
:
300
}
},
{
...
...
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