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
e5559777
authored
May 30, 2023
by
puhui999
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: 修改 review 提到的问题,完善分类选择层级校验、完整层级展示
parent
820d8ab7
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
238 additions
and
88 deletions
+238
-88
src/api/mall/product/spu.ts
+6
-8
src/utils/tree.ts
+81
-0
src/views/mall/product/spu/addForm.vue
+8
-7
src/views/mall/product/spu/components/BasicInfoForm.vue
+36
-23
src/views/mall/product/spu/components/DescriptionForm.vue
+3
-3
src/views/mall/product/spu/components/OtherSettingsForm.vue
+3
-3
src/views/mall/product/spu/components/ProductAttributesAddForm.vue
+1
-2
src/views/mall/product/spu/components/SkuList.vue
+36
-22
src/views/mall/product/spu/index.vue
+64
-20
No files found.
src/api/mall/product/spu.ts
View file @
e5559777
...
@@ -7,8 +7,7 @@ export interface Property {
...
@@ -7,8 +7,7 @@ export interface Property {
valueName
?:
string
// 属性值名称
valueName
?:
string
// 属性值名称
}
}
// TODO puhui999:是不是直接叫 Sku 更简洁一点哈。type 待后面,总感觉有个类型?
export
interface
Sku
{
export
interface
SkuType
{
id
?:
number
// 商品 SKU 编号
id
?:
number
// 商品 SKU 编号
spuId
?:
number
// SPU 编号
spuId
?:
number
// SPU 编号
properties
?:
Property
[]
// 属性数组
properties
?:
Property
[]
// 属性数组
...
@@ -25,8 +24,7 @@ export interface SkuType {
...
@@ -25,8 +24,7 @@ export interface SkuType {
salesCount
?:
number
// 商品销量
salesCount
?:
number
// 商品销量
}
}
// TODO puhui999:是不是直接叫 Spu 更简洁一点哈。type 待后面,总感觉有个类型?
export
interface
Spu
{
export
interface
SpuType
{
id
?:
number
id
?:
number
name
?:
string
// 商品名称
name
?:
string
// 商品名称
categoryId
?:
number
|
null
// 商品分类
categoryId
?:
number
|
null
// 商品分类
...
@@ -39,9 +37,9 @@ export interface SpuType {
...
@@ -39,9 +37,9 @@ export interface SpuType {
brandId
?:
number
|
null
// 商品品牌编号
brandId
?:
number
|
null
// 商品品牌编号
specType
?:
boolean
// 商品规格
specType
?:
boolean
// 商品规格
subCommissionType
?:
boolean
// 分销类型
subCommissionType
?:
boolean
// 分销类型
skus
:
Sku
Type
[]
// sku数组
skus
:
Sku
[]
// sku数组
description
?:
string
// 商品详情
description
?:
string
// 商品详情
sort
?:
string
// 商品排序
sort
?:
number
// 商品排序
giveIntegral
?:
number
// 赠送积分
giveIntegral
?:
number
// 赠送积分
virtualSalesCount
?:
number
// 虚拟销量
virtualSalesCount
?:
number
// 虚拟销量
recommendHot
?:
boolean
// 是否热卖
recommendHot
?:
boolean
// 是否热卖
...
@@ -62,12 +60,12 @@ export const getTabsCount = () => {
...
@@ -62,12 +60,12 @@ export const getTabsCount = () => {
}
}
// 创建商品 Spu
// 创建商品 Spu
export
const
createSpu
=
(
data
:
Spu
Type
)
=>
{
export
const
createSpu
=
(
data
:
Spu
)
=>
{
return
request
.
post
({
url
:
'/product/spu/create'
,
data
})
return
request
.
post
({
url
:
'/product/spu/create'
,
data
})
}
}
// 更新商品 Spu
// 更新商品 Spu
export
const
updateSpu
=
(
data
:
Spu
Type
)
=>
{
export
const
updateSpu
=
(
data
:
Spu
)
=>
{
return
request
.
put
({
url
:
'/product/spu/update'
,
data
})
return
request
.
put
({
url
:
'/product/spu/update'
,
data
})
}
}
...
...
src/utils/tree.ts
View file @
e5559777
...
@@ -3,6 +3,7 @@ interface TreeHelperConfig {
...
@@ -3,6 +3,7 @@ interface TreeHelperConfig {
children
:
string
children
:
string
pid
:
string
pid
:
string
}
}
const
DEFAULT_CONFIG
:
TreeHelperConfig
=
{
const
DEFAULT_CONFIG
:
TreeHelperConfig
=
{
id
:
'id'
,
id
:
'id'
,
children
:
'children'
,
children
:
'children'
,
...
@@ -133,6 +134,7 @@ export const filter = <T = any>(
...
@@ -133,6 +134,7 @@ export const filter = <T = any>(
):
T
[]
=>
{
):
T
[]
=>
{
config
=
getConfig
(
config
)
config
=
getConfig
(
config
)
const
children
=
config
.
children
as
string
const
children
=
config
.
children
as
string
function
listFilter
(
list
:
T
[])
{
function
listFilter
(
list
:
T
[])
{
return
list
return
list
.
map
((
node
:
any
)
=>
({
...
node
}))
.
map
((
node
:
any
)
=>
({
...
node
}))
...
@@ -141,6 +143,7 @@ export const filter = <T = any>(
...
@@ -141,6 +143,7 @@ export const filter = <T = any>(
return
func
(
node
)
||
(
node
[
children
]
&&
node
[
children
].
length
)
return
func
(
node
)
||
(
node
[
children
]
&&
node
[
children
].
length
)
})
})
}
}
return
listFilter
(
tree
)
return
listFilter
(
tree
)
}
}
...
@@ -264,6 +267,7 @@ export const handleTree = (data: any[], id?: string, parentId?: string, children
...
@@ -264,6 +267,7 @@ export const handleTree = (data: any[], id?: string, parentId?: string, children
}
}
}
}
}
}
return
tree
return
tree
}
}
...
@@ -302,3 +306,80 @@ export const handleTree2 = (data, id, parentId, children, rootId) => {
...
@@ -302,3 +306,80 @@ export const handleTree2 = (data, id, parentId, children, rootId) => {
})
})
return
treeData
!==
''
?
treeData
:
data
return
treeData
!==
''
?
treeData
:
data
}
}
/**
*
* @param tree 要操作的树结构数据
* @param nodeId 需要判断在什么层级的数据
* @param level 检查的级别, 默认检查到二级
*/
export
const
checkSelectedNode
=
(
tree
:
any
[],
nodeId
,
level
=
2
)
=>
{
if
(
typeof
tree
===
'undefined'
||
!
Array
.
isArray
(
tree
)
||
tree
.
length
===
0
)
{
console
.
warn
(
'tree must be an array'
)
return
false
}
// 校验是否是一级节点
if
(
tree
.
some
((
item
)
=>
item
.
id
===
nodeId
))
{
return
false
}
// 递归计数
let
count
=
1
// 深层次校验
function
performAThoroughValidation
(
arr
)
{
count
+=
1
for
(
const
item
of
arr
)
{
if
(
item
.
id
===
nodeId
)
{
return
true
}
else
if
(
typeof
item
.
children
!==
'undefined'
&&
item
.
children
.
length
!==
0
)
{
performAThoroughValidation
(
item
.
children
)
}
}
return
false
}
for
(
const
item
of
tree
)
{
count
=
1
if
(
performAThoroughValidation
(
item
.
children
))
{
// 找到后对比是否是期望的层级
if
(
count
>=
level
)
return
true
}
}
return
false
}
/**
* 获取节点的完整结构
* @param tree 树数据
* @param nodeId 节点 id
*/
export
const
treeToString
=
(
tree
:
any
[],
nodeId
)
=>
{
if
(
typeof
tree
===
'undefined'
||
!
Array
.
isArray
(
tree
)
||
tree
.
length
===
0
)
{
console
.
warn
(
'tree must be an array'
)
return
''
}
// 校验是否是一级节点
const
node
=
tree
.
find
((
item
)
=>
item
.
id
===
nodeId
)
if
(
typeof
node
!==
'undefined'
)
{
return
node
.
name
}
let
str
=
''
function
performAThoroughValidation
(
arr
)
{
for
(
const
item
of
arr
)
{
if
(
item
.
id
===
nodeId
)
{
str
+=
`/
${
item
.
name
}
`
return
true
}
else
if
(
typeof
item
.
children
!==
'undefined'
&&
item
.
children
.
length
!==
0
)
{
performAThoroughValidation
(
item
.
children
)
}
}
return
false
}
for
(
const
item
of
tree
)
{
str
=
`
${
item
.
name
}
`
if
(
performAThoroughValidation
(
item
.
children
))
{
break
}
}
return
str
}
src/views/mall/product/spu/addForm.vue
View file @
e5559777
...
@@ -51,15 +51,15 @@ const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() // 商品信息Re
...
@@ -51,15 +51,15 @@ const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() // 商品信息Re
const
descriptionRef
=
ref
<
ComponentRef
<
typeof
DescriptionForm
>>
()
// 商品详情Ref
const
descriptionRef
=
ref
<
ComponentRef
<
typeof
DescriptionForm
>>
()
// 商品详情Ref
const
otherSettingsRef
=
ref
<
ComponentRef
<
typeof
OtherSettingsForm
>>
()
// 其他设置Ref
const
otherSettingsRef
=
ref
<
ComponentRef
<
typeof
OtherSettingsForm
>>
()
// 其他设置Ref
// spu 表单数据
// spu 表单数据
const
formData
=
ref
<
ProductSpuApi
.
Spu
Type
>
({
const
formData
=
ref
<
ProductSpuApi
.
Spu
>
({
name
:
''
,
// 商品名称
name
:
''
,
// 商品名称
categoryId
:
null
,
// 商品分类
categoryId
:
null
,
// 商品分类
keyword
:
''
,
// 关键字
keyword
:
''
,
// 关键字
unit
:
null
,
// 单位
unit
:
null
,
// 单位
picUrl
:
''
,
// 商品封面图
picUrl
:
''
,
// 商品封面图
sliderPicUrls
:
[],
// 商品轮播图
sliderPicUrls
:
[
''
],
// 商品轮播图
introduction
:
''
,
// 商品简介
introduction
:
''
,
// 商品简介
deliveryTemplateId
:
1
,
// 运费模版
deliveryTemplateId
:
null
,
// 运费模版
brandId
:
null
,
// 商品品牌
brandId
:
null
,
// 商品品牌
specType
:
false
,
// 商品规格
specType
:
false
,
// 商品规格
subCommissionType
:
false
,
// 分销类型
subCommissionType
:
false
,
// 分销类型
...
@@ -94,7 +94,7 @@ const getDetail = async () => {
...
@@ -94,7 +94,7 @@ const getDetail = async () => {
if
(
id
)
{
if
(
id
)
{
formLoading
.
value
=
true
formLoading
.
value
=
true
try
{
try
{
const
res
=
(
await
ProductSpuApi
.
getSpu
(
id
))
as
ProductSpuApi
.
Spu
Type
const
res
=
(
await
ProductSpuApi
.
getSpu
(
id
))
as
ProductSpuApi
.
Spu
res
.
skus
.
forEach
((
item
)
=>
{
res
.
skus
.
forEach
((
item
)
=>
{
// 回显价格分转元
// 回显价格分转元
item
.
price
=
formatToFraction
(
item
.
price
)
item
.
price
=
formatToFraction
(
item
.
price
)
...
@@ -120,8 +120,9 @@ const submitForm = async () => {
...
@@ -120,8 +120,9 @@ const submitForm = async () => {
await
unref
(
basicInfoRef
)?.
validate
()
await
unref
(
basicInfoRef
)?.
validate
()
await
unref
(
descriptionRef
)?.
validate
()
await
unref
(
descriptionRef
)?.
validate
()
await
unref
(
otherSettingsRef
)?.
validate
()
await
unref
(
otherSettingsRef
)?.
validate
()
const
deepCopyFormData
=
cloneDeep
(
unref
(
formData
.
value
))
// 深拷贝一份 fix:这样最终 server 端不满足,不需要恢复,
// 深拷贝一份, 这样最终 server 端不满足,不需要恢复,
// TODO 兜底处理 sku 空数据
const
deepCopyFormData
=
cloneDeep
(
unref
(
formData
.
value
))
// 兜底处理 sku 空数据
formData
.
value
.
skus
.
forEach
((
sku
)
=>
{
formData
.
value
.
skus
.
forEach
((
sku
)
=>
{
// 因为是空数据这里判断一下商品条码是否为空就行
// 因为是空数据这里判断一下商品条码是否为空就行
if
(
sku
.
barCode
===
''
)
{
if
(
sku
.
barCode
===
''
)
{
...
@@ -150,7 +151,7 @@ const submitForm = async () => {
...
@@ -150,7 +151,7 @@ const submitForm = async () => {
})
})
deepCopyFormData
.
sliderPicUrls
=
newSliderPicUrls
deepCopyFormData
.
sliderPicUrls
=
newSliderPicUrls
// 校验都通过后提交表单
// 校验都通过后提交表单
const
data
=
deepCopyFormData
as
ProductSpuApi
.
Spu
Type
const
data
=
deepCopyFormData
as
ProductSpuApi
.
Spu
const
id
=
params
.
spuId
as
number
const
id
=
params
.
spuId
as
number
if
(
!
id
)
{
if
(
!
id
)
{
await
ProductSpuApi
.
createSpu
(
data
)
await
ProductSpuApi
.
createSpu
(
data
)
...
...
src/views/mall/product/spu/components/BasicInfoForm.vue
View file @
e5559777
...
@@ -7,7 +7,7 @@
...
@@ -7,7 +7,7 @@
</el-form-item>
</el-form-item>
</el-col>
</el-col>
<el-col
:span=
"12"
>
<el-col
:span=
"12"
>
<!-- TODO @puhui999:只能选根节点 -->
<!-- TODO @puhui999:只能选根节点
fix: 已完善
-->
<el-form-item
label=
"商品分类"
prop=
"categoryId"
>
<el-form-item
label=
"商品分类"
prop=
"categoryId"
>
<el-tree-select
<el-tree-select
v-model=
"formData.categoryId"
v-model=
"formData.categoryId"
...
@@ -17,6 +17,7 @@
...
@@ -17,6 +17,7 @@
class=
"w-1/1"
class=
"w-1/1"
node-key=
"id"
node-key=
"id"
placeholder=
"请选择商品分类"
placeholder=
"请选择商品分类"
@
change=
"nodeClick"
/>
/>
</el-form-item>
</el-form-item>
</el-col>
</el-col>
...
@@ -119,9 +120,9 @@
...
@@ -119,9 +120,9 @@
import
{
PropType
}
from
'vue'
import
{
PropType
}
from
'vue'
import
{
copyValueToTarget
}
from
'@/utils'
import
{
copyValueToTarget
}
from
'@/utils'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
defaultProps
,
handleTree
}
from
'@/utils/tree'
import
{
checkSelectedNode
,
defaultProps
,
handleTree
}
from
'@/utils/tree'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
type
{
Spu
Type
}
from
'@/api/mall/product/spu'
import
type
{
Spu
}
from
'@/api/mall/product/spu'
import
{
UploadImg
,
UploadImgs
}
from
'@/components/UploadFile'
import
{
UploadImg
,
UploadImgs
}
from
'@/components/UploadFile'
import
{
ProductAttributes
,
ProductAttributesAddForm
,
SkuList
}
from
'./index'
import
{
ProductAttributes
,
ProductAttributesAddForm
,
SkuList
}
from
'./index'
import
*
as
ProductCategoryApi
from
'@/api/mall/product/category'
import
*
as
ProductCategoryApi
from
'@/api/mall/product/category'
...
@@ -131,7 +132,7 @@ const message = useMessage() // 消息弹窗
...
@@ -131,7 +132,7 @@ const message = useMessage() // 消息弹窗
const
props
=
defineProps
({
const
props
=
defineProps
({
propFormData
:
{
propFormData
:
{
type
:
Object
as
PropType
<
Spu
Type
>
,
type
:
Object
as
PropType
<
Spu
>
,
default
:
()
=>
{}
default
:
()
=>
{}
},
},
activeName
:
propTypes
.
string
.
def
(
''
)
activeName
:
propTypes
.
string
.
def
(
''
)
...
@@ -144,7 +145,7 @@ const skuListRef = ref() // 商品属性列表Ref
...
@@ -144,7 +145,7 @@ const skuListRef = ref() // 商品属性列表Ref
const
generateSkus
=
(
propertyList
)
=>
{
const
generateSkus
=
(
propertyList
)
=>
{
skuListRef
.
value
.
generateTableData
(
propertyList
)
skuListRef
.
value
.
generateTableData
(
propertyList
)
}
}
const
formData
=
reactive
<
Spu
Type
>
({
const
formData
=
reactive
<
Spu
>
({
name
:
''
,
// 商品名称
name
:
''
,
// 商品名称
categoryId
:
null
,
// 商品分类
categoryId
:
null
,
// 商品分类
keyword
:
''
,
// 关键字
keyword
:
''
,
// 关键字
...
@@ -185,26 +186,24 @@ watch(
...
@@ -185,26 +186,24 @@ watch(
formData
.
sliderPicUrls
=
data
[
'sliderPicUrls'
].
map
((
item
)
=>
({
formData
.
sliderPicUrls
=
data
[
'sliderPicUrls'
].
map
((
item
)
=>
({
url
:
item
url
:
item
}))
}))
// TODO @puhui999:if return,减少嵌套层级
// 只有是多规格才处理
// 只有是多规格才处理
if
(
formData
.
specType
)
{
if
(
!
formData
.
specType
)
return
// 直接拿返回的 skus 属性逆向生成出 propertyList
// 直接拿返回的 skus 属性逆向生成出 propertyList
const
properties
=
[]
const
properties
=
[]
formData
.
skus
.
forEach
((
sku
)
=>
{
formData
.
skus
.
forEach
((
sku
)
=>
{
sku
.
properties
.
forEach
(({
propertyId
,
propertyName
,
valueId
,
valueName
})
=>
{
sku
.
properties
.
forEach
(({
propertyId
,
propertyName
,
valueId
,
valueName
})
=>
{
// 添加属性
// 添加属性
if
(
!
properties
.
some
((
item
)
=>
item
.
id
===
propertyId
))
{
if
(
!
properties
.
some
((
item
)
=>
item
.
id
===
propertyId
))
{
properties
.
push
({
id
:
propertyId
,
name
:
propertyName
,
values
:
[]
})
properties
.
push
({
id
:
propertyId
,
name
:
propertyName
,
values
:
[]
})
}
}
// 添加属性值
// 添加属性值
const
index
=
properties
.
findIndex
((
item
)
=>
item
.
id
===
propertyId
)
const
index
=
properties
.
findIndex
((
item
)
=>
item
.
id
===
propertyId
)
if
(
!
properties
[
index
].
values
.
some
((
value
)
=>
value
.
id
===
valueId
))
{
if
(
!
properties
[
index
].
values
.
some
((
value
)
=>
value
.
id
===
valueId
))
{
properties
[
index
].
values
.
push
({
id
:
valueId
,
name
:
valueName
})
properties
[
index
].
values
.
push
({
id
:
valueId
,
name
:
valueName
})
}
}
})
})
})
propertyList
.
value
=
properties
})
}
propertyList
.
value
=
properties
},
},
{
{
immediate
:
true
immediate
:
true
...
@@ -216,6 +215,11 @@ watch(
...
@@ -216,6 +215,11 @@ watch(
*/
*/
const
emit
=
defineEmits
([
'update:activeName'
])
const
emit
=
defineEmits
([
'update:activeName'
])
const
validate
=
async
()
=>
{
const
validate
=
async
()
=>
{
// 校验 sku
if
(
!
skuListRef
.
value
.
validateSku
())
{
message
.
warning
(
'商品相关价格不能低于0.01元!!'
)
throw
new
Error
(
'商品相关价格不能低于0.01元!!'
)
}
// 校验表单
// 校验表单
if
(
!
productSpuBasicInfoRef
)
return
if
(
!
productSpuBasicInfoRef
)
return
return
await
unref
(
productSpuBasicInfoRef
).
validate
((
valid
)
=>
{
return
await
unref
(
productSpuBasicInfoRef
).
validate
((
valid
)
=>
{
...
@@ -263,6 +267,15 @@ const onChangeSpec = () => {
...
@@ -263,6 +267,15 @@ const onChangeSpec = () => {
}
}
const
categoryList
=
ref
([])
// 分类树
const
categoryList
=
ref
([])
// 分类树
/**
* 选择分类时触发校验
*/
const
nodeClick
=
()
=>
{
if
(
!
checkSelectedNode
(
categoryList
.
value
,
formData
.
categoryId
))
{
formData
.
categoryId
=
null
message
.
warning
(
'必须选择二级节点!!'
)
}
}
const
brandList
=
ref
([])
// 精简商品品牌列表
const
brandList
=
ref
([])
// 精简商品品牌列表
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
// 获得分类树
// 获得分类树
...
...
src/views/mall/product/spu/components/DescriptionForm.vue
View file @
e5559777
...
@@ -7,7 +7,7 @@
...
@@ -7,7 +7,7 @@
</el-form>
</el-form>
</
template
>
</
template
>
<
script
lang=
"ts"
name=
"DescriptionForm"
setup
>
<
script
lang=
"ts"
name=
"DescriptionForm"
setup
>
import
type
{
Spu
Type
}
from
'@/api/mall/product/spu'
import
type
{
Spu
}
from
'@/api/mall/product/spu'
import
{
Editor
}
from
'@/components/Editor'
import
{
Editor
}
from
'@/components/Editor'
import
{
PropType
}
from
'vue'
import
{
PropType
}
from
'vue'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
propTypes
}
from
'@/utils/propTypes'
...
@@ -16,13 +16,13 @@ import { copyValueToTarget } from '@/utils'
...
@@ -16,13 +16,13 @@ import { copyValueToTarget } from '@/utils'
const
message
=
useMessage
()
// 消息弹窗
const
message
=
useMessage
()
// 消息弹窗
const
props
=
defineProps
({
const
props
=
defineProps
({
propFormData
:
{
propFormData
:
{
type
:
Object
as
PropType
<
Spu
Type
>
,
type
:
Object
as
PropType
<
Spu
>
,
default
:
()
=>
{}
default
:
()
=>
{}
},
},
activeName
:
propTypes
.
string
.
def
(
''
)
activeName
:
propTypes
.
string
.
def
(
''
)
})
})
const
descriptionFormRef
=
ref
()
// 表单Ref
const
descriptionFormRef
=
ref
()
// 表单Ref
const
formData
=
ref
<
Spu
Type
>
({
const
formData
=
ref
<
Spu
>
({
description
:
''
// 商品详情
description
:
''
// 商品详情
})
})
// 表单规则
// 表单规则
...
...
src/views/mall/product/spu/components/OtherSettingsForm.vue
View file @
e5559777
...
@@ -52,7 +52,7 @@
...
@@ -52,7 +52,7 @@
</el-form>
</el-form>
</
template
>
</
template
>
<
script
lang=
"ts"
name=
"OtherSettingsForm"
setup
>
<
script
lang=
"ts"
name=
"OtherSettingsForm"
setup
>
import
type
{
Spu
Type
}
from
'@/api/mall/product/spu'
import
type
{
Spu
}
from
'@/api/mall/product/spu'
import
{
PropType
}
from
'vue'
import
{
PropType
}
from
'vue'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
copyValueToTarget
}
from
'@/utils'
import
{
copyValueToTarget
}
from
'@/utils'
...
@@ -61,7 +61,7 @@ const message = useMessage() // 消息弹窗
...
@@ -61,7 +61,7 @@ const message = useMessage() // 消息弹窗
const
props
=
defineProps
({
const
props
=
defineProps
({
propFormData
:
{
propFormData
:
{
type
:
Object
as
PropType
<
Spu
Type
>
,
type
:
Object
as
PropType
<
Spu
>
,
default
:
()
=>
{}
default
:
()
=>
{}
},
},
activeName
:
propTypes
.
string
.
def
(
''
)
activeName
:
propTypes
.
string
.
def
(
''
)
...
@@ -69,7 +69,7 @@ const props = defineProps({
...
@@ -69,7 +69,7 @@ const props = defineProps({
const
otherSettingsFormRef
=
ref
()
// 表单Ref
const
otherSettingsFormRef
=
ref
()
// 表单Ref
// 表单数据
// 表单数据
const
formData
=
ref
<
Spu
Type
>
({
const
formData
=
ref
<
Spu
>
({
sort
:
1
,
// 商品排序
sort
:
1
,
// 商品排序
giveIntegral
:
1
,
// 赠送积分
giveIntegral
:
1
,
// 赠送积分
virtualSalesCount
:
1
,
// 虚拟销量
virtualSalesCount
:
1
,
// 虚拟销量
...
...
src/views/mall/product/spu/components/ProductAttributesAddForm.vue
View file @
e5559777
...
@@ -90,8 +90,7 @@ const submitForm = async () => {
...
@@ -90,8 +90,7 @@ const submitForm = async () => {
/** 重置表单 */
/** 重置表单 */
const
resetForm
=
()
=>
{
const
resetForm
=
()
=>
{
formData
.
value
=
{
formData
.
value
=
{
name
:
''
,
name
:
''
remark
:
''
}
}
formRef
.
value
?.
resetFields
()
formRef
.
value
?.
resetFields
()
}
}
...
...
src/views/mall/product/spu/components/SkuList.vue
View file @
e5559777
<
template
>
<
template
>
<el-table
<el-table
:data=
"isBatch ? skuList : formData.skus"
:data=
"isBatch ? skuList : formData
!
.skus"
border
border
class=
"tabNumWidth"
class=
"tabNumWidth"
max-height=
"500"
max-height=
"500"
...
@@ -11,7 +11,7 @@
...
@@ -11,7 +11,7 @@
<UploadImg
v-model=
"row.picUrl"
height=
"80px"
width=
"100%"
/>
<UploadImg
v-model=
"row.picUrl"
height=
"80px"
width=
"100%"
/>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<
template
v-if=
"formData.specType && !isBatch"
>
<
template
v-if=
"formData
!
.specType && !isBatch"
>
<!-- 根据商品属性动态添加 -->
<!-- 根据商品属性动态添加 -->
<el-table-column
<el-table-column
v-for=
"(item, index) in tableHeaders"
v-for=
"(item, index) in tableHeaders"
...
@@ -21,8 +21,10 @@
...
@@ -21,8 +21,10 @@
min-width=
"120"
min-width=
"120"
>
>
<template
#
default=
"
{ row }">
<template
#
default=
"
{ row }">
<!-- TODO puhui999:展示成蓝色,有点区分度哈 -->
<!-- TODO puhui999:展示成蓝色,有点区分度哈 fix: 字体加粗,颜色使用 #99a9bf 蓝色有点不好看哈哈-->
{{
row
.
properties
[
index
]?.
valueName
}}
<span
style=
"font-weight: bold; color: #99a9bf"
>
{{
row
.
properties
[
index
]?.
valueName
}}
</span>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
</template>
</template>
...
@@ -73,7 +75,7 @@
...
@@ -73,7 +75,7 @@
<el-input-number
v-model=
"row.volume"
:min=
"0"
:precision=
"2"
:step=
"0.1"
class=
"w-100%"
/>
<el-input-number
v-model=
"row.volume"
:min=
"0"
:precision=
"2"
:step=
"0.1"
class=
"w-100%"
/>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<
template
v-if=
"formData.subCommissionType"
>
<
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 }">
<template
#
default=
"
{ row }">
<el-input-number
<el-input-number
...
@@ -97,7 +99,7 @@
...
@@ -97,7 +99,7 @@
</
template
>
</
template
>
</el-table-column>
</el-table-column>
</template>
</template>
<el-table-column
v-if=
"formData.specType"
align=
"center"
fixed=
"right"
label=
"操作"
width=
"80"
>
<el-table-column
v-if=
"formData
?
.specType"
align=
"center"
fixed=
"right"
label=
"操作"
width=
"80"
>
<
template
#
default=
"{ row }"
>
<
template
#
default=
"{ row }"
>
<el-button
v-if=
"isBatch"
link
size=
"small"
type=
"primary"
@
click=
"batchAdd"
>
<el-button
v-if=
"isBatch"
link
size=
"small"
type=
"primary"
@
click=
"batchAdd"
>
批量添加
批量添加
...
@@ -108,15 +110,15 @@
...
@@ -108,15 +110,15 @@
</el-table>
</el-table>
</template>
</template>
<
script
lang=
"ts"
name=
"SkuList"
setup
>
<
script
lang=
"ts"
name=
"SkuList"
setup
>
import
{
PropType
}
from
'vue'
import
{
PropType
,
Ref
}
from
'vue'
import
{
copyValueToTarget
}
from
'@/utils'
import
{
copyValueToTarget
}
from
'@/utils'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
UploadImg
}
from
'@/components/UploadFile'
import
{
UploadImg
}
from
'@/components/UploadFile'
import
type
{
Property
,
Sku
Type
,
SpuType
}
from
'@/api/mall/product/spu'
import
type
{
Property
,
Sku
,
Spu
}
from
'@/api/mall/product/spu'
const
props
=
defineProps
({
const
props
=
defineProps
({
propFormData
:
{
propFormData
:
{
type
:
Object
as
PropType
<
Spu
Type
>
,
type
:
Object
as
PropType
<
Spu
>
,
default
:
()
=>
{}
default
:
()
=>
{}
},
},
propertyList
:
{
propertyList
:
{
...
@@ -125,8 +127,8 @@ const props = defineProps({
...
@@ -125,8 +127,8 @@ const props = defineProps({
},
},
isBatch
:
propTypes
.
bool
.
def
(
false
)
// 是否作为批量操作组件
isBatch
:
propTypes
.
bool
.
def
(
false
)
// 是否作为批量操作组件
})
})
const
formData
=
ref
<
SpuType
>
()
// 表单数据
const
formData
:
Ref
<
Spu
|
undefined
>
=
ref
<
Spu
>
()
// 表单数据
const
skuList
=
ref
<
Sku
Type
[]
>
([
const
skuList
=
ref
<
Sku
[]
>
([
{
{
price
:
0
,
// 商品价格
price
:
0
,
// 商品价格
marketPrice
:
0
,
// 市场价
marketPrice
:
0
,
// 市场价
...
@@ -140,24 +142,37 @@ const skuList = ref<SkuType[]>([
...
@@ -140,24 +142,37 @@ const skuList = ref<SkuType[]>([
subCommissionSecondPrice
:
0
// 二级分销的佣金
subCommissionSecondPrice
:
0
// 二级分销的佣金
}
}
])
// 批量添加时的临时数据
])
// 批量添加时的临时数据
// TODO @puhui999:保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
/** 批量添加 */
/** 批量添加 */
const
batchAdd
=
()
=>
{
const
batchAdd
=
()
=>
{
formData
.
value
.
skus
.
forEach
((
item
)
=>
{
formData
.
value
!
.
skus
.
forEach
((
item
)
=>
{
copyValueToTarget
(
item
,
skuList
.
value
[
0
])
copyValueToTarget
(
item
,
skuList
.
value
[
0
])
})
})
}
}
/** 删除 sku */
/** 删除 sku */
const
deleteSku
=
(
row
)
=>
{
const
deleteSku
=
(
row
)
=>
{
const
index
=
formData
.
value
.
skus
.
findIndex
(
const
index
=
formData
.
value
!
.
skus
.
findIndex
(
// 直接把列表转成字符串比较
// 直接把列表转成字符串比较
(
sku
)
=>
JSON
.
stringify
(
sku
.
properties
)
===
JSON
.
stringify
(
row
.
properties
)
(
sku
)
=>
JSON
.
stringify
(
sku
.
properties
)
===
JSON
.
stringify
(
row
.
properties
)
)
)
formData
.
value
.
skus
.
splice
(
index
,
1
)
formData
.
value
!
.
skus
.
splice
(
index
,
1
)
}
}
const
tableHeaders
=
ref
<
{
prop
:
string
;
label
:
string
}[]
>
([])
// 多属性表头
const
tableHeaders
=
ref
<
{
prop
:
string
;
label
:
string
}[]
>
([])
// 多属性表头
/**
* 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
*/
const
validateSku
=
():
boolean
=>
{
const
checks
=
[
'price'
,
'marketPrice'
,
'costPrice'
]
let
validate
=
true
// 默认通过
for
(
const
sku
of
formData
.
value
!
.
skus
)
{
if
(
checks
.
some
((
check
)
=>
sku
[
check
]
<
0.01
))
{
validate
=
false
// 只要有一个不通过则直接不通过
break
}
}
return
validate
}
/**
/**
* 将传进来的值赋值给 skuList
* 将传进来的值赋值给 skuList
...
@@ -185,14 +200,13 @@ const generateTableData = (propertyList: any[]) => {
...
@@ -185,14 +200,13 @@ const generateTableData = (propertyList: any[]) => {
valueName
:
v
.
name
valueName
:
v
.
name
}))
}))
)
)
// TODO @puhui:是不是 buildSkuList,这样容易理解一点哈。item 改成 sku
const
buildSkuList
=
build
(
propertyValues
)
const
buildList
=
build
(
propertyValues
)
// 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表
// 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表
if
(
!
validateData
(
propertyList
))
{
if
(
!
validateData
(
propertyList
))
{
// 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表
// 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表
formData
.
value
!
.
skus
=
[]
formData
.
value
!
.
skus
=
[]
}
}
for
(
const
item
of
buildList
)
{
for
(
const
item
of
build
Sku
List
)
{
const
row
=
{
const
row
=
{
properties
:
Array
.
isArray
(
item
)
?
item
:
[
item
],
// 如果只有一个属性的话返回的是一个 property 对象
properties
:
Array
.
isArray
(
item
)
?
item
:
[
item
],
// 如果只有一个属性的话返回的是一个 property 对象
price
:
0
,
price
:
0
,
...
@@ -213,7 +227,7 @@ const generateTableData = (propertyList: any[]) => {
...
@@ -213,7 +227,7 @@ const generateTableData = (propertyList: any[]) => {
if
(
index
!==
-
1
)
{
if
(
index
!==
-
1
)
{
continue
continue
}
}
formData
.
value
.
skus
.
push
(
row
)
formData
.
value
!
.
skus
.
push
(
row
)
}
}
}
}
...
@@ -222,7 +236,7 @@ const generateTableData = (propertyList: any[]) => {
...
@@ -222,7 +236,7 @@ const generateTableData = (propertyList: any[]) => {
*/
*/
const
validateData
=
(
propertyList
:
any
[])
=>
{
const
validateData
=
(
propertyList
:
any
[])
=>
{
const
skuPropertyIds
=
[]
const
skuPropertyIds
=
[]
formData
.
value
.
skus
.
forEach
((
sku
)
=>
formData
.
value
!
.
skus
.
forEach
((
sku
)
=>
sku
.
properties
sku
.
properties
?.
map
((
property
)
=>
property
.
propertyId
)
?.
map
((
property
)
=>
property
.
propertyId
)
.
forEach
((
propertyId
)
=>
{
.
forEach
((
propertyId
)
=>
{
...
@@ -263,7 +277,7 @@ watch(
...
@@ -263,7 +277,7 @@ watch(
()
=>
props
.
propertyList
,
()
=>
props
.
propertyList
,
(
propertyList
)
=>
{
(
propertyList
)
=>
{
// 如果不是多规格则结束
// 如果不是多规格则结束
if
(
!
formData
.
value
.
specType
)
{
if
(
!
formData
.
value
!
.
specType
)
{
return
return
}
}
// 如果当前组件作为批量添加数据使用,则重置表数据
// 如果当前组件作为批量添加数据使用,则重置表数据
...
@@ -313,5 +327,5 @@ watch(
...
@@ -313,5 +327,5 @@ watch(
}
}
)
)
// 暴露出生成 sku 方法,给添加属性成功时调用
// 暴露出生成 sku 方法,给添加属性成功时调用
defineExpose
({
generateTableData
})
defineExpose
({
generateTableData
,
validateSku
})
</
script
>
</
script
>
src/views/mall/product/spu/index.vue
View file @
e5559777
...
@@ -8,18 +8,16 @@
...
@@ -8,18 +8,16 @@
class=
"-mb-15px"
class=
"-mb-15px"
label-width=
"68px"
label-width=
"68px"
>
>
<!-- TODO @puhui999:品牌应该是数据下拉哈 -->
<el-form-item
label=
"商品名称"
prop=
"name"
>
<el-form-item
label=
"品牌名称"
prop=
"name"
>
<el-input
<el-input
v-model=
"queryParams.name"
v-model=
"queryParams.name"
class=
"!w-240px"
class=
"!w-240px"
clearable
clearable
placeholder=
"请输入
品牌
名称"
placeholder=
"请输入
商品
名称"
@
keyup
.
enter=
"handleQuery"
@
keyup
.
enter=
"handleQuery"
/>
/>
</el-form-item>
</el-form-item>
<!-- TODO 分类只能选择二级分类目前还没做,还是先以联调通顺为主 -->
<!-- TODO 分类只能选择二级分类目前还没做,还是先以联调通顺为主 fixL: 已完善 -->
<!-- TODO puhui999:我们要不改成支持选择一级。如果选择一级,后端要递归查询下子分类,然后去 in? -->
<el-form-item
label=
"商品分类"
prop=
"categoryId"
>
<el-form-item
label=
"商品分类"
prop=
"categoryId"
>
<el-tree-select
<el-tree-select
v-model=
"queryParams.categoryId"
v-model=
"queryParams.categoryId"
...
@@ -29,6 +27,7 @@
...
@@ -29,6 +27,7 @@
class=
"w-1/1"
class=
"w-1/1"
node-key=
"id"
node-key=
"id"
placeholder=
"请选择商品分类"
placeholder=
"请选择商品分类"
@
change=
"nodeClick"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label=
"创建时间"
prop=
"createTime"
>
<el-form-item
label=
"创建时间"
prop=
"createTime"
>
...
@@ -80,31 +79,60 @@
...
@@ -80,31 +79,60 @@
/>
/>
</el-tabs>
</el-tabs>
<el-table
v-loading=
"loading"
:data=
"list"
>
<el-table
v-loading=
"loading"
:data=
"list"
>
<!-- TODO puhui:这几个属性哈,一行三个
<!-- TODO puhui:这几个属性哈,一行三个
fix
商品分类:服装鞋包/箱包
商品分类:服装鞋包/箱包
商品市场价格:100.00
商品市场价格:100.00
成本价:0.00
成本价:0.00
收藏:5
收藏:5
虚拟销量:999
-->
虚拟销量:999 -->
<el-table-column
type=
"expand"
width=
"30"
>
<el-table-column
type=
"expand"
width=
"30"
>
<template
#
default=
"
{ row }">
<template
#
default=
"
{ row }">
<el-form
class=
"demo-table-expand"
inline
label-position=
"left"
>
<el-form
class=
"demo-table-expand"
label-position=
"left"
>
<el-form-item
label=
"市场价:"
>
<el-row>
<span>
{{
formatToFraction
(
row
.
marketPrice
)
}}
</span>
<el-col
:span=
"24"
>
</el-form-item>
<el-row>
<el-form-item
label=
"成本价:"
>
<el-col
:span=
"8"
>
<span>
{{
formatToFraction
(
row
.
costPrice
)
}}
</span>
<el-form-item
label=
"商品分类:"
>
</el-form-item>
<span>
{{
categoryString
(
row
.
categoryId
)
}}
</span>
<el-form-item
label=
"虚拟销量:"
>
</el-form-item>
<span>
{{
row
.
virtualSalesCount
}}
</span>
</el-col>
</el-form-item>
<el-col
:span=
"8"
>
<el-form-item
label=
"市场价:"
>
<span>
{{
formatToFraction
(
row
.
marketPrice
)
}}
</span>
</el-form-item>
</el-col>
<el-col
:span=
"8"
>
<el-form-item
label=
"成本价:"
>
<span>
{{
formatToFraction
(
row
.
costPrice
)
}}
</span>
</el-form-item>
</el-col>
</el-row>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"24"
>
<el-row>
<el-col
:span=
"8"
>
<el-form-item
label=
"收藏:"
>
<!-- TODO 没有这个属性,暂时写死 5 个 -->
<span>
5
</span>
</el-form-item>
</el-col>
<el-col
:span=
"8"
>
<el-form-item
label=
"虚拟销量:"
>
<span>
{{
row
.
virtualSalesCount
}}
</span>
</el-form-item>
</el-col>
</el-row>
</el-col>
</el-row>
</el-form>
</el-form>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
key=
"id"
align=
"center"
label=
"商品编号"
prop=
"id"
/>
<el-table-column
key=
"id"
align=
"center"
label=
"商品编号"
prop=
"id"
/>
<el-table-column
label=
"商品图"
min-width=
"80"
>
<el-table-column
label=
"商品图"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
<
template
#
default=
"{ row }"
>
<el-image
:src=
"row.picUrl"
@
click=
"imagePreview(row.picUrl)"
class=
"w-30px h-30px
"
/>
<el-image
:src=
"row.picUrl"
class=
"w-30px h-30px"
@
click=
"imagePreview(row.picUrl)
"
/>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
:show-overflow-tooltip=
"true"
label=
"商品名称"
min-width=
"300"
prop=
"name"
/>
<el-table-column
:show-overflow-tooltip=
"true"
label=
"商品名称"
min-width=
"300"
prop=
"name"
/>
...
@@ -202,7 +230,7 @@ import { TabsPaneContext } from 'element-plus'
...
@@ -202,7 +230,7 @@ import { TabsPaneContext } from 'element-plus'
import
{
cloneDeep
}
from
'lodash-es'
import
{
cloneDeep
}
from
'lodash-es'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
createImageViewer
}
from
'@/components/ImageViewer'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
{
defaultProps
,
handleTree
}
from
'@/utils/tree'
import
{
checkSelectedNode
,
defaultProps
,
handleTree
,
treeToString
}
from
'@/utils/tree'
import
{
ProductSpuStatusEnum
}
from
'@/utils/constants'
import
{
ProductSpuStatusEnum
}
from
'@/utils/constants'
import
{
formatToFraction
}
from
'@/utils'
import
{
formatToFraction
}
from
'@/utils'
import
download
from
'@/utils/download'
import
download
from
'@/utils/download'
...
@@ -391,7 +419,7 @@ const handleExport = async () => {
...
@@ -391,7 +419,7 @@ const handleExport = async () => {
}
}
}
}
// 监听路由变化更新列表
TODO @puhui999:这个是必须加的么?fix: 因为编辑表单是以路由的方式打开,保存表单后列表不会刷新
// 监听路由变化更新列表
,解决商品保存后,列表不刷新的问题。
watch
(
watch
(
()
=>
currentRoute
.
value
,
()
=>
currentRoute
.
value
,
()
=>
{
()
=>
{
...
@@ -400,6 +428,22 @@ watch(
...
@@ -400,6 +428,22 @@ watch(
)
)
const
categoryList
=
ref
()
// 分类树
const
categoryList
=
ref
()
// 分类树
/**
* 获取分类的节点的完整结构
* @param categoryId 分类id
*/
const
categoryString
=
(
categoryId
)
=>
{
return
treeToString
(
categoryList
.
value
,
categoryId
)
}
/**
* 校验所选是否为二级节点
*/
const
nodeClick
=
()
=>
{
if
(
!
checkSelectedNode
(
categoryList
.
value
,
queryParams
.
value
.
categoryId
))
{
queryParams
.
value
.
categoryId
=
null
message
.
warning
(
'必须选择二级节点!!'
)
}
}
/** 初始化 **/
/** 初始化 **/
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
await
getTabsCount
()
await
getTabsCount
()
...
...
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