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
e92361ed
authored
May 06, 2023
by
YunaiV
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
code review 商品管理的实现
parent
4a965b8c
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
108 additions
and
48 deletions
+108
-48
src/api/mall/product/management/spu.ts
+12
-4
src/router/modules/remaining.ts
+2
-2
src/utils/constants.ts
+1
-0
src/utils/object.ts
+1
-0
src/views/mall/product/spu/addForm.vue
+6
-2
src/views/mall/product/spu/components/BasicInfoForm.vue
+18
-15
src/views/mall/product/spu/components/DescriptionForm.vue
+8
-5
src/views/mall/product/spu/components/OtherSettingsForm.vue
+10
-8
src/views/mall/product/spu/components/ProductAttributes.vue
+3
-0
src/views/mall/product/spu/components/ProductAttributesAddForm.vue
+0
-0
src/views/mall/product/spu/components/SkuList.vue
+12
-4
src/views/mall/product/spu/components/index.ts
+0
-0
src/views/mall/product/spu/index.vue
+35
-8
No files found.
src/api/mall/product/management/spu.ts
View file @
e92361ed
import
request
from
'@/config/axios'
import
request
from
'@/config/axios'
import
type
{
SpuType
}
from
'./type/spuType'
import
type
{
SpuType
}
from
'./type/spuType'
// TODO @puhui999: type 和 api 一起放,简单一点哈~
// 获得spu列表
// TODO @puhui999:中英文之间有空格
export
const
getSpuList
=
(
params
:
any
)
=>
{
// 获得spu列表 TODO @puhui999:这个是 getSpuPage 哈
export
const
getSpuList
=
(
params
:
PageParam
)
=>
{
return
request
.
get
({
url
:
'/product/spu/page'
,
params
})
return
request
.
get
({
url
:
'/product/spu/page'
,
params
})
}
}
// 获得spu列表tabsCount
// 获得spu列表tabsCount
export
const
getTabsCount
=
()
=>
{
export
const
getTabsCount
=
()
=>
{
return
request
.
get
({
url
:
'/product/spu/tabsCount'
})
return
request
.
get
({
url
:
'/product/spu/tabsCount'
})
}
}
// 创建商品spu
// 创建商品spu
export
const
createSpu
=
(
data
:
SpuType
)
=>
{
export
const
createSpu
=
(
data
:
SpuType
)
=>
{
return
request
.
post
({
url
:
'/product/spu/create'
,
data
})
return
request
.
post
({
url
:
'/product/spu/create'
,
data
})
}
}
// 更新商品spu
// 更新商品spu
export
const
updateSpu
=
(
data
:
SpuType
)
=>
{
export
const
updateSpu
=
(
data
:
SpuType
)
=>
{
return
request
.
put
({
url
:
'/product/spu/update'
,
data
})
return
request
.
put
({
url
:
'/product/spu/update'
,
data
})
}
}
// 更新商品spu status
// 更新商品spu status
export
const
updateStatus
=
(
data
:
{
id
:
number
;
status
:
number
})
=>
{
export
const
updateStatus
=
(
data
:
{
id
:
number
;
status
:
number
})
=>
{
return
request
.
put
({
url
:
'/product/spu/updateStatus'
,
data
})
return
request
.
put
({
url
:
'/product/spu/updateStatus'
,
data
})
}
}
// 获得商品spu
// 获得商品 spu
export
const
getSpu
=
(
id
:
number
)
=>
{
export
const
getSpu
=
(
id
:
number
)
=>
{
return
request
.
get
({
url
:
`/product/spu/get-detail?id=
${
id
}
`
})
return
request
.
get
({
url
:
`/product/spu/get-detail?id=
${
id
}
`
})
}
}
// 删除商品Spu
// 删除商品Spu
export
const
deleteSpu
=
(
id
:
number
)
=>
{
export
const
deleteSpu
=
(
id
:
number
)
=>
{
return
request
.
delete
({
url
:
`/product/spu/delete?id=
${
id
}
`
})
return
request
.
delete
({
url
:
`/product/spu/delete?id=
${
id
}
`
})
...
...
src/router/modules/remaining.ts
View file @
e92361ed
...
@@ -355,8 +355,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
...
@@ -355,8 +355,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
},
},
children
:
[
children
:
[
{
{
path
:
'productManagementAdd'
,
path
:
'productManagementAdd'
,
// TODO @puhui999:最好拆成 add 和 edit 两个路由;添加商品;修改商品
component
:
()
=>
import
(
'@/views/mall/product/
management
/addForm.vue'
),
component
:
()
=>
import
(
'@/views/mall/product/
spu
/addForm.vue'
),
name
:
'ProductManagementAdd'
,
name
:
'ProductManagementAdd'
,
meta
:
{
meta
:
{
noCache
:
true
,
noCache
:
true
,
...
...
src/utils/constants.ts
View file @
e92361ed
...
@@ -220,6 +220,7 @@ export const PayRefundStatusEnum = {
...
@@ -220,6 +220,7 @@ export const PayRefundStatusEnum = {
name
:
'退款关闭'
name
:
'退款关闭'
}
}
}
}
/**
/**
* 商品SPU枚举类
* 商品SPU枚举类
*/
*/
...
...
src/utils/object.ts
View file @
e92361ed
// TODO @puhui999:这个方法,可以考虑放到 index.js
/**
/**
* 将值复制到目标对象,且以目标对象属性为准,例:target: {a:1} source:{a:2,b:3} 结果为:{a:2}
* 将值复制到目标对象,且以目标对象属性为准,例:target: {a:1} source:{a:2,b:3} 结果为:{a:2}
* @param target 目标对象
* @param target 目标对象
...
...
src/views/mall/product/
management
/addForm.vue
→
src/views/mall/product/
spu
/addForm.vue
View file @
e92361ed
...
@@ -37,7 +37,6 @@ import { BasicInfoForm, DescriptionForm, OtherSettingsForm } from './components'
...
@@ -37,7 +37,6 @@ import { BasicInfoForm, DescriptionForm, OtherSettingsForm } from './components'
import
type
{
SpuType
}
from
'@/api/mall/product/management/type/spuType'
// 业务api
import
type
{
SpuType
}
from
'@/api/mall/product/management/type/spuType'
// 业务api
import
*
as
managementApi
from
'@/api/mall/product/management/spu'
import
*
as
managementApi
from
'@/api/mall/product/management/spu'
import
*
as
PropertyApi
from
'@/api/mall/product/property'
import
*
as
PropertyApi
from
'@/api/mall/product/property'
const
{
t
}
=
useI18n
()
// 国际化
const
{
t
}
=
useI18n
()
// 国际化
const
message
=
useMessage
()
// 消息弹窗
const
message
=
useMessage
()
// 消息弹窗
const
{
push
,
currentRoute
}
=
useRouter
()
// 路由
const
{
push
,
currentRoute
}
=
useRouter
()
// 路由
...
@@ -69,7 +68,7 @@ const formData = ref<SpuType>({
...
@@ -69,7 +68,7 @@ const formData = ref<SpuType>({
skus
:
[
skus
:
[
{
{
/**
/**
* 商品价格,单位:分
* 商品价格,单位:分
TODO @puhui999:注释放在尾巴哈,简洁一点~
*/
*/
price
:
0
,
price
:
0
,
/**
/**
...
@@ -120,6 +119,7 @@ const formData = ref<SpuType>({
...
@@ -120,6 +119,7 @@ const formData = ref<SpuType>({
recommendNew
:
false
,
// 是否新品
recommendNew
:
false
,
// 是否新品
recommendGood
:
false
// 是否优品
recommendGood
:
false
// 是否优品
})
})
/** 获得详情 */
/** 获得详情 */
const
getDetail
=
async
()
=>
{
const
getDetail
=
async
()
=>
{
const
id
=
query
.
id
as
unknown
as
number
const
id
=
query
.
id
as
unknown
as
number
...
@@ -129,6 +129,7 @@ const getDetail = async () => {
...
@@ -129,6 +129,7 @@ const getDetail = async () => {
const
res
=
(
await
managementApi
.
getSpu
(
id
))
as
SpuType
const
res
=
(
await
managementApi
.
getSpu
(
id
))
as
SpuType
formData
.
value
=
res
formData
.
value
=
res
// 直接取第一个值就能得到所有属性的id
// 直接取第一个值就能得到所有属性的id
// TODO @puhui999:可以直接拿 propertyName 拼接处规格 id + 属性,可以看下商品 uniapp 详情的做法
const
propertyIds
=
res
.
skus
[
0
]?.
properties
.
map
((
item
)
=>
item
.
propertyId
)
const
propertyIds
=
res
.
skus
[
0
]?.
properties
.
map
((
item
)
=>
item
.
propertyId
)
const
PropertyS
=
await
PropertyApi
.
getPropertyListAndValue
({
propertyIds
})
const
PropertyS
=
await
PropertyApi
.
getPropertyListAndValue
({
propertyIds
})
await
nextTick
()
await
nextTick
()
...
@@ -151,6 +152,7 @@ const submitForm = async () => {
...
@@ -151,6 +152,7 @@ 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
()
// TODO @puhui:直接做深拷贝?这样最终 server 端不满足,不需要恢复
// 处理掉一些无关数据
// 处理掉一些无关数据
formData
.
value
.
skus
.
forEach
((
item
)
=>
{
formData
.
value
.
skus
.
forEach
((
item
)
=>
{
// 给sku name赋值
// 给sku name赋值
...
@@ -166,6 +168,7 @@ const submitForm = async () => {
...
@@ -166,6 +168,7 @@ const submitForm = async () => {
const
newSliderPicUrls
=
[]
const
newSliderPicUrls
=
[]
formData
.
value
.
sliderPicUrls
.
forEach
((
item
)
=>
{
formData
.
value
.
sliderPicUrls
.
forEach
((
item
)
=>
{
// 如果是前端选的图
// 如果是前端选的图
// TODO @puhui999:疑问哈,为啥会是 object 呀?
if
(
typeof
item
===
'object'
)
{
if
(
typeof
item
===
'object'
)
{
newSliderPicUrls
.
push
(
item
.
url
)
newSliderPicUrls
.
push
(
item
.
url
)
}
else
{
}
else
{
...
@@ -224,6 +227,7 @@ const resetForm = async () => {
...
@@ -224,6 +227,7 @@ const resetForm = async () => {
}
}
/** 关闭按钮 */
/** 关闭按钮 */
const
close
=
()
=>
{
const
close
=
()
=>
{
// TODO @puhui999:是不是不用 reset 呀?close 默认销毁
resetForm
()
resetForm
()
delView
(
unref
(
currentRoute
))
delView
(
unref
(
currentRoute
))
push
(
'/product/product-management'
)
push
(
'/product/product-management'
)
...
...
src/views/mall/product/
management
/components/BasicInfoForm.vue
→
src/views/mall/product/
spu
/components/BasicInfoForm.vue
View file @
e92361ed
...
@@ -7,6 +7,7 @@
...
@@ -7,6 +7,7 @@
</el-form-item>
</el-form-item>
</el-col>
</el-col>
<el-col
:span=
"12"
>
<el-col
:span=
"12"
>
<!-- TODO @puhui999:只能选根节点 -->
<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"
...
@@ -15,6 +16,7 @@
...
@@ -15,6 +16,7 @@
check-strictly
check-strictly
node-key=
"id"
node-key=
"id"
placeholder=
"请选择商品分类"
placeholder=
"请选择商品分类"
class=
"w-1/1"
/>
/>
</el-form-item>
</el-form-item>
</el-col>
</el-col>
...
@@ -25,7 +27,7 @@
...
@@ -25,7 +27,7 @@
</el-col>
</el-col>
<el-col
:span=
"12"
>
<el-col
:span=
"12"
>
<el-form-item
label=
"单位"
prop=
"unit"
>
<el-form-item
label=
"单位"
prop=
"unit"
>
<el-select
v-model=
"formData.unit"
placeholder=
"请选择单位"
>
<el-select
v-model=
"formData.unit"
placeholder=
"请选择单位"
class=
"w-1/1"
>
<el-option
<el-option
v-for=
"dict in getIntDictOptions(DICT_TYPE.PRODUCT_UNIT)"
v-for=
"dict in getIntDictOptions(DICT_TYPE.PRODUCT_UNIT)"
:key=
"dict.value"
:key=
"dict.value"
...
@@ -57,7 +59,7 @@
...
@@ -57,7 +59,7 @@
</el-col>
</el-col>
<el-col
:span=
"12"
>
<el-col
:span=
"12"
>
<el-form-item
label=
"运费模板"
prop=
"deliveryTemplateId"
>
<el-form-item
label=
"运费模板"
prop=
"deliveryTemplateId"
>
<el-select
v-model=
"formData.deliveryTemplateId"
placeholder=
"请选择"
style=
"width: 100%
"
>
<el-select
v-model=
"formData.deliveryTemplateId"
placeholder=
"请选择"
class=
"w-1/1
"
>
<el-option
v-for=
"item in []"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
/>
<el-option
v-for=
"item in []"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
/>
</el-select>
</el-select>
</el-form-item>
</el-form-item>
...
@@ -84,9 +86,8 @@
...
@@ -84,9 +86,8 @@
<!-- 多规格添加-->
<!-- 多规格添加-->
<el-col
:span=
"24"
>
<el-col
:span=
"24"
>
<el-form-item
v-if=
"formData.specType"
label=
"商品属性"
>
<el-form-item
v-if=
"formData.specType"
label=
"商品属性"
>
<el-button
class=
"mr-15px mb-10px"
@
click=
"AttributesAddFormRef.open()"
<!-- TODO @puhui999:参考 https://admin.java.crmeb.net/store/list/creatProduct 添加规格好做么?添加的时候,不用输入备注哈 -->
>
添加规格
<el-button
class=
"mr-15px mb-10px"
@
click=
"AttributesAddFormRef.open"
>
添加规格
</el-button>
</el-button>
<ProductAttributes
:attribute-data=
"attributeList"
/>
<ProductAttributes
:attribute-data=
"attributeList"
/>
</el-form-item>
</el-form-item>
<template
v-if=
"formData.specType && attributeList.length > 0"
>
<template
v-if=
"formData.specType && attributeList.length > 0"
>
...
@@ -108,17 +109,15 @@
...
@@ -108,17 +109,15 @@
<
script
lang=
"ts"
name=
"ProductManagementBasicInfoForm"
setup
>
<
script
lang=
"ts"
name=
"ProductManagementBasicInfoForm"
setup
>
import
{
PropType
}
from
'vue'
import
{
PropType
}
from
'vue'
import
{
defaultProps
,
handleTree
}
from
'@/utils/tree'
import
{
defaultProps
,
handleTree
}
from
'@/utils/tree'
import
{
ElInput
}
from
'element-plus'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
type
{
SpuType
}
from
'@/api/mall/product/management/type/spuType'
import
type
{
SpuType
}
from
'@/api/mall/product/management/type/spuType'
import
{
UploadImg
,
UploadImgs
}
from
'@/components/UploadFile'
import
{
UploadImg
,
UploadImgs
}
from
'@/components/UploadFile'
import
{
copyValueToTarget
}
from
'@/utils/object'
import
{
copyValueToTarget
}
from
'@/utils/object'
import
{
ProductAttributes
,
ProductAttributesAddForm
,
SkuList
}
from
'./index'
import
{
ProductAttributes
,
ProductAttributesAddForm
,
SkuList
}
from
'./index'
// 业务Api
import
*
as
ProductCategoryApi
from
'@/api/mall/product/category'
import
*
as
ProductCategoryApi
from
'@/api/mall/product/category'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
propTypes
}
from
'@/utils/propTypes'
const
message
=
useMessage
()
// 消息弹窗
const
message
=
useMessage
()
// 消息弹窗
const
props
=
defineProps
({
const
props
=
defineProps
({
propFormData
:
{
propFormData
:
{
type
:
Object
as
PropType
<
SpuType
>
,
type
:
Object
as
PropType
<
SpuType
>
,
...
@@ -126,10 +125,11 @@ const props = defineProps({
...
@@ -126,10 +125,11 @@ const props = defineProps({
},
},
activeName
:
propTypes
.
string
.
def
(
''
)
activeName
:
propTypes
.
string
.
def
(
''
)
})
})
const
AttributesAddFormRef
=
ref
()
// 添加商品属性表单
const
AttributesAddFormRef
=
ref
()
// 添加商品属性表单 TODO @puhui999:小写开头哈
const
ProductManagementBasicInfoRef
=
ref
()
// 表单Ref
const
ProductManagementBasicInfoRef
=
ref
()
// 表单Ref TODO @puhui999:小写开头哈
// TODO @puhui999:attributeList 改成 propertyList,会更统一一点
const
attributeList
=
ref
([])
// 商品属性列表
const
attributeList
=
ref
([])
// 商品属性列表
/** 添加商品属性 */
/** 添加商品属性 */
// TODO @puhui999:propFormData 算出来
const
addAttribute
=
(
property
:
any
)
=>
{
const
addAttribute
=
(
property
:
any
)
=>
{
if
(
Array
.
isArray
(
property
))
{
if
(
Array
.
isArray
(
property
))
{
attributeList
.
value
=
property
attributeList
.
value
=
property
...
@@ -162,8 +162,9 @@ const rules = reactive({
...
@@ -162,8 +162,9 @@ const rules = reactive({
specType
:
[
required
],
specType
:
[
required
],
subCommissionType
:
[
required
]
subCommissionType
:
[
required
]
})
})
/**
/**
* 将传进来的值赋值给formData
* 将传进来的值赋值给
formData
*/
*/
watch
(
watch
(
()
=>
props
.
propFormData
,
()
=>
props
.
propFormData
,
...
@@ -176,10 +177,11 @@ watch(
...
@@ -176,10 +177,11 @@ watch(
immediate
:
true
immediate
:
true
}
}
)
)
const
emit
=
defineEmits
([
'update:activeName'
])
/**
/**
* 表单校验
* 表单校验
*/
*/
const
emit
=
defineEmits
([
'update:activeName'
])
const
validate
=
async
()
=>
{
const
validate
=
async
()
=>
{
// 校验表单
// 校验表单
if
(
!
ProductManagementBasicInfoRef
)
return
if
(
!
ProductManagementBasicInfoRef
)
return
...
@@ -197,7 +199,7 @@ const validate = async () => {
...
@@ -197,7 +199,7 @@ const validate = async () => {
}
}
defineExpose
({
validate
,
addAttribute
})
defineExpose
({
validate
,
addAttribute
})
/
/ 分销类型
/
** 分销类型 */
const
changeSubCommissionType
=
()
=>
{
const
changeSubCommissionType
=
()
=>
{
// 默认为零,类型切换后也要重置为零
// 默认为零,类型切换后也要重置为零
for
(
const
item
of
formData
.
skus
)
{
for
(
const
item
of
formData
.
skus
)
{
...
@@ -205,7 +207,8 @@ const changeSubCommissionType = () => {
...
@@ -205,7 +207,8 @@ const changeSubCommissionType = () => {
item
.
subCommissionSecondPrice
=
0
item
.
subCommissionSecondPrice
=
0
}
}
}
}
// 选择规格
/** 选择规格 */
const
onChangeSpec
=
()
=>
{
const
onChangeSpec
=
()
=>
{
// 重置商品属性列表
// 重置商品属性列表
attributeList
.
value
=
[]
attributeList
.
value
=
[]
...
...
src/views/mall/product/
management
/components/DescriptionForm.vue
→
src/views/mall/product/
spu
/components/DescriptionForm.vue
View file @
e92361ed
...
@@ -25,6 +25,11 @@ const DescriptionFormRef = ref() // 表单Ref
...
@@ -25,6 +25,11 @@ const DescriptionFormRef = ref() // 表单Ref
const
formData
=
ref
<
SpuType
>
({
const
formData
=
ref
<
SpuType
>
({
description
:
''
// 商品详情
description
:
''
// 商品详情
})
})
// 表单规则
const
rules
=
reactive
({
description
:
[
required
]
})
/**
/**
* 富文本编辑器如果输入过再清空会有残留,需再重置一次
* 富文本编辑器如果输入过再清空会有残留,需再重置一次
*/
*/
...
@@ -40,10 +45,7 @@ watch(
...
@@ -40,10 +45,7 @@ watch(
immediate
:
true
immediate
:
true
}
}
)
)
// 表单规则
const
rules
=
reactive
({
description
:
[
required
]
})
/**
/**
* 将传进来的值赋值给formData
* 将传进来的值赋值给formData
*/
*/
...
@@ -58,10 +60,11 @@ watch(
...
@@ -58,10 +60,11 @@ watch(
immediate
:
true
immediate
:
true
}
}
)
)
const
emit
=
defineEmits
([
'update:activeName'
])
/**
/**
* 表单校验
* 表单校验
*/
*/
const
emit
=
defineEmits
([
'update:activeName'
])
const
validate
=
async
()
=>
{
const
validate
=
async
()
=>
{
// 校验表单
// 校验表单
if
(
!
DescriptionFormRef
)
return
if
(
!
DescriptionFormRef
)
return
...
...
src/views/mall/product/
management
/components/OtherSettingsForm.vue
→
src/views/mall/product/
spu
/components/OtherSettingsForm.vue
View file @
e92361ed
<
template
>
<
template
>
<el-form
ref=
"OtherSettingsFormRef"
:model=
"formData"
:rules=
"rules"
label-width=
"120px"
>
<el-form
ref=
"OtherSettingsFormRef"
:model=
"formData"
:rules=
"rules"
label-width=
"120px"
>
<el-row>
<el-row>
<!-- TODO @puhui999:横着三个哈 -->
<el-col
:span=
"24"
>
<el-col
:span=
"24"
>
<el-col
:span=
"8"
>
<el-col
:span=
"8"
>
<el-form-item
label=
"商品排序"
prop=
"sort"
>
<el-form-item
label=
"商品排序"
prop=
"sort"
>
...
@@ -40,6 +41,7 @@
...
@@ -40,6 +41,7 @@
<el-tag
class=
"ml-2"
type=
"warning"
>
拼团
</el-tag>
<el-tag
class=
"ml-2"
type=
"warning"
>
拼团
</el-tag>
</el-form-item>
</el-form-item>
</el-col>
</el-col>
<!-- TODO @puhui999:等优惠劵 ok 在搞 -->
<el-col
:span=
"24"
>
<el-col
:span=
"24"
>
<el-form-item
label=
"赠送优惠劵"
>
<el-form-item
label=
"赠送优惠劵"
>
<el-button>
选择优惠券
</el-button>
<el-button>
选择优惠券
</el-button>
...
@@ -49,13 +51,12 @@
...
@@ -49,13 +51,12 @@
</el-form>
</el-form>
</
template
>
</
template
>
<
script
lang=
"ts"
name=
"OtherSettingsForm"
setup
>
<
script
lang=
"ts"
name=
"OtherSettingsForm"
setup
>
// 商品推荐
import
type
{
SpuType
}
from
'@/api/mall/product/management/type/spuType'
import
type
{
SpuType
}
from
'@/api/mall/product/management/type/spuType'
import
{
PropType
}
from
'vue'
import
{
PropType
}
from
'vue'
import
{
copyValueToTarget
}
from
'@/utils/object'
import
{
copyValueToTarget
}
from
'@/utils/object'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
propTypes
}
from
'@/utils/propTypes'
const
message
=
useMessage
()
// 消息弹窗
const
message
=
useMessage
()
// 消息弹窗
const
props
=
defineProps
({
const
props
=
defineProps
({
propFormData
:
{
propFormData
:
{
type
:
Object
as
PropType
<
SpuType
>
,
type
:
Object
as
PropType
<
SpuType
>
,
...
@@ -63,7 +64,7 @@ const props = defineProps({
...
@@ -63,7 +64,7 @@ const props = defineProps({
},
},
activeName
:
propTypes
.
string
.
def
(
''
)
activeName
:
propTypes
.
string
.
def
(
''
)
})
})
// 商品推荐选项
// 商品推荐选项
TODO @puhui999:这种叫 recommendOptions 会更合适哈
const
recommend
=
[
const
recommend
=
[
{
name
:
'是否热卖'
,
value
:
'recommendHot'
},
{
name
:
'是否热卖'
,
value
:
'recommendHot'
},
{
name
:
'是否优惠'
,
value
:
'recommendBenefit'
},
{
name
:
'是否优惠'
,
value
:
'recommendBenefit'
},
...
@@ -71,10 +72,10 @@ const recommend = [
...
@@ -71,10 +72,10 @@ const recommend = [
{
name
:
'是否新品'
,
value
:
'recommendNew'
},
{
name
:
'是否新品'
,
value
:
'recommendNew'
},
{
name
:
'是否优品'
,
value
:
'recommendGood'
}
{
name
:
'是否优品'
,
value
:
'recommendGood'
}
]
]
// 选中推荐选项
const
checkboxGroup
=
ref
<
string
[]
>
([
'recommendHot'
])
// 选中推荐选项
const
checkboxGroup
=
ref
<
string
[]
>
([
'recommendHot'
])
/** 选择商品后赋值 */
// 选择商品后赋值
const
onChangeGroup
=
()
=>
{
const
onChangeGroup
=
()
=>
{
// TODO @puhui999:是不是可以遍历 recommend,然后进行是否选中;
checkboxGroup
.
value
.
includes
(
'recommendHot'
)
checkboxGroup
.
value
.
includes
(
'recommendHot'
)
?
(
formData
.
value
.
recommendHot
=
true
)
?
(
formData
.
value
.
recommendHot
=
true
)
:
(
formData
.
value
.
recommendHot
=
false
)
:
(
formData
.
value
.
recommendHot
=
false
)
...
@@ -109,6 +110,7 @@ const rules = reactive({
...
@@ -109,6 +110,7 @@ const rules = reactive({
giveIntegral
:
[
required
],
giveIntegral
:
[
required
],
virtualSalesCount
:
[
required
]
virtualSalesCount
:
[
required
]
})
})
/**
/**
* 将传进来的值赋值给formData
* 将传进来的值赋值给formData
*/
*/
...
@@ -130,10 +132,11 @@ watch(
...
@@ -130,10 +132,11 @@ watch(
immediate
:
true
immediate
:
true
}
}
)
)
const
emit
=
defineEmits
([
'update:activeName'
])
/**
/**
* 表单校验
* 表单校验
*/
*/
const
emit
=
defineEmits
([
'update:activeName'
])
const
validate
=
async
()
=>
{
const
validate
=
async
()
=>
{
// 校验表单
// 校验表单
if
(
!
OtherSettingsFormRef
)
return
if
(
!
OtherSettingsFormRef
)
return
...
@@ -149,6 +152,5 @@ const validate = async () => {
...
@@ -149,6 +152,5 @@ const validate = async () => {
}
}
})
})
}
}
defineExpose
({
validate
})
defineExpose
({
validate
})
</
script
>
</
script
>
src/views/mall/product/
management
/components/ProductAttributes.vue
→
src/views/mall/product/
spu
/components/ProductAttributes.vue
View file @
e92361ed
...
@@ -71,16 +71,19 @@ watch(
...
@@ -71,16 +71,19 @@ watch(
immediate
:
true
immediate
:
true
}
}
)
)
/** 删除标签 tagValue 标签值*/
/** 删除标签 tagValue 标签值*/
const
handleClose
=
(
index
,
valueIndex
)
=>
{
const
handleClose
=
(
index
,
valueIndex
)
=>
{
attributeList
.
value
[
index
].
values
?.
splice
(
valueIndex
,
1
)
attributeList
.
value
[
index
].
values
?.
splice
(
valueIndex
,
1
)
}
}
/** 显示输入框并获取焦点 */
/** 显示输入框并获取焦点 */
const
showInput
=
async
(
index
)
=>
{
const
showInput
=
async
(
index
)
=>
{
attributeIndex
.
value
=
index
attributeIndex
.
value
=
index
// 因为组件在ref中所以需要用索引获取对应的Ref
// 因为组件在ref中所以需要用索引获取对应的Ref
InputRef
.
value
[
index
]
!
.
input
!
.
focus
()
InputRef
.
value
[
index
]
!
.
input
!
.
focus
()
}
}
/** 输入框失去焦点或点击回车时触发 */
/** 输入框失去焦点或点击回车时触发 */
const
handleInputConfirm
=
async
(
index
,
propertyId
)
=>
{
const
handleInputConfirm
=
async
(
index
,
propertyId
)
=>
{
if
(
inputValue
.
value
)
{
if
(
inputValue
.
value
)
{
...
...
src/views/mall/product/
management
/components/ProductAttributesAddForm.vue
→
src/views/mall/product/
spu
/components/ProductAttributesAddForm.vue
View file @
e92361ed
File moved
src/views/mall/product/
management
/components/SkuList.vue
→
src/views/mall/product/
spu
/components/SkuList.vue
View file @
e92361ed
...
@@ -25,11 +25,13 @@
...
@@ -25,11 +25,13 @@
</
template
>
</
template
>
</el-table-column>
</el-table-column>
</template>
</template>
<!-- TODO @puhui999: controls-position="right" 可以去掉哈,不然太长了,手动输入更方便 -->
<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
v-model=
"row.barCode"
class=
"w-100%"
/>
<el-input
v-model=
"row.barCode"
class=
"w-100%"
/>
</
template
>
</
template
>
</el-table-column>
</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 }"
>
<
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"
class=
"w-100%"
controls-position=
"right"
/>
...
@@ -94,15 +96,14 @@
...
@@ -94,15 +96,14 @@
</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
>
<
template
#
default
>
<el-button
v-if=
"isBatch"
link
size=
"small"
type=
"primary"
@
click=
"batchAdd"
<el-button
v-if=
"isBatch"
link
size=
"small"
type=
"primary"
@
click=
"batchAdd"
>
>
批量添加
批量添加
</el-button>
</el-button>
<el-button
v-else
link
size=
"small"
type=
"primary"
>
删除
</el-button>
<el-button
v-else
link
size=
"small"
type=
"primary"
>
删除
</el-button>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
</el-table>
</el-table>
</template>
</template>
<
script
lang=
"ts"
name=
"SkuList"
setup
>
<
script
lang=
"ts"
name=
"SkuList"
setup
>
import
{
UploadImg
}
from
'@/components/UploadFile'
import
{
UploadImg
}
from
'@/components/UploadFile'
import
{
PropType
}
from
'vue'
import
{
PropType
}
from
'vue'
...
@@ -123,7 +124,7 @@ const props = defineProps({
...
@@ -123,7 +124,7 @@ const props = defineProps({
isBatch
:
propTypes
.
bool
.
def
(
false
)
// 是否批量操作
isBatch
:
propTypes
.
bool
.
def
(
false
)
// 是否批量操作
})
})
const
formData
=
ref
<
SpuType
>
()
// 表单数据
const
formData
=
ref
<
SpuType
>
()
// 表单数据
// 批量添加时的零时数据
// 批量添加时的零时数据
TODO @puhui999:小写开头哈;然后变量都尾注释
const
SkuData
=
ref
<
SkuType
[]
>
([
const
SkuData
=
ref
<
SkuType
[]
>
([
{
{
/**
/**
...
@@ -168,13 +169,16 @@ const SkuData = ref<SkuType[]>([
...
@@ -168,13 +169,16 @@ const SkuData = ref<SkuType[]>([
subCommissionSecondPrice
:
0
subCommissionSecondPrice
:
0
}
}
])
])
/** 批量添加 */
/** 批量添加 */
const
batchAdd
=
()
=>
{
const
batchAdd
=
()
=>
{
formData
.
value
.
skus
.
forEach
((
item
)
=>
{
formData
.
value
.
skus
.
forEach
((
item
)
=>
{
copyValueToTarget
(
item
,
SkuData
.
value
[
0
])
copyValueToTarget
(
item
,
SkuData
.
value
[
0
])
})
})
}
}
const
tableHeaderList
=
ref
<
{
prop
:
string
;
label
:
string
}[]
>
([])
const
tableHeaderList
=
ref
<
{
prop
:
string
;
label
:
string
}[]
>
([])
/**
/**
* 将传进来的值赋值给SkuData
* 将传进来的值赋值给SkuData
*/
*/
...
@@ -189,6 +193,8 @@ watch(
...
@@ -189,6 +193,8 @@ watch(
immediate
:
true
immediate
:
true
}
}
)
)
// TODO @芋艿:看看 chatgpt 可以进一步下面几个方法的实现不
/** 生成表数据 */
/** 生成表数据 */
const
generateTableData
=
(
data
:
any
[])
=>
{
const
generateTableData
=
(
data
:
any
[])
=>
{
// 构建数据结构
// 构建数据结构
...
@@ -237,6 +243,7 @@ const generateTableData = (data: any[]) => {
...
@@ -237,6 +243,7 @@ const generateTableData = (data: any[]) => {
formData
.
value
.
skus
.
push
(
row
)
formData
.
value
.
skus
.
push
(
row
)
})
})
}
}
/** 构建所有排列组合 */
/** 构建所有排列组合 */
const
build
=
(
list
:
any
[])
=>
{
const
build
=
(
list
:
any
[])
=>
{
if
(
list
.
length
===
0
)
{
if
(
list
.
length
===
0
)
{
...
@@ -259,6 +266,7 @@ const build = (list: any[]) => {
...
@@ -259,6 +266,7 @@ const build = (list: any[]) => {
return
result
return
result
}
}
}
}
/** 监听属性列表生成相关参数和表头 */
/** 监听属性列表生成相关参数和表头 */
watch
(
watch
(
()
=>
props
.
attributeList
,
()
=>
props
.
attributeList
,
...
...
src/views/mall/product/
management
/components/index.ts
→
src/views/mall/product/
spu
/components/index.ts
View file @
e92361ed
File moved
src/views/mall/product/
management
/index.vue
→
src/views/mall/product/
spu
/index.vue
View file @
e92361ed
...
@@ -8,6 +8,7 @@
...
@@ -8,6 +8,7 @@
class=
"-mb-15px"
class=
"-mb-15px"
label-width=
"68px"
label-width=
"68px"
>
>
<!-- TODO @puhui999:https://admin.java.crmeb.net/store/index,参考,使用分类 + 标题搜索 -->
<el-form-item
label=
"品牌名称"
prop=
"name"
>
<el-form-item
label=
"品牌名称"
prop=
"name"
>
<el-input
<el-input
v-model=
"queryParams.name"
v-model=
"queryParams.name"
...
@@ -51,6 +52,7 @@
...
@@ -51,6 +52,7 @@
<Icon
class=
"mr-5px"
icon=
"ep:plus"
/>
<Icon
class=
"mr-5px"
icon=
"ep:plus"
/>
新增
新增
</el-button>
</el-button>
<!-- TODO @puhui999:增加一个【导出】操作 -->
</el-form-item>
</el-form-item>
</el-form>
</el-form>
</ContentWrap>
</ContentWrap>
...
@@ -66,6 +68,7 @@
...
@@ -66,6 +68,7 @@
/>
/>
</el-tabs>
</el-tabs>
<el-table
v-loading=
"loading"
:data=
"list"
>
<el-table
v-loading=
"loading"
:data=
"list"
>
<!-- TODO puhui999: ID 编号的展示 -->
<!-- TODO 暂时不做折叠数据 -->
<!-- TODO 暂时不做折叠数据 -->
<!--
<el-table-column
type=
"expand"
>
-->
<!--
<el-table-column
type=
"expand"
>
-->
<!--
<template
#
default=
"
{ row }">-->
<!--
<template
#
default=
"
{ row }">-->
...
@@ -92,6 +95,7 @@
...
@@ -92,6 +95,7 @@
</
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"
/>
<!-- 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"
/>
<el-table-column
align=
"center"
label=
"销量"
min-width=
"90"
prop=
"salesCount"
/>
<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=
"90"
prop=
"stock"
/>
...
@@ -105,6 +109,7 @@
...
@@ -105,6 +109,7 @@
/>
/>
<el-table-column
fixed=
"right"
label=
"状态"
min-width=
"80"
>
<el-table-column
fixed=
"right"
label=
"状态"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
<
template
#
default=
"{ row }"
>
<!-- TODO @puhui:是不是不用 Number(row.status) 去比较哈,直接 row.status
<
0
--
>
<el-switch
<el-switch
v-model=
"row.status"
v-model=
"row.status"
:active-value=
"1"
:active-value=
"1"
...
@@ -119,6 +124,7 @@
...
@@ -119,6 +124,7 @@
</el-table-column>
</el-table-column>
<el-table-column
align=
"center"
fixed=
"right"
label=
"操作"
min-width=
"150"
>
<el-table-column
align=
"center"
fixed=
"right"
label=
"操作"
min-width=
"150"
>
<
template
#
default=
"{ row }"
>
<
template
#
default=
"{ row }"
>
<!-- TODO @puhui999:【详情】,可以后面点做哈 -->
<template
v-if=
"queryParams.tabType === 4"
>
<template
v-if=
"queryParams.tabType === 4"
>
<el-button
<el-button
v-hasPermi=
"['product:spu:delete']"
v-hasPermi=
"['product:spu:delete']"
...
@@ -166,6 +172,7 @@
...
@@ -166,6 +172,7 @@
@
pagination=
"getList"
@
pagination=
"getList"
/>
/>
</ContentWrap>
</ContentWrap>
<!-- https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/image-viewer.html,可以用这个么? -->
<!-- 必须在表格外面展示。不然单元格会遮挡图层 -->
<!-- 必须在表格外面展示。不然单元格会遮挡图层 -->
<el-image-viewer
<el-image-viewer
v-if=
"imgViewVisible"
v-if=
"imgViewVisible"
...
@@ -173,20 +180,21 @@
...
@@ -173,20 +180,21 @@
@
close=
"imgViewVisible = false"
@
close=
"imgViewVisible = false"
/>
/>
</template>
</template>
<
script
lang=
"ts"
name=
"Product
Managemen
t"
setup
>
<
script
lang=
"ts"
name=
"Product
Lis
t"
setup
>
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
dateFormatter
}
from
'@/utils/formatTime'
// 业务api
import
{
dateFormatter
}
from
'@/utils/formatTime'
// TODO @puhui999:managementApi=》ProductSpuApi
import
*
as
managementApi
from
'@/api/mall/product/management/spu'
import
*
as
managementApi
from
'@/api/mall/product/management/spu'
import
{
ProductSpuStatusEnum
}
from
'@/utils/constants'
import
{
ProductSpuStatusEnum
}
from
'@/utils/constants'
import
{
TabsPaneContext
}
from
'element-plus'
import
{
TabsPaneContext
}
from
'element-plus'
const
message
=
useMessage
()
// 消息弹窗
const
message
=
useMessage
()
// 消息弹窗
const
{
t
}
=
useI18n
()
// 国际化
const
{
t
}
=
useI18n
()
// 国际化
const
{
currentRoute
,
push
}
=
useRouter
()
// 路由跳转
const
{
currentRoute
,
push
}
=
useRouter
()
// 路由跳转
const
loading
=
ref
(
false
)
// 列表的加载中
const
loading
=
ref
(
false
)
// 列表的加载中
const
total
=
ref
(
0
)
// 列表的总页数
const
total
=
ref
(
0
)
// 列表的总页数
const
list
=
ref
<
any
[]
>
([])
// 列表的数据
const
list
=
ref
<
any
[]
>
([])
// 列表的数据
// tabs数据
// tabs
数据
const
tabsData
=
ref
([
const
tabsData
=
ref
([
{
{
count
:
0
,
count
:
0
,
...
@@ -214,7 +222,10 @@ const tabsData = ref([
...
@@ -214,7 +222,10 @@ const tabsData = ref([
type
:
4
type
:
4
}
}
])
])
/** 获得每个 Tab 的数量 */
const
getTabsCount
=
async
()
=>
{
const
getTabsCount
=
async
()
=>
{
// TODO @puhui999:这里是不是可以不要 try catch 哈
try
{
try
{
const
res
=
await
managementApi
.
getTabsCount
()
const
res
=
await
managementApi
.
getTabsCount
()
for
(
let
objName
in
res
)
{
for
(
let
objName
in
res
)
{
...
@@ -222,6 +233,7 @@ const getTabsCount = async () => {
...
@@ -222,6 +233,7 @@ const getTabsCount = async () => {
}
}
}
catch
{}
}
catch
{}
}
}
const
imgViewVisible
=
ref
(
false
)
// 商品图预览
const
imgViewVisible
=
ref
(
false
)
// 商品图预览
const
imageViewerList
=
ref
<
string
[]
>
([])
// 商品图预览列表
const
imageViewerList
=
ref
<
string
[]
>
([])
// 商品图预览列表
const
queryParams
=
ref
({
const
queryParams
=
ref
({
...
@@ -230,10 +242,13 @@ const queryParams = ref({
...
@@ -230,10 +242,13 @@ const queryParams = ref({
tabType
:
0
tabType
:
0
})
})
const
queryFormRef
=
ref
()
// 搜索的表单
const
queryFormRef
=
ref
()
// 搜索的表单
// TODO @puhui999:可以改成 handleTabClick:更准确一点;
const
handleClick
=
(
tab
:
TabsPaneContext
)
=>
{
const
handleClick
=
(
tab
:
TabsPaneContext
)
=>
{
queryParams
.
value
.
tabType
=
tab
.
paneName
queryParams
.
value
.
tabType
=
tab
.
paneName
getList
()
getList
()
}
}
/** 查询列表 */
/** 查询列表 */
const
getList
=
async
()
=>
{
const
getList
=
async
()
=>
{
loading
.
value
=
true
loading
.
value
=
true
...
@@ -246,8 +261,10 @@ const getList = async () => {
...
@@ -246,8 +261,10 @@ const getList = async () => {
}
}
}
}
// TODO @puhui999:是不是 changeStatus 和 addToTrash 调用一个统一的方法,去更新状态。这样逻辑会更干净一些。
/**
/**
* 更改SPU状态
* 更改 SPU 状态
*
* @param row
* @param row
* @param status 更改前的值
* @param status 更改前的值
*/
*/
...
@@ -271,7 +288,7 @@ const changeStatus = async (row, status?: number) => {
...
@@ -271,7 +288,7 @@ const changeStatus = async (row, status?: number) => {
)
)
await
managementApi
.
updateStatus
({
id
:
row
.
id
,
status
:
row
.
status
})
await
managementApi
.
updateStatus
({
id
:
row
.
id
,
status
:
row
.
status
})
message
.
success
(
'更新状态成功'
)
message
.
success
(
'更新状态成功'
)
// 刷新
tabs
数据
// 刷新
tabs
数据
await
getTabsCount
()
await
getTabsCount
()
// 刷新列表
// 刷新列表
await
getList
()
await
getList
()
...
@@ -288,8 +305,10 @@ const changeStatus = async (row, status?: number) => {
...
@@ -288,8 +305,10 @@ const changeStatus = async (row, status?: number) => {
:
ProductSpuStatusEnum
.
DISABLE
.
status
:
ProductSpuStatusEnum
.
DISABLE
.
status
}
}
}
}
/**
/**
* 加入回收站
* 加入回收站
*
* @param row
* @param row
* @param status
* @param status
*/
*/
...
@@ -299,6 +318,7 @@ const addToTrash = (row, status) => {
...
@@ -299,6 +318,7 @@ const addToTrash = (row, status) => {
row
.
status
=
status
row
.
status
=
status
changeStatus
(
row
,
num
)
changeStatus
(
row
,
num
)
}
}
/** 删除按钮操作 */
/** 删除按钮操作 */
const
handleDelete
=
async
(
id
:
number
)
=>
{
const
handleDelete
=
async
(
id
:
number
)
=>
{
try
{
try
{
...
@@ -313,6 +333,7 @@ const handleDelete = async (id: number) => {
...
@@ -313,6 +333,7 @@ const handleDelete = async (id: number) => {
await
getList
()
await
getList
()
}
catch
{}
}
catch
{}
}
}
/**
/**
* 商品图预览
* 商品图预览
* @param imgUrl
* @param imgUrl
...
@@ -321,6 +342,7 @@ const imagePreview = (imgUrl: string) => {
...
@@ -321,6 +342,7 @@ const imagePreview = (imgUrl: string) => {
imageViewerList
.
value
=
[
imgUrl
]
imageViewerList
.
value
=
[
imgUrl
]
imgViewVisible
.
value
=
true
imgViewVisible
.
value
=
true
}
}
/** 搜索按钮操作 */
/** 搜索按钮操作 */
const
handleQuery
=
()
=>
{
const
handleQuery
=
()
=>
{
getList
()
getList
()
...
@@ -334,16 +356,20 @@ const resetQuery = () => {
...
@@ -334,16 +356,20 @@ const resetQuery = () => {
/**
/**
* 新增或修改
* 新增或修改
* @param id
*
* @param id 商品 SPU 编号
*/
*/
const
openForm
=
(
id
?:
number
)
=>
{
const
openForm
=
(
id
?:
number
)
=>
{
// 修改
if
(
typeof
id
===
'number'
)
{
if
(
typeof
id
===
'number'
)
{
push
(
'/product/productManagementAdd?id='
+
id
)
push
(
'/product/productManagementAdd?id='
+
id
)
return
return
}
}
// 新增
push
(
'/product/productManagementAdd'
)
push
(
'/product/productManagementAdd'
)
}
}
// 监听路由变化更新列表
// 监听路由变化更新列表 TODO @puhui999:这个是必须加的么?
watch
(
watch
(
()
=>
currentRoute
.
value
,
()
=>
currentRoute
.
value
,
()
=>
{
()
=>
{
...
@@ -353,6 +379,7 @@ watch(
...
@@ -353,6 +379,7 @@ watch(
immediate
:
true
immediate
:
true
}
}
)
)
/** 初始化 **/
/** 初始化 **/
onMounted
(()
=>
{
onMounted
(()
=>
{
getTabsCount
()
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