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
fa71d3e7
authored
Jun 24, 2023
by
芋道源码
Committed by
Gitee
Jun 24, 2023
Browse files
Options
Browse Files
Download
Plain Diff
!175 mall seckill
Merge pull request !175 from puhui999/dev-to-dev
parents
0395b8f1
c9ae0fbd
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
1429 additions
and
19 deletions
+1429
-19
src/api/mall/product/spu.ts
+10
-0
src/api/mall/promotion/seckill/seckillActivity.ts
+67
-0
src/api/mall/promotion/seckill/seckillConfig.ts
+54
-0
src/components/Dialog/src/Dialog.vue
+1
-1
src/hooks/web/useCrudSchemas.ts
+5
-2
src/types/table.d.ts
+2
-0
src/utils/formatTime.ts
+2
-2
src/views/mall/product/spu/components/SkuList.vue
+96
-5
src/views/mall/product/spu/components/index.ts
+43
-1
src/views/mall/product/spu/index.vue
+2
-8
src/views/mall/promotion/seckill/activity/SeckillActivityForm.vue
+102
-0
src/views/mall/promotion/seckill/activity/components/SpuAndSkuList.vue
+157
-0
src/views/mall/promotion/seckill/activity/components/SpuAndSkuSelectForm.vue
+272
-0
src/views/mall/promotion/seckill/activity/components/index.ts
+4
-0
src/views/mall/promotion/seckill/activity/index.vue
+86
-0
src/views/mall/promotion/seckill/activity/seckillActivity.data.ts
+261
-0
src/views/mall/promotion/seckill/config/SeckillConfigForm.vue
+66
-0
src/views/mall/promotion/seckill/config/index.vue
+120
-0
src/views/mall/promotion/seckill/config/seckillConfig.data.ts
+79
-0
No files found.
src/api/mall/product/spu.ts
View file @
fa71d3e7
...
...
@@ -49,6 +49,16 @@ export interface Spu {
recommendGood
?:
boolean
// 是否优品
}
export
interface
SpuRespVO
extends
Spu
{
price
:
number
salesCount
:
number
marketPrice
:
number
costPrice
:
number
stock
:
number
createTime
:
Date
status
:
number
}
// 获得 Spu 列表
export
const
getSpuPage
=
(
params
:
PageParam
)
=>
{
return
request
.
get
({
url
:
'/product/spu/page'
,
params
})
...
...
src/api/mall/promotion/seckill/seckillActivity.ts
0 → 100644
View file @
fa71d3e7
import
request
from
'@/config/axios'
import
{
Sku
,
SpuRespVO
}
from
'@/api/mall/product/spu'
export
interface
SeckillActivityVO
{
id
:
number
spuIds
:
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
}
type
SkuExtension
=
Sku
&
{
productConfig
:
SeckillProductVO
}
export
interface
SpuExtension
extends
SpuRespVO
{
skus
:
SkuExtension
[]
// 重写类型
}
// 查询秒杀活动列表
export
const
getSeckillActivityPage
=
async
(
params
)
=>
{
return
await
request
.
get
({
url
:
'/promotion/seckill-activity/page'
,
params
})
}
// 查询秒杀活动详情
export
const
getSeckillActivity
=
async
(
id
:
number
)
=>
{
return
await
request
.
get
({
url
:
'/promotion/seckill-activity/get?id='
+
id
})
}
// 新增秒杀活动
export
const
createSeckillActivity
=
async
(
data
:
SeckillActivityVO
)
=>
{
return
await
request
.
post
({
url
:
'/promotion/seckill-activity/create'
,
data
})
}
// 修改秒杀活动
export
const
updateSeckillActivity
=
async
(
data
:
SeckillActivityVO
)
=>
{
return
await
request
.
put
({
url
:
'/promotion/seckill-activity/update'
,
data
})
}
// 删除秒杀活动
export
const
deleteSeckillActivity
=
async
(
id
:
number
)
=>
{
return
await
request
.
delete
({
url
:
'/promotion/seckill-activity/delete?id='
+
id
})
}
// 导出秒杀活动 Excel
export
const
exportSeckillActivityApi
=
async
(
params
)
=>
{
return
await
request
.
download
({
url
:
'/promotion/seckill-activity/export-excel'
,
params
})
}
src/api/mall/promotion/seckill/seckillConfig.ts
0 → 100644
View file @
fa71d3e7
import
request
from
'@/config/axios'
export
interface
SeckillConfigVO
{
id
:
number
name
:
string
startTime
:
string
endTime
:
string
picUrl
:
string
status
:
number
}
// 查询秒杀时段配置列表
export
const
getSeckillConfigPage
=
async
(
params
)
=>
{
return
await
request
.
get
({
url
:
'/promotion/seckill-config/page'
,
params
})
}
// 查询秒杀时段配置详情
export
const
getSeckillConfig
=
async
(
id
:
number
)
=>
{
return
await
request
.
get
({
url
:
'/promotion/seckill-config/get?id='
+
id
})
}
// 获得所有开启状态的秒杀时段精简列表
export
const
getListAllSimple
=
async
()
=>
{
return
await
request
.
get
({
url
:
'/promotion/seckill-config/list-all-simple'
})
}
// 新增秒杀时段配置
export
const
createSeckillConfig
=
async
(
data
:
SeckillConfigVO
)
=>
{
return
await
request
.
post
({
url
:
'/promotion/seckill-config/create'
,
data
})
}
// 修改秒杀时段配置
export
const
updateSeckillConfig
=
async
(
data
:
SeckillConfigVO
)
=>
{
return
await
request
.
put
({
url
:
'/promotion/seckill-config/update'
,
data
})
}
// 修改时段配置状态
export
const
updateSeckillConfigStatus
=
(
id
:
number
,
status
:
number
)
=>
{
const
data
=
{
id
,
status
}
return
request
.
put
({
url
:
'/promotion/seckill-config/update-status'
,
data
:
data
})
}
// 删除秒杀时段配置
export
const
deleteSeckillConfig
=
async
(
id
:
number
)
=>
{
return
await
request
.
delete
({
url
:
'/promotion/seckill-config/delete?id='
+
id
})
}
// 导出秒杀时段配置 Excel
export
const
exportSeckillConfigApi
=
async
(
params
)
=>
{
return
await
request
.
download
({
url
:
'/promotion/seckill-config/export-excel'
,
params
})
}
src/components/Dialog/src/Dialog.vue
View file @
fa71d3e7
...
...
@@ -17,7 +17,7 @@ const props = defineProps({
})
const
getBindValue
=
computed
(()
=>
{
const
delArr
:
string
[]
=
[
'fullscreen'
,
'title'
,
'maxHeight'
]
const
delArr
:
string
[]
=
[
'fullscreen'
,
'title'
,
'maxHeight'
,
'appendToBody'
]
const
attrs
=
useAttrs
()
const
obj
=
{
...
attrs
,
...
props
}
for
(
const
key
in
obj
)
{
...
...
src/hooks/web/useCrudSchemas.ts
View file @
fa71d3e7
import
{
reactive
}
from
'vue'
import
{
AxiosPromise
}
from
'axios'
import
{
findIndex
}
from
'@/utils'
import
{
eachTree
,
treeMap
,
filter
}
from
'@/utils/tree'
import
{
eachTree
,
filter
,
treeMap
}
from
'@/utils/tree'
import
{
getBoolDictOptions
,
getDictOptions
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
FormSchema
}
from
'@/types/form'
...
...
@@ -36,8 +36,11 @@ type CrudSearchParams = {
type
CrudTableParams
=
{
// 是否显示表头
show
?:
boolean
// 列宽配置
width
?:
number
|
string
// 列是否固定在左侧或者右侧
fixed
?:
'left'
|
'right'
}
&
Omit
<
FormSchema
,
'field'
>
type
CrudFormParams
=
{
// 是否显示表单项
show
?:
boolean
...
...
src/types/table.d.ts
View file @
fa71d3e7
export
type
TableColumn
=
{
field
:
string
label
?:
string
width
?:
number
|
string
fixed
?:
'left'
|
'right'
children
?:
TableColumn
[]
}
&
Recordable
...
...
src/utils/formatTime.ts
View file @
fa71d3e7
...
...
@@ -155,7 +155,7 @@ export const dateFormatter = (row, column, cellValue) => {
* @returns 带时间00:00:00的日期
*/
export
function
beginOfDay
(
param
:
Date
)
{
return
new
Date
(
param
.
getFullYear
(),
param
.
getMonth
(),
param
.
getDate
(),
0
,
0
,
0
,
0
)
return
new
Date
(
param
.
getFullYear
(),
param
.
getMonth
(),
param
.
getDate
(),
0
,
0
,
0
)
}
/**
...
...
@@ -164,7 +164,7 @@ export function beginOfDay(param: Date) {
* @returns 带时间23:59:59的日期
*/
export
function
endOfDay
(
param
:
Date
)
{
return
new
Date
(
param
.
getFullYear
(),
param
.
getMonth
(),
param
.
getDate
(),
23
,
59
,
59
,
999
)
return
new
Date
(
param
.
getFullYear
(),
param
.
getMonth
(),
param
.
getDate
(),
23
,
59
,
59
)
}
/**
...
...
src/views/mall/product/spu/components/SkuList.vue
View file @
fa71d3e7
<
template
>
<!-- 情况一:添加/修改 -->
<el-table
v-if=
"!isDetail"
v-if=
"!isDetail
&& !isActivityComponent
"
:data=
"isBatch ? skuList : formData!.skus"
border
class=
"tabNumWidth"
...
...
@@ -118,7 +118,9 @@
max-height=
"500"
size=
"small"
style=
"width: 99%"
@
selection-change=
"handleSelectionChange"
>
<el-table-column
v-if=
"isComponent"
type=
"selection"
width=
"45"
/>
<el-table-column
align=
"center"
label=
"图片"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
<el-image
:src=
"row.picUrl"
class=
"w-60px h-60px"
@
click=
"imagePreview(row.picUrl)"
/>
...
...
@@ -188,6 +190,66 @@
</el-table-column>
</template>
</el-table>
<!-- 情况三:作为活动组件 -->
<el-table
v-if=
"isActivityComponent"
:data=
"formData!.skus"
border
max-height=
"500"
size=
"small"
style=
"width: 99%"
>
<el-table-column
v-if=
"isComponent"
type=
"selection"
width=
"45"
/>
<el-table-column
align=
"center"
label=
"图片"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
<el-image
:src=
"row.picUrl"
class=
"w-60px h-60px"
@
click=
"imagePreview(row.picUrl)"
/>
</
template
>
</el-table-column>
<
template
v-if=
"formData!.specType"
>
<!-- 根据商品属性动态添加 -->
<el-table-column
v-for=
"(item, index) in tableHeaders"
:key=
"index"
:label=
"item.label"
align=
"center"
min-width=
"80"
>
<template
#
default=
"
{ row }">
<span
style=
"font-weight: bold; color: #40aaff"
>
{{
row
.
properties
[
index
]?.
valueName
}}
</span>
</
template
>
</el-table-column>
</template>
<el-table-column
align=
"center"
label=
"商品条码"
min-width=
"100"
>
<
template
#
default=
"{ row }"
>
{{
row
.
barCode
}}
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"销售价(元)"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
{{
row
.
price
}}
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"市场价(元)"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
{{
row
.
marketPrice
}}
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"成本价(元)"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
{{
row
.
costPrice
}}
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"库存"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
{{
row
.
stock
}}
</
template
>
</el-table-column>
<!-- 方便扩展每个活动配置的属性不一样 -->
<slot
name=
"extension"
></slot>
</el-table>
</template>
<
script
lang=
"ts"
setup
>
import
{
PropType
,
Ref
}
from
'vue'
...
...
@@ -196,6 +258,7 @@ import { propTypes } from '@/utils/propTypes'
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'
defineOptions
({
name
:
'SkuList'
})
...
...
@@ -208,8 +271,14 @@ const props = defineProps({
type
:
Array
,
default
:
()
=>
[]
},
ruleConfig
:
{
type
:
Array
as
PropType
<
RuleConfig
[]
>
,
default
:
()
=>
[]
},
isBatch
:
propTypes
.
bool
.
def
(
false
),
// 是否作为批量操作组件
isDetail
:
propTypes
.
bool
.
def
(
false
)
// 是否作为 sku 详情组件
isDetail
:
propTypes
.
bool
.
def
(
false
),
// 是否作为 sku 详情组件
isComponent
:
propTypes
.
bool
.
def
(
false
),
// 是否作为 sku 选择组件
isActivityComponent
:
propTypes
.
bool
.
def
(
false
)
// 是否作为 sku 活动配置组件
})
const
formData
:
Ref
<
Spu
|
undefined
>
=
ref
<
Spu
>
()
// 表单数据
const
skuList
=
ref
<
Sku
[]
>
([
...
...
@@ -230,6 +299,7 @@ const skuList = ref<Sku[]>([
/** 商品图预览 */
const
imagePreview
=
(
imgUrl
:
string
)
=>
{
createImageViewer
({
zIndex
:
9999999
,
urlList
:
[
imgUrl
]
})
}
...
...
@@ -257,14 +327,35 @@ const validateSku = (): boolean => {
const
checks
=
[
'price'
,
'marketPrice'
,
'costPrice'
]
let
validate
=
true
// 默认通过
for
(
const
sku
of
formData
.
value
!
.
skus
)
{
if
(
checks
.
some
((
check
)
=>
sku
[
check
]
<
0.01
))
{
validate
=
false
// 只要有一个不通过则直接不通过
break
// 作为活动组件的校验
if
(
props
.
isActivityComponent
)
{
for
(
const
rule
of
props
.
ruleConfig
)
{
if
(
sku
[
rule
.
name
]
<
rule
.
geValue
)
{
validate
=
false
// 只要有一个不通过则直接不通过
break
}
}
}
else
{
if
(
checks
.
some
((
check
)
=>
sku
[
check
]
<
0.01
))
{
validate
=
false
// 只要有一个不通过则直接不通过
break
}
}
}
return
validate
}
const
emit
=
defineEmits
<
{
(
e
:
'selectionChange'
,
value
:
Sku
[]):
void
}
>
()
/**
* 选择时触发
* @param Sku 传递过来的选中的 sku 是一个数组
*/
const
handleSelectionChange
=
(
val
:
Sku
[])
=>
{
emit
(
'selectionChange'
,
val
)
}
/**
* 将传进来的值赋值给 skuList
*/
...
...
src/views/mall/product/spu/components/index.ts
View file @
fa71d3e7
...
...
@@ -5,11 +5,53 @@ import ProductAttributes from './ProductAttributes.vue'
import
ProductPropertyAddForm
from
'./ProductPropertyAddForm.vue'
import
SkuList
from
'./SkuList.vue'
import
{
Spu
}
from
'@/api/mall/product/spu'
interface
Properties
{
id
:
number
name
:
string
values
?:
Properties
[]
}
interface
RuleConfig
{
name
:
string
// 需要校验的字段
geValue
:
number
// TODO 暂定大于一个数字
}
/**
* 商品通用函数
* @param spu
*/
const
getPropertyList
=
(
spu
:
Spu
):
Properties
[]
=>
{
// 直接拿返回的 skus 属性逆向生成出 propertyList
const
properties
:
Properties
[]
=
[]
// 只有是多规格才处理
if
(
spu
.
specType
)
{
spu
.
skus
?.
forEach
((
sku
)
=>
{
sku
.
properties
?.
forEach
(({
propertyId
,
propertyName
,
valueId
,
valueName
})
=>
{
// 添加属性
if
(
!
properties
?.
some
((
item
)
=>
item
.
id
===
propertyId
))
{
properties
.
push
({
id
:
propertyId
!
,
name
:
propertyName
!
,
values
:
[]
})
}
// 添加属性值
const
index
=
properties
?.
findIndex
((
item
)
=>
item
.
id
===
propertyId
)
if
(
!
properties
[
index
].
values
?.
some
((
value
)
=>
value
.
id
===
valueId
))
{
properties
[
index
].
values
?.
push
({
id
:
valueId
!
,
name
:
valueName
!
})
}
})
})
}
return
properties
}
export
{
BasicInfoForm
,
DescriptionForm
,
OtherSettingsForm
,
ProductAttributes
,
ProductPropertyAddForm
,
SkuList
SkuList
,
getPropertyList
,
Properties
,
RuleConfig
}
src/views/mall/product/spu/index.vue
View file @
fa71d3e7
...
...
@@ -17,7 +17,6 @@
@
keyup
.
enter=
"handleQuery"
/>
</el-form-item>
<!-- TODO 分类只能选择二级分类目前还没做,还是先以联调通顺为主 fixL: 已完善 -->
<el-form-item
label=
"商品分类"
prop=
"categoryId"
>
<el-tree-select
v-model=
"queryParams.categoryId"
...
...
@@ -79,12 +78,6 @@
/>
</el-tabs>
<el-table
v-loading=
"loading"
:data=
"list"
>
<!-- TODO puhui:这几个属性哈,一行三个 fix
商品分类:服装鞋包/箱包
商品市场价格:100.00
成本价:0.00
收藏:5
虚拟销量:999 -->
<el-table-column
type=
"expand"
width=
"30"
>
<template
#
default=
"
{ row }">
<el-form
class=
"demo-table-expand"
label-position=
"left"
>
...
...
@@ -292,7 +285,8 @@ const queryParams = ref({
pageSize
:
10
,
tabType
:
0
,
name
:
''
,
categoryId
:
null
categoryId
:
null
,
createTime
:
[]
})
// 查询参数
const
queryFormRef
=
ref
()
// 搜索的表单Ref
...
...
src/views/mall/promotion/seckill/activity/SeckillActivityForm.vue
0 → 100644
View file @
fa71d3e7
<
template
>
<Dialog
v-model=
"dialogVisible"
:title=
"dialogTitle"
width=
"65%"
>
<Form
ref=
"formRef"
v-loading=
"formLoading"
:isCol=
"true"
:rules=
"rules"
:schema=
"allSchemas.formSchema"
>
<!-- 先选择 -->
<template
#
spuId
>
<el-button
@
click=
"spuAndSkuSelectForm.open('秒杀商品选择')"
>
添加商品
</el-button>
<SpuAndSkuList
ref=
"spuAndSkuListRef"
:spu-list=
"spuList"
/>
</
template
>
</Form>
<
template
#
footer
>
<el-button
:disabled=
"formLoading"
type=
"primary"
@
click=
"submitForm"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</Dialog>
<SpuAndSkuSelectForm
ref=
"spuAndSkuSelectForm"
@
confirm=
"selectSpu"
/>
</template>
<
script
lang=
"ts"
name=
"PromotionSeckillActivityForm"
setup
>
import
{
SpuAndSkuList
,
SpuAndSkuSelectForm
}
from
'./components'
import
{
allSchemas
,
rules
}
from
'./seckillActivity.data'
import
{
Spu
}
from
'@/api/mall/product/spu'
import
*
as
SeckillActivityApi
from
'@/api/mall/promotion/seckill/seckillActivity'
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
spuAndSkuSelectForm
=
ref
()
// 商品和属性选择 Ref
const
spuAndSkuListRef
=
ref
()
// sku 秒杀配置组件Ref
/** 打开弹窗 */
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
dialogVisible
.
value
=
true
dialogTitle
.
value
=
t
(
'action.'
+
type
)
formType
.
value
=
type
// 修改时,设置数据 TODO 没测试估计有问题
if
(
id
)
{
formLoading
.
value
=
true
try
{
const
data
=
await
SeckillActivityApi
.
getSeckillActivity
(
id
)
formRef
.
value
.
setValues
(
data
)
}
finally
{
formLoading
.
value
=
false
}
}
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
const
spuList
=
ref
<
Spu
[]
>
([])
// 选择的 spu
const
selectSpu
=
(
val
:
Spu
)
=>
{
formRef
.
value
.
setValues
({
spuId
:
val
.
id
})
spuList
.
value
=
[
val
]
}
/** 提交表单 */
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
SeckillActivityApi
.
SeckillActivityVO
data
.
spuIds
=
spuList
.
value
.
map
((
spu
)
=>
spu
.
id
!
)
data
.
products
=
spuAndSkuListRef
.
value
.
getSkuConfigs
()
if
(
formType
.
value
===
'create'
)
{
await
SeckillActivityApi
.
createSeckillActivity
(
data
)
message
.
success
(
t
(
'common.createSuccess'
))
}
else
{
await
SeckillActivityApi
.
updateSeckillActivity
(
data
)
message
.
success
(
t
(
'common.updateSuccess'
))
}
dialogVisible
.
value
=
false
// 发送操作成功的事件
emit
(
'success'
)
}
finally
{
formLoading
.
value
=
false
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.demo-table-expand
{
padding-left
:
42px
;
:deep(.el-form-item__label)
{
width
:
82px
;
font-weight
:
bold
;
color
:
#99a9bf
;
}
}
</
style
>
src/views/mall/promotion/seckill/activity/components/SpuAndSkuList.vue
0 → 100644
View file @
fa71d3e7
<
template
>
<el-table
:data=
"spuData"
>
<el-table-column
type=
"expand"
width=
"30"
>
<template
#
default=
"
{ row }">
<SkuList
ref=
"skuListRef"
:is-activity-component=
"true"
:prop-form-data=
"spuPropertyList.find((item) => item.spuId === row.id)?.spuDetail"
:property-list=
"spuPropertyList.find((item) => item.spuId === row.id)?.propertyList"
: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>
</template>
</SkuList>
</template>
</el-table-column>
<el-table-column
key=
"id"
align=
"center"
label=
"商品编号"
prop=
"id"
/>
<el-table-column
label=
"商品图"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
<el-image
:src=
"row.picUrl"
class=
"w-30px h-30px"
@
click=
"imagePreview(row.picUrl)"
/>
</
template
>
</el-table-column>
<el-table-column
:show-overflow-tooltip=
"true"
label=
"商品名称"
min-width=
"300"
prop=
"name"
/>
<el-table-column
align=
"center"
label=
"商品售价"
min-width=
"90"
prop=
"price"
>
<
template
#
default=
"{ row }"
>
{{
formatToFraction
(
row
.
price
)
}}
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"销量"
min-width=
"90"
prop=
"salesCount"
/>
<el-table-column
align=
"center"
label=
"库存"
min-width=
"90"
prop=
"stock"
/>
</el-table>
</template>
<
script
lang=
"ts"
name=
"SpuAndSkuList"
setup
>
// TODO 后续计划重新封装作为活动商品配置通用组件
import
{
formatToFraction
}
from
'@/utils'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
{
SpuRespVO
}
from
'@/api/mall/product/spu'
import
{
getPropertyList
,
Properties
,
RuleConfig
,
SkuList
}
from
'@/views/mall/product/spu/components'
import
{
SeckillProductVO
,
SpuExtension
}
from
'@/api/mall/promotion/seckill/seckillActivity'
const
props
=
defineProps
({
spuList
:
{
type
:
Array
,
default
:
()
=>
[]
}
})
const
spuData
=
ref
<
SpuRespVO
[]
>
([])
// spu 详情数据列表
const
skuListRef
=
ref
()
// 商品属性列表Ref
interface
spuProperty
{
spuId
:
number
spuDetail
:
SpuExtension
propertyList
:
Properties
[]
}
const
spuPropertyList
=
ref
<
spuProperty
[]
>
([])
// 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
}
]
const
message
=
useMessage
()
// 消息弹窗
/**
* 获取所有 sku 秒杀配置
*/
const
getSkuConfigs
=
():
SeckillProductVO
[]
=>
{
if
(
!
skuListRef
.
value
.
validateSku
())
{
// TODO 作为通用组件是需要进一步完善
message
.
warning
(
'请检查商品相关属性配置!!'
)
throw
new
Error
(
'请检查商品相关属性配置!!'
)
}
const
seckillProducts
:
SeckillProductVO
[]
=
[]
spuPropertyList
.
value
.
forEach
((
item
)
=>
{
item
.
spuDetail
.
skus
.
forEach
((
sku
)
=>
{
seckillProducts
.
push
(
sku
.
productConfig
)
})
})
return
seckillProducts
}
// 暴露出给表单提交时使用
defineExpose
({
getSkuConfigs
})
/** 商品图预览 */
const
imagePreview
=
(
imgUrl
:
string
)
=>
{
createImageViewer
({
zIndex
:
99999999
,
urlList
:
[
imgUrl
]
})
}
/**
* 将传进来的值赋值给 skuList
*/
watch
(
()
=>
props
.
spuList
,
(
data
)
=>
{
if
(
!
data
)
return
spuData
.
value
=
data
as
SpuRespVO
[]
getSpuDetails
(
spuData
.
value
.
map
((
spu
)
=>
spu
.
id
!
))
},
{
deep
:
true
,
immediate
:
true
}
)
</
script
>
src/views/mall/promotion/seckill/activity/components/SpuAndSkuSelectForm.vue
0 → 100644
View file @
fa71d3e7
<
template
>
<Dialog
v-model=
"dialogVisible"
:appendToBody=
"true"
:title=
"dialogTitle"
width=
"70%"
>
<ContentWrap>
<el-row
:gutter=
"20"
class=
"mb-10px"
>
<el-col
:span=
"6"
>
<el-input
v-model=
"queryParams.name"
class=
"!w-240px"
clearable
placeholder=
"请输入商品名称"
@
keyup
.
enter=
"handleQuery"
/>
</el-col>
<el-col
:span=
"6"
>
<el-tree-select
v-model=
"queryParams.categoryId"
:data=
"categoryList"
:props=
"defaultProps"
check-strictly
class=
"w-1/1"
node-key=
"id"
placeholder=
"请选择商品分类"
@
change=
"nodeClick"
/>
</el-col>
<el-col
:span=
"6"
>
<el-date-picker
v-model=
"queryParams.createTime"
:default-time=
"[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class=
"!w-240px"
end-placeholder=
"结束日期"
start-placeholder=
"开始日期"
type=
"daterange"
value-format=
"YYYY-MM-DD HH:mm:ss"
/>
</el-col>
<el-col
:span=
"6"
>
<el-button
@
click=
"handleQuery"
>
<Icon
class=
"mr-5px"
icon=
"ep:search"
/>
搜索
</el-button>
<el-button
@
click=
"resetQuery"
>
<Icon
class=
"mr-5px"
icon=
"ep:refresh"
/>
重置
</el-button>
</el-col>
</el-row>
<el-table
ref=
"spuListRef"
v-loading=
"loading"
:data=
"list"
:expand-row-keys=
"expandRowKeys"
row-key=
"id"
@
expand-change=
"expandChange"
@
selection-change=
"selectSpu"
>
<el-table-column
v-if=
"isSelectSku"
type=
"expand"
width=
"30"
>
<template
#
default
>
<SkuList
v-if=
"isExpand"
:isComponent=
"true"
:isDetail=
"true"
:prop-form-data=
"spuData"
:property-list=
"propertyList"
@
selection-change=
"selectSku"
/>
</
template
>
</el-table-column>
<el-table-column
type=
"selection"
width=
"55"
/>
<el-table-column
key=
"id"
align=
"center"
label=
"商品编号"
prop=
"id"
/>
<el-table-column
label=
"商品图"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
<el-image
:src=
"row.picUrl"
class=
"w-30px h-30px"
@
click=
"imagePreview(row.picUrl)"
/>
</
template
>
</el-table-column>
<el-table-column
:show-overflow-tooltip=
"true"
label=
"商品名称"
min-width=
"300"
prop=
"name"
/>
<el-table-column
align=
"center"
label=
"商品售价"
min-width=
"90"
prop=
"price"
>
<
template
#
default=
"{ row }"
>
{{
formatToFraction
(
row
.
price
)
}}
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"销量"
min-width=
"90"
prop=
"salesCount"
/>
<el-table-column
align=
"center"
label=
"库存"
min-width=
"90"
prop=
"stock"
/>
<el-table-column
align=
"center"
label=
"排序"
min-width=
"70"
prop=
"sort"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"创建时间"
prop=
"createTime"
width=
"180"
/>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit=
"queryParams.pageSize"
v-model:page=
"queryParams.pageNo"
:total=
"total"
@
pagination=
"getList"
/>
</ContentWrap>
<
template
#
footer
>
<el-button
type=
"primary"
@
click=
"confirm"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</Dialog>
</template>
<
script
lang=
"ts"
name=
"SeckillActivitySpuAndSkuSelect"
setup
>
import
{
getPropertyList
,
Properties
,
SkuList
}
from
'@/views/mall/product/spu/components'
import
{
ElTable
}
from
'element-plus'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
formatToFraction
}
from
'@/utils'
import
{
checkSelectedNode
,
defaultProps
,
handleTree
}
from
'@/utils/tree'
import
*
as
ProductCategoryApi
from
'@/api/mall/product/category'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
{
propTypes
}
from
'@/utils/propTypes'
const
props
=
defineProps
({
// 默认不需要(不需要的情况下只返回 spu,需要的情况下返回 选中的 spu 和 sku 列表)
// 其它活动需要选择商品和商品属性导入此组件即可,需添加组件属性 :isSelectSku='true'
isSelectSku
:
propTypes
.
bool
.
def
(
false
)
// 是否需要选择 sku 属性
})
const
message
=
useMessage
()
// 消息弹窗
const
total
=
ref
(
0
)
// 列表的总页数
const
list
=
ref
<
any
[]
>
([])
// 列表的数据
const
loading
=
ref
(
false
)
// 列表的加载中
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
dialogTitle
=
ref
(
''
)
// 弹窗的标题
const
queryParams
=
ref
({
pageNo
:
1
,
pageSize
:
10
,
tabType
:
0
,
// 默认获取上架的商品
name
:
''
,
categoryId
:
null
,
createTime
:
[]
})
// 查询参数
const
propertyList
=
ref
<
Properties
[]
>
([])
// 商品属性列表
const
spuListRef
=
ref
<
InstanceType
<
typeof
ElTable
>>
()
const
spuData
=
ref
<
ProductSpuApi
.
Spu
|
{}
>
()
// 商品详情
const
isExpand
=
ref
(
false
)
// 控制 SKU 列表显示
const
expandRowKeys
=
ref
<
number
[]
>
()
// 控制展开行需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。
// 计算商品属性
const
expandChange
=
async
(
row
:
ProductSpuApi
.
Spu
,
expandedRows
:
ProductSpuApi
.
Spu
[])
=>
{
spuData
.
value
=
{}
propertyList
.
value
=
[]
isExpand
.
value
=
false
// 如果展开个数为 0
if
(
expandedRows
.
length
===
0
)
{
expandRowKeys
.
value
=
[]
return
}
// 获取 SPU 详情
const
res
=
(
await
ProductSpuApi
.
getSpu
(
row
.
id
as
number
))
as
ProductSpuApi
.
SpuRespVO
propertyList
.
value
=
getPropertyList
(
res
)
spuData
.
value
=
res
isExpand
.
value
=
true
expandRowKeys
.
value
=
[
row
.
id
!
]
}
//============ 商品选择相关 ============
const
selectedSpu
=
ref
<
ProductSpuApi
.
Spu
>
()
// 选中的商品 spu 只能选择一个
const
selectedSku
=
ref
<
ProductSpuApi
.
Sku
[]
>
()
// 选中的商品 sku
const
selectSku
=
(
val
:
ProductSpuApi
.
Sku
[])
=>
{
selectedSku
.
value
=
val
}
const
selectSpu
=
(
val
:
ProductSpuApi
.
Spu
[])
=>
{
// 只选择一个
selectedSpu
.
value
=
val
[
0
]
// 如果大于1个
if
(
val
.
length
>
1
)
{
// 清空选择
spuListRef
.
value
.
clearSelection
()
// 变更为最后一次选择的
spuListRef
.
value
.
toggleRowSelection
(
val
.
pop
(),
true
)
}
}
// 确认选择时的触发事件
const
emits
=
defineEmits
<
{
(
e
:
'confirm'
,
value
:
ProductSpuApi
.
Spu
,
value1
?:
ProductSpuApi
.
Sku
[]):
void
}
>
()
/**
* 确认选择返回选中的 spu 和 sku (如果需要选择sku的话)
*/
const
confirm
=
()
=>
{
if
(
typeof
selectedSpu
.
value
===
'undefined'
)
{
message
.
warning
(
'没有选择任何商品'
)
return
}
if
(
(
props
.
isSelectSku
&&
typeof
selectedSku
.
value
===
'undefined'
)
||
selectedSku
.
value
?.
length
===
0
)
{
message
.
warning
(
'没有选择任何商品属性'
)
return
}
// TODO 返回选择 sku 没测试过,后续测试完善
props
.
isSelectSku
?
emits
(
'confirm'
,
selectedSpu
.
value
!
,
selectedSku
.
value
!
)
:
emits
(
'confirm'
,
selectedSpu
.
value
!
)
// 关闭弹窗
dialogVisible
.
value
=
false
}
/** 打开弹窗 TODO 没做国际化 */
const
open
=
(
title
:
string
)
=>
{
dialogTitle
.
value
=
title
dialogVisible
.
value
=
true
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
/** 查询列表 */
const
getList
=
async
()
=>
{
loading
.
value
=
true
try
{
const
data
=
await
ProductSpuApi
.
getSpuPage
(
queryParams
.
value
)
list
.
value
=
data
.
list
total
.
value
=
data
.
total
}
finally
{
loading
.
value
=
false
}
}
/** 搜索按钮操作 */
const
handleQuery
=
()
=>
{
getList
()
}
/** 重置按钮操作 */
const
resetQuery
=
()
=>
{
queryParams
.
value
=
{
pageNo
:
1
,
pageSize
:
10
,
tabType
:
0
,
// 默认获取上架的商品
name
:
''
,
categoryId
:
null
,
createTime
:
[]
}
getList
()
}
/** 商品图预览 */
const
imagePreview
=
(
imgUrl
:
string
)
=>
{
createImageViewer
({
zIndex
:
99999999
,
urlList
:
[
imgUrl
]
})
}
const
categoryList
=
ref
()
// 分类树
/**
* 校验所选是否为二级及以下节点
*/
const
nodeClick
=
()
=>
{
if
(
!
checkSelectedNode
(
categoryList
.
value
,
queryParams
.
value
.
categoryId
))
{
queryParams
.
value
.
categoryId
=
null
message
.
warning
(
'必须选择二级及以下节点!!'
)
}
}
/** 初始化 **/
onMounted
(
async
()
=>
{
await
getList
()
// 获得分类树
const
data
=
await
ProductCategoryApi
.
getCategoryList
({})
categoryList
.
value
=
handleTree
(
data
,
'id'
,
'parentId'
)
})
</
script
>
src/views/mall/promotion/seckill/activity/components/index.ts
0 → 100644
View file @
fa71d3e7
import
SpuAndSkuSelectForm
from
'./SpuAndSkuSelectForm.vue'
import
SpuAndSkuList
from
'./SpuAndSkuList.vue'
export
{
SpuAndSkuSelectForm
,
SpuAndSkuList
}
src/views/mall/promotion/seckill/activity/index.vue
0 → 100644
View file @
fa71d3e7
<
template
>
<!-- 搜索工作栏 -->
<ContentWrap>
<Search
:schema=
"allSchemas.searchSchema"
@
reset=
"setSearchParams"
@
search=
"setSearchParams"
>
<!-- 新增等操作按钮 -->
<template
#
actionMore
>
<el-button
v-hasPermi=
"['promotion:seckill-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
#
action=
"{ row }"
>
<el-button
v-hasPermi=
"['promotion:seckill-activity:update']"
link
type=
"primary"
@
click=
"openForm('update', row.id)"
>
编辑
</el-button>
<el-button
v-hasPermi=
"['promotion:seckill-activity:delete']"
link
type=
"danger"
@
click=
"handleDelete(row.id)"
>
删除
</el-button>
</
template
>
</Table>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<SeckillActivityForm
ref=
"formRef"
@
success=
"getList"
/>
</template>
<
script
lang=
"ts"
name=
"PromotionSeckillActivity"
setup
>
import
{
allSchemas
}
from
'./seckillActivity.data'
import
*
as
SeckillActivityApi
from
'@/api/mall/promotion/seckill/seckillActivity'
import
SeckillActivityForm
from
'./SeckillActivityForm.vue'
// tableObject:表格的属性对象,可获得分页大小、条数等属性
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
const
{
tableObject
,
tableMethods
}
=
useTable
({
getListApi
:
SeckillActivityApi
.
getSeckillActivityPage
,
// 分页接口
delListApi
:
SeckillActivityApi
.
deleteSeckillActivity
// 删除接口
})
// 获得表格的各种操作
const
{
getList
,
setSearchParams
}
=
tableMethods
/** 添加/修改操作 */
const
formRef
=
ref
()
const
openForm
=
(
type
:
string
,
id
?:
number
)
=>
{
formRef
.
value
.
open
(
type
,
id
)
}
/** 删除按钮操作 */
const
handleDelete
=
(
id
:
number
)
=>
{
tableMethods
.
delList
(
id
,
false
)
}
/** 初始化 **/
onMounted
(()
=>
{
getList
()
})
</
script
>
src/views/mall/promotion/seckill/activity/seckillActivity.data.ts
0 → 100644
View file @
fa71d3e7
import
type
{
CrudSchema
}
from
'@/hooks/web/useCrudSchemas'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
{
getListAllSimple
}
from
'@/api/mall/promotion/seckill/seckillConfig'
// 表单校验
export
const
rules
=
reactive
({
spuId
:
[
required
],
name
:
[
required
],
startTime
:
[
required
],
endTime
:
[
required
],
sort
:
[
required
],
configIds
:
[
required
],
totalLimitCount
:
[
required
],
singleLimitCount
:
[
required
],
totalStock
:
[
required
]
})
// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
const
crudSchemas
=
reactive
<
CrudSchema
[]
>
([
{
label
:
'秒杀活动名称'
,
field
:
'name'
,
isSearch
:
true
,
form
:
{
colProps
:
{
span
:
24
}
},
table
:
{
width
:
120
}
},
{
label
:
'活动开始时间'
,
field
:
'startTime'
,
formatter
:
dateFormatter
,
isSearch
:
true
,
search
:
{
component
:
'DatePicker'
,
componentProps
:
{
valueFormat
:
'YYYY-MM-DD HH:mm:ss'
,
type
:
'daterange'
,
defaultTime
:
[
new
Date
(
'1 00:00:00'
),
new
Date
(
'1 23:59:59'
)]
}
},
form
:
{
component
:
'DatePicker'
,
componentProps
:
{
type
:
'date'
,
valueFormat
:
'x'
}
},
table
:
{
width
:
300
}
},
{
label
:
'活动结束时间'
,
field
:
'endTime'
,
formatter
:
dateFormatter
,
isSearch
:
true
,
search
:
{
component
:
'DatePicker'
,
componentProps
:
{
valueFormat
:
'YYYY-MM-DD HH:mm:ss'
,
type
:
'daterange'
,
defaultTime
:
[
new
Date
(
'1 00:00:00'
),
new
Date
(
'1 23:59:59'
)]
}
},
form
:
{
component
:
'DatePicker'
,
componentProps
:
{
type
:
'date'
,
valueFormat
:
'x'
}
},
table
:
{
width
:
300
}
},
{
label
:
'秒杀时段'
,
field
:
'configIds'
,
form
:
{
component
:
'Select'
,
componentProps
:
{
multiple
:
true
,
optionsAlias
:
{
labelField
:
'name'
,
valueField
:
'id'
}
},
api
:
getListAllSimple
},
table
:
{
width
:
300
}
},
{
label
:
'新增订单数'
,
field
:
'orderCount'
,
isForm
:
false
,
form
:
{
component
:
'InputNumber'
,
value
:
0
},
table
:
{
width
:
300
}
},
{
label
:
'付款人数'
,
field
:
'userCount'
,
isForm
:
false
,
form
:
{
component
:
'InputNumber'
,
value
:
0
},
table
:
{
width
:
300
}
},
{
label
:
'订单实付金额'
,
field
:
'totalPrice'
,
isForm
:
false
,
form
:
{
component
:
'InputNumber'
,
value
:
0
},
table
:
{
width
:
300
}
},
{
label
:
'总限购数量'
,
field
:
'totalLimitCount'
,
form
:
{
component
:
'InputNumber'
,
value
:
0
},
table
:
{
width
:
300
}
},
{
label
:
'单次限够数量'
,
field
:
'singleLimitCount'
,
form
:
{
component
:
'InputNumber'
,
value
:
0
},
table
:
{
width
:
300
}
},
{
label
:
'秒杀库存'
,
field
:
'stock'
,
isForm
:
false
,
form
:
{
component
:
'InputNumber'
,
value
:
0
},
table
:
{
width
:
300
}
},
{
label
:
'秒杀总库存'
,
field
:
'totalStock'
,
form
:
{
component
:
'InputNumber'
,
value
:
0
},
table
:
{
width
:
300
}
},
{
label
:
'秒杀活动商品'
,
field
:
'spuId'
,
form
:
{
colProps
:
{
span
:
24
}
},
table
:
{
width
:
200
}
},
{
label
:
'创建时间'
,
field
:
'createTime'
,
formatter
:
dateFormatter
,
search
:
{
component
:
'DatePicker'
,
componentProps
:
{
valueFormat
:
'YYYY-MM-DD HH:mm:ss'
,
type
:
'daterange'
,
defaultTime
:
[
new
Date
(
'1 00:00:00'
),
new
Date
(
'1 23:59:59'
)]
}
},
isForm
:
false
,
table
:
{
width
:
300
}
},
{
label
:
'排序'
,
field
:
'sort'
,
form
:
{
component
:
'InputNumber'
,
value
:
0
},
table
:
{
width
:
300
}
},
{
label
:
'状态'
,
field
:
'status'
,
dictType
:
DICT_TYPE
.
COMMON_STATUS
,
dictClass
:
'number'
,
isForm
:
false
,
isSearch
:
true
,
form
:
{
component
:
'Radio'
},
table
:
{
width
:
80
}
},
{
label
:
'备注'
,
field
:
'remark'
,
form
:
{
component
:
'Input'
,
componentProps
:
{
type
:
'textarea'
,
rows
:
4
},
colProps
:
{
span
:
24
}
},
table
:
{
width
:
300
}
},
{
label
:
'操作'
,
field
:
'action'
,
isForm
:
false
,
table
:
{
width
:
120
,
fixed
:
'right'
}
}
])
export
const
{
allSchemas
}
=
useCrudSchemas
(
crudSchemas
)
src/views/mall/promotion/seckill/config/SeckillConfigForm.vue
0 → 100644
View file @
fa71d3e7
<
template
>
<Dialog
v-model=
"dialogVisible"
:title=
"dialogTitle"
>
<Form
ref=
"formRef"
v-loading=
"formLoading"
:rules=
"rules"
:schema=
"allSchemas.formSchema"
/>
<template
#
footer
>
<el-button
:disabled=
"formLoading"
type=
"primary"
@
click=
"submitForm"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</Dialog>
</template>
<
script
lang=
"ts"
name=
"SeckillConfigForm"
setup
>
import
*
as
SeckillConfigApi
from
'@/api/mall/promotion/seckill/seckillConfig'
import
{
allSchemas
,
rules
}
from
'./seckillConfig.data'
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
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
dialogVisible
.
value
=
true
dialogTitle
.
value
=
t
(
'action.'
+
type
)
formType
.
value
=
type
// 修改时,设置数据
if
(
id
)
{
formLoading
.
value
=
true
try
{
const
data
=
await
SeckillConfigApi
.
getSeckillConfig
(
id
)
formRef
.
value
.
setValues
(
data
)
}
finally
{
formLoading
.
value
=
false
}
}
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
/** 提交表单 */
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
SeckillConfigApi
.
SeckillConfigVO
if
(
formType
.
value
===
'create'
)
{
await
SeckillConfigApi
.
createSeckillConfig
(
data
)
message
.
success
(
t
(
'common.createSuccess'
))
}
else
{
await
SeckillConfigApi
.
updateSeckillConfig
(
data
)
message
.
success
(
t
(
'common.updateSuccess'
))
}
dialogVisible
.
value
=
false
// 发送操作成功的事件
emit
(
'success'
)
}
finally
{
formLoading
.
value
=
false
}
}
</
script
>
src/views/mall/promotion/seckill/config/index.vue
0 → 100644
View file @
fa71d3e7
<
template
>
<!-- 搜索工作栏 -->
<ContentWrap>
<Search
:schema=
"allSchemas.searchSchema"
@
reset=
"setSearchParams"
@
search=
"setSearchParams"
>
<!-- 新增等操作按钮 -->
<template
#
actionMore
>
<el-button
v-hasPermi=
"['promotion:seckill-config: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
#
picUrl=
"{ row }"
>
<el-image
:src=
"row.picUrl"
class=
"w-30px h-30px"
@
click=
"imagePreview(row.picUrl)"
/>
</
template
>
<
template
#
status=
"{ row }"
>
<el-switch
v-model=
"row.status"
:active-value=
"0"
:inactive-value=
"1"
@
change=
"handleStatusChange(row)"
/>
</
template
>
<
template
#
action=
"{ row }"
>
<el-button
v-hasPermi=
"['promotion:seckill-config:update']"
link
type=
"primary"
@
click=
"openForm('update', row.id)"
>
编辑
</el-button>
<el-button
v-hasPermi=
"['promotion:seckill-config:delete']"
link
type=
"danger"
@
click=
"handleDelete(row.id)"
>
删除
</el-button>
</
template
>
</Table>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<SeckillConfigForm
ref=
"formRef"
@
success=
"getList"
/>
</template>
<
script
lang=
"ts"
name=
"SeckillConfig"
setup
>
import
{
allSchemas
}
from
'./seckillConfig.data'
import
*
as
SeckillConfigApi
from
'@/api/mall/promotion/seckill/seckillConfig'
import
SeckillConfigForm
from
'./SeckillConfigForm.vue'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
CommonStatusEnum
}
from
'@/utils/constants'
const
message
=
useMessage
()
// 消息弹窗
// tableObject:表格的属性对象,可获得分页大小、条数等属性
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
const
{
tableObject
,
tableMethods
}
=
useTable
({
getListApi
:
SeckillConfigApi
.
getSeckillConfigPage
,
// 分页接口
delListApi
:
SeckillConfigApi
.
deleteSeckillConfig
// 删除接口
})
// 获得表格的各种操作
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
)
}
/** 修改用户状态 */
const
handleStatusChange
=
async
(
row
:
SeckillConfigApi
.
SeckillConfigVO
)
=>
{
try
{
// 修改状态的二次确认
const
text
=
row
.
status
===
CommonStatusEnum
.
ENABLE
?
'启用'
:
'停用'
await
message
.
confirm
(
'确认要"'
+
text
+
'""'
+
row
.
name
+
'?'
)
// 发起修改状态
await
SeckillConfigApi
.
updateSeckillConfigStatus
(
row
.
id
,
row
.
status
)
// 刷新列表
await
getList
()
}
catch
{
// 取消后,进行恢复按钮
row
.
status
=
row
.
status
===
CommonStatusEnum
.
ENABLE
?
CommonStatusEnum
.
DISABLE
:
CommonStatusEnum
.
ENABLE
}
}
/** 初始化 **/
onMounted
(()
=>
{
getList
()
})
</
script
>
src/views/mall/promotion/seckill/config/seckillConfig.data.ts
0 → 100644
View file @
fa71d3e7
import
type
{
CrudSchema
}
from
'@/hooks/web/useCrudSchemas'
import
{
dateFormatter
}
from
'@/utils/formatTime'
// 表单校验
export
const
rules
=
reactive
({
name
:
[
required
],
startTime
:
[
required
],
endTime
:
[
required
],
picUrl
:
[
required
],
status
:
[
required
]
})
// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
const
crudSchemas
=
reactive
<
CrudSchema
[]
>
([
{
label
:
'秒杀时段名称'
,
field
:
'name'
,
isSearch
:
true
},
{
label
:
'开始时间点'
,
field
:
'startTime'
,
isSearch
:
false
,
search
:
{
component
:
'TimePicker'
},
form
:
{
component
:
'TimePicker'
,
componentProps
:
{
valueFormat
:
'HH:mm:ss'
}
}
},
{
label
:
'结束时间点'
,
field
:
'endTime'
,
isSearch
:
false
,
search
:
{
component
:
'TimePicker'
},
form
:
{
component
:
'TimePicker'
,
componentProps
:
{
valueFormat
:
'HH:mm:ss'
}
}
},
{
label
:
'秒杀主图'
,
field
:
'picUrl'
,
isSearch
:
false
,
form
:
{
component
:
'UploadImg'
}
},
{
label
:
'状态'
,
field
:
'status'
,
dictType
:
DICT_TYPE
.
COMMON_STATUS
,
dictClass
:
'number'
,
isSearch
:
true
,
form
:
{
component
:
'Radio'
}
},
{
label
:
'创建时间'
,
field
:
'createTime'
,
isForm
:
false
,
isSearch
:
false
,
formatter
:
dateFormatter
},
{
label
:
'操作'
,
field
:
'action'
,
isForm
:
false
}
])
export
const
{
allSchemas
}
=
useCrudSchemas
(
crudSchemas
)
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