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
ec3b028d
authored
Jun 06, 2023
by
xiaobai
Browse files
Options
Browse Files
Download
Plain Diff
sync dev-20230606
parents
985d764f
70c50827
Hide whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
1741 additions
and
696 deletions
+1741
-696
build/vite/optimize.ts
+2
-1
src/api/mall/product/brand.ts
+5
-0
src/api/mall/product/management/spu.ts
+0
-39
src/api/mall/product/management/type/skuType.ts
+0
-79
src/api/mall/product/management/type/spuType.ts
+0
-25
src/api/mall/product/spu.ts
+92
-0
src/api/mall/trade/delivery/express/index.ts
+40
-0
src/api/mall/trade/delivery/expressTemplate/index.ts
+54
-0
src/api/system/area/index.ts
+8
-0
src/components/UploadFile/src/UploadImgs.vue
+44
-14
src/router/modules/remaining.ts
+17
-4
src/utils/dict.ts
+3
-0
src/utils/index.ts
+54
-0
src/utils/object.ts
+0
-18
src/utils/tree.ts
+2
-1
src/views/mall/product/spu/addForm.vue
+80
-142
src/views/mall/product/spu/components/BasicInfoForm.vue
+73
-37
src/views/mall/product/spu/components/DescriptionForm.vue
+8
-9
src/views/mall/product/spu/components/OtherSettingsForm.vue
+54
-65
src/views/mall/product/spu/components/ProductAttributes.vue
+27
-12
src/views/mall/product/spu/components/ProductAttributesAddForm.vue
+22
-9
src/views/mall/product/spu/components/SkuList.vue
+131
-123
src/views/mall/product/spu/index.vue
+152
-118
src/views/mall/trade/delivery/express/ExpressForm.vue
+124
-0
src/views/mall/trade/delivery/express/index.vue
+184
-0
src/views/mall/trade/delivery/expressTemplate/ExpressTemplateForm.vue
+405
-0
src/views/mall/trade/delivery/expressTemplate/index.vue
+160
-0
No files found.
build/vite/optimize.ts
View file @
ec3b028d
...
...
@@ -102,7 +102,8 @@ const include = [
'element-plus/es/components/timeline-item/style/css'
,
'element-plus/es/components/collapse/style/css'
,
'element-plus/es/components/collapse-item/style/css'
,
'element-plus/es/components/button-group/style/css'
'element-plus/es/components/button-group/style/css'
,
'element-plus/es/components/text/style/css'
]
const
exclude
=
[
'@iconify/json'
]
...
...
src/api/mall/product/brand.ts
View file @
ec3b028d
...
...
@@ -54,3 +54,8 @@ export const getBrand = (id: number) => {
export
const
getBrandParam
=
(
params
:
PageParam
)
=>
{
return
request
.
get
({
url
:
'/product/brand/page'
,
params
})
}
// 获得商品品牌精简信息列表
export
const
getSimpleBrandList
=
()
=>
{
return
request
.
get
({
url
:
'/product/brand/list-all-simple'
})
}
src/api/mall/product/management/spu.ts
deleted
100644 → 0
View file @
985d764f
import
request
from
'@/config/axios'
import
type
{
SpuType
}
from
'./type/spuType'
// TODO @puhui999: type 和 api 一起放,简单一点哈~
// TODO @puhui999:中英文之间有空格
// 获得spu列表 TODO @puhui999:这个是 getSpuPage 哈
export
const
getSpuList
=
(
params
:
PageParam
)
=>
{
return
request
.
get
({
url
:
'/product/spu/page'
,
params
})
}
// 获得spu列表tabsCount
export
const
getTabsCount
=
()
=>
{
return
request
.
get
({
url
:
'/product/spu/tabsCount'
})
}
// 创建商品spu
export
const
createSpu
=
(
data
:
SpuType
)
=>
{
return
request
.
post
({
url
:
'/product/spu/create'
,
data
})
}
// 更新商品spu
export
const
updateSpu
=
(
data
:
SpuType
)
=>
{
return
request
.
put
({
url
:
'/product/spu/update'
,
data
})
}
// 更新商品spu status
export
const
updateStatus
=
(
data
:
{
id
:
number
;
status
:
number
})
=>
{
return
request
.
put
({
url
:
'/product/spu/updateStatus'
,
data
})
}
// 获得商品 spu
export
const
getSpu
=
(
id
:
number
)
=>
{
return
request
.
get
({
url
:
`/product/spu/get-detail?id=
${
id
}
`
})
}
// 删除商品Spu
export
const
deleteSpu
=
(
id
:
number
)
=>
{
return
request
.
delete
({
url
:
`/product/spu/delete?id=
${
id
}
`
})
}
src/api/mall/product/management/type/skuType.ts
deleted
100644 → 0
View file @
985d764f
export
interface
Property
{
/**
* 属性编号
*
* 关联 {@link ProductPropertyDO#getId()}
*/
propertyId
?:
number
/**
* 属性值编号
*
* 关联 {@link ProductPropertyValueDO#getId()}
*/
valueId
?:
number
/**
* 属性值名称
*/
valueName
?:
string
}
export
interface
SkuType
{
/**
* 商品 SKU 编号,自增
*/
id
?:
number
/**
* SPU 编号
*/
spuId
?:
number
/**
* 属性数组,JSON 格式
*/
properties
?:
Property
[]
/**
* 商品价格,单位:分
*/
price
?:
number
/**
* 市场价,单位:分
*/
marketPrice
?:
number
/**
* 成本价,单位:分
*/
costPrice
?:
number
/**
* 商品条码
*/
barCode
?:
string
/**
* 图片地址
*/
picUrl
?:
string
/**
* 库存
*/
stock
?:
number
/**
* 商品重量,单位:kg 千克
*/
weight
?:
number
/**
* 商品体积,单位:m^3 平米
*/
volume
?:
number
/**
* 一级分销的佣金,单位:分
*/
subCommissionFirstPrice
?:
number
/**
* 二级分销的佣金,单位:分
*/
subCommissionSecondPrice
?:
number
/**
* 商品销量
*/
salesCount
?:
number
}
src/api/mall/product/management/type/spuType.ts
deleted
100644 → 0
View file @
985d764f
import
{
SkuType
}
from
'./skuType'
export
interface
SpuType
{
id
?:
number
name
?:
string
// 商品名称
categoryId
?:
number
|
null
// 商品分类
keyword
?:
string
// 关键字
unit
?:
number
|
null
// 单位
picUrl
?:
string
// 商品封面图
sliderPicUrls
?:
string
[]
// 商品轮播图
introduction
?:
string
// 商品简介
deliveryTemplateId
?:
number
// 运费模版
specType
?:
boolean
// 商品规格
subCommissionType
?:
boolean
// 分销类型
skus
:
SkuType
[]
// sku数组
description
?:
string
// 商品详情
sort
?:
string
// 商品排序
giveIntegral
?:
number
// 赠送积分
virtualSalesCount
?:
number
// 虚拟销量
recommendHot
?:
boolean
// 是否热卖
recommendBenefit
?:
boolean
// 是否优惠
recommendBest
?:
boolean
// 是否精品
recommendNew
?:
boolean
// 是否新品
recommendGood
?:
boolean
// 是否优品
}
src/api/mall/product/spu.ts
0 → 100644
View file @
ec3b028d
import
request
from
'@/config/axios'
export
interface
Property
{
propertyId
?:
number
// 属性编号
propertyName
?:
string
// 属性名称
valueId
?:
number
// 属性值编号
valueName
?:
string
// 属性值名称
}
// TODO puhui999:是不是直接叫 Sku 更简洁一点哈。type 待后面,总感觉有个类型?
export
interface
SkuType
{
id
?:
number
// 商品 SKU 编号
spuId
?:
number
// SPU 编号
properties
?:
Property
[]
// 属性数组
price
?:
number
// 商品价格
marketPrice
?:
number
// 市场价
costPrice
?:
number
// 成本价
barCode
?:
string
// 商品条码
picUrl
?:
string
// 图片地址
stock
?:
number
// 库存
weight
?:
number
// 商品重量,单位:kg 千克
volume
?:
number
// 商品体积,单位:m^3 平米
subCommissionFirstPrice
?:
number
// 一级分销的佣金
subCommissionSecondPrice
?:
number
// 二级分销的佣金
salesCount
?:
number
// 商品销量
}
// TODO puhui999:是不是直接叫 Spu 更简洁一点哈。type 待后面,总感觉有个类型?
export
interface
SpuType
{
id
?:
number
name
?:
string
// 商品名称
categoryId
?:
number
|
null
// 商品分类
keyword
?:
string
// 关键字
unit
?:
number
|
null
// 单位
picUrl
?:
string
// 商品封面图
sliderPicUrls
?:
string
[]
// 商品轮播图
introduction
?:
string
// 商品简介
deliveryTemplateId
?:
number
|
null
// 运费模版
brandId
?:
number
|
null
// 商品品牌编号
specType
?:
boolean
// 商品规格
subCommissionType
?:
boolean
// 分销类型
skus
:
SkuType
[]
// sku数组
description
?:
string
// 商品详情
sort
?:
string
// 商品排序
giveIntegral
?:
number
// 赠送积分
virtualSalesCount
?:
number
// 虚拟销量
recommendHot
?:
boolean
// 是否热卖
recommendBenefit
?:
boolean
// 是否优惠
recommendBest
?:
boolean
// 是否精品
recommendNew
?:
boolean
// 是否新品
recommendGood
?:
boolean
// 是否优品
}
// 获得 Spu 列表
export
const
getSpuPage
=
(
params
:
PageParam
)
=>
{
return
request
.
get
({
url
:
'/product/spu/page'
,
params
})
}
// 获得 Spu 列表 tabsCount
export
const
getTabsCount
=
()
=>
{
return
request
.
get
({
url
:
'/product/spu/get-count'
})
}
// 创建商品 Spu
export
const
createSpu
=
(
data
:
SpuType
)
=>
{
return
request
.
post
({
url
:
'/product/spu/create'
,
data
})
}
// 更新商品 Spu
export
const
updateSpu
=
(
data
:
SpuType
)
=>
{
return
request
.
put
({
url
:
'/product/spu/update'
,
data
})
}
// 更新商品 Spu status
export
const
updateStatus
=
(
data
:
{
id
:
number
;
status
:
number
})
=>
{
return
request
.
put
({
url
:
'/product/spu/update-status'
,
data
})
}
// 获得商品 Spu
export
const
getSpu
=
(
id
:
number
)
=>
{
return
request
.
get
({
url
:
`/product/spu/get-detail?id=
${
id
}
`
})
}
// 删除商品 Spu
export
const
deleteSpu
=
(
id
:
number
)
=>
{
return
request
.
delete
({
url
:
`/product/spu/delete?id=
${
id
}
`
})
}
// 导出商品 Spu Excel
export
const
exportSpu
=
async
(
params
)
=>
{
return
await
request
.
download
({
url
:
'/product/spu/export'
,
params
})
}
src/api/mall/trade/delivery/express/index.ts
0 → 100644
View file @
ec3b028d
import
request
from
'@/config/axios'
export
interface
DeliveryExpressVO
{
id
:
number
code
:
string
name
:
string
logo
:
string
sort
:
number
status
:
number
}
// 查询快递公司列表
export
const
getDeliveryExpressPage
=
async
(
params
:
PageParam
)
=>
{
return
await
request
.
get
({
url
:
'/trade/delivery/express/page'
,
params
})
}
// 查询快递公司详情
export
const
getDeliveryExpress
=
async
(
id
:
number
)
=>
{
return
await
request
.
get
({
url
:
'/trade/delivery/express/get?id='
+
id
})
}
// 新增快递公司
export
const
createDeliveryExpress
=
async
(
data
:
DeliveryExpressVO
)
=>
{
return
await
request
.
post
({
url
:
'/trade/delivery/express/create'
,
data
})
}
// 修改快递公司
export
const
updateDeliveryExpress
=
async
(
data
:
DeliveryExpressVO
)
=>
{
return
await
request
.
put
({
url
:
'/trade/delivery/express/update'
,
data
})
}
// 删除快递公司
export
const
deleteDeliveryExpress
=
async
(
id
:
number
)
=>
{
return
await
request
.
delete
({
url
:
'/trade/delivery/express/delete?id='
+
id
})
}
// 导出快递公司 Excel
export
const
exportDeliveryExpressApi
=
async
(
params
)
=>
{
return
await
request
.
download
({
url
:
'/trade/delivery/express/export-excel'
,
params
})
}
src/api/mall/trade/delivery/expressTemplate/index.ts
0 → 100644
View file @
ec3b028d
import
request
from
'@/config/axios'
export
interface
DeliveryExpressTemplateVO
{
id
:
number
name
:
string
chargeMode
:
number
sort
:
number
templateCharge
:
ExpressTemplateChargeVO
[]
templateFree
:
ExpressTemplateFreeVO
[]
}
export
declare
type
ExpressTemplateChargeVO
=
{
areaIds
:
number
[]
startCount
:
number
startPrice
:
number
extraCount
:
number
extraPrice
:
number
}
export
declare
type
ExpressTemplateFreeVO
=
{
areaIds
:
number
[]
freeCount
:
number
freePrice
:
number
}
// 查询快递运费模板列表
export
const
getDeliveryExpressTemplatePage
=
async
(
params
:
PageParam
)
=>
{
return
await
request
.
get
({
url
:
'/trade/delivery/express-template/page'
,
params
})
}
// 查询快递运费模板详情
export
const
getDeliveryExpressTemplate
=
async
(
id
:
number
)
=>
{
return
await
request
.
get
({
url
:
'/trade/delivery/express-template/get?id='
+
id
})
}
// 新增快递运费模板
export
const
createDeliveryExpressTemplate
=
async
(
data
:
DeliveryExpressTemplateVO
)
=>
{
return
await
request
.
post
({
url
:
'/trade/delivery/express-template/create'
,
data
})
}
// 修改快递运费模板
export
const
updateDeliveryExpressTemplate
=
async
(
data
:
DeliveryExpressTemplateVO
)
=>
{
return
await
request
.
put
({
url
:
'/trade/delivery/express-template/update'
,
data
})
}
// 删除快递运费模板
export
const
deleteDeliveryExpressTemplate
=
async
(
id
:
number
)
=>
{
return
await
request
.
delete
({
url
:
'/trade/delivery/express-template/delete?id='
+
id
})
}
// 导出快递运费模板 Excel
export
const
exportDeliveryExpressTemplateApi
=
async
(
params
)
=>
{
return
await
request
.
download
({
url
:
'/trade/delivery/express-template/export-excel'
,
params
})
}
src/api/system/area/index.ts
View file @
ec3b028d
...
...
@@ -5,6 +5,14 @@ export const getAreaTree = async () => {
return
await
request
.
get
({
url
:
'/system/area/tree'
})
}
export
const
getChildrenArea
=
async
(
id
:
number
)
=>
{
return
await
request
.
get
({
url
:
'/system/area/get-children?id='
+
id
})
}
export
const
getAreaListByIds
=
async
(
ids
)
=>
{
return
await
request
.
get
({
url
:
'/system/area/get-by-ids?ids='
+
ids
})
}
// 获得 IP 对应的地区名
export
const
getAreaByIp
=
async
(
ip
:
string
)
=>
{
return
await
request
.
get
({
url
:
'/system/area/get-by-ip?ip='
+
ip
})
...
...
src/components/UploadFile/src/UploadImgs.vue
View file @
ec3b028d
<
template
>
<div
class=
"upload-box"
>
<el-upload
v-model:file-list=
"fileList"
:accept=
"fileType.join(',')"
:action=
"updateUrl"
list-type=
"picture-car
d"
:before-upload=
"beforeUploa
d"
:class=
"['upload', drag ? 'no-border' : '']"
v-model:file-list=
"fileList"
:multiple=
"true"
:limit=
"limit"
:drag=
"drag"
:headers=
"uploadHeaders"
:before-upload=
"beforeUpload"
:limit=
"limit"
:multiple=
"true"
:on-error=
"uploadError"
:on-exceed=
"handleExceed"
:on-success=
"uploadSuccess"
:on-error=
"uploadError"
:drag=
"drag"
:accept=
"fileType.join(',')"
list-type=
"picture-card"
>
<div
class=
"upload-empty"
>
<slot
name=
"empty"
>
...
...
@@ -40,15 +40,15 @@
</div>
<el-image-viewer
v-if=
"imgViewVisible"
@
close=
"imgViewVisible = false"
:url-list=
"[viewImageUrl]"
@
close=
"imgViewVisible = false"
/>
</div>
</template>
<
script
setup
lang=
"ts"
name=
"UploadImgs"
>
<
script
lang=
"ts"
name=
"UploadImgs"
setup
>
import
{
PropType
}
from
'vue'
import
type
{
UploadFile
,
UploadProps
,
UploadUserFile
}
from
'element-plus'
import
{
ElNotification
}
from
'element-plus'
import
type
{
UploadProps
,
UploadFile
,
UploadUserFile
}
from
'element-plus'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
getAccessToken
,
getTenantId
}
from
'@/utils/auth'
...
...
@@ -88,8 +88,19 @@ const uploadHeaders = ref({
'tenant-id'
:
getTenantId
()
})
const
fileList
=
ref
<
UploadUserFile
[]
>
(
props
.
modelValue
)
const
fileList
=
ref
<
UploadUserFile
[]
>
()
// fix: 改为动态监听赋值解决图片回显问题
watch
(
()
=>
props
.
modelValue
,
(
data
)
=>
{
if
(
!
data
)
return
fileList
.
value
=
data
},
{
deep
:
true
,
immediate
:
true
}
)
/**
* @description 文件上传之前判断
* @param rawFile 上传的文件
...
...
@@ -116,9 +127,11 @@ const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
interface
UploadEmits
{
(
e
:
'update:modelValue'
,
value
:
UploadUserFile
[]):
void
}
const
emit
=
defineEmits
<
UploadEmits
>
()
const
uploadSuccess
=
(
response
,
uploadFile
:
UploadFile
)
=>
{
if
(
!
response
)
return
// TODO 多图上传组件成功后只是把保存成功后的url替换掉组件选图时的文件路径,所以返回的fileList包含的是一个包含文件信息的对象列表
uploadFile
.
url
=
response
.
data
emit
(
'update:modelValue'
,
fileList
.
value
)
message
.
success
(
'上传成功'
)
...
...
@@ -159,35 +172,40 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
}
</
script
>
<
style
scoped
lang=
"scss"
>
<
style
lang=
"scss"
scoped
>
.is-error
{
.upload
{
:deep(.el-upload--picture-card),
:deep(.el-upload-dragger)
{
border
:
1px
dashed
var
(
--el-color-danger
)
!important
;
&:hover
{
border-color
:
var
(
--el-color-primary
)
!important
;
}
}
}
}
:deep
(
.disabled
)
{
.el-upload--picture-card,
.el-upload-dragger
{
cursor
:
not-allowed
;
background
:
var
(
--el-disabled-bg-color
)
!important
;
border
:
1px
dashed
var
(
--el-border-color-darker
);
&:hover
{
border-color
:
var
(
--el-border-color-darker
)
!important
;
}
}
}
.upload-box
{
.no-border
{
:deep(.el-upload--picture-card)
{
border
:
none
!important
;
}
}
:deep
(
.upload
)
{
.el-upload-dragger
{
display
:
flex
;
...
...
@@ -199,14 +217,17 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
overflow
:
hidden
;
border
:
1px
dashed
var
(
--el-border-color-darker
);
border-radius
:
v-bind
(
borderRadius
);
&:hover
{
border
:
1px
dashed
var
(
--el-color-primary
);
}
}
.el-upload-dragger.is-dragover
{
background-color
:
var
(
--el-color-primary-light-9
);
border
:
2px
dashed
var
(
--el-color-primary
)
!important
;
}
.el-upload-list__item
,
.el-upload--picture-card
{
width
:
v-bind
(
width
);
...
...
@@ -214,11 +235,13 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
background-color
:
transparent
;
border-radius
:
v-bind
(
borderRadius
);
}
.upload-image
{
width
:
100%
;
height
:
100%
;
object-fit
:
contain
;
}
.upload-handle
{
position
:
absolute
;
top
:
0
;
...
...
@@ -233,6 +256,7 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
background
:
rgb
(
0
0
0
/
60%
);
opacity
:
0
;
transition
:
var
(
--el-transition-duration-fast
);
.handle-icon
{
display
:
flex
;
flex-direction
:
column
;
...
...
@@ -240,15 +264,18 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
justify-content
:
center
;
padding
:
0
6%
;
color
:
aliceblue
;
.el-icon
{
margin-bottom
:
15%
;
font-size
:
140%
;
}
span
{
font-size
:
100%
;
}
}
}
.el-upload-list__item
{
&:hover
{
.upload-handle
{
...
...
@@ -256,6 +283,7 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
}
}
}
.upload-empty
{
display
:
flex
;
flex-direction
:
column
;
...
...
@@ -263,12 +291,14 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
font-size
:
12px
;
line-height
:
30px
;
color
:
var
(
--el-color-info
);
.el-icon
{
font-size
:
28px
;
color
:
var
(
--el-text-color-secondary
);
}
}
}
.el-upload__tip
{
line-height
:
15px
;
text-align
:
center
;
...
...
src/router/modules/remaining.ts
View file @
ec3b028d
...
...
@@ -365,22 +365,35 @@ const remainingRouter: AppRouteRecordRaw[] = [
{
path
:
'/product'
,
component
:
Layout
,
name
:
'Product
ManagementEdit
'
,
name
:
'Product'
,
meta
:
{
hidden
:
true
},
children
:
[
{
path
:
'product
ManagementAdd'
,
// TODO @puhui999:最好拆成 add 和 edit 两个路由;添加商品;修改商品
path
:
'product
SpuAdd'
,
// TODO @puhui999:最好拆成 add 和 edit 两个路由;添加商品;修改商品 fix
component
:
()
=>
import
(
'@/views/mall/product/spu/addForm.vue'
),
name
:
'Product
Management
Add'
,
name
:
'Product
Spu
Add'
,
meta
:
{
noCache
:
true
,
hidden
:
true
,
canTo
:
true
,
icon
:
'ep:edit'
,
title
:
'添加商品'
,
activeMenu
:
'/product/product-management'
activeMenu
:
'/product/product-spu'
}
},
{
path
:
'productSpuEdit/:spuId(\\d+)'
,
component
:
()
=>
import
(
'@/views/mall/product/spu/addForm.vue'
),
name
:
'productSpuEdit'
,
meta
:
{
noCache
:
true
,
hidden
:
true
,
canTo
:
true
,
icon
:
'ep:edit'
,
title
:
'编辑商品'
,
activeMenu
:
'/product/product-spu'
}
}
]
...
...
src/utils/dict.ts
View file @
ec3b028d
...
...
@@ -149,6 +149,9 @@ export enum DICT_TYPE {
PRODUCT_UNIT
=
'product_unit'
,
// 商品单位
PRODUCT_SPU_STATUS
=
'product_spu_status'
,
//商品状态
// ========== MALL 交易模块 ==========
EXPRESS_CHARGE_MODE
=
'trade_delivery_express_charge_mode'
,
//快递的计费方式
//===add by 20230530====
// ========== MALL - ORDER 模块 ==========
TRADE_AFTER_SALE_STATUS
=
'trade_after_sale_status'
,
// 售后 - 状态
...
...
src/utils/index.ts
View file @
ec3b028d
...
...
@@ -155,3 +155,57 @@ export const fileSizeFormatter = (row, column, cellValue) => {
const
sizeStr
=
size
.
toFixed
(
2
)
//保留的小数位数
return
sizeStr
+
' '
+
unitArr
[
index
]
}
/**
* 将值复制到目标对象,且以目标对象属性为准,例:target: {a:1} source:{a:2,b:3} 结果为:{a:2}
* @param target 目标对象
* @param source 源对象
*/
export
const
copyValueToTarget
=
(
target
,
source
)
=>
{
const
newObj
=
Object
.
assign
({},
target
,
source
)
// 删除多余属性
Object
.
keys
(
newObj
).
forEach
((
key
)
=>
{
// 如果不是target中的属性则删除
if
(
Object
.
keys
(
target
).
indexOf
(
key
)
===
-
1
)
{
delete
newObj
[
key
]
}
})
// 更新目标对象值
Object
.
assign
(
target
,
newObj
)
}
// TODO @puhui999:返回要带上 .00 哈.例如说 1.00
/**
* 将一个整数转换为分数保留两位小数
* @param num
*/
export
const
formatToFraction
=
(
num
:
number
|
string
|
undefined
):
number
=>
{
if
(
typeof
num
===
'undefined'
)
return
0
const
parsedNumber
=
typeof
num
===
'string'
?
parseFloat
(
num
)
:
num
return
parseFloat
((
parsedNumber
/
100
).
toFixed
(
2
))
}
/**
* 将一个分数转换为整数
* @param num
*/
export
const
convertToInteger
=
(
num
:
number
|
string
|
undefined
):
number
=>
{
if
(
typeof
num
===
'undefined'
)
return
0
const
parsedNumber
=
typeof
num
===
'string'
?
parseFloat
(
num
)
:
num
// TODO 分转元后还有小数则四舍五入
return
Math
.
round
(
parsedNumber
*
100
)
}
/**
* 元转分
*/
export
const
yuanToFen
=
(
amount
:
string
|
number
):
number
=>
{
return
Math
.
round
(
Number
(
amount
)
*
100
)
}
/**
* 分转元
*/
export
const
fenToYuan
=
(
amount
:
string
|
number
):
number
=>
{
return
Number
((
Number
(
amount
)
/
100
).
toFixed
(
2
))
}
src/utils/object.ts
deleted
100644 → 0
View file @
985d764f
// TODO @puhui999:这个方法,可以考虑放到 index.js
/**
* 将值复制到目标对象,且以目标对象属性为准,例:target: {a:1} source:{a:2,b:3} 结果为:{a:2}
* @param target 目标对象
* @param source 源对象
*/
export
const
copyValueToTarget
=
(
target
,
source
)
=>
{
const
newObj
=
Object
.
assign
({},
target
,
source
)
// 删除多余属性
Object
.
keys
(
newObj
).
forEach
((
key
)
=>
{
// 如果不是target中的属性则删除
if
(
Object
.
keys
(
target
).
indexOf
(
key
)
===
-
1
)
{
delete
newObj
[
key
]
}
})
// 更新目标对象值
Object
.
assign
(
target
,
newObj
)
}
src/utils/tree.ts
View file @
ec3b028d
...
...
@@ -11,7 +11,8 @@ const DEFAULT_CONFIG: TreeHelperConfig = {
export
const
defaultProps
=
{
children
:
'children'
,
label
:
'name'
,
value
:
'id'
value
:
'id'
,
isLeaf
:
'leaf'
}
const
getConfig
=
(
config
:
Partial
<
TreeHelperConfig
>
)
=>
Object
.
assign
({},
DEFAULT_CONFIG
,
config
)
...
...
src/views/mall/product/spu/addForm.vue
View file @
ec3b028d
...
...
@@ -3,21 +3,21 @@
<el-tabs
v-model=
"activeName"
>
<el-tab-pane
label=
"商品信息"
name=
"basicInfo"
>
<BasicInfoForm
ref=
"
B
asicInfoRef"
ref=
"
b
asicInfoRef"
v-model:activeName=
"activeName"
:propFormData=
"formData"
/>
</el-tab-pane>
<el-tab-pane
label=
"商品详情"
name=
"description"
>
<DescriptionForm
ref=
"
D
escriptionRef"
ref=
"
d
escriptionRef"
v-model:activeName=
"activeName"
:propFormData=
"formData"
/>
</el-tab-pane>
<el-tab-pane
label=
"其他设置"
name=
"otherSettings"
>
<OtherSettingsForm
ref=
"
O
therSettingsRef"
ref=
"
o
therSettingsRef"
v-model:activeName=
"activeName"
:propFormData=
"formData"
/>
...
...
@@ -31,88 +31,56 @@
</el-form>
</ContentWrap>
</
template
>
<
script
lang=
"ts"
name=
"ProductManagementForm"
setup
>
<
script
lang=
"ts"
name=
"ProductSpuForm"
setup
>
import
{
cloneDeep
}
from
'lodash-es'
import
{
useTagsViewStore
}
from
'@/store/modules/tagsView'
import
{
BasicInfoForm
,
DescriptionForm
,
OtherSettingsForm
}
from
'./components'
import
type
{
SpuType
}
from
'@/api/mall/product/management/type/spuType'
// 业务api
import
*
as
managementApi
from
'@/api/mall/product/management/spu'
import
*
as
PropertyApi
from
'@/api/mall/product/property'
// 业务api
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
{
convertToInteger
,
formatToFraction
}
from
'@/utils'
const
{
t
}
=
useI18n
()
// 国际化
const
message
=
useMessage
()
// 消息弹窗
const
{
push
,
currentRoute
}
=
useRouter
()
// 路由
const
{
query
}
=
useRoute
()
// 查询参数
const
{
params
}
=
useRoute
()
// 查询参数
const
{
delView
}
=
useTagsViewStore
()
// 视图操作
const
formLoading
=
ref
(
false
)
// 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const
activeName
=
ref
(
'basicInfo'
)
// Tag 激活的窗口
const
BasicInfoRef
=
ref
<
ComponentRef
<
typeof
BasicInfoForm
>>
()
// 商品信息Ref
const
DescriptionRef
=
ref
<
ComponentRef
<
typeof
DescriptionForm
>>
()
// 商品详情Ref
const
OtherSettingsRef
=
ref
<
ComponentRef
<
typeof
OtherSettingsForm
>>
()
// 其他设置Ref
const
formData
=
ref
<
SpuType
>
({
name
:
'213'
,
// 商品名称
const
basicInfoRef
=
ref
<
ComponentRef
<
typeof
BasicInfoForm
>>
()
// 商品信息Ref
const
descriptionRef
=
ref
<
ComponentRef
<
typeof
DescriptionForm
>>
()
// 商品详情Ref
const
otherSettingsRef
=
ref
<
ComponentRef
<
typeof
OtherSettingsForm
>>
()
// 其他设置Ref
// spu 表单数据
const
formData
=
ref
<
ProductSpuApi
.
SpuType
>
({
name
:
''
,
// 商品名称
categoryId
:
null
,
// 商品分类
keyword
:
'
213
'
,
// 关键字
keyword
:
''
,
// 关键字
unit
:
null
,
// 单位
picUrl
:
'http://127.0.0.1:48080/admin-api/infra/file/4/get/6ffdf8f5dfc03f7ceec1ff1ebc138adb8b721a057d505ccfb0e61a8783af1371.png'
,
// 商品封面图
sliderPicUrls
:
[
{
name
:
'http://127.0.0.1:48080/admin-api/infra/file/4/get/6ffdf8f5dfc03f7ceec1ff1ebc138adb8b721a057d505ccfb0e61a8783af1371.png'
,
url
:
'http://127.0.0.1:48080/admin-api/infra/file/4/get/6ffdf8f5dfc03f7ceec1ff1ebc138adb8b721a057d505ccfb0e61a8783af1371.png'
}
],
// 商品轮播图
introduction
:
'213'
,
// 商品简介
deliveryTemplateId
:
0
,
// 运费模版
picUrl
:
''
,
// 商品封面图
sliderPicUrls
:
[],
// 商品轮播图
introduction
:
''
,
// 商品简介
deliveryTemplateId
:
1
,
// 运费模版
brandId
:
null
,
// 商品品牌
specType
:
false
,
// 商品规格
subCommissionType
:
false
,
// 分销类型
skus
:
[
{
/**
* 商品价格,单位:分 TODO @puhui999:注释放在尾巴哈,简洁一点~
*/
price
:
0
,
/**
* 市场价,单位:分
*/
marketPrice
:
0
,
/**
* 成本价,单位:分
*/
costPrice
:
0
,
/**
* 商品条码
*/
barCode
:
''
,
/**
* 图片地址
*/
picUrl
:
''
,
/**
* 库存
*/
stock
:
0
,
/**
* 商品重量,单位:kg 千克
*/
weight
:
0
,
/**
* 商品体积,单位:m^3 平米
*/
volume
:
0
,
/**
* 一级分销的佣金,单位:分
*/
subCommissionFirstPrice
:
0
,
/**
* 二级分销的佣金,单位:分
*/
subCommissionSecondPrice
:
0
price
:
0
,
// 商品价格
marketPrice
:
0
,
// 市场价
costPrice
:
0
,
// 成本价
barCode
:
''
,
// 商品条码
picUrl
:
''
,
// 图片地址
stock
:
0
,
// 库存
weight
:
0
,
// 商品重量
volume
:
0
,
// 商品体积
subCommissionFirstPrice
:
0
,
// 一级分销的佣金
subCommissionSecondPrice
:
0
// 二级分销的佣金
}
],
description
:
'
5425
'
,
// 商品详情
sort
:
1
,
// 商品排序
giveIntegral
:
1
,
// 赠送积分
virtualSalesCount
:
1
,
// 虚拟销量
description
:
''
,
// 商品详情
sort
:
0
,
// 商品排序
giveIntegral
:
0
,
// 赠送积分
virtualSalesCount
:
0
,
// 虚拟销量
recommendHot
:
false
,
// 是否热卖
recommendBenefit
:
false
,
// 是否优惠
recommendBest
:
false
,
// 是否精品
...
...
@@ -122,19 +90,20 @@ const formData = ref<SpuType>({
/** 获得详情 */
const
getDetail
=
async
()
=>
{
const
id
=
query
.
id
as
unknown
as
number
const
id
=
params
.
spuId
as
number
if
(
id
)
{
formLoading
.
value
=
true
try
{
const
res
=
(
await
managementApi
.
getSpu
(
id
))
as
SpuType
const
res
=
(
await
ProductSpuApi
.
getSpu
(
id
))
as
ProductSpuApi
.
SpuType
res
.
skus
.
forEach
((
item
)
=>
{
// 回显价格分转元
item
.
price
=
formatToFraction
(
item
.
price
)
item
.
marketPrice
=
formatToFraction
(
item
.
marketPrice
)
item
.
costPrice
=
formatToFraction
(
item
.
costPrice
)
item
.
subCommissionFirstPrice
=
formatToFraction
(
item
.
subCommissionFirstPrice
)
item
.
subCommissionSecondPrice
=
formatToFraction
(
item
.
subCommissionSecondPrice
)
})
formData
.
value
=
res
// 直接取第一个值就能得到所有属性的id
// TODO @puhui999:可以直接拿 propertyName 拼接处规格 id + 属性,可以看下商品 uniapp 详情的做法
const
propertyIds
=
res
.
skus
[
0
]?.
properties
.
map
((
item
)
=>
item
.
propertyId
)
const
PropertyS
=
await
PropertyApi
.
getPropertyListAndValue
({
propertyIds
})
await
nextTick
()
// 回显商品属性
BasicInfoRef
.
value
.
addAttribute
(
PropertyS
)
}
finally
{
formLoading
.
value
=
false
}
...
...
@@ -145,96 +114,65 @@ const getDetail = async () => {
const
submitForm
=
async
()
=>
{
// 提交请求
formLoading
.
value
=
true
const
newSkus
=
JSON
.
parse
(
JSON
.
stringify
(
formData
.
value
.
skus
))
//深拷贝一份skus保存失败时使用
// TODO 三个表单逐一校验,如果有一个表单校验不通过则切换到对应表单,如果有两个及以上的情况则切换到最前面的一个并弹出提示消息
// 三个表单逐一校验,如果有一个表单校验不通过则切换到对应表单,如果有两个及以上的情况则切换到最前面的一个并弹出提示消息
// 校验各表单
try
{
await
unref
(
B
asicInfoRef
)?.
validate
()
await
unref
(
D
escriptionRef
)?.
validate
()
await
unref
(
O
therSettingsRef
)?.
validate
()
// TODO @puhui:直接做深拷贝?这样最终 server 端不满足,不需要恢复
//
处理掉一些无关
数据
formData
.
value
.
skus
.
forEach
((
item
)
=>
{
//
给sku name赋值
i
tem
.
name
=
formData
.
value
.
name
// 多规格情况移除skus相关属性值value
if
(
formData
.
value
.
specType
)
{
item
.
properties
.
forEach
((
item2
)
=>
{
delete
item2
.
valueName
}
)
await
unref
(
b
asicInfoRef
)?.
validate
()
await
unref
(
d
escriptionRef
)?.
validate
()
await
unref
(
o
therSettingsRef
)?.
validate
()
const
deepCopyFormData
=
cloneDeep
(
unref
(
formData
.
value
))
// 深拷贝一份 fix:这样最终 server 端不满足,不需要恢复,
//
TODO 兜底处理 sku 空
数据
formData
.
value
.
skus
.
forEach
((
sku
)
=>
{
//
因为是空数据这里判断一下商品条码是否为空就行
i
f
(
sku
.
barCode
===
''
)
{
const
index
=
deepCopyFormData
.
skus
.
findIndex
(
(
item
)
=>
JSON
.
stringify
(
item
.
properties
)
===
JSON
.
stringify
(
sku
.
properties
)
)
// 删除这条 sku
deepCopyFormData
.
skus
.
splice
(
index
,
1
)
}
})
deepCopyFormData
.
skus
.
forEach
((
item
)
=>
{
// 给sku name赋值
item
.
name
=
deepCopyFormData
.
name
// sku相关价格元转分
item
.
price
=
convertToInteger
(
item
.
price
)
item
.
marketPrice
=
convertToInteger
(
item
.
marketPrice
)
item
.
costPrice
=
convertToInteger
(
item
.
costPrice
)
item
.
subCommissionFirstPrice
=
convertToInteger
(
item
.
subCommissionFirstPrice
)
item
.
subCommissionSecondPrice
=
convertToInteger
(
item
.
subCommissionSecondPrice
)
})
// 处理轮播图列表
const
newSliderPicUrls
=
[]
formData
.
value
.
sliderPicUrls
.
forEach
((
item
)
=>
{
deepCopyFormData
.
sliderPicUrls
.
forEach
((
item
)
=>
{
// 如果是前端选的图
// TODO @puhui999:疑问哈,为啥会是 object 呀?
if
(
typeof
item
===
'object'
)
{
newSliderPicUrls
.
push
(
item
.
url
)
}
else
{
newSliderPicUrls
.
push
(
item
)
}
typeof
item
===
'object'
?
newSliderPicUrls
.
push
(
item
.
url
)
:
newSliderPicUrls
.
push
(
item
)
})
formData
.
value
.
sliderPicUrls
=
newSliderPicUrls
deepCopyFormData
.
sliderPicUrls
=
newSliderPicUrls
// 校验都通过后提交表单
const
data
=
formData
.
value
as
SpuType
// 移除skus.
const
id
=
query
.
id
as
unknown
as
number
const
data
=
deepCopyFormData
as
ProductSpuApi
.
SpuType
const
id
=
params
.
spuId
as
number
if
(
!
id
)
{
await
management
Api
.
createSpu
(
data
)
await
ProductSpu
Api
.
createSpu
(
data
)
message
.
success
(
t
(
'common.createSuccess'
))
}
else
{
await
management
Api
.
updateSpu
(
data
)
await
ProductSpu
Api
.
updateSpu
(
data
)
message
.
success
(
t
(
'common.updateSuccess'
))
}
close
()
}
catch
(
e
)
{
// 如果是后端校验失败,恢复skus数据
if
(
typeof
e
===
'string'
)
{
formData
.
value
.
skus
=
newSkus
}
}
finally
{
formLoading
.
value
=
false
}
}
/**
* 重置表单
*/
const
resetForm
=
async
()
=>
{
formData
.
value
=
{
name
:
''
,
// 商品名称
categoryId
:
0
,
// 商品分类
keyword
:
''
,
// 关键字
unit
:
''
,
// 单位
picUrl
:
''
,
// 商品封面图
sliderPicUrls
:
[],
// 商品轮播图
introduction
:
''
,
// 商品简介
deliveryTemplateId
:
0
,
// 运费模版
selectRule
:
''
,
specType
:
false
,
// 商品规格
subCommissionType
:
false
,
// 分销类型
description
:
''
,
// 商品详情
sort
:
1
,
// 商品排序
giveIntegral
:
1
,
// 赠送积分
virtualSalesCount
:
1
,
// 虚拟销量
recommendHot
:
false
,
// 是否热卖
recommendBenefit
:
false
,
// 是否优惠
recommendBest
:
false
,
// 是否精品
recommendNew
:
false
,
// 是否新品
recommendGood
:
false
// 是否优品
}
}
/** 关闭按钮 */
const
close
=
()
=>
{
// TODO @puhui999:是不是不用 reset 呀?close 默认销毁
resetForm
()
delView
(
unref
(
currentRoute
))
push
(
'/product/product-
management
'
)
push
(
'/product/product-
spu
'
)
}
/** 初始化 */
onMounted
(()
=>
{
getDetail
()
onMounted
(
async
()
=>
{
await
getDetail
()
})
</
script
>
src/views/mall/product/spu/components/BasicInfoForm.vue
View file @
ec3b028d
<
template
>
<el-form
ref=
"
ProductManagement
BasicInfoRef"
:model=
"formData"
:rules=
"rules"
label-width=
"120px"
>
<el-form
ref=
"
productSpu
BasicInfoRef"
:model=
"formData"
:rules=
"rules"
label-width=
"120px"
>
<el-row>
<el-col
:span=
"12"
>
<el-form-item
label=
"商品名称"
prop=
"name"
>
...
...
@@ -14,9 +14,9 @@
:data=
"categoryList"
:props=
"defaultProps"
check-strictly
class=
"w-1/1"
node-key=
"id"
placeholder=
"请选择商品分类"
class=
"w-1/1"
/>
</el-form-item>
</el-col>
...
...
@@ -27,7 +27,7 @@
</el-col>
<el-col
:span=
"12"
>
<el-form-item
label=
"单位"
prop=
"unit"
>
<el-select
v-model=
"formData.unit"
placeholder=
"请选择单位"
class=
"w-1/1
"
>
<el-select
v-model=
"formData.unit"
class=
"w-1/1"
placeholder=
"请选择单位
"
>
<el-option
v-for=
"dict in getIntDictOptions(DICT_TYPE.PRODUCT_UNIT)"
:key=
"dict.value"
...
...
@@ -54,18 +54,28 @@
</el-col>
<el-col
:span=
"24"
>
<el-form-item
label=
"商品轮播图"
prop=
"sliderPicUrls"
>
<UploadImgs
v-model=
"formData.sliderPicUrls"
/>
<UploadImgs
v-model
:modelValue
=
"formData.sliderPicUrls"
/>
</el-form-item>
</el-col>
<el-col
:span=
"12"
>
<el-form-item
label=
"运费模板"
prop=
"deliveryTemplateId"
>
<el-select
v-model=
"formData.deliveryTemplateId"
placeholder=
"请选择"
class=
"w-1/1"
>
<el-select
v-model=
"formData.deliveryTemplateId"
placeholder=
"请选择"
>
<el-option
v-for=
"item in []"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
/>
</el-select>
<el-button
class=
"ml-20px"
>
运费模板
</el-button>
</el-form-item>
</el-col>
<el-col
:span=
"12"
>
<el-button
class=
"ml-20px"
>
运费模板
</el-button>
<el-form-item
label=
"品牌"
prop=
"brandId"
>
<el-select
v-model=
"formData.brandId"
placeholder=
"请选择"
>
<el-option
v-for=
"item in brandList"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col
:span=
"12"
>
<el-form-item
label=
"商品规格"
props=
"specType"
>
...
...
@@ -86,36 +96,37 @@
<!-- 多规格添加-->
<el-col
:span=
"24"
>
<el-form-item
v-if=
"formData.specType"
label=
"商品属性"
>
<!-- TODO @puhui999:参考 https://admin.java.crmeb.net/store/list/creatProduct 添加规格好做么?添加的时候,不用输入备注哈 -->
<el-button
class=
"mr-15px mb-10px"
@
click=
"AttributesAddFormRef.open"
>
添加规格
</el-button>
<ProductAttributes
:attribute-data=
"attributeList"
/>
<el-button
class=
"mr-15px mb-10px"
@
click=
"attributesAddFormRef.open"
>
添加规格
</el-button>
<ProductAttributes
:propertyList=
"propertyList"
@
success=
"generateSkus"
/>
</el-form-item>
<template
v-if=
"formData.specType &&
attribute
List.length > 0"
>
<template
v-if=
"formData.specType &&
property
List.length > 0"
>
<el-form-item
label=
"批量设置"
>
<SkuList
:
attributeList=
"attributeList"
:is-batch=
"true"
:prop-form-data=
"formData
"
/>
<SkuList
:
is-batch=
"true"
:prop-form-data=
"formData"
:propertyList=
"propertyList
"
/>
</el-form-item>
<el-form-item
label=
"属性列表"
>
<SkuList
:attributeList=
"attributeList"
:prop-form-data=
"formData
"
/>
<SkuList
ref=
"skuListRef"
:prop-form-data=
"formData"
:propertyList=
"propertyList
"
/>
</el-form-item>
</
template
>
<el-form-item
v-if=
"!formData.specType"
>
<SkuList
:
attributeList=
"attributeList"
:prop-form-data=
"formData
"
/>
<SkuList
:
prop-form-data=
"formData"
:propertyList=
"propertyList
"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<ProductAttributesAddForm
ref=
"
AttributesAddFormRef"
@
success=
"addAttribute
"
/>
<ProductAttributesAddForm
ref=
"
attributesAddFormRef"
:propertyList=
"propertyList
"
/>
</template>
<
script
lang=
"ts"
name=
"Product
Management
BasicInfoForm"
setup
>
<
script
lang=
"ts"
name=
"Product
Spu
BasicInfoForm"
setup
>
import
{
PropType
}
from
'vue'
import
{
copyValueToTarget
}
from
'@/utils'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
defaultProps
,
handleTree
}
from
'@/utils/tree'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
type
{
SpuType
}
from
'@/api/mall/product/
management/type/spuType
'
import
type
{
SpuType
}
from
'@/api/mall/product/
spu
'
import
{
UploadImg
,
UploadImgs
}
from
'@/components/UploadFile'
import
{
copyValueToTarget
}
from
'@/utils/object'
import
{
ProductAttributes
,
ProductAttributesAddForm
,
SkuList
}
from
'./index'
import
*
as
ProductCategoryApi
from
'@/api/mall/product/category'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
getSimpleBrandList
}
from
'@/api/mall/product/brand'
const
message
=
useMessage
()
// 消息弹窗
const
props
=
defineProps
({
...
...
@@ -125,27 +136,24 @@ const props = defineProps({
},
activeName
:
propTypes
.
string
.
def
(
''
)
})
const
AttributesAddFormRef
=
ref
()
// 添加商品属性表单 TODO @puhui999:小写开头哈
const
ProductManagementBasicInfoRef
=
ref
()
// 表单Ref TODO @puhui999:小写开头哈
// TODO @puhui999:attributeList 改成 propertyList,会更统一一点
const
attributeList
=
ref
([])
// 商品属性列表
/** 添加商品属性 */
// TODO @puhui999:propFormData 算出来
const
addAttribute
=
(
property
:
any
)
=>
{
if
(
Array
.
isArray
(
property
))
{
attributeList
.
value
=
property
return
}
attributeList
.
value
.
push
(
property
)
const
attributesAddFormRef
=
ref
()
// 添加商品属性表单
const
productSpuBasicInfoRef
=
ref
()
// 表单 Ref
const
propertyList
=
ref
([])
// 商品属性列表
const
skuListRef
=
ref
()
// 商品属性列表Ref
/** 调用 SkuList generateTableData 方法*/
const
generateSkus
=
(
propertyList
)
=>
{
skuListRef
.
value
.
generateTableData
(
propertyList
)
}
const
formData
=
reactive
<
SpuType
>
({
name
:
''
,
// 商品名称
categoryId
:
undefined
,
// 商品分类
categoryId
:
null
,
// 商品分类
keyword
:
''
,
// 关键字
unit
:
''
,
// 单位
picUrl
:
''
,
// 商品封面图
sliderPicUrls
:
[],
// 商品轮播图
introduction
:
''
,
// 商品简介
deliveryTemplateId
:
1
,
// 运费模版
brandId
:
null
,
// 商品品牌
specType
:
false
,
// 商品规格
subCommissionType
:
false
,
// 分销类型
skus
:
[]
...
...
@@ -159,6 +167,7 @@ const rules = reactive({
picUrl
:
[
required
],
sliderPicUrls
:
[
required
],
// deliveryTemplateId: [required],
brandId
:
[
required
],
specType
:
[
required
],
subCommissionType
:
[
required
]
})
...
...
@@ -169,11 +178,35 @@ const rules = reactive({
watch
(
()
=>
props
.
propFormData
,
(
data
)
=>
{
if
(
!
data
)
return
if
(
!
data
)
{
return
}
copyValueToTarget
(
formData
,
data
)
formData
.
sliderPicUrls
=
data
[
'sliderPicUrls'
].
map
((
item
)
=>
({
url
:
item
}))
// TODO @puhui999:if return,减少嵌套层级
// 只有是多规格才处理
if
(
formData
.
specType
)
{
// 直接拿返回的 skus 属性逆向生成出 propertyList
const
properties
=
[]
formData
.
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
})
}
})
})
propertyList
.
value
=
properties
}
},
{
deep
:
true
,
immediate
:
true
}
)
...
...
@@ -184,8 +217,8 @@ watch(
const
emit
=
defineEmits
([
'update:activeName'
])
const
validate
=
async
()
=>
{
// 校验表单
if
(
!
ProductManagement
BasicInfoRef
)
return
return
await
unref
(
ProductManagement
BasicInfoRef
).
validate
((
valid
)
=>
{
if
(
!
productSpu
BasicInfoRef
)
return
return
await
unref
(
productSpu
BasicInfoRef
).
validate
((
valid
)
=>
{
if
(
!
valid
)
{
message
.
warning
(
'商品信息未完善!!'
)
emit
(
'update:activeName'
,
'basicInfo'
)
...
...
@@ -197,7 +230,7 @@ const validate = async () => {
}
})
}
defineExpose
({
validate
,
addAttribute
})
defineExpose
({
validate
})
/** 分销类型 */
const
changeSubCommissionType
=
()
=>
{
...
...
@@ -211,7 +244,7 @@ const changeSubCommissionType = () => {
/** 选择规格 */
const
onChangeSpec
=
()
=>
{
// 重置商品属性列表
attribute
List
.
value
=
[]
property
List
.
value
=
[]
// 重置sku列表
formData
.
skus
=
[
{
...
...
@@ -229,10 +262,13 @@ const onChangeSpec = () => {
]
}
const
categoryList
=
ref
()
// 分类树
const
categoryList
=
ref
([])
// 分类树
const
brandList
=
ref
([])
// 精简商品品牌列表
onMounted
(
async
()
=>
{
// 获得分类树
const
data
=
await
ProductCategoryApi
.
getCategoryList
({})
categoryList
.
value
=
handleTree
(
data
,
'id'
,
'parentId'
)
// 获取商品品牌列表
brandList
.
value
=
await
getSimpleBrandList
()
})
</
script
>
src/views/mall/product/spu/components/DescriptionForm.vue
View file @
ec3b028d
<
template
>
<el-form
ref=
"
D
escriptionFormRef"
:model=
"formData"
:rules=
"rules"
label-width=
"120px"
>
<el-form
ref=
"
d
escriptionFormRef"
:model=
"formData"
:rules=
"rules"
label-width=
"120px"
>
<!--富文本编辑器组件-->
<el-form-item
label=
"商品详情"
prop=
"description"
>
<Editor
v-model:modelValue=
"formData.description"
/>
...
...
@@ -7,11 +7,11 @@
</el-form>
</
template
>
<
script
lang=
"ts"
name=
"DescriptionForm"
setup
>
import
type
{
SpuType
}
from
'@/api/mall/product/
management/type/spuType
'
import
type
{
SpuType
}
from
'@/api/mall/product/
spu
'
import
{
Editor
}
from
'@/components/Editor'
import
{
PropType
}
from
'vue'
import
{
copyValueToTarget
}
from
'@/utils/object'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
copyValueToTarget
}
from
'@/utils'
const
message
=
useMessage
()
// 消息弹窗
const
props
=
defineProps
({
...
...
@@ -21,7 +21,7 @@ const props = defineProps({
},
activeName
:
propTypes
.
string
.
def
(
''
)
})
const
D
escriptionFormRef
=
ref
()
// 表单Ref
const
d
escriptionFormRef
=
ref
()
// 表单Ref
const
formData
=
ref
<
SpuType
>
({
description
:
''
// 商品详情
})
...
...
@@ -29,7 +29,6 @@ const formData = ref<SpuType>({
const
rules
=
reactive
({
description
:
[
required
]
})
/**
* 富文本编辑器如果输入过再清空会有残留,需再重置一次
*/
...
...
@@ -45,7 +44,6 @@ watch(
immediate
:
true
}
)
/**
* 将传进来的值赋值给formData
*/
...
...
@@ -53,10 +51,11 @@ watch(
()
=>
props
.
propFormData
,
(
data
)
=>
{
if
(
!
data
)
return
// fix:三个表单组件监听赋值必须使用 copyValueToTarget 使用 formData.value = data 会监听非常多次
copyValueToTarget
(
formData
.
value
,
data
)
},
{
deep
:
true
,
// fix: 去掉深度监听只有对象引用发生改变的时候才执行,解决改一动多的问题
immediate
:
true
}
)
...
...
@@ -67,8 +66,8 @@ watch(
const
emit
=
defineEmits
([
'update:activeName'
])
const
validate
=
async
()
=>
{
// 校验表单
if
(
!
D
escriptionFormRef
)
return
return
unref
(
D
escriptionFormRef
).
validate
((
valid
)
=>
{
if
(
!
d
escriptionFormRef
)
return
return
await
unref
(
d
escriptionFormRef
).
validate
((
valid
)
=>
{
if
(
!
valid
)
{
message
.
warning
(
'商品详情为完善!!'
)
emit
(
'update:activeName'
,
'description'
)
...
...
src/views/mall/product/spu/components/OtherSettingsForm.vue
View file @
ec3b028d
<
template
>
<el-form
ref=
"
O
therSettingsFormRef"
:model=
"formData"
:rules=
"rules"
label-width=
"120px"
>
<el-form
ref=
"
o
therSettingsFormRef"
:model=
"formData"
:rules=
"rules"
label-width=
"120px"
>
<el-row>
<!-- TODO @puhui999:横着三个哈 -->
<el-col
:span=
"24"
>
<el-col
:span=
"8"
>
<el-form-item
label=
"商品排序"
prop=
"sort"
>
<el-input-number
v-model=
"formData.sort"
:min=
"0"
/>
</el-form-item>
</el-col>
<el-col
:span=
"8"
>
<el-form-item
label=
"赠送积分"
prop=
"giveIntegral"
>
<el-input-number
v-model=
"formData.giveIntegral"
:min=
"0"
/>
</el-form-item>
</el-col>
<el-col
:span=
"8"
>
<el-form-item
label=
"虚拟销量"
prop=
"virtualSalesCount"
>
<el-input-number
v-model=
"formData.virtualSalesCount"
:min=
"0"
placeholder=
"请输入虚拟销量"
/>
</el-form-item>
</el-col>
<el-row
:gutter=
"20"
>
<el-col
:span=
"8"
>
<el-form-item
label=
"商品排序"
prop=
"sort"
>
<el-input-number
v-model=
"formData.sort"
:min=
"0"
/>
</el-form-item>
</el-col>
<el-col
:span=
"8"
>
<el-form-item
label=
"赠送积分"
prop=
"giveIntegral"
>
<el-input-number
v-model=
"formData.giveIntegral"
:min=
"0"
/>
</el-form-item>
</el-col>
<el-col
:span=
"8"
>
<el-form-item
label=
"虚拟销量"
prop=
"virtualSalesCount"
>
<el-input-number
v-model=
"formData.virtualSalesCount"
:min=
"0"
placeholder=
"请输入虚拟销量"
/>
</el-form-item>
</el-col>
</el-row>
</el-col>
<el-col
:span=
"24"
>
<el-form-item
label=
"商品推荐"
>
<el-checkbox-group
v-model=
"checkboxGroup"
@
change=
"onChangeGroup"
>
<el-checkbox
v-for=
"(item, index) in recommend"
:key=
"index"
:label=
"item.value"
>
<el-checkbox
v-for=
"(item, index) in recommend
Options
"
:key=
"index"
:label=
"item.value"
>
{{
item
.
name
}}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-col>
<el-col
:span=
"24"
>
<!-- TODO tag展示暂时不考虑排序
-->
<!-- TODO tag展示暂时不考虑排序 -->
<el-form-item
label=
"活动优先级"
>
<el-tag>
默认
</el-tag>
<el-tag
class=
"ml-2"
type=
"success"
>
秒杀
</el-tag>
...
...
@@ -51,10 +52,11 @@
</el-form>
</
template
>
<
script
lang=
"ts"
name=
"OtherSettingsForm"
setup
>
import
type
{
SpuType
}
from
'@/api/mall/product/
management/type/spuType
'
import
type
{
SpuType
}
from
'@/api/mall/product/
spu
'
import
{
PropType
}
from
'vue'
import
{
copyValueToTarget
}
from
'@/utils/object'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
copyValueToTarget
}
from
'@/utils'
const
message
=
useMessage
()
// 消息弹窗
const
props
=
defineProps
({
...
...
@@ -64,35 +66,8 @@ const props = defineProps({
},
activeName
:
propTypes
.
string
.
def
(
''
)
})
// 商品推荐选项 TODO @puhui999:这种叫 recommendOptions 会更合适哈
const
recommend
=
[
{
name
:
'是否热卖'
,
value
:
'recommendHot'
},
{
name
:
'是否优惠'
,
value
:
'recommendBenefit'
},
{
name
:
'是否精品'
,
value
:
'recommendBest'
},
{
name
:
'是否新品'
,
value
:
'recommendNew'
},
{
name
:
'是否优品'
,
value
:
'recommendGood'
}
]
const
checkboxGroup
=
ref
<
string
[]
>
([
'recommendHot'
])
// 选中推荐选项
/** 选择商品后赋值 */
const
onChangeGroup
=
()
=>
{
// TODO @puhui999:是不是可以遍历 recommend,然后进行是否选中;
checkboxGroup
.
value
.
includes
(
'recommendHot'
)
?
(
formData
.
value
.
recommendHot
=
true
)
:
(
formData
.
value
.
recommendHot
=
false
)
checkboxGroup
.
value
.
includes
(
'recommendBenefit'
)
?
(
formData
.
value
.
recommendBenefit
=
true
)
:
(
formData
.
value
.
recommendBenefit
=
false
)
checkboxGroup
.
value
.
includes
(
'recommendBest'
)
?
(
formData
.
value
.
recommendBest
=
true
)
:
(
formData
.
value
.
recommendBest
=
false
)
checkboxGroup
.
value
.
includes
(
'recommendNew'
)
?
(
formData
.
value
.
recommendNew
=
true
)
:
(
formData
.
value
.
recommendNew
=
false
)
checkboxGroup
.
value
.
includes
(
'recommendGood'
)
?
(
formData
.
value
.
recommendGood
=
true
)
:
(
formData
.
value
.
recommendGood
=
false
)
}
const
OtherSettingsFormRef
=
ref
()
// 表单Ref
const
otherSettingsFormRef
=
ref
()
// 表单Ref
// 表单数据
const
formData
=
ref
<
SpuType
>
({
sort
:
1
,
// 商品排序
...
...
@@ -110,6 +85,21 @@ const rules = reactive({
giveIntegral
:
[
required
],
virtualSalesCount
:
[
required
]
})
const
recommendOptions
=
[
{
name
:
'是否热卖'
,
value
:
'recommendHot'
},
{
name
:
'是否优惠'
,
value
:
'recommendBenefit'
},
{
name
:
'是否精品'
,
value
:
'recommendBest'
},
{
name
:
'是否新品'
,
value
:
'recommendNew'
},
{
name
:
'是否优品'
,
value
:
'recommendGood'
}
]
// 商品推荐选项
const
checkboxGroup
=
ref
<
string
[]
>
([])
// 选中的推荐选项
/** 选择商品后赋值 */
const
onChangeGroup
=
()
=>
{
recommendOptions
.
forEach
(({
value
})
=>
{
formData
.
value
[
value
]
=
checkboxGroup
.
value
.
includes
(
value
)
})
}
/**
* 将传进来的值赋值给formData
...
...
@@ -117,18 +107,17 @@ const rules = reactive({
watch
(
()
=>
props
.
propFormData
,
(
data
)
=>
{
if
(
!
data
)
return
if
(
!
data
)
{
return
}
copyValueToTarget
(
formData
.
value
,
data
)
// TODO 如果先修改其他设置的值,再改变商品详情或是商品信息会重置其他设置页面中的相关值 下一个版本修复
checkboxGroup
.
value
=
[]
formData
.
value
.
recommendHot
?
checkboxGroup
.
value
.
push
(
'recommendHot'
)
:
''
formData
.
value
.
recommendBenefit
?
checkboxGroup
.
value
.
push
(
'recommendBenefit'
)
:
''
formData
.
value
.
recommendBest
?
checkboxGroup
.
value
.
push
(
'recommendBest'
)
:
''
formData
.
value
.
recommendNew
?
checkboxGroup
.
value
.
push
(
'recommendNew'
)
:
''
formData
.
value
.
recommendGood
?
checkboxGroup
.
value
.
push
(
'recommendGood'
)
:
''
recommendOptions
.
forEach
(({
value
})
=>
{
if
(
formData
.
value
[
value
]
&&
!
checkboxGroup
.
value
.
includes
(
value
))
{
checkboxGroup
.
value
.
push
(
value
)
}
})
},
{
deep
:
true
,
immediate
:
true
}
)
...
...
@@ -139,8 +128,8 @@ watch(
const
emit
=
defineEmits
([
'update:activeName'
])
const
validate
=
async
()
=>
{
// 校验表单
if
(
!
O
therSettingsFormRef
)
return
return
await
unref
(
O
therSettingsFormRef
).
validate
((
valid
)
=>
{
if
(
!
o
therSettingsFormRef
)
return
return
await
unref
(
o
therSettingsFormRef
).
validate
((
valid
)
=>
{
if
(
!
valid
)
{
message
.
warning
(
'商品其他设置未完善!!'
)
emit
(
'update:activeName'
,
'otherSettings'
)
...
...
src/views/mall/product/spu/components/ProductAttributes.vue
View file @
ec3b028d
...
...
@@ -2,23 +2,25 @@
<el-col
v-for=
"(item, index) in attributeList"
:key=
"index"
>
<div>
<el-text
class=
"mx-1"
>
属性名:
</el-text>
<el-text
class=
"mx-1"
>
{{
item
.
name
}}
</el-text>
<el-tag
class=
"mx-1"
closable
type=
"success"
@
close=
"handleCloseProperty(index)"
>
{{
item
.
name
}}
</el-tag>
</div>
<div>
<el-text
class=
"mx-1"
>
属性值:
</el-text>
<el-tag
v-for=
"(value, valueIndex) in item.values"
:key=
"value.id"
:disable-transitions=
"false"
class=
"mx-1"
closable
@
close=
"handleClose(index, valueIndex)"
@
close=
"handleClose
Value
(index, valueIndex)"
>
{{
value
.
name
}}
</el-tag>
<el-input
v-show=
"inputVisible(index)"
ref=
"InputRef"
:id=
"`input$
{index}`"
:ref="setInputRef"
v-model="inputValue"
class="!w-20"
size="small"
...
...
@@ -51,17 +53,25 @@ const inputVisible = computed(() => (index) => {
if
(
attributeIndex
.
value
===
null
)
return
false
if
(
attributeIndex
.
value
===
index
)
return
true
})
const
InputRef
=
ref
()
//标签输入框Ref
const
inputRef
=
ref
([])
//标签输入框Ref
/** 解决 ref 在 v-for 中的获取问题*/
const
setInputRef
=
(
el
)
=>
{
if
(
el
===
null
||
typeof
el
===
'undefined'
)
return
// 如果不存在id相同的元素才添加
if
(
!
inputRef
.
value
.
some
((
item
)
=>
item
.
input
?.
attributes
.
id
===
el
.
input
?.
attributes
.
id
))
{
inputRef
.
value
.
push
(
el
)
}
}
const
attributeList
=
ref
([])
// 商品属性列表
const
props
=
defineProps
({
attributeData
:
{
propertyList
:
{
type
:
Array
,
default
:
()
=>
{}
}
})
watch
(
()
=>
props
.
attributeData
,
()
=>
props
.
propertyList
,
(
data
)
=>
{
if
(
!
data
)
return
attributeList
.
value
=
data
...
...
@@ -72,18 +82,22 @@ watch(
}
)
/** 删除
标签 tagValue 标签
值*/
const
handleClose
=
(
index
,
valueIndex
)
=>
{
/** 删除
属性
值*/
const
handleClose
Value
=
(
index
,
valueIndex
)
=>
{
attributeList
.
value
[
index
].
values
?.
splice
(
valueIndex
,
1
)
}
/** 删除属性*/
const
handleCloseProperty
=
(
index
)
=>
{
attributeList
.
value
?.
splice
(
index
,
1
)
}
/** 显示输入框并获取焦点 */
const
showInput
=
async
(
index
)
=>
{
attributeIndex
.
value
=
index
// 因为组件在ref中所以需要用索引获取对应的Ref
InputRef
.
value
[
index
]
!
.
input
!
.
focus
()
inputRef
.
value
[
index
].
focus
()
}
const
emit
=
defineEmits
([
'success'
])
// 定义 success 事件,用于操作成功后的回调
/** 输入框失去焦点或点击回车时触发 */
const
handleInputConfirm
=
async
(
index
,
propertyId
)
=>
{
if
(
inputValue
.
value
)
{
...
...
@@ -92,6 +106,7 @@ const handleInputConfirm = async (index, propertyId) => {
const
id
=
await
PropertyApi
.
createPropertyValue
({
propertyId
,
name
:
inputValue
.
value
})
attributeList
.
value
[
index
].
values
.
push
({
id
,
name
:
inputValue
.
value
})
message
.
success
(
t
(
'common.createSuccess'
))
emit
(
'success'
,
attributeList
.
value
)
}
catch
{
message
.
error
(
'添加失败,请重试'
)
// TODO 缺少国际化
}
...
...
src/views/mall/product/spu/components/ProductAttributesAddForm.vue
View file @
ec3b028d
...
...
@@ -7,12 +7,9 @@
:rules=
"formRules"
label-width=
"80px"
>
<el-form-item
label=
"名称"
prop=
"name"
>
<el-form-item
label=
"
属性
名称"
prop=
"name"
>
<el-input
v-model=
"formData.name"
placeholder=
"请输入名称"
/>
</el-form-item>
<el-form-item
label=
"备注"
prop=
"remark"
>
<el-input
v-model=
"formData.remark"
placeholder=
"请输入内容"
type=
"textarea"
/>
</el-form-item>
</el-form>
<template
#
footer
>
<el-button
:disabled=
"formLoading"
type=
"primary"
@
click=
"submitForm"
>
确 定
</el-button>
...
...
@@ -30,14 +27,31 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const
dialogTitle
=
ref
(
'添加商品属性'
)
// 弹窗的标题
const
formLoading
=
ref
(
false
)
// 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const
formData
=
ref
({
name
:
''
,
remark
:
''
name
:
''
})
const
formRules
=
reactive
({
name
:
[{
required
:
true
,
message
:
'名称不能为空'
,
trigger
:
'blur'
}]
})
const
formRef
=
ref
()
// 表单 Ref
const
attributeList
=
ref
([])
// 商品属性列表
const
props
=
defineProps
({
propertyList
:
{
type
:
Array
,
default
:
()
=>
{}
}
})
watch
(
()
=>
props
.
propertyList
,
(
data
)
=>
{
if
(
!
data
)
return
attributeList
.
value
=
data
},
{
deep
:
true
,
immediate
:
true
}
)
/** 打开弹窗 */
const
open
=
async
()
=>
{
dialogVisible
.
value
=
true
...
...
@@ -46,7 +60,6 @@ const open = async () => {
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
/** 提交表单 */
const
emit
=
defineEmits
([
'success'
])
// 定义 success 事件,用于操作成功后的回调
const
submitForm
=
async
()
=>
{
// 校验表单
if
(
!
formRef
)
return
...
...
@@ -60,12 +73,12 @@ const submitForm = async () => {
const
res
=
await
PropertyApi
.
getPropertyListAndValue
({
name
:
data
.
name
})
if
(
res
.
length
===
0
)
{
const
propertyId
=
await
PropertyApi
.
createProperty
(
data
)
emit
(
'success'
,
{
id
:
propertyId
,
...
formData
.
value
,
values
:
[]
})
attributeList
.
value
.
push
(
{
id
:
propertyId
,
...
formData
.
value
,
values
:
[]
})
}
else
{
if
(
res
[
0
].
values
===
null
)
{
res
[
0
].
values
=
[]
}
emit
(
'success'
,
res
[
0
])
// 因为只用一个
attributeList
.
value
.
push
(
res
[
0
])
// 因为只用一个
}
message
.
success
(
t
(
'common.createSuccess'
))
dialogVisible
.
value
=
false
...
...
src/views/mall/product/spu/components/SkuList.vue
View file @
ec3b028d
<
template
>
<el-table
:data=
"isBatch ?
SkuData
: formData.skus"
:data=
"isBatch ?
skuList
: formData.skus"
border
class=
"tabNumWidth"
max-height=
"500"
...
...
@@ -12,175 +12,155 @@
</
template
>
</el-table-column>
<
template
v-if=
"formData.specType && !isBatch"
>
<!-- 根据商品属性动态添加
-->
<!-- 根据商品属性动态添加 -->
<el-table-column
v-for=
"(item, index) in tableHeader
List
"
v-for=
"(item, index) in tableHeader
s
"
:key=
"index"
:label=
"item.label"
align=
"center"
min-width=
"120"
>
<template
#
default=
"
{ row }">
<!-- TODO puhui999:展示成蓝色,有点区分度哈 -->
{{
row
.
properties
[
index
]?.
valueName
}}
</
template
>
</el-table-column>
</template>
<!-- TODO @puhui999: controls-position="right" 可以去掉哈,不然太长了,手动输入更方便 -->
<el-table-column
align=
"center"
label=
"商品条码"
min-width=
"168"
>
<
template
#
default=
"{ row }"
>
<el-input
v-model=
"row.barCode"
class=
"w-100%"
/>
</
template
>
</el-table-column>
<!-- TODO @puhui999:用户输入的时候,是按照元;分主要是我们自己用; -->
<el-table-column
align=
"center"
label=
"销售价(分)"
min-width=
"168"
>
<el-table-column
align=
"center"
label=
"销售价(元)"
min-width=
"168"
>
<
template
#
default=
"{ row }"
>
<el-input-number
v-model=
"row.price"
:min=
"0"
class=
"w-100%"
controls-position=
"right
"
/>
<el-input-number
v-model=
"row.price"
:min=
"0"
:precision=
"2"
:step=
"0.1"
class=
"w-100%
"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"市场价(
分
)"
min-width=
"168"
>
<el-table-column
align=
"center"
label=
"市场价(
元
)"
min-width=
"168"
>
<
template
#
default=
"{ row }"
>
<el-input-number
v-model=
"row.marketPrice"
:min=
"0"
:precision=
"2"
:step=
"0.1"
class=
"w-100%"
controls-position=
"right"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"成本价(
分
)"
min-width=
"168"
>
<el-table-column
align=
"center"
label=
"成本价(
元
)"
min-width=
"168"
>
<
template
#
default=
"{ row }"
>
<el-input-number
v-model=
"row.costPrice"
:min=
"0"
:precision=
"2"
:step=
"0.1"
class=
"w-100%"
controls-position=
"right"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"库存"
min-width=
"168"
>
<
template
#
default=
"{ row }"
>
<el-input-number
v-model=
"row.stock"
:min=
"0"
class=
"w-100%"
controls-position=
"right"
/>
<el-input-number
v-model=
"row.stock"
:min=
"0"
class=
"w-100%"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"重量(kg)"
min-width=
"168"
>
<
template
#
default=
"{ row }"
>
<el-input-number
v-model=
"row.weight"
:min=
"0"
class=
"w-100%"
controls-position=
"right
"
/>
<el-input-number
v-model=
"row.weight"
:min=
"0"
:precision=
"2"
:step=
"0.1"
class=
"w-100%
"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"体积(m^3)"
min-width=
"168"
>
<
template
#
default=
"{ row }"
>
<el-input-number
v-model=
"row.volume"
:min=
"0"
class=
"w-100%"
controls-position=
"right
"
/>
<el-input-number
v-model=
"row.volume"
:min=
"0"
:precision=
"2"
:step=
"0.1"
class=
"w-100%
"
/>
</
template
>
</el-table-column>
<
template
v-if=
"formData.subCommissionType"
>
<el-table-column
align=
"center"
label=
"一级返佣(
分
)"
min-width=
"168"
>
<el-table-column
align=
"center"
label=
"一级返佣(
元
)"
min-width=
"168"
>
<template
#
default=
"
{ row }">
<el-input-number
v-model=
"row.subCommissionFirstPrice"
:min=
"0"
:precision=
"2"
:step=
"0.1"
class=
"w-100%"
controls-position=
"right"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"二级返佣(
分
)"
min-width=
"168"
>
<el-table-column
align=
"center"
label=
"二级返佣(
元
)"
min-width=
"168"
>
<
template
#
default=
"{ row }"
>
<el-input-number
v-model=
"row.subCommissionSecondPrice"
:min=
"0"
:precision=
"2"
:step=
"0.1"
class=
"w-100%"
controls-position=
"right"
/>
</
template
>
</el-table-column>
</template>
<el-table-column
v-if=
"formData.specType"
align=
"center"
fixed=
"right"
label=
"操作"
width=
"80"
>
<
template
#
default
>
<
template
#
default
=
"{ row }"
>
<el-button
v-if=
"isBatch"
link
size=
"small"
type=
"primary"
@
click=
"batchAdd"
>
批量添加
</el-button>
<el-button
v-else
link
size=
"small"
type=
"primary"
>
删除
</el-button>
<el-button
v-else
link
size=
"small"
type=
"primary"
@
click=
"deleteSku(row)"
>
删除
</el-button>
</
template
>
</el-table-column>
</el-table>
</template>
<
script
lang=
"ts"
name=
"SkuList"
setup
>
import
{
UploadImg
}
from
'@/components/UploadFile'
import
{
PropType
}
from
'vue'
import
{
SpuType
}
from
'@/api/mall/product/management/type/spuType
'
import
{
copyValueToTarget
}
from
'@/utils
'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
SkuType
}
from
'@/api/mall/product/management/type/skuTyp
e'
import
{
copyValueToTarget
}
from
'@/utils/object
'
import
{
UploadImg
}
from
'@/components/UploadFil
e'
import
type
{
Property
,
SkuType
,
SpuType
}
from
'@/api/mall/product/spu
'
const
props
=
defineProps
({
propFormData
:
{
type
:
Object
as
PropType
<
SpuType
>
,
default
:
()
=>
{}
},
attribute
List
:
{
property
List
:
{
type
:
Array
,
default
:
()
=>
[]
},
isBatch
:
propTypes
.
bool
.
def
(
false
)
// 是否
批量操作
isBatch
:
propTypes
.
bool
.
def
(
false
)
// 是否
作为批量操作组件
})
const
formData
=
ref
<
SpuType
>
()
// 表单数据
// 批量添加时的零时数据 TODO @puhui999:小写开头哈;然后变量都尾注释
const
SkuData
=
ref
<
SkuType
[]
>
([
const
skuList
=
ref
<
SkuType
[]
>
([
{
/**
* 商品价格,单位:分
*/
price
:
0
,
/**
* 市场价,单位:分
*/
marketPrice
:
0
,
/**
* 成本价,单位:分
*/
costPrice
:
0
,
/**
* 商品条码
*/
barCode
:
''
,
/**
* 图片地址
*/
picUrl
:
''
,
/**
* 库存
*/
stock
:
0
,
/**
* 商品重量,单位:kg 千克
*/
weight
:
0
,
/**
* 商品体积,单位:m^3 平米
*/
volume
:
0
,
/**
* 一级分销的佣金,单位:分
*/
subCommissionFirstPrice
:
0
,
/**
* 二级分销的佣金,单位:分
*/
subCommissionSecondPrice
:
0
price
:
0
,
// 商品价格
marketPrice
:
0
,
// 市场价
costPrice
:
0
,
// 成本价
barCode
:
''
,
// 商品条码
picUrl
:
''
,
// 图片地址
stock
:
0
,
// 库存
weight
:
0
,
// 商品重量
volume
:
0
,
// 商品体积
subCommissionFirstPrice
:
0
,
// 一级分销的佣金
subCommissionSecondPrice
:
0
// 二级分销的佣金
}
])
])
// 批量添加时的临时数据
// TODO @puhui999:保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
/** 批量添加 */
const
batchAdd
=
()
=>
{
formData
.
value
.
skus
.
forEach
((
item
)
=>
{
copyValueToTarget
(
item
,
SkuData
.
value
[
0
])
copyValueToTarget
(
item
,
skuList
.
value
[
0
])
})
}
const
tableHeaderList
=
ref
<
{
prop
:
string
;
label
:
string
}[]
>
([])
/** 删除 sku */
const
deleteSku
=
(
row
)
=>
{
const
index
=
formData
.
value
.
skus
.
findIndex
(
// 直接把列表转成字符串比较
(
sku
)
=>
JSON
.
stringify
(
sku
.
properties
)
===
JSON
.
stringify
(
row
.
properties
)
)
formData
.
value
.
skus
.
splice
(
index
,
1
)
}
const
tableHeaders
=
ref
<
{
prop
:
string
;
label
:
string
}[]
>
([])
// 多属性表头
/**
* 将传进来的值赋值给
SkuData
* 将传进来的值赋值给
skuList
*/
watch
(
()
=>
props
.
propFormData
,
...
...
@@ -194,35 +174,27 @@ watch(
}
)
// TODO @芋艿:看看 chatgpt 可以进一步下面几个方法的实现不
/** 生成表数据 */
const
generateTableData
=
(
data
:
any
[])
=>
{
const
generateTableData
=
(
propertyList
:
any
[])
=>
{
// 构建数据结构
const
propertiesItemList
=
[]
for
(
const
item
of
data
)
{
const
objList
=
[]
for
(
const
v
of
item
.
values
)
{
const
obj
=
{
propertyId
:
0
,
valueId
:
0
,
valueName
:
''
}
obj
.
propertyId
=
item
.
id
obj
.
valueId
=
v
.
id
obj
.
valueName
=
v
.
name
objList
.
push
(
obj
)
}
propertiesItemList
.
push
(
objList
)
const
propertyValues
=
propertyList
.
map
((
item
)
=>
item
.
values
.
map
((
v
)
=>
({
propertyId
:
item
.
id
,
propertyName
:
item
.
name
,
valueId
:
v
.
id
,
valueName
:
v
.
name
}))
)
// TODO @puhui:是不是 buildSkuList,这样容易理解一点哈。item 改成 sku
const
buildList
=
build
(
propertyValues
)
// 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表
if
(
!
validateData
(
propertyList
))
{
// 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表
formData
.
value
!
.
skus
=
[]
}
const
buildList
=
build
(
propertiesItemList
)
// 如果构建后的组合数跟sku数量一样的话则不用处理,添加新属性没有属性值也不做处理 (解决编辑表单时或查看详情时数据回显问题)
if
(
buildList
.
length
===
formData
.
value
.
skus
.
length
||
data
.
some
((
item
)
=>
item
.
values
.
length
===
0
)
)
{
return
}
// 重置表数据
formData
.
value
!
.
skus
=
[]
buildList
.
forEach
((
item
)
=>
{
for
(
const
item
of
buildList
)
{
const
row
=
{
properties
:
[],
properties
:
Array
.
isArray
(
item
)
?
item
:
[
item
],
// 如果只有一个属性的话返回的是一个 property 对象
price
:
0
,
marketPrice
:
0
,
costPrice
:
0
,
...
...
@@ -234,32 +206,51 @@ const generateTableData = (data: any[]) => {
subCommissionFirstPrice
:
0
,
subCommissionSecondPrice
:
0
}
// 判断是否是单一属性的情况
if
(
Array
.
isArray
(
item
))
{
row
.
properties
=
item
}
else
{
row
.
properties
.
push
(
item
)
// 如果存在属性相同的 sku 则不做处理
const
index
=
formData
.
value
!
.
skus
.
findIndex
(
(
sku
)
=>
JSON
.
stringify
(
sku
.
properties
)
===
JSON
.
stringify
(
row
.
properties
)
)
if
(
index
!==
-
1
)
{
continue
}
formData
.
value
.
skus
.
push
(
row
)
})
}
}
/**
* 生成 skus 前置校验
*/
const
validateData
=
(
propertyList
:
any
[])
=>
{
const
skuPropertyIds
=
[]
formData
.
value
.
skus
.
forEach
((
sku
)
=>
sku
.
properties
?.
map
((
property
)
=>
property
.
propertyId
)
.
forEach
((
propertyId
)
=>
{
if
(
skuPropertyIds
.
indexOf
(
propertyId
)
===
-
1
)
{
skuPropertyIds
.
push
(
propertyId
)
}
})
)
const
propertyIds
=
propertyList
.
map
((
item
)
=>
item
.
id
)
return
skuPropertyIds
.
length
===
propertyIds
.
length
}
/** 构建所有排列组合 */
const
build
=
(
list
:
any
[])
=>
{
if
(
l
ist
.
length
===
0
)
{
const
build
=
(
propertyValuesList
:
Property
[]
[])
=>
{
if
(
propertyValuesL
ist
.
length
===
0
)
{
return
[]
}
else
if
(
l
ist
.
length
===
1
)
{
return
l
ist
[
0
]
}
else
if
(
propertyValuesL
ist
.
length
===
1
)
{
return
propertyValuesL
ist
[
0
]
}
else
{
const
result
=
[]
const
rest
=
build
(
l
ist
.
slice
(
1
))
for
(
let
i
=
0
;
i
<
l
ist
[
0
].
length
;
i
++
)
{
const
result
:
Property
[][]
=
[]
const
rest
=
build
(
propertyValuesL
ist
.
slice
(
1
))
for
(
let
i
=
0
;
i
<
propertyValuesL
ist
[
0
].
length
;
i
++
)
{
for
(
let
j
=
0
;
j
<
rest
.
length
;
j
++
)
{
// 第一次不是数组结构,后面的都是数组结构
if
(
Array
.
isArray
(
rest
[
j
]))
{
result
.
push
([
l
ist
[
0
][
i
],
...
rest
[
j
]])
result
.
push
([
propertyValuesL
ist
[
0
][
i
],
...
rest
[
j
]])
}
else
{
result
.
push
([
l
ist
[
0
][
i
],
rest
[
j
]])
result
.
push
([
propertyValuesL
ist
[
0
][
i
],
rest
[
j
]])
}
}
}
...
...
@@ -267,15 +258,17 @@ const build = (list: any[]) => {
}
}
/** 监听属性列表生成相关参数和表头 */
/** 监听属性列表
,
生成相关参数和表头 */
watch
(
()
=>
props
.
attribute
List
,
(
data
)
=>
{
()
=>
props
.
property
List
,
(
propertyList
)
=>
{
// 如果不是多规格则结束
if
(
!
formData
.
value
.
specType
)
return
// 如果当前组件作为批量添加数据使用则重置表数据
if
(
!
formData
.
value
.
specType
)
{
return
}
// 如果当前组件作为批量添加数据使用,则重置表数据
if
(
props
.
isBatch
)
{
SkuData
.
value
=
[
skuList
.
value
=
[
{
price
:
0
,
marketPrice
:
0
,
...
...
@@ -290,20 +283,35 @@ watch(
}
]
}
// 判断代理对象是否为空
if
(
JSON
.
stringify
(
data
)
===
'[]'
)
return
if
(
JSON
.
stringify
(
propertyList
)
===
'[]'
)
{
return
}
// 重置表头
tableHeader
List
.
value
=
[]
tableHeader
s
.
value
=
[]
// 生成表头
data
.
forEach
((
item
,
index
)
=>
{
propertyList
.
forEach
((
item
,
index
)
=>
{
// name加属性项index区分属性值
tableHeader
List
.
value
.
push
({
prop
:
`name
${
index
}
`
,
label
:
item
.
name
})
tableHeader
s
.
value
.
push
({
prop
:
`name
${
index
}
`
,
label
:
item
.
name
})
})
generateTableData
(
data
)
// 如果回显的 sku 属性和添加的属性一致则不处理
if
(
validateData
(
propertyList
))
{
return
}
// 添加新属性没有属性值也不做处理
if
(
propertyList
.
some
((
item
)
=>
item
.
values
.
length
===
0
))
{
return
}
// 生成 table 数据,即 sku 列表
generateTableData
(
propertyList
)
},
{
deep
:
true
,
immediate
:
true
}
)
// 暴露出生成 sku 方法,给添加属性成功时调用
defineExpose
({
generateTableData
})
</
script
>
src/views/mall/product/spu/index.vue
View file @
ec3b028d
...
...
@@ -8,7 +8,7 @@
class=
"-mb-15px"
label-width=
"68px"
>
<!-- TODO @puhui999:
https://admin.java.crmeb.net/store/index,参考,使用分类 + 标题搜索
-->
<!-- TODO @puhui999:
品牌应该是数据下拉哈
-->
<el-form-item
label=
"品牌名称"
prop=
"name"
>
<el-input
v-model=
"queryParams.name"
...
...
@@ -18,15 +18,18 @@
@
keyup
.
enter=
"handleQuery"
/>
</el-form-item>
<el-form-item
label=
"状态"
prop=
"status"
>
<el-select
v-model=
"queryParams.status"
class=
"!w-240px"
clearable
placeholder=
"请选择状态"
>
<el-option
v-for=
"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key=
"dict.value"
:label=
"dict.label"
:value=
"dict.value"
/>
</el-select>
<!-- TODO 分类只能选择二级分类目前还没做,还是先以联调通顺为主 -->
<!-- TODO puhui999:我们要不改成支持选择一级。如果选择一级,后端要递归查询下子分类,然后去 in? -->
<el-form-item
label=
"商品分类"
prop=
"categoryId"
>
<el-tree-select
v-model=
"queryParams.categoryId"
:data=
"categoryList"
:props=
"defaultProps"
check-strictly
class=
"w-1/1"
node-key=
"id"
placeholder=
"请选择商品分类"
/>
</el-form-item>
<el-form-item
label=
"创建时间"
prop=
"createTime"
>
<el-date-picker
...
...
@@ -48,18 +51,27 @@
<Icon
class=
"mr-5px"
icon=
"ep:refresh"
/>
重置
</el-button>
<el-button
v-hasPermi=
"['product:
brand
:create']"
plain
type=
"primary"
@
click=
"openForm"
>
<el-button
v-hasPermi=
"['product:
spu
:create']"
plain
type=
"primary"
@
click=
"openForm"
>
<Icon
class=
"mr-5px"
icon=
"ep:plus"
/>
新增
</el-button>
<!-- TODO @puhui999:增加一个【导出】操作 -->
<el-button
v-hasPermi=
"['product:spu:export']"
:loading=
"exportLoading"
plain
type=
"success"
@
click=
"handleExport"
>
<Icon
class=
"mr-5px"
icon=
"ep:download"
/>
导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-tabs
v-model=
"queryParams.tabType"
@
tab-click=
"handleClick"
>
<el-tabs
v-model=
"queryParams.tabType"
@
tab-click=
"handle
Tab
Click"
>
<el-tab-pane
v-for=
"item in tabsData"
:key=
"item.type"
...
...
@@ -68,35 +80,39 @@
/>
</el-tabs>
<el-table
v-loading=
"loading"
:data=
"list"
>
<!-- TODO puhui999: ID 编号的展示 -->
<!-- TODO 暂时不做折叠数据 -->
<!--
<el-table-column
type=
"expand"
>
-->
<!--
<template
#
default=
"
{ row }">-->
<!--
<el-form
inline
label-position=
"left"
>
-->
<!--
<el-form-item
label=
"市场价:"
>
-->
<!--
<span>
{{
row
.
marketPrice
}}
</span>
-->
<!--
</el-form-item>
-->
<!--
<el-form-item
label=
"成本价:"
>
-->
<!--
<span>
{{
row
.
costPrice
}}
</span>
-->
<!--
</el-form-item>
-->
<!--
<el-form-item
label=
"虚拟销量:"
>
-->
<!--
<span>
{{
row
.
virtualSalesCount
}}
</span>
-->
<!--
</el-form-item>
-->
<!--
</el-form>
-->
<!--
</
template
>
-->
<!-- </el-table-column>-->
<!-- TODO puhui:这几个属性哈,一行三个
商品分类:服装鞋包/箱包
商品市场价格:100.00
成本价:0.00
收藏:5
虚拟销量:999 -->
<el-table-column
type=
"expand"
width=
"30"
>
<template
#
default=
"
{ row }">
<el-form
class=
"demo-table-expand"
inline
label-position=
"left"
>
<el-form-item
label=
"市场价:"
>
<span>
{{
formatToFraction
(
row
.
marketPrice
)
}}
</span>
</el-form-item>
<el-form-item
label=
"成本价:"
>
<span>
{{
formatToFraction
(
row
.
costPrice
)
}}
</span>
</el-form-item>
<el-form-item
label=
"虚拟销量:"
>
<span>
{{
row
.
virtualSalesCount
}}
</span>
</el-form-item>
</el-form>
</
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"
style=
"width: 36px; height: 36px"
@
click=
"imagePreview(row.picUrl)"
/>
<el-image
:src=
"row.picUrl"
@
click=
"imagePreview(row.picUrl)"
class=
"w-30px h-30px"
/>
</
template
>
</el-table-column>
<el-table-column
:show-overflow-tooltip=
"true"
label=
"商品名称"
min-width=
"300"
prop=
"name"
/>
<!-- TODO 价格 / 100.0 -->
<el-table-column
align=
"center"
label=
"商品售价"
min-width=
"90"
prop=
"price"
/>
<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"
/>
...
...
@@ -107,24 +123,30 @@
prop=
"createTime"
width=
"180"
/>
<el-table-column
fixed=
"right
"
label=
"状态"
min-width=
"80"
>
<el-table-column
align=
"center
"
label=
"状态"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
<!-- TODO @puhui:是不是不用 Number(row.status) 去比较哈,直接 row.status
<
0
--
>
<el-switch
v-model=
"row.status"
:active-value=
"1"
:disabled=
"Number(row.status)
<
0
"
:inactive-value=
"0"
active-text=
"上架"
inactive-text=
"下架"
inline-prompt
@
change=
"changeStatus(row)"
/>
<template
v-if=
"row.status >= 0"
>
<el-switch
v-model=
"row.status"
:active-value=
"1"
:inactive-value=
"0"
active-text=
"上架"
inactive-text=
"下架"
inline-prompt
@
change=
"changeStatus(row)"
/>
</
template
>
<
template
v-else
>
<el-tag
type=
"info"
>
回收站
</el-tag>
</
template
>
</template>
</el-table-column>
<el-table-column
align=
"center"
fixed=
"right"
label=
"操作"
min-width=
"
15
0"
>
<el-table-column
align=
"center"
fixed=
"right"
label=
"操作"
min-width=
"
20
0"
>
<
template
#
default=
"{ row }"
>
<!-- TODO @puhui999:【详情】,可以后面点做哈 -->
<el-button
v-hasPermi=
"['product:spu:update']"
link
type=
"primary"
@
click=
"openDetail"
>
详情
</el-button>
<template
v-if=
"queryParams.tabType === 4"
>
<el-button
v-hasPermi=
"['product:spu:delete']"
...
...
@@ -138,13 +160,15 @@
v-hasPermi=
"['product:spu:update']"
link
type=
"primary"
@
click=
"
addToTrash
(row, ProductSpuStatusEnum.DISABLE.status)"
@
click=
"
changeStatus
(row, ProductSpuStatusEnum.DISABLE.status)"
>
恢复到仓库
</el-button>
</
template
>
<
template
v-else
>
<!-- 只有不是上架和回收站的商品可以编辑 -->
<el-button
v-if=
"queryParams.tabType !== 0"
v-hasPermi=
"['product:spu:update']"
link
type=
"primary"
...
...
@@ -156,7 +180,7 @@
v-hasPermi=
"['product:spu:update']"
link
type=
"primary"
@
click=
"
addToTrash
(row, ProductSpuStatusEnum.RECYCLE.status)"
@
click=
"
changeStatus
(row, ProductSpuStatusEnum.RECYCLE.status)"
>
加入回收站
</el-button>
...
...
@@ -172,26 +196,25 @@
@
pagination=
"getList"
/>
</ContentWrap>
<!-- https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/image-viewer.html,可以用这个么? -->
<!-- 必须在表格外面展示。不然单元格会遮挡图层 -->
<el-image-viewer
v-if=
"imgViewVisible"
:url-list=
"imageViewerList"
@
close=
"imgViewVisible = false"
/>
</template>
<
script
lang=
"ts"
name=
"ProductList"
setup
>
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
<
script
lang=
"ts"
name=
"ProductSpu"
setup
>
import
{
TabsPaneContext
}
from
'element-plus'
import
{
cloneDeep
}
from
'lodash-es'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
dateFormatter
}
from
'@/utils/formatTime'
// TODO @puhui999:managementApi=》ProductSpuApi
import
*
as
managementApi
from
'@/api/mall/product/management/spu'
import
{
defaultProps
,
handleTree
}
from
'@/utils/tree'
import
{
ProductSpuStatusEnum
}
from
'@/utils/constants'
import
{
TabsPaneContext
}
from
'element-plus'
import
{
formatToFraction
}
from
'@/utils'
import
download
from
'@/utils/download'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
*
as
ProductCategoryApi
from
'@/api/mall/product/category'
const
message
=
useMessage
()
// 消息弹窗
const
{
t
}
=
useI18n
()
// 国际化
const
{
currentRoute
,
push
}
=
useRouter
()
// 路由跳转
const
loading
=
ref
(
false
)
// 列表的加载中
const
exportLoading
=
ref
(
false
)
// 导出的加载中
const
total
=
ref
(
0
)
// 列表的总页数
const
list
=
ref
<
any
[]
>
([])
// 列表的数据
// tabs 数据
...
...
@@ -225,26 +248,19 @@ const tabsData = ref([
/** 获得每个 Tab 的数量 */
const
getTabsCount
=
async
()
=>
{
// TODO @puhui999:这里是不是可以不要 try catch 哈
try
{
const
res
=
await
managementApi
.
getTabsCount
()
for
(
let
objName
in
res
)
{
tabsData
.
value
[
Number
(
objName
)].
count
=
res
[
objName
]
}
}
catch
{}
const
res
=
await
ProductSpuApi
.
getTabsCount
()
for
(
let
objName
in
res
)
{
tabsData
.
value
[
Number
(
objName
)].
count
=
res
[
objName
]
}
}
const
imgViewVisible
=
ref
(
false
)
// 商品图预览
const
imageViewerList
=
ref
<
string
[]
>
([])
// 商品图预览列表
const
queryParams
=
ref
({
pageNo
:
1
,
pageSize
:
10
,
tabType
:
0
})
const
queryFormRef
=
ref
()
// 搜索的表单
})
// 查询参数
const
queryFormRef
=
ref
()
// 搜索的表单
Ref
// TODO @puhui999:可以改成 handleTabClick:更准确一点;
const
handleClick
=
(
tab
:
TabsPaneContext
)
=>
{
const
handleTabClick
=
(
tab
:
TabsPaneContext
)
=>
{
queryParams
.
value
.
tabType
=
tab
.
paneName
getList
()
}
...
...
@@ -253,7 +269,7 @@ const handleClick = (tab: TabsPaneContext) => {
const
getList
=
async
()
=>
{
loading
.
value
=
true
try
{
const
data
=
await
managementApi
.
getSpuList
(
queryParams
.
value
)
const
data
=
await
ProductSpuApi
.
getSpuPage
(
queryParams
.
value
)
list
.
value
=
data
.
list
total
.
value
=
data
.
total
}
finally
{
...
...
@@ -261,7 +277,6 @@ const getList = async () => {
}
}
// TODO @puhui999:是不是 changeStatus 和 addToTrash 调用一个统一的方法,去更新状态。这样逻辑会更干净一些。
/**
* 更改 SPU 状态
*
...
...
@@ -269,10 +284,11 @@ const getList = async () => {
* @param status 更改前的值
*/
const
changeStatus
=
async
(
row
,
status
?:
number
)
=>
{
// TODO 测试过程中似乎有点问题,下一版修复
const
deepCopyValue
=
cloneDeep
(
unref
(
row
))
if
(
typeof
status
!==
'undefined'
)
deepCopyValue
.
status
=
status
try
{
let
text
=
''
switch
(
row
.
status
)
{
switch
(
deepCopyValue
.
status
)
{
case
ProductSpuStatusEnum
.
DISABLE
.
status
:
text
=
ProductSpuStatusEnum
.
DISABLE
.
name
break
...
...
@@ -284,20 +300,19 @@ const changeStatus = async (row, status?: number) => {
break
}
await
message
.
confirm
(
row
.
status
===
-
1
?
`确认要将[
${
row
.
name
}
]
${
text
}
吗?`
:
`确认要
${
text
}
[
${
row
.
name
}
]吗?`
deepCopyValue
.
status
===
-
1
?
`确认要将[
${
row
.
name
}
]
${
text
}
吗?`
:
row
.
status
===
-
1
// 再判断一次原对象是否等于-1,例: 把回收站中的商品恢复到仓库中,事件触发时原对象status为-1 深拷贝对象status被赋值为0
?
`确认要将[
${
row
.
name
}
]恢复到仓库吗?`
:
`确认要
${
text
}
[
${
row
.
name
}
]吗?`
)
await
managementApi
.
updateStatus
({
id
:
row
.
id
,
status
:
row
.
status
})
await
ProductSpuApi
.
updateStatus
({
id
:
deepCopyValue
.
id
,
status
:
deepCopyValue
.
status
})
message
.
success
(
'更新状态成功'
)
// 刷新 tabs 数据
await
getTabsCount
()
// 刷新列表
await
getList
()
}
catch
{
// 取消加入回收站时回显数据
if
(
typeof
status
!==
'undefined'
)
{
row
.
status
=
status
return
}
// 取消更改状态时回显数据
row
.
status
=
row
.
status
===
ProductSpuStatusEnum
.
DISABLE
.
status
...
...
@@ -306,26 +321,13 @@ const changeStatus = async (row, status?: number) => {
}
}
/**
* 加入回收站
*
* @param row
* @param status
*/
const
addToTrash
=
(
row
,
status
)
=>
{
// 复制一份原值
const
num
=
Number
(
`
${
row
.
status
}
`
)
row
.
status
=
status
changeStatus
(
row
,
num
)
}
/** 删除按钮操作 */
const
handleDelete
=
async
(
id
:
number
)
=>
{
try
{
// 删除的二次确认
await
message
.
delConfirm
()
// 发起删除
await
management
Api
.
deleteSpu
(
id
)
await
ProductSpu
Api
.
deleteSpu
(
id
)
message
.
success
(
t
(
'common.delSuccess'
))
// 刷新tabs数据
await
getTabsCount
()
...
...
@@ -334,13 +336,11 @@ const handleDelete = async (id: number) => {
}
catch
{}
}
/**
* 商品图预览
* @param imgUrl
*/
/** 商品图预览 */
const
imagePreview
=
(
imgUrl
:
string
)
=>
{
imageViewerList
.
value
=
[
imgUrl
]
imgViewVisible
.
value
=
true
createImageViewer
({
urlList
:
[
imgUrl
]
})
}
/** 搜索按钮操作 */
...
...
@@ -362,27 +362,61 @@ const resetQuery = () => {
const
openForm
=
(
id
?:
number
)
=>
{
// 修改
if
(
typeof
id
===
'number'
)
{
push
(
'/product/product
ManagementAdd?id=
'
+
id
)
push
(
'/product/product
SpuEdit/
'
+
id
)
return
}
// 新增
push
(
'/product/productManagementAdd'
)
push
(
'/product/productSpuAdd'
)
}
/**
* 查看商品详情
*/
const
openDetail
=
()
=>
{
message
.
alert
(
'查看详情未完善!!!'
)
}
/** 导出按钮操作 */
const
handleExport
=
async
()
=>
{
try
{
// 导出的二次确认
await
message
.
exportConfirm
()
// 发起导出
exportLoading
.
value
=
true
const
data
=
await
ProductSpuApi
.
exportSpu
(
queryParams
)
download
.
excel
(
data
,
'商品列表.xls'
)
}
catch
{
}
finally
{
exportLoading
.
value
=
false
}
}
// 监听路由变化更新列表 TODO @puhui999:这个是必须加的么?
// 监听路由变化更新列表 TODO @puhui999:这个是必须加的么?
fix: 因为编辑表单是以路由的方式打开,保存表单后列表不会刷新
watch
(
()
=>
currentRoute
.
value
,
()
=>
{
getList
()
},
{
immediate
:
true
}
)
const
categoryList
=
ref
()
// 分类树
/** 初始化 **/
onMounted
(()
=>
{
getTabsCount
()
getList
()
onMounted
(
async
()
=>
{
await
getTabsCount
()
await
getList
()
// 获得分类树
const
data
=
await
ProductCategoryApi
.
getCategoryList
({})
categoryList
.
value
=
handleTree
(
data
,
'id'
,
'parentId'
)
})
</
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/trade/delivery/express/ExpressForm.vue
0 → 100644
View file @
ec3b028d
<
template
>
<Dialog
:title=
"dialogTitle"
v-model=
"dialogVisible"
>
<el-form
ref=
"formRef"
:model=
"formData"
:rules=
"formRules"
label-width=
"120px"
v-loading=
"formLoading"
>
<el-form-item
label=
"快递公司编码"
prop=
"code"
>
<el-input
v-model=
"formData.code"
placeholder=
"请输入快递编码"
/>
</el-form-item>
<el-form-item
label=
"快递公司名称"
prop=
"name"
>
<el-input
v-model=
"formData.name"
placeholder=
"请输入快递名称"
/>
</el-form-item>
<el-form-item
label=
"快递公司 logo"
prop=
"logo"
>
<UploadImg
v-model=
"formData.logo"
:limit=
"1"
:is-show-tip=
"false"
/>
<div
style=
"font-size: 10px"
class=
"pl-10px"
>
推荐 180x180 图片分辨率
</div>
</el-form-item>
<el-form-item
label=
"分类排序"
prop=
"sort"
>
<el-input-number
v-model=
"formData.sort"
controls-position=
"right"
:min=
"0"
/>
</el-form-item>
<el-form-item
label=
"开启状态"
prop=
"status"
>
<el-radio-group
v-model=
"formData.status"
>
<el-radio
v-for=
"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key=
"dict.value"
:label=
"dict.value"
>
{{
dict
.
label
}}
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template
#
footer
>
<el-button
@
click=
"submitForm"
type=
"primary"
:disabled=
"formLoading"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</Dialog>
</template>
<
script
setup
lang=
"ts"
name=
"ExpressForm"
>
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
CommonStatusEnum
}
from
'@/utils/constants'
import
*
as
DeliveryExpressApi
from
'@/api/mall/trade/delivery/express'
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
formData
=
ref
({
id
:
undefined
,
code
:
''
,
name
:
''
,
logo
:
''
,
sort
:
0
,
status
:
CommonStatusEnum
.
ENABLE
})
const
formRules
=
reactive
({
code
:
[{
required
:
true
,
message
:
'快递编码不能为空'
,
trigger
:
'blur'
}],
name
:
[{
required
:
true
,
message
:
'分类名称不能为空'
,
trigger
:
'blur'
}],
logo
:
[{
required
:
true
,
message
:
'分类图片不能为空'
,
trigger
:
'blur'
}],
sort
:
[{
required
:
true
,
message
:
'分类排序不能为空'
,
trigger
:
'blur'
}],
status
:
[{
required
:
true
,
message
:
'开启状态不能为空'
,
trigger
:
'blur'
}]
})
const
formRef
=
ref
()
// 表单 Ref
/** 打开弹窗 */
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
dialogVisible
.
value
=
true
dialogTitle
.
value
=
t
(
'action.'
+
type
)
formType
.
value
=
type
resetForm
()
// 修改时,设置数据
if
(
id
)
{
formLoading
.
value
=
true
try
{
formData
.
value
=
await
DeliveryExpressApi
.
getDeliveryExpress
(
id
)
}
finally
{
formLoading
.
value
=
false
}
}
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
/** 提交表单 */
const
emit
=
defineEmits
([
'success'
])
// 定义 success 事件,用于操作成功后的回调
const
submitForm
=
async
()
=>
{
// 校验表单
if
(
!
formRef
)
return
const
valid
=
await
formRef
.
value
.
validate
()
if
(
!
valid
)
return
// 提交请求
formLoading
.
value
=
true
try
{
const
data
=
formData
.
value
as
DeliveryExpressApi
.
DeliveryExpressVO
if
(
formType
.
value
===
'create'
)
{
await
DeliveryExpressApi
.
createDeliveryExpress
(
data
)
message
.
success
(
t
(
'common.createSuccess'
))
}
else
{
await
DeliveryExpressApi
.
updateDeliveryExpress
(
data
)
message
.
success
(
t
(
'common.updateSuccess'
))
}
dialogVisible
.
value
=
false
// 发送操作成功的事件
emit
(
'success'
)
}
finally
{
formLoading
.
value
=
false
}
}
/** 重置表单 */
const
resetForm
=
()
=>
{
formData
.
value
=
{
id
:
undefined
,
name
:
''
,
picUrl
:
''
,
bigPicUrl
:
''
,
status
:
CommonStatusEnum
.
ENABLE
}
formRef
.
value
?.
resetFields
()
}
</
script
>
src/views/mall/trade/delivery/express/index.vue
0 → 100644
View file @
ec3b028d
<
template
>
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class=
"-mb-15px"
:model=
"queryParams"
ref=
"queryFormRef"
:inline=
"true"
label-width=
"100px"
>
<el-form-item
label=
"快递公司编号"
prop=
"code"
>
<el-input
v-model=
"queryParams.code"
placeholder=
"请输快递公司编号"
clearable
@
keyup
.
enter=
"handleQuery"
class=
"!w-240px"
/>
</el-form-item>
<el-form-item
label=
"快递公司名称"
prop=
"name"
>
<el-input
v-model=
"queryParams.name"
placeholder=
"请输快递公司名称"
clearable
@
keyup
.
enter=
"handleQuery"
class=
"!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button
@
click=
"handleQuery"
><Icon
icon=
"ep:search"
class=
"mr-5px"
/>
搜索
</el-button>
<el-button
@
click=
"resetQuery"
><Icon
icon=
"ep:refresh"
class=
"mr-5px"
/>
重置
</el-button>
<el-button
type=
"primary"
plain
@
click=
"openForm('create')"
v-hasPermi=
"['trade:delivery:express:create']"
>
<Icon
icon=
"ep:plus"
class=
"mr-5px"
/>
新增
</el-button>
<el-button
type=
"success"
plain
@
click=
"handleExport"
:loading=
"exportLoading"
v-hasPermi=
"['trade:delivery:express:export']"
>
<Icon
icon=
"ep:download"
class=
"mr-5px"
/>
导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table
v-loading=
"loading"
:data=
"list"
>
<el-table-column
label=
"快递公司编号"
prop=
"code"
/>
<el-table-column
label=
"快递公司名称"
prop=
"name"
/>
<el-table-column
label=
"快递公司 logo "
prop=
"logo"
>
<template
#
default=
"scope"
>
<img
v-if=
"scope.row.logo"
:src=
"scope.row.logo"
alt=
"快递公司logo"
class=
"h-100px"
/>
</
template
>
</el-table-column>
<el-table-column
label=
"排序"
align=
"center"
prop=
"sort"
/>
<el-table-column
label=
"开启状态"
align=
"center"
prop=
"status"
>
<
template
#
default=
"scope"
>
<dict-tag
:type=
"DICT_TYPE.COMMON_STATUS"
:value=
"scope.row.status"
/>
</
template
>
</el-table-column>
<el-table-column
label=
"创建时间"
align=
"center"
prop=
"createTime"
width=
"180"
:formatter=
"dateFormatter"
/>
<el-table-column
label=
"操作"
align=
"center"
>
<
template
#
default=
"scope"
>
<el-button
link
type=
"primary"
@
click=
"openForm('update', scope.row.id)"
v-hasPermi=
"['trade:delivery:express:update']"
>
编辑
</el-button>
<el-button
link
type=
"danger"
@
click=
"handleDelete(scope.row.id)"
v-hasPermi=
"['trade:delivery:express:delete']"
>
删除
</el-button>
</
template
>
</el-table-column>
</el-table>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<ExpressForm
ref=
"formRef"
@
success=
"getList"
/>
</template>
<
script
setup
lang=
"ts"
name=
"Express"
>
import
{
DICT_TYPE
}
from
'@/utils/dict'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
download
from
'@/utils/download'
import
*
as
DeliveryExpressApi
from
'@/api/mall/trade/delivery/express'
import
ExpressForm
from
'./ExpressForm.vue'
const
message
=
useMessage
()
// 消息弹窗
const
{
t
}
=
useI18n
()
// 国际化
const
total
=
ref
(
0
)
// 列表的总页数
const
loading
=
ref
(
true
)
// 列表的加载中
const
list
=
ref
<
any
[]
>
([])
// 列表的数据
const
queryParams
=
reactive
({
pageNo
:
1
,
pageSize
:
10
,
code
:
''
,
name
:
''
})
const
queryFormRef
=
ref
()
// 搜索的表单
const
exportLoading
=
ref
(
false
)
// 导出的加载中
/** 查询列表 */
const
getList
=
async
()
=>
{
loading
.
value
=
true
try
{
const
data
=
await
DeliveryExpressApi
.
getDeliveryExpressPage
(
queryParams
)
list
.
value
=
data
.
list
total
.
value
=
data
.
total
}
finally
{
loading
.
value
=
false
}
}
/** 搜索按钮操作 */
const
handleQuery
=
()
=>
{
queryParams
.
pageNo
=
1
getList
()
}
/** 重置按钮操作 */
const
resetQuery
=
()
=>
{
queryFormRef
.
value
.
resetFields
()
handleQuery
()
}
/** 添加/修改操作 */
const
formRef
=
ref
()
const
openForm
=
(
type
:
string
,
id
?:
number
)
=>
{
formRef
.
value
.
open
(
type
,
id
)
}
/** 删除按钮操作 */
const
handleDelete
=
async
(
id
:
number
)
=>
{
try
{
// 删除的二次确认
await
message
.
delConfirm
()
// 发起删除
await
DeliveryExpressApi
.
deleteDeliveryExpress
(
id
)
message
.
success
(
t
(
'common.delSuccess'
))
// 刷新列表
await
getList
()
}
catch
{}
}
/** 导出按钮操作 */
const
handleExport
=
async
()
=>
{
try
{
// 导出的二次确认
await
message
.
exportConfirm
()
// 发起导出
exportLoading
.
value
=
true
const
data
=
await
DeliveryExpressApi
.
exportDeliveryExpressApi
(
queryParams
)
download
.
excel
(
data
,
'快递公司.xls'
)
}
catch
{
}
finally
{
exportLoading
.
value
=
false
}
}
/** 初始化 **/
onMounted
(()
=>
{
getList
()
})
</
script
>
src/views/mall/trade/delivery/expressTemplate/ExpressTemplateForm.vue
0 → 100644
View file @
ec3b028d
<
template
>
<Dialog
:title=
"dialogTitle"
v-model=
"dialogVisible"
width=
"80%"
>
<el-form
ref=
"formRef"
:model=
"formData"
:rules=
"formRules"
label-width=
"80px"
v-loading=
"formLoading"
>
<el-form-item
label=
"模板名称"
prop=
"name"
>
<el-input
v-model=
"formData.name"
placeholder=
"请输入模板名称"
/>
</el-form-item>
<el-form-item
label=
"计费方式"
prop=
"chargeMode"
>
<el-radio-group
v-model=
"formData.chargeMode"
@
change=
"changeChargeMode"
>
<el-radio
v-for=
"dict in getIntDictOptions(DICT_TYPE.EXPRESS_CHARGE_MODE)"
:key=
"dict.value"
:label=
"dict.value"
>
{{
dict
.
label
}}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label=
"运费"
prop=
"templateCharge"
>
<el-table
border
style=
"width: 100%"
:data=
"formData.templateCharge"
>
<el-table-column
align=
"center"
label=
"区域"
width=
"180"
>
<template
#
default=
"
{ row }">
<!-- 区域数据太多,用赖加载方式,要不然性能有问题 -->
<el-tree-select
v-model=
"row.areaIds"
lazy
:load=
"loadChargeArea"
:props=
"defaultProps"
multiple
node-key=
"id"
check-strictly
show-checkbox
check-on-click-node
:render-after-expand=
"false"
:cache-data=
"areaCache"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
:label=
"columnTitle.startCountTitle"
width=
"180"
prop=
"startCount"
>
<
template
#
default=
"{ row }"
>
<el-input-number
v-model=
"row.startCount"
:min=
"1"
/>
</
template
>
</el-table-column>
<el-table-column
width=
"180"
align=
"center"
label=
"运费(元)"
prop=
"startPrice"
>
<
template
#
default=
"{ row }"
>
<el-input-number
v-model=
"row.startPrice"
:min=
"1"
/>
</
template
>
</el-table-column>
<el-table-column
width=
"180"
align=
"center"
:label=
"columnTitle.extraCountTitle"
prop=
"extraCount"
>
<
template
#
default=
"{ row }"
>
<el-input-number
v-model=
"row.extraCount"
:min=
"1"
/>
</
template
>
</el-table-column>
<el-table-column
width=
"180"
align=
"center"
label=
"续费(元)"
prop=
"extraPrice"
>
<
template
#
default=
"{ row }"
>
<el-input-number
v-model=
"row.extraPrice"
:min=
"1"
/>
</
template
>
</el-table-column>
<el-table-column
label=
"操作"
align=
"center"
>
<
template
#
default=
"scope"
>
<el-button
link
type=
"danger"
@
click=
"deleteChargeArea(scope.$index)"
>
删除
</el-button>
</
template
>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item>
<el-button
type=
"primary"
plain
@
click=
"addChargeArea()"
>
<Icon
icon=
"ep:plus"
class=
"mr-5px"
/>
添加区域
</el-button>
</el-form-item>
<el-form-item
label=
"包邮区域"
prop=
"templateFree"
>
<el-table
border
style=
"width: 100%"
:data=
"formData.templateFree"
>
<el-table-column
align=
"center"
label=
"区域"
>
<
template
#
default=
"{ row }"
>
<!-- 区域数据太多,用赖加载方式,要不然性能有问题 -->
<el-tree-select
v-model=
"row.areaIds"
multiple
lazy
:load=
"loadFreeArea"
:props=
"defaultProps"
node-key=
"id"
check-strictly
show-checkbox
check-on-click-node
:render-after-expand=
"true"
:cache-data=
"areaCache"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
:label=
"columnTitle.freeCountTitle"
prop=
"freeCount"
>
<
template
#
default=
"{ row }"
>
<el-input-number
v-model=
"row.freeCount"
:min=
"1"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"包邮金额(元)"
prop=
"freePrice"
>
<
template
#
default=
"{ row }"
>
<el-input-number
v-model=
"row.freePrice"
:min=
"1"
/>
</
template
>
</el-table-column>
<el-table-column
label=
"操作"
align=
"center"
>
<
template
#
default=
"scope"
>
<el-button
link
type=
"danger"
@
click=
"deleteFreeArea(scope.$index)"
>
删除
</el-button>
</
template
>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item>
<el-button
type=
"primary"
plain
@
click=
"addFreeArea()"
>
<Icon
icon=
"ep:plus"
class=
"mr-5px"
/>
添加区域
</el-button>
</el-form-item>
<el-form-item
label=
"排序"
prop=
"sort"
>
<el-input-number
v-model=
"formData.sort"
controls-position=
"right"
:min=
"0"
/>
</el-form-item>
</el-form>
<
template
#
footer
>
<el-button
@
click=
"submitForm"
type=
"primary"
:disabled=
"formLoading"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</Dialog>
</template>
<
script
setup
lang=
"ts"
>
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
*
as
DeliveryExpressTemplateApi
from
'@/api/mall/trade/delivery/expressTemplate'
import
{
defaultProps
}
from
'@/utils/tree'
import
{
yuanToFen
,
fenToYuan
}
from
'@/utils'
import
{
getChildrenArea
,
getAreaListByIds
}
from
'@/api/system/area'
import
{
cloneDeep
}
from
'lodash-es'
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
formData
=
ref
({
id
:
undefined
,
name
:
''
,
chargeMode
:
1
,
sort
:
0
,
templateCharge
:
[],
templateFree
:
[]
})
const
columnTitleMap
=
new
Map
()
const
columnTitle
=
ref
({
startCountTitle
:
'首件'
,
extraCountTitle
:
'续件'
,
freeCountTitle
:
'包邮件数'
})
const
formRules
=
reactive
({
name
:
[{
required
:
true
,
message
:
'模板名称不能为空'
,
trigger
:
'blur'
}],
chargeMode
:
[{
required
:
true
,
message
:
'配送计费方式不能为空'
,
trigger
:
'blur'
}],
sort
:
[{
required
:
true
,
message
:
'分类排序不能为空'
,
trigger
:
'blur'
}]
})
const
formRef
=
ref
()
// 表单 Ref
const
areaCache
=
ref
([])
//由于区域节点懒加载,已选区域节点需要缓存展示
/** 打开弹窗 */
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
dialogVisible
.
value
=
true
dialogTitle
.
value
=
t
(
'action.'
+
type
)
formType
.
value
=
type
resetForm
()
try
{
// 修改时,设置数据
if
(
id
)
{
formLoading
.
value
=
true
formData
.
value
=
await
DeliveryExpressTemplateApi
.
getDeliveryExpressTemplate
(
id
)
columnTitle
.
value
=
columnTitleMap
.
get
(
formData
.
value
.
chargeMode
)
const
chargeAreaIds
=
[]
const
freeAreaIds
=
[]
formData
.
value
.
templateCharge
.
forEach
((
item
)
=>
{
for
(
let
i
=
0
;
i
<
item
.
areaIds
.
length
;
i
++
)
{
if
(
!
chargeAreaIds
.
includes
(
item
.
areaIds
[
i
]))
{
chargeAreaIds
.
push
(
item
.
areaIds
[
i
])
}
}
//前端价格以元展示
item
.
startPrice
=
fenToYuan
(
item
.
startPrice
)
item
.
extraPrice
=
fenToYuan
(
item
.
extraPrice
)
})
formData
.
value
.
templateFree
.
forEach
((
item
)
=>
{
for
(
let
i
=
0
;
i
<
item
.
areaIds
.
length
;
i
++
)
{
if
(
!
chargeAreaIds
.
includes
(
item
.
areaIds
[
i
])
&&
!
freeAreaIds
.
includes
(
item
.
areaIds
[
i
]))
{
freeAreaIds
.
push
(
item
.
areaIds
[
i
])
}
}
item
.
freePrice
=
fenToYuan
(
item
.
freePrice
)
})
//已选的区域节点
const
areaIds
=
chargeAreaIds
.
concat
(
freeAreaIds
)
//区域节点,懒加载方式。 已选节点需要缓存展示
areaCache
.
value
=
await
getAreaListByIds
(
areaIds
.
join
(
','
))
}
}
finally
{
formLoading
.
value
=
false
}
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
/** 提交表单 */
const
emit
=
defineEmits
([
'success'
])
// 定义 success 事件,用于操作成功后的回调
const
submitForm
=
async
()
=>
{
// 校验表单
if
(
!
formRef
)
return
const
valid
=
await
formRef
.
value
.
validate
()
if
(
!
valid
)
return
// 提交请求
formLoading
.
value
=
true
try
{
const
data
=
formData
.
value
as
DeliveryExpressTemplateApi
.
DeliveryExpressTemplateVO
data
.
templateCharge
.
forEach
((
item
)
=>
{
//前端价格以元展示,提交到后端。用分计算
item
.
startPrice
=
yuanToFen
(
item
.
startPrice
)
item
.
extraPrice
=
yuanToFen
(
item
.
extraPrice
)
})
data
.
templateFree
.
forEach
((
item
)
=>
{
item
.
freePrice
=
yuanToFen
(
item
.
freePrice
)
})
if
(
formType
.
value
===
'create'
)
{
await
DeliveryExpressTemplateApi
.
createDeliveryExpressTemplate
(
data
)
message
.
success
(
t
(
'common.createSuccess'
))
}
else
{
await
DeliveryExpressTemplateApi
.
updateDeliveryExpressTemplate
(
data
)
message
.
success
(
t
(
'common.updateSuccess'
))
}
dialogVisible
.
value
=
false
// 发送操作成功的事件
emit
(
'success'
)
}
finally
{
formLoading
.
value
=
false
}
}
/** 重置表单 */
const
resetForm
=
()
=>
{
formData
.
value
=
{
id
:
undefined
,
name
:
''
,
chargeMode
:
1
,
templateCharge
:
[
{
areaIds
:
[
1
],
startCount
:
2
,
startPrice
:
5
,
extraCount
:
5
,
extraPrice
:
10
}
],
templateFree
:
[],
sort
:
0
}
columnTitle
.
value
=
columnTitleMap
.
get
(
1
)
formRef
.
value
?.
resetFields
()
}
/** 配送计费方法改变 */
const
changeChargeMode
=
(
chargeMode
:
number
)
=>
{
columnTitle
.
value
=
columnTitleMap
.
get
(
chargeMode
)
}
const
defaultArea
=
[{
id
:
1
,
name
:
'全国'
,
disabled
:
false
}]
/** 初始化数据 */
const
initData
=
async
()
=>
{
// TODO 从服务端全量加载数据, 后面看懒加载是不是可以从前端获取数据。 目前从后端获取数据
// formLoading.value = true
// try {
// const data = await getAreaTree()
// areaTree = data
// console.log('areaTree', areaTree)
// } finally {
// formLoading.value = false
// }
//表头标题和计费方式的映射
columnTitleMap
.
set
(
1
,
{
startCountTitle
:
'首件'
,
extraCountTitle
:
'续件'
,
freeCountTitle
:
'包邮件数'
})
columnTitleMap
.
set
(
2
,
{
startCountTitle
:
'首件重量(kg)'
,
extraCountTitle
:
'续件重量(kg)'
,
freeCountTitle
:
'包邮重量(kg)'
})
columnTitleMap
.
set
(
3
,
{
startCountTitle
:
'首件体积(m³)'
,
extraCountTitle
:
'续件体积(m³)'
,
freeCountTitle
:
'包邮体积(m³)'
})
}
/** 懒加载运费区域树 */
const
loadChargeArea
=
async
(
node
,
resolve
)
=>
{
//已选区域需要禁止再次选择
const
areaIds
=
[]
formData
.
value
.
templateCharge
.
forEach
((
item
)
=>
{
if
(
item
.
areaIds
.
length
>
0
)
{
item
.
areaIds
.
forEach
((
areaId
)
=>
areaIds
.
push
(
areaId
))
}
})
if
(
node
.
isLeaf
)
return
resolve
([])
const
length
=
node
.
data
.
length
if
(
length
===
0
)
{
const
data
=
cloneDeep
(
defaultArea
)
const
item
=
data
[
0
]
if
(
areaIds
.
includes
(
item
.
id
))
{
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
//item.disabled = true
}
resolve
(
data
)
}
else
{
const
id
=
node
.
data
.
id
const
data
=
await
getChildrenArea
(
id
)
data
.
forEach
((
item
)
=>
{
if
(
areaIds
.
includes
(
item
.
id
))
{
//item.disabled = true
}
})
resolve
(
data
)
}
}
/** 懒加载包邮区域树 */
const
loadFreeArea
=
async
(
node
,
resolve
)
=>
{
if
(
node
.
isLeaf
)
return
resolve
([])
//已选区域需要禁止再次选择
const
areaIds
=
[]
formData
.
value
.
templateFree
.
forEach
((
item
)
=>
{
if
(
item
.
areaIds
.
length
>
0
)
{
item
.
areaIds
.
forEach
((
areaId
)
=>
areaIds
.
push
(
areaId
))
}
})
const
length
=
node
.
data
.
length
if
(
length
===
0
)
{
// 为空,从全国开始选择。全国 id == 1
const
data
=
cloneDeep
(
defaultArea
)
const
item
=
data
[
0
]
if
(
areaIds
.
includes
(
item
.
id
))
{
//item.disabled = true
}
resolve
(
data
)
}
else
{
const
id
=
node
.
data
.
id
const
data
=
await
getChildrenArea
(
id
)
//已选区域需要禁止再次选择
data
.
forEach
((
item
)
=>
{
if
(
areaIds
.
includes
(
item
.
id
))
{
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
//item.disabled = true
}
})
resolve
(
data
)
}
}
/** 添加计费区域 */
const
addChargeArea
=
()
=>
{
const
data
=
formData
.
value
data
.
templateCharge
.
push
({
areaIds
:
[],
startCount
:
1
,
startPrice
:
1
,
extraCount
:
1
,
extraPrice
:
1
})
}
/** 删除计费区域 */
const
deleteChargeArea
=
(
index
)
=>
{
const
data
=
formData
.
value
data
.
templateCharge
.
splice
(
index
,
1
)
}
/** 添加包邮区域 */
const
addFreeArea
=
()
=>
{
const
data
=
formData
.
value
data
.
templateFree
.
push
({
areaIds
:
[],
freeCount
:
1
,
freePrice
:
1
})
}
/** 删除包邮区域 */
const
deleteFreeArea
=
(
index
)
=>
{
const
data
=
formData
.
value
data
.
templateFree
.
splice
(
index
,
1
)
}
/** 初始化 **/
onMounted
(()
=>
{
initData
()
})
</
script
>
src/views/mall/trade/delivery/expressTemplate/index.vue
0 → 100644
View file @
ec3b028d
<
template
>
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class=
"-mb-15px"
:model=
"queryParams"
ref=
"queryFormRef"
:inline=
"true"
label-width=
"100px"
>
<el-form-item
label=
"模板名称"
prop=
"name"
>
<el-input
v-model=
"queryParams.name"
placeholder=
"请输入模板名称"
clearable
@
keyup
.
enter=
"handleQuery"
class=
"!w-240px"
/>
</el-form-item>
<el-form-item
label=
"计费方式"
prop=
"chargeMode"
>
<el-select
v-model=
"queryParams.chargeMode"
placeholder=
"计费方式"
clearable
class=
"!w-240px"
>
<el-option
v-for=
"dict in getIntDictOptions(DICT_TYPE.EXPRESS_CHARGE_MODE)"
:key=
"dict.value"
:label=
"dict.label"
:value=
"dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button
@
click=
"handleQuery"
><Icon
icon=
"ep:search"
class=
"mr-5px"
/>
搜索
</el-button>
<el-button
@
click=
"resetQuery"
><Icon
icon=
"ep:refresh"
class=
"mr-5px"
/>
重置
</el-button>
<el-button
type=
"primary"
plain
@
click=
"openForm('create')"
v-hasPermi=
"['trade:delivery:express-template:create']"
>
<Icon
icon=
"ep:plus"
class=
"mr-5px"
/>
新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table
v-loading=
"loading"
:data=
"list"
>
<el-table-column
label=
"编号"
prop=
"id"
/>
<el-table-column
label=
"模板名称"
prop=
"name"
/>
<el-table-column
label=
"计费方式"
prop=
"chargeMode"
align=
"center"
>
<template
#
default=
"scope"
>
<dict-tag
:type=
"DICT_TYPE.EXPRESS_CHARGE_MODE"
:value=
"scope.row.chargeMode"
/>
</
template
>
</el-table-column>
<el-table-column
label=
"排序"
prop=
"sort"
/>
<el-table-column
label=
"创建时间"
align=
"center"
prop=
"createTime"
width=
"180"
:formatter=
"dateFormatter"
/>
<el-table-column
label=
"操作"
align=
"center"
>
<
template
#
default=
"scope"
>
<el-button
link
type=
"primary"
@
click=
"openForm('update', scope.row.id)"
v-hasPermi=
"['trade:delivery:express-template:update']"
>
编辑
</el-button>
<el-button
link
type=
"danger"
@
click=
"handleDelete(scope.row.id)"
v-hasPermi=
"['trade:delivery:express-template:delete']"
>
删除
</el-button>
</
template
>
</el-table-column>
</el-table>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<ExpressTemplateForm
ref=
"formRef"
@
success=
"getList"
/>
</template>
<
script
setup
lang=
"ts"
name=
"DeliveryExpressTemplate"
>
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
*
as
DeliveryExpressTemplateApi
from
'@/api/mall/trade/delivery/expressTemplate'
import
ExpressTemplateForm
from
'./ExpressTemplateForm.vue'
const
message
=
useMessage
()
// 消息弹窗
const
{
t
}
=
useI18n
()
// 国际化
const
total
=
ref
(
0
)
// 列表的总页数
const
loading
=
ref
(
true
)
// 列表的加载中
const
list
=
ref
<
any
[]
>
([])
// 列表的数据
const
queryParams
=
reactive
({
pageNo
:
1
,
pageSize
:
10
,
name
:
''
,
chargeMode
:
undefined
})
const
queryFormRef
=
ref
()
// 搜索的表单
/** 查询列表 */
const
getList
=
async
()
=>
{
loading
.
value
=
true
try
{
const
data
=
await
DeliveryExpressTemplateApi
.
getDeliveryExpressTemplatePage
(
queryParams
)
list
.
value
=
data
.
list
total
.
value
=
data
.
total
}
finally
{
loading
.
value
=
false
}
}
/** 搜索按钮操作 */
const
handleQuery
=
()
=>
{
queryParams
.
pageNo
=
1
getList
()
}
/** 重置按钮操作 */
const
resetQuery
=
()
=>
{
queryFormRef
.
value
.
resetFields
()
handleQuery
()
}
/** 添加/修改操作 */
const
formRef
=
ref
()
const
openForm
=
(
type
:
string
,
id
?:
number
)
=>
{
formRef
.
value
.
open
(
type
,
id
)
}
/** 删除按钮操作 */
const
handleDelete
=
async
(
id
:
number
)
=>
{
try
{
// 删除的二次确认
await
message
.
delConfirm
()
// 发起删除
await
DeliveryExpressTemplateApi
.
deleteDeliveryExpressTemplate
(
id
)
message
.
success
(
t
(
'common.delSuccess'
))
// 刷新列表
await
getList
()
}
catch
{}
}
/** 初始化 **/
onMounted
(()
=>
{
getList
()
})
</
script
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment