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
4ddba9d4
authored
May 17, 2023
by
puhui999
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: 解决商品上一版遗留的各种小bug关键部分已添加fix注释。完成的TODO也已添加fix标记
parent
3c4a39df
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
215 additions
and
217 deletions
+215
-217
src/api/mall/product/spu.ts
+5
-0
src/components/UploadFile/src/UploadImgs.vue
+44
-14
src/router/modules/remaining.ts
+17
-4
src/views/mall/product/spu/addForm.vue
+80
-131
src/views/mall/product/spu/components/BasicInfoForm.vue
+29
-27
src/views/mall/product/spu/components/DescriptionForm.vue
+8
-9
src/views/mall/product/spu/components/OtherSettingsForm.vue
+14
-18
src/views/mall/product/spu/components/ProductAttributes.vue
+3
-2
src/views/mall/product/spu/components/SkuList.vue
+9
-9
src/views/mall/product/spu/index.vue
+6
-3
No files found.
src/api/mall/product/spu.ts
View file @
4ddba9d4
...
...
@@ -82,3 +82,8 @@ export const getSpu = (id: number) => {
export
const
deleteSpu
=
(
id
:
number
)
=>
{
return
request
.
delete
({
url
:
`/product/spu/delete?id=
${
id
}
`
})
}
// 导出商品 Spu
export
const
exportUser
=
(
params
)
=>
{
return
request
.
download
({
url
:
'/product/spu/export'
,
params
})
}
src/components/UploadFile/src/UploadImgs.vue
View file @
4ddba9d4
<
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 @
4ddba9d4
...
...
@@ -349,22 +349,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/views/mall/product/spu/addForm.vue
View file @
4ddba9d4
...
...
@@ -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,55 @@
</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/managemen
t/spu'
// 业务api
import
*
as
ProductSpuApi
from
'@/api/mall/produc
t/spu'
import
*
as
PropertyApi
from
'@/api/mall/product/property'
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
,
// 运费模版
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,11 +89,11 @@ 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
formData
.
value
=
res
// 直接取第一个值就能得到所有属性的id
// TODO @puhui999:可以直接拿 propertyName 拼接处规格 id + 属性,可以看下商品 uniapp 详情的做法
...
...
@@ -134,7 +101,7 @@ const getDetail = async () => {
const
PropertyS
=
await
PropertyApi
.
getPropertyListAndValue
({
propertyIds
})
await
nextTick
()
// 回显商品属性
B
asicInfoRef
.
value
.
addAttribute
(
PropertyS
)
b
asicInfoRef
.
value
.
addAttribute
(
PropertyS
)
}
finally
{
formLoading
.
value
=
false
}
...
...
@@ -145,54 +112,37 @@ 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 端不满足,不需要恢复
await
unref
(
b
asicInfoRef
)?.
validate
()
await
unref
(
d
escriptionRef
)?.
validate
()
await
unref
(
o
therSettingsRef
)?.
validate
()
const
deepCopyFormData
=
cloneDeep
(
unref
(
formData
.
value
))
// 深拷贝一份 fix:这样最终 server 端不满足,不需要恢复,
// 处理掉一些无关数据
formData
.
value
.
skus
.
forEach
((
item
)
=>
{
deepCopyFormData
.
skus
.
forEach
((
item
)
=>
{
// 给sku name赋值
item
.
name
=
formData
.
value
.
name
// 多规格情况移除skus相关属性值value
if
(
formData
.
value
.
specType
)
{
item
.
properties
.
forEach
((
item2
)
=>
{
delete
item2
.
valueName
})
}
item
.
name
=
deepCopyFormData
.
name
})
// 处理轮播图列表
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
)
}
// TODO @puhui999:疑问哈,为啥会是 object 呀?fix
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
}
...
...
@@ -200,41 +150,40 @@ const submitForm = async () => {
/**
* 重置表单
* fix:先注释保留,如果后期没有使用到则移除
*/
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 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 @
4ddba9d4
<
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"
>
...
...
@@ -54,7 +54,7 @@
</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"
>
...
...
@@ -86,36 +86,36 @@
<!-- 多规格添加-->
<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=
"
A
ttributesAddFormRef.open"
>
添加规格
</el-button>
<ProductAttributes
:
attribute-data=
"attribute
List"
/>
<!-- TODO @puhui999:参考 https://admin.java.crmeb.net/store/list/creatProduct 添加规格好做么?添加的时候,不用输入备注哈
fix
-->
<el-button
class=
"mr-15px mb-10px"
@
click=
"
a
ttributesAddFormRef.open"
>
添加规格
</el-button>
<ProductAttributes
:
propertyList=
"property
List"
/>
</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
:
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=
"
A
ttributesAddFormRef"
@
success=
"addAttribute"
/>
<ProductAttributesAddForm
ref=
"
a
ttributesAddFormRef"
@
success=
"addAttribute"
/>
</template>
<
script
lang=
"ts"
name=
"Product
Management
BasicInfoForm"
setup
>
<
script
lang=
"ts"
name=
"Product
Spu
BasicInfoForm"
setup
>
import
{
PropType
}
from
'vue'
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'
import
{
ProductAttributes
,
ProductAttributesAddForm
,
SkuList
}
from
'./index'
import
*
as
ProductCategoryApi
from
'@/api/mall/product/category'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
copyValueToTarget
}
from
'@/utils'
const
message
=
useMessage
()
// 消息弹窗
...
...
@@ -126,17 +126,14 @@ 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
attributesAddFormRef
=
ref
()
// 添加商品属性表单 TODO @puhui999:小写开头哈 fix
const
productSpuBasicInfoRef
=
ref
()
// 表单Ref TODO @puhui999:小写开头哈 fix
// TODO @puhui999:attributeList 改成 propertyList,会更统一一点 fix
const
propertyList
=
ref
([])
// 商品属性列表
/** 添加商品属性 */
// TODO @puhui999:propFormData 算出来 fix: 因为ProductAttributesAddForm添加属性成功回调得使用不能完全依赖于propFormData
const
addAttribute
=
(
property
:
any
)
=>
{
if
(
Array
.
isArray
(
property
))
{
attributeList
.
value
=
property
return
}
attributeList
.
value
.
push
(
property
)
Array
.
isArray
(
property
)
?
(
propertyList
.
value
=
property
)
:
propertyList
.
value
.
push
(
property
)
}
const
formData
=
reactive
<
SpuType
>
({
name
:
''
,
// 商品名称
...
...
@@ -171,10 +168,15 @@ watch(
()
=>
props
.
propFormData
,
(
data
)
=>
{
if
(
!
data
)
return
// fix:三个表单组件监听赋值必须使用 copyValueToTarget 使用 formData.value = data 会监听非常多次
copyValueToTarget
(
formData
,
data
)
// fix: 多图上传组件需要一个包含url属性的对象才能正常回显
formData
.
sliderPicUrls
=
data
[
'sliderPicUrls'
].
map
((
item
)
=>
({
url
:
item
}))
},
{
deep
:
true
,
// fix: 去掉深度监听只有对象引用发生改变的时候才执行,解决改一动多的问题
immediate
:
true
}
)
...
...
@@ -185,8 +187,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'
)
...
...
@@ -212,7 +214,7 @@ const changeSubCommissionType = () => {
/** 选择规格 */
const
onChangeSpec
=
()
=>
{
// 重置商品属性列表
attribute
List
.
value
=
[]
property
List
.
value
=
[]
// 重置sku列表
formData
.
skus
=
[
{
...
...
src/views/mall/product/spu/components/DescriptionForm.vue
View file @
4ddba9d4
<
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'
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 @
4ddba9d4
<
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:横着三个哈 fix-->
<el-col
:span=
"24"
>
...
...
@@ -55,8 +55,8 @@
<
script
lang=
"ts"
name=
"OtherSettingsForm"
setup
>
import
type
{
SpuType
}
from
'@/api/mall/product/spu'
import
{
PropType
}
from
'vue'
import
{
copyValueToTarget
}
from
'@/utils'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
copyValueToTarget
}
from
'@/utils'
const
message
=
useMessage
()
// 消息弹窗
...
...
@@ -68,7 +68,7 @@ const props = defineProps({
activeName
:
propTypes
.
string
.
def
(
''
)
})
const
O
therSettingsFormRef
=
ref
()
// 表单Ref
const
o
therSettingsFormRef
=
ref
()
// 表单Ref
// 表单数据
const
formData
=
ref
<
SpuType
>
({
sort
:
1
,
// 商品排序
...
...
@@ -100,7 +100,7 @@ const checkboxGroup = ref<string[]>([]) // 选中的推荐选项
const
onChangeGroup
=
()
=>
{
// TODO @puhui999:是不是可以遍历 recommend,然后进行是否选中;fix
recommendOptions
.
forEach
(({
value
})
=>
{
formData
.
value
[
value
]
=
checkboxGroup
.
value
.
includes
(
value
)
?
true
:
false
formData
.
value
[
value
]
=
checkboxGroup
.
value
.
includes
(
value
)
})
}
...
...
@@ -111,22 +111,28 @@ watch(
()
=>
props
.
propFormData
,
(
data
)
=>
{
if
(
!
data
)
return
// fix:三个表单组件监听赋值必须使用 copyValueToTarget 使用 formData.value = data 会监听非常多次
copyValueToTarget
(
formData
.
value
,
data
)
recommendOptions
.
forEach
(({
value
})
=>
{
// TODO 如果先修改其他设置的值,再改变商品详情或是商品信息会重置其他设置页面中的相关值 fix:已修复
if
(
formData
.
value
[
value
]
&&
!
checkboxGroup
.
value
.
includes
(
value
))
{
checkboxGroup
.
value
.
push
(
value
)
}
})
},
{
deep
:
true
,
// fix: 去掉深度监听只有对象引用发生改变的时候才执行,解决改一动多的问题
immediate
:
true
}
)
/**
* 表单校验
*/
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'
)
...
...
@@ -139,14 +145,4 @@ const validate = async () => {
})
}
defineExpose
({
validate
})
onMounted
(
async
()
=>
{
await
nextTick
()
// TODO 如果先修改其他设置的值,再改变商品详情或是商品信息会重置其他设置页面中的相关值 fix:已修复,改为组件初始化时赋值
checkboxGroup
.
value
=
[]
recommendOptions
.
forEach
(({
value
})
=>
{
if
(
formData
.
value
[
value
])
{
checkboxGroup
.
value
.
push
(
value
)
}
})
})
</
script
>
src/views/mall/product/spu/components/ProductAttributes.vue
View file @
4ddba9d4
...
...
@@ -54,14 +54,14 @@ const inputVisible = computed(() => (index) => {
const
InputRef
=
ref
()
//标签输入框Ref
const
attributeList
=
ref
([])
// 商品属性列表
const
props
=
defineProps
({
attributeData
:
{
propertyList
:
{
type
:
Array
,
default
:
()
=>
{}
}
})
watch
(
()
=>
props
.
attributeData
,
()
=>
props
.
propertyList
,
(
data
)
=>
{
if
(
!
data
)
return
attributeList
.
value
=
data
...
...
@@ -80,6 +80,7 @@ const handleClose = (index, valueIndex) => {
/** 显示输入框并获取焦点 */
const
showInput
=
async
(
index
)
=>
{
attributeIndex
.
value
=
index
// TODO 嗯!!!自动获取焦点还是有点问题,后续继续改进
// 因为组件在ref中所以需要用索引获取对应的Ref
InputRef
.
value
[
index
]
!
.
input
!
.
focus
()
}
...
...
src/views/mall/product/spu/components/SkuList.vue
View file @
4ddba9d4
...
...
@@ -25,13 +25,13 @@
</
template
>
</el-table-column>
</template>
<!-- TODO @puhui999: controls-position=" " 可以去掉哈,不然太长了,手动输入更方便 -->
<!-- TODO @puhui999: controls-position=" " 可以去掉哈,不然太长了,手动输入更方便
fix
-->
<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:用户输入的时候,是按照元;分主要是我们自己用; -->
<!-- TODO @puhui999:用户输入的时候,是按照元;分主要是我们自己用;
fix
-->
<el-table-column
align=
"center"
label=
"销售价(元)"
min-width=
"168"
>
<
template
#
default=
"{ row }"
>
<el-input-number
v-model=
"row.price"
:min=
"0"
class=
"w-100%"
/>
...
...
@@ -96,7 +96,7 @@ const props = defineProps({
type
:
Object
as
PropType
<
SpuType
>
,
default
:
()
=>
{}
},
attribute
List
:
{
property
List
:
{
type
:
Array
,
default
:
()
=>
[]
},
...
...
@@ -142,7 +142,7 @@ watch(
}
)
// TODO @芋艿:看看 chatgpt 可以进一步下面几个方法的实现不
// TODO @芋艿:看看 chatgpt 可以进一步下面几个方法的实现不
fix
/** 生成表数据 */
const
generateTableData
=
(
data
:
any
[])
=>
{
// 构建数据结构 fix: 使用map替换多重for循环
...
...
@@ -207,8 +207,8 @@ const build = (propertyValuesList: Property[][]) => {
/** 监听属性列表生成相关参数和表头 */
watch
(
()
=>
props
.
attribute
List
,
(
attribute
List
)
=>
{
()
=>
props
.
property
List
,
(
property
List
)
=>
{
// 如果不是多规格则结束
if
(
!
formData
.
value
.
specType
)
return
// 如果当前组件作为批量添加数据使用则重置表数据
...
...
@@ -229,15 +229,15 @@ watch(
]
}
// 判断代理对象是否为空
if
(
JSON
.
stringify
(
attribute
List
)
===
'[]'
)
return
if
(
JSON
.
stringify
(
property
List
)
===
'[]'
)
return
// 重置表头
tableHeaders
.
value
=
[]
// 生成表头
attribute
List
.
forEach
((
item
,
index
)
=>
{
property
List
.
forEach
((
item
,
index
)
=>
{
// name加属性项index区分属性值
tableHeaders
.
value
.
push
({
prop
:
`name
${
index
}
`
,
label
:
item
.
name
})
})
generateTableData
(
attribute
List
)
generateTableData
(
property
List
)
},
{
deep
:
true
,
...
...
src/views/mall/product/spu/index.vue
View file @
4ddba9d4
...
...
@@ -8,7 +8,7 @@
class=
"-mb-15px"
label-width=
"68px"
>
<!-- TODO @puhui999:https://admin.java.crmeb.net/store/index,参考,使用分类 + 标题搜索 -->
<!-- TODO @puhui999:https://admin.java.crmeb.net/store/index,参考,使用分类 + 标题搜索
fix
-->
<el-form-item
label=
"品牌名称"
prop=
"name"
>
<el-input
v-model=
"queryParams.name"
...
...
@@ -351,11 +351,11 @@ const resetQuery = () => {
const
openForm
=
(
id
?:
number
)
=>
{
// 修改
if
(
typeof
id
===
'number'
)
{
push
(
'/product/product
ManagementAdd?id=
'
+
id
)
push
(
'/product/product
SpuEdit/
'
+
id
)
return
}
// 新增
push
(
'/product/product
Management
Add'
)
push
(
'/product/product
Spu
Add'
)
}
// 监听路由变化更新列表 TODO @puhui999:这个是必须加的么?fix: 因为编辑表单是以路由的方式打开,保存表单后列表不会刷新
...
...
@@ -377,8 +377,11 @@ onMounted(async () => {
</
script
>
<
style
lang=
"scss"
scoped
>
.demo-table-expand
{
padding-left
:
42px
;
:deep(.el-form-item__label)
{
width
:
82px
;
font-weight
:
bold
;
color
:
#99a9bf
;
}
}
...
...
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