Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
1abff21e
authored
Dec 15, 2023
by
芋道源码
Committed by
Gitee
Dec 15, 2023
Browse files
Options
Browse Files
Download
Plain Diff
!347 商城装修
Merge pull request !347 from 疯狂的世界/dev
parents
26a08278
80355f3a
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
650 additions
and
87 deletions
+650
-87
src/components/AppLinkInput/AppLinkSelectDialog.vue
+25
-16
src/components/AppLinkInput/data.ts
+18
-1
src/components/AppLinkInput/index.vue
+1
-1
src/components/DiyEditor/components/mobile/HotZone/components/HotZoneEditDialog/controller.ts
+143
-0
src/components/DiyEditor/components/mobile/HotZone/components/HotZoneEditDialog/index.vue
+236
-0
src/components/DiyEditor/components/mobile/HotZone/config.ts
+42
-0
src/components/DiyEditor/components/mobile/HotZone/index.vue
+42
-0
src/components/DiyEditor/components/mobile/HotZone/property.vue
+63
-0
src/components/DiyEditor/components/mobile/MenuSwiper/index.vue
+2
-2
src/components/DiyEditor/components/mobile/MenuSwiper/property.vue
+1
-1
src/components/DiyEditor/components/mobile/TitleBar/config.ts
+15
-11
src/components/DiyEditor/components/mobile/TitleBar/index.vue
+7
-14
src/components/DiyEditor/components/mobile/TitleBar/property.vue
+46
-40
src/components/DiyEditor/util.ts
+9
-1
No files found.
src/components/AppLinkInput/AppLinkSelectDialog.vue
View file @
1abff21e
...
@@ -29,10 +29,11 @@
...
@@ -29,10 +29,11 @@
:key=
"appLinkIndex"
:key=
"appLinkIndex"
:content=
"appLink.path"
:content=
"appLink.path"
placement=
"bottom"
placement=
"bottom"
:show-after=
"300"
>
>
<el-button
<el-button
class=
"m-b-8px m-r-8px m-l-0px!"
class=
"m-b-8px m-r-8px m-l-0px!"
:type=
"isSameLink(appLink.path, activeAppLink) ? 'primary' : 'default'"
:type=
"isSameLink(appLink.path, activeAppLink
.path
) ? 'primary' : 'default'"
@
click=
"handleAppLinkSelected(appLink)"
@
click=
"handleAppLinkSelected(appLink)"
>
>
{{
appLink
.
name
}}
{{
appLink
.
name
}}
...
@@ -63,7 +64,7 @@
...
@@ -63,7 +64,7 @@
</Dialog>
</Dialog>
</template>
</template>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
APP_LINK_GROUP_LIST
,
APP_LINK_TYPE_ENUM
}
from
'./data'
import
{
APP_LINK_GROUP_LIST
,
APP_LINK_TYPE_ENUM
,
AppLink
}
from
'./data'
import
{
ButtonInstance
,
ScrollbarInstance
}
from
'element-plus'
import
{
ButtonInstance
,
ScrollbarInstance
}
from
'element-plus'
import
{
split
}
from
'lodash-es'
import
{
split
}
from
'lodash-es'
import
ProductCategorySelect
from
'@/views/mall/product/category/components/ProductCategorySelect.vue'
import
ProductCategorySelect
from
'@/views/mall/product/category/components/ProductCategorySelect.vue'
...
@@ -74,17 +75,23 @@ defineOptions({ name: 'AppLinkSelectDialog' })
...
@@ -74,17 +75,23 @@ defineOptions({ name: 'AppLinkSelectDialog' })
// 选中的分组,默认选中第一个
// 选中的分组,默认选中第一个
const
activeGroup
=
ref
(
APP_LINK_GROUP_LIST
[
0
].
name
)
const
activeGroup
=
ref
(
APP_LINK_GROUP_LIST
[
0
].
name
)
// 选中的 APP 链接
// 选中的 APP 链接
const
activeAppLink
=
ref
(
''
)
const
activeAppLink
=
ref
(
{}
as
AppLink
)
/** 打开弹窗 */
/** 打开弹窗 */
const
dialogVisible
=
ref
(
false
)
const
dialogVisible
=
ref
(
false
)
const
open
=
(
link
:
string
)
=>
{
const
open
=
(
link
:
string
)
=>
{
activeAppLink
.
value
=
link
activeAppLink
.
value
.
path
=
link
dialogVisible
.
value
=
true
dialogVisible
.
value
=
true
// 滚动到当前的链接
// 滚动到当前的链接
const
group
=
APP_LINK_GROUP_LIST
.
find
((
group
)
=>
const
group
=
APP_LINK_GROUP_LIST
.
find
((
group
)
=>
group
.
links
.
some
((
linkItem
)
=>
isSameLink
(
linkItem
.
path
,
link
))
group
.
links
.
some
((
linkItem
)
=>
{
const
sameLink
=
isSameLink
(
linkItem
.
path
,
link
)
if
(
sameLink
)
{
activeAppLink
.
value
=
{
...
linkItem
,
path
:
link
}
}
return
sameLink
})
)
)
if
(
group
)
{
if
(
group
)
{
// 使用 nextTick 的原因:可能 Dom 还没生成,导致滚动失败
// 使用 nextTick 的原因:可能 Dom 还没生成,导致滚动失败
...
@@ -94,9 +101,9 @@ const open = (link: string) => {
...
@@ -94,9 +101,9 @@ const open = (link: string) => {
defineExpose
({
open
})
defineExpose
({
open
})
// 处理 APP 链接选中
// 处理 APP 链接选中
const
handleAppLinkSelected
=
(
appLink
:
any
)
=>
{
const
handleAppLinkSelected
=
(
appLink
:
AppLink
)
=>
{
if
(
!
isSameLink
(
appLink
.
path
,
activeAppLink
.
value
))
{
if
(
!
isSameLink
(
appLink
.
path
,
activeAppLink
.
value
.
path
))
{
activeAppLink
.
value
=
appLink
.
path
activeAppLink
.
value
=
appLink
}
}
switch
(
appLink
.
type
)
{
switch
(
appLink
.
type
)
{
case
APP_LINK_TYPE_ENUM
.
PRODUCT_CATEGORY_LIST
:
case
APP_LINK_TYPE_ENUM
.
PRODUCT_CATEGORY_LIST
:
...
@@ -104,7 +111,7 @@ const handleAppLinkSelected = (appLink: any) => {
...
@@ -104,7 +111,7 @@ const handleAppLinkSelected = (appLink: any) => {
detailSelectDialog
.
value
.
type
=
appLink
.
type
detailSelectDialog
.
value
.
type
=
appLink
.
type
// 返显
// 返显
detailSelectDialog
.
value
.
id
=
detailSelectDialog
.
value
.
id
=
getUrlNumberValue
(
'id'
,
'http://127.0.0.1'
+
activeAppLink
.
value
)
||
undefined
getUrlNumberValue
(
'id'
,
'http://127.0.0.1'
+
activeAppLink
.
value
.
path
)
||
undefined
break
break
default
:
default
:
break
break
...
@@ -114,10 +121,12 @@ const handleAppLinkSelected = (appLink: any) => {
...
@@ -114,10 +121,12 @@ const handleAppLinkSelected = (appLink: any) => {
// 处理绑定值更新
// 处理绑定值更新
const
emit
=
defineEmits
<
{
const
emit
=
defineEmits
<
{
change
:
[
link
:
string
]
change
:
[
link
:
string
]
appLinkChange
:
[
appLink
:
AppLink
]
}
>
()
}
>
()
const
handleSubmit
=
()
=>
{
const
handleSubmit
=
()
=>
{
dialogVisible
.
value
=
false
dialogVisible
.
value
=
false
emit
(
'change'
,
activeAppLink
.
value
)
emit
(
'change'
,
activeAppLink
.
value
.
path
)
emit
(
'appLinkChange'
,
activeAppLink
.
value
)
}
}
// 分组标题引用列表
// 分组标题引用列表
...
@@ -127,7 +136,7 @@ const groupTitleRefs = ref<HTMLInputElement[]>([])
...
@@ -127,7 +136,7 @@ const groupTitleRefs = ref<HTMLInputElement[]>([])
* @param scrollTop 滚动条的位置
* @param scrollTop 滚动条的位置
*/
*/
const
handleScroll
=
({
scrollTop
}:
{
scrollTop
:
number
})
=>
{
const
handleScroll
=
({
scrollTop
}:
{
scrollTop
:
number
})
=>
{
const
titleEl
=
groupTitleRefs
.
value
.
find
((
titleEl
)
=>
{
const
titleEl
=
groupTitleRefs
.
value
.
find
((
titleEl
:
HTMLInputElement
)
=>
{
// 获取标题的位置信息
// 获取标题的位置信息
const
{
offsetHeight
,
offsetTop
}
=
titleEl
const
{
offsetHeight
,
offsetTop
}
=
titleEl
// 判断标题是否在可视范围内
// 判断标题是否在可视范围内
...
@@ -146,7 +155,7 @@ const linkScrollbar = ref<ScrollbarInstance>()
...
@@ -146,7 +155,7 @@ const linkScrollbar = ref<ScrollbarInstance>()
// 处理分组选中
// 处理分组选中
const
handleGroupSelected
=
(
group
:
string
)
=>
{
const
handleGroupSelected
=
(
group
:
string
)
=>
{
activeGroup
.
value
=
group
activeGroup
.
value
=
group
const
titleRef
=
groupTitleRefs
.
value
.
find
((
item
)
=>
item
.
textContent
===
group
)
const
titleRef
=
groupTitleRefs
.
value
.
find
((
item
:
HTMLInputElement
)
=>
item
.
textContent
===
group
)
if
(
titleRef
)
{
if
(
titleRef
)
{
// 滚动分组标题
// 滚动分组标题
linkScrollbar
.
value
?.
setScrollTop
(
titleRef
.
offsetTop
)
linkScrollbar
.
value
?.
setScrollTop
(
titleRef
.
offsetTop
)
...
@@ -160,8 +169,8 @@ const groupBtnRefs = ref<ButtonInstance[]>([])
...
@@ -160,8 +169,8 @@ const groupBtnRefs = ref<ButtonInstance[]>([])
// 自动滚动分组按钮,确保分组按钮保持在可视区域内
// 自动滚动分组按钮,确保分组按钮保持在可视区域内
const
scrollToGroupBtn
=
(
group
:
string
)
=>
{
const
scrollToGroupBtn
=
(
group
:
string
)
=>
{
const
groupBtn
=
groupBtnRefs
.
value
const
groupBtn
=
groupBtnRefs
.
value
.
map
((
btn
)
=>
btn
[
'ref'
])
.
map
((
btn
:
ButtonInstance
)
=>
btn
[
'ref'
])
.
find
((
ref
)
=>
ref
.
textContent
===
group
)
.
find
((
ref
:
Node
)
=>
ref
.
textContent
===
group
)
if
(
groupBtn
)
{
if
(
groupBtn
)
{
groupScrollbar
.
value
?.
setScrollTop
(
groupBtn
.
offsetTop
)
groupScrollbar
.
value
?.
setScrollTop
(
groupBtn
.
offsetTop
)
}
}
...
@@ -184,11 +193,11 @@ const detailSelectDialog = ref<{
...
@@ -184,11 +193,11 @@ const detailSelectDialog = ref<{
})
})
// 处理详情选择
// 处理详情选择
const
handleProductCategorySelected
=
(
id
:
number
)
=>
{
const
handleProductCategorySelected
=
(
id
:
number
)
=>
{
const
url
=
new
URL
(
activeAppLink
.
value
,
'http://127.0.0.1'
)
const
url
=
new
URL
(
activeAppLink
.
value
.
path
,
'http://127.0.0.1'
)
// 修改 id 参数
// 修改 id 参数
url
.
searchParams
.
set
(
'id'
,
`
${
id
}
`
)
url
.
searchParams
.
set
(
'id'
,
`
${
id
}
`
)
// 排除域名
// 排除域名
activeAppLink
.
value
=
`
${
url
.
pathname
}${
url
.
search
}
`
activeAppLink
.
value
.
path
=
`
${
url
.
pathname
}${
url
.
search
}
`
// 关闭对话框
// 关闭对话框
detailSelectDialog
.
value
.
visible
=
false
detailSelectDialog
.
value
.
visible
=
false
// 重置 id
// 重置 id
...
...
src/components/AppLinkInput/data.ts
View file @
1abff21e
// APP 链接分组
export
interface
AppLinkGroup
{
// 分组名称
name
:
string
// 链接列表
links
:
AppLink
[]
}
// APP 链接
export
interface
AppLink
{
// 链接名称
name
:
string
// 链接地址
path
:
string
// 链接的类型
type
?:
APP_LINK_TYPE_ENUM
}
// APP 链接类型(需要特殊处理,例如商品详情)
// APP 链接类型(需要特殊处理,例如商品详情)
export
const
enum
APP_LINK_TYPE_ENUM
{
export
const
enum
APP_LINK_TYPE_ENUM
{
// 拼团活动
// 拼团活动
...
@@ -243,4 +260,4 @@ export const APP_LINK_GROUP_LIST = [
...
@@ -243,4 +260,4 @@ export const APP_LINK_GROUP_LIST = [
}
}
]
]
}
}
]
]
as
AppLinkGroup
[]
src/components/AppLinkInput/index.vue
View file @
1abff21e
...
@@ -37,7 +37,7 @@ const emit = defineEmits<{
...
@@ -37,7 +37,7 @@ const emit = defineEmits<{
'update:modelValue'
:
[
link
:
string
]
'update:modelValue'
:
[
link
:
string
]
}
>
()
}
>
()
watch
(
watch
(
()
=>
appLink
,
()
=>
appLink
.
value
,
()
=>
emit
(
'update:modelValue'
,
appLink
.
value
)
()
=>
emit
(
'update:modelValue'
,
appLink
.
value
)
)
)
</
script
>
</
script
>
src/components/DiyEditor/components/mobile/HotZone/components/HotZoneEditDialog/controller.ts
0 → 100644
View file @
1abff21e
import
{
HotZoneItemProperty
}
from
'@/components/DiyEditor/components/mobile/HotZone/config'
import
{
StyleValue
}
from
'vue'
// 热区的最小宽高
export
const
HOT_ZONE_MIN_SIZE
=
100
// 控制的类型
export
enum
CONTROL_TYPE_ENUM
{
LEFT
,
TOP
,
WIDTH
,
HEIGHT
}
// 定义热区的控制点
export
interface
ControlDot
{
position
:
string
types
:
CONTROL_TYPE_ENUM
[]
style
:
StyleValue
}
// 热区的8个控制点
export
const
CONTROL_DOT_LIST
=
[
{
position
:
'左上角'
,
types
:
[
CONTROL_TYPE_ENUM
.
LEFT
,
CONTROL_TYPE_ENUM
.
TOP
,
CONTROL_TYPE_ENUM
.
WIDTH
,
CONTROL_TYPE_ENUM
.
HEIGHT
],
style
:
{
left
:
'-5px'
,
top
:
'-5px'
,
cursor
:
'nwse-resize'
}
},
{
position
:
'上方中间'
,
types
:
[
CONTROL_TYPE_ENUM
.
TOP
,
CONTROL_TYPE_ENUM
.
HEIGHT
],
style
:
{
left
:
'50%'
,
top
:
'-5px'
,
cursor
:
'n-resize'
,
transform
:
'translateX(-50%)'
}
},
{
position
:
'右上角'
,
types
:
[
CONTROL_TYPE_ENUM
.
TOP
,
CONTROL_TYPE_ENUM
.
WIDTH
,
CONTROL_TYPE_ENUM
.
HEIGHT
],
style
:
{
right
:
'-5px'
,
top
:
'-5px'
,
cursor
:
'nesw-resize'
}
},
{
position
:
'右侧中间'
,
types
:
[
CONTROL_TYPE_ENUM
.
WIDTH
],
style
:
{
right
:
'-5px'
,
top
:
'50%'
,
cursor
:
'e-resize'
,
transform
:
'translateX(-50%)'
}
},
{
position
:
'右下角'
,
types
:
[
CONTROL_TYPE_ENUM
.
WIDTH
,
CONTROL_TYPE_ENUM
.
HEIGHT
],
style
:
{
right
:
'-5px'
,
bottom
:
'-5px'
,
cursor
:
'nwse-resize'
}
},
{
position
:
'下方中间'
,
types
:
[
CONTROL_TYPE_ENUM
.
HEIGHT
],
style
:
{
left
:
'50%'
,
bottom
:
'-5px'
,
cursor
:
's-resize'
,
transform
:
'translateX(-50%)'
}
},
{
position
:
'左下角'
,
types
:
[
CONTROL_TYPE_ENUM
.
LEFT
,
CONTROL_TYPE_ENUM
.
WIDTH
,
CONTROL_TYPE_ENUM
.
HEIGHT
],
style
:
{
left
:
'-5px'
,
bottom
:
'-5px'
,
cursor
:
'nesw-resize'
}
},
{
position
:
'左侧中间'
,
types
:
[
CONTROL_TYPE_ENUM
.
LEFT
,
CONTROL_TYPE_ENUM
.
WIDTH
],
style
:
{
left
:
'-5px'
,
top
:
'50%'
,
cursor
:
'w-resize'
,
transform
:
'translateX(-50%)'
}
}
]
as
ControlDot
[]
//region 热区的缩放
// 热区的缩放比例
export
const
HOT_ZONE_SCALE_RATE
=
2
// 缩小:缩回适合手机屏幕的大小
export
const
zoomOut
=
(
list
?:
HotZoneItemProperty
[])
=>
{
return
(
list
?.
map
((
hotZone
)
=>
({
...
hotZone
,
left
:
(
hotZone
.
left
/=
HOT_ZONE_SCALE_RATE
),
top
:
(
hotZone
.
top
/=
HOT_ZONE_SCALE_RATE
),
width
:
(
hotZone
.
width
/=
HOT_ZONE_SCALE_RATE
),
height
:
(
hotZone
.
height
/=
HOT_ZONE_SCALE_RATE
)
}))
||
[]
)
}
// 放大:作用是为了方便在电脑屏幕上编辑
export
const
zoomIn
=
(
list
?:
HotZoneItemProperty
[])
=>
{
return
(
list
?.
map
((
hotZone
)
=>
({
...
hotZone
,
left
:
(
hotZone
.
left
*=
HOT_ZONE_SCALE_RATE
),
top
:
(
hotZone
.
top
*=
HOT_ZONE_SCALE_RATE
),
width
:
(
hotZone
.
width
*=
HOT_ZONE_SCALE_RATE
),
height
:
(
hotZone
.
height
*=
HOT_ZONE_SCALE_RATE
)
}))
||
[]
)
}
//endregion
/**
* 封装热区拖拽
*
* 注:为什么不使用vueuse的useDraggable。在本场景下,其使用方式比较复杂
* @param hotZone 热区
* @param downEvent 鼠标按下事件
* @param callback 回调函数
*/
export
const
useDraggable
=
(
hotZone
:
HotZoneItemProperty
,
downEvent
:
MouseEvent
,
callback
:
(
left
:
number
,
top
:
number
,
width
:
number
,
height
:
number
,
moveWidth
:
number
,
moveHeight
:
number
)
=>
void
)
=>
{
// 阻止事件冒泡
downEvent
.
stopPropagation
()
// 移动前的鼠标坐标
const
{
clientX
:
startX
,
clientY
:
startY
}
=
downEvent
// 移动前的热区坐标、大小
const
{
left
,
top
,
width
,
height
}
=
hotZone
// 监听鼠标移动
document
.
onmousemove
=
(
e
)
=>
{
// 移动宽度
const
moveWidth
=
e
.
clientX
-
startX
// 移动高度
const
moveHeight
=
e
.
clientY
-
startY
// 移动回调
callback
(
left
,
top
,
width
,
height
,
moveWidth
,
moveHeight
)
}
// 松开鼠标后,结束拖拽
document
.
onmouseup
=
()
=>
{
document
.
onmousemove
=
null
document
.
onmouseup
=
null
}
}
src/components/DiyEditor/components/mobile/HotZone/components/HotZoneEditDialog/index.vue
0 → 100644
View file @
1abff21e
<
template
>
<Dialog
v-model=
"dialogVisible"
title=
"设置热区"
width=
"780"
@
close=
"handleClose"
>
<div
ref=
"container"
class=
"relative h-full w-750px"
>
<el-image
:src=
"imgUrl"
class=
"pointer-events-none h-full w-750px select-none"
/>
<div
v-for=
"(item, hotZoneIndex) in formData"
:key=
"hotZoneIndex"
class=
"hot-zone"
:style=
"
{
width: `${item.width}px`,
height: `${item.height}px`,
top: `${item.top}px`,
left: `${item.left}px`
}"
@mousedown="handleMove(item, $event)"
@dblclick="handleShowAppLinkDialog(item)"
>
<span
class=
"pointer-events-none select-none"
>
{{
item
.
name
||
'双击选择链接'
}}
</span>
<Icon
icon=
"ep:close"
class=
"delete"
:size=
"14"
@
click=
"handleRemove(item)"
/>
<!-- 8个控制点 -->
<span
class=
"ctrl-dot"
v-for=
"(dot, dotIndex) in CONTROL_DOT_LIST"
:key=
"dotIndex"
:style=
"dot.style"
@
mousedown=
"handleResize(item, dot, $event)"
></span>
</div>
</div>
<template
#
footer
>
<el-button
@
click=
"handleAdd"
type=
"primary"
plain
>
<Icon
icon=
"ep:plus"
class=
"mr-5px"
/>
添加热区
</el-button>
<el-button
@
click=
"handleSubmit"
type=
"primary"
plain
>
<Icon
icon=
"ep:check"
class=
"mr-5px"
/>
确定
</el-button>
</
template
>
</Dialog>
<AppLinkSelectDialog
ref=
"appLinkDialogRef"
@
app-link-change=
"handleAppLinkChange"
/>
</template>
<
script
setup
lang=
"ts"
>
import
{
HotZoneItemProperty
}
from
'@/components/DiyEditor/components/mobile/HotZone/config'
import
{
array
,
string
}
from
'vue-types'
import
{
CONTROL_DOT_LIST
,
CONTROL_TYPE_ENUM
,
ControlDot
,
HOT_ZONE_MIN_SIZE
,
useDraggable
,
zoomIn
,
zoomOut
}
from
'./controller'
import
{
AppLink
}
from
'@/components/AppLinkInput/data'
import
{
remove
}
from
'lodash-es'
/** 热区编辑对话框 */
defineOptions
({
name
:
'HotZoneEditDialog'
})
// 定义属性
const
props
=
defineProps
({
modelValue
:
array
<
HotZoneItemProperty
>
(),
imgUrl
:
string
().
def
(
''
)
})
const
emit
=
defineEmits
([
'update:modelValue'
])
const
formData
=
ref
<
HotZoneItemProperty
[]
>
([])
// 弹窗的是否显示
const
dialogVisible
=
ref
(
false
)
// 打开弹窗
const
open
=
()
=>
{
// 放大
formData
.
value
=
zoomIn
(
props
.
modelValue
)
dialogVisible
.
value
=
true
}
// 提供 open 方法,用于打开弹窗
defineExpose
({
open
})
// 热区容器
const
container
=
ref
<
HTMLDivElement
>
()
// 增加热区
const
handleAdd
=
()
=>
{
formData
.
value
.
push
({
width
:
HOT_ZONE_MIN_SIZE
,
height
:
HOT_ZONE_MIN_SIZE
,
top
:
0
,
left
:
0
}
as
HotZoneItemProperty
)
}
// 删除热区
const
handleRemove
=
(
hotZone
:
HotZoneItemProperty
)
=>
{
remove
(
formData
.
value
,
hotZone
)
}
// 移动热区
const
handleMove
=
(
item
:
HotZoneItemProperty
,
e
:
MouseEvent
)
=>
{
useDraggable
(
item
,
e
,
(
left
,
top
,
_
,
__
,
moveWidth
,
moveHeight
)
=>
{
setLeft
(
item
,
left
+
moveWidth
)
setTop
(
item
,
top
+
moveHeight
)
})
}
// 调整热区大小、位置
const
handleResize
=
(
item
:
HotZoneItemProperty
,
ctrlDot
:
ControlDot
,
e
:
MouseEvent
)
=>
{
useDraggable
(
item
,
e
,
(
left
,
top
,
width
,
height
,
moveWidth
,
moveHeight
)
=>
{
ctrlDot
.
types
.
forEach
((
type
)
=>
{
switch
(
type
)
{
case
CONTROL_TYPE_ENUM
.
LEFT
:
setLeft
(
item
,
left
+
moveWidth
)
break
case
CONTROL_TYPE_ENUM
.
TOP
:
setTop
(
item
,
top
+
moveHeight
)
break
case
CONTROL_TYPE_ENUM
.
WIDTH
:
{
// 上移时,高度为减少
const
direction
=
ctrlDot
.
types
.
includes
(
CONTROL_TYPE_ENUM
.
LEFT
)
?
-
1
:
1
setWidth
(
item
,
width
+
moveWidth
*
direction
)
}
break
case
CONTROL_TYPE_ENUM
.
HEIGHT
:
{
// 左移时,宽度为减少
const
direction
=
ctrlDot
.
types
.
includes
(
CONTROL_TYPE_ENUM
.
TOP
)
?
-
1
:
1
setHeight
(
item
,
height
+
moveHeight
*
direction
)
}
break
}
})
})
}
// 设置X轴坐标
const
setLeft
=
(
item
:
HotZoneItemProperty
,
left
:
number
)
=>
{
// 不能超出容器
if
(
left
>=
0
&&
left
<=
container
.
value
!
.
offsetWidth
-
item
.
width
)
{
item
.
left
=
left
}
}
// 设置Y轴坐标
const
setTop
=
(
item
:
HotZoneItemProperty
,
top
:
number
)
=>
{
// 不能超出容器
if
(
top
>=
0
&&
top
<=
container
.
value
!
.
offsetHeight
-
item
.
height
)
{
item
.
top
=
top
}
}
// 设置宽度
const
setWidth
=
(
item
:
HotZoneItemProperty
,
width
:
number
)
=>
{
// 不能小于最小宽度 && 不能超出容器右边
if
(
width
>=
HOT_ZONE_MIN_SIZE
&&
item
.
left
+
width
<=
container
.
value
!
.
offsetWidth
)
{
item
.
width
=
width
}
}
// 设置高度
const
setHeight
=
(
item
:
HotZoneItemProperty
,
height
:
number
)
=>
{
// 不能小于最小高度 && 不能超出容器底部
if
(
height
>=
HOT_ZONE_MIN_SIZE
&&
item
.
top
+
height
<=
container
.
value
!
.
offsetHeight
)
{
item
.
height
=
height
}
}
// 处理对话框关闭
const
handleSubmit
=
()
=>
{
// 会自动触发handleClose
dialogVisible
.
value
=
false
}
// 处理对话框关闭
const
handleClose
=
()
=>
{
// 缩小
const
list
=
zoomOut
(
formData
.
value
)
emit
(
'update:modelValue'
,
list
)
}
const
activeHotZone
=
ref
<
HotZoneItemProperty
>
()
const
appLinkDialogRef
=
ref
()
const
handleShowAppLinkDialog
=
(
hotZone
:
HotZoneItemProperty
)
=>
{
activeHotZone
.
value
=
hotZone
appLinkDialogRef
.
value
.
open
(
hotZone
.
url
)
}
const
handleAppLinkChange
=
(
appLink
:
AppLink
)
=>
{
if
(
!
appLink
||
!
activeHotZone
.
value
)
return
activeHotZone
.
value
.
name
=
appLink
.
name
activeHotZone
.
value
.
url
=
appLink
.
path
}
</
script
>
<
style
scoped
lang=
"scss"
>
.hot-zone
{
position
:
absolute
;
background
:
var
(
--el-color-primary-light-7
);
opacity
:
0.8
;
border
:
1px
solid
var
(
--el-color-primary
);
color
:
var
(
--el-color-primary
);
font-size
:
16px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
move
;
z-index
:
10
;
/* 控制点 */
.ctrl-dot
{
position
:
absolute
;
width
:
8px
;
height
:
8px
;
border-radius
:
50%
;
border
:
inherit
;
background-color
:
#fff
;
z-index
:
11
;
}
.delete
{
display
:
none
;
position
:
absolute
;
top
:
0
;
right
:
0
;
padding
:
2px
2px
6px
6px
;
background-color
:
var
(
--el-color-primary
);
border-radius
:
0
0
0
80%
;
cursor
:
pointer
;
color
:
#fff
;
text-align
:
right
;
}
&
:hover
{
.delete
{
display
:
block
;
}
}
}
</
style
>
src/components/DiyEditor/components/mobile/HotZone/config.ts
0 → 100644
View file @
1abff21e
import
{
ComponentStyle
,
DiyComponent
}
from
'@/components/DiyEditor/util'
/** 热区属性 */
export
interface
HotZoneProperty
{
// 图片地址
imgUrl
:
string
// 导航菜单列表
list
:
HotZoneItemProperty
[]
// 组件样式
style
:
ComponentStyle
}
/** 热区项目属性 */
export
interface
HotZoneItemProperty
{
// 链接的名称
name
:
string
// 链接
url
:
string
// 宽
width
:
number
// 高
height
:
number
// 上
top
:
number
// 左
left
:
number
}
// 定义组件
export
const
component
=
{
id
:
'HotZone'
,
name
:
'热区'
,
icon
:
'tabler:hand-click'
,
property
:
{
imgUrl
:
''
,
list
:
[]
as
HotZoneItemProperty
[],
style
:
{
bgType
:
'color'
,
bgColor
:
'#fff'
,
marginBottom
:
8
}
as
ComponentStyle
}
}
as
DiyComponent
<
HotZoneProperty
>
src/components/DiyEditor/components/mobile/HotZone/index.vue
0 → 100644
View file @
1abff21e
<
template
>
<div
class=
"relative h-full min-h-30px w-full"
>
<el-image
:src=
"property.imgUrl"
class=
"pointer-events-none h-full w-full select-none"
/>
<div
v-for=
"(item, index) in property.list"
:key=
"index"
class=
"hot-zone"
:style=
"
{
width: `${item.width}px`,
height: `${item.height}px`,
top: `${item.top}px`,
left: `${item.left}px`
}"
>
{{
item
.
name
}}
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
HotZoneProperty
}
from
'./config'
/** 热区 */
defineOptions
({
name
:
'HotZone'
})
const
props
=
defineProps
<
{
property
:
HotZoneProperty
}
>
()
</
script
>
<
style
scoped
lang=
"scss"
>
.hot-zone
{
position
:
absolute
;
background
:
var
(
--el-color-primary-light-7
);
opacity
:
0.8
;
border
:
1px
solid
var
(
--el-color-primary
);
color
:
var
(
--el-color-primary
);
font-size
:
14px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
move
;
z-index
:
10
;
}
</
style
>
src/components/DiyEditor/components/mobile/HotZone/property.vue
0 → 100644
View file @
1abff21e
<
template
>
<ComponentContainerProperty
v-model=
"formData.style"
>
<!-- 表单 -->
<el-form
label-width=
"80px"
:model=
"formData"
class=
"m-t-8px"
>
<el-form-item
label=
"上传图片"
prop=
"imgUrl"
>
<UploadImg
v-model=
"formData.imgUrl"
height=
"50px"
width=
"auto"
class=
"min-w-80px"
>
<template
#
tip
>
<el-text
type=
"info"
size=
"small"
>
推荐宽度 750
</el-text>
</
template
>
</UploadImg>
</el-form-item>
</el-form>
<el-button
type=
"primary"
plain
class=
"w-full"
@
click=
"handleOpenEditDialog"
>
设置热区
</el-button>
</ComponentContainerProperty>
<!-- 热区编辑对话框 -->
<HotZoneEditDialog
ref=
"editDialogRef"
v-model=
"formData.list"
:img-url=
"formData.imgUrl"
/>
</template>
<
script
setup
lang=
"ts"
>
import
{
usePropertyForm
}
from
'@/components/DiyEditor/util'
import
{
HotZoneProperty
}
from
'@/components/DiyEditor/components/mobile/HotZone/config'
import
HotZoneEditDialog
from
'./components/HotZoneEditDialog/index.vue'
/** 热区属性面板 */
defineOptions
({
name
:
'HotZoneProperty'
})
const
props
=
defineProps
<
{
modelValue
:
HotZoneProperty
}
>
()
const
emit
=
defineEmits
([
'update:modelValue'
])
const
{
formData
}
=
usePropertyForm
(
props
.
modelValue
,
emit
)
// 热区编辑对话框
const
editDialogRef
=
ref
()
// 打开热区编辑对话框
const
handleOpenEditDialog
=
()
=>
{
editDialogRef
.
value
.
open
()
}
</
script
>
<
style
scoped
lang=
"scss"
>
.hot-zone
{
position
:
absolute
;
background
:
#409eff
bf
;
border
:
1px
solid
var
(
--el-color-primary
);
color
:
#fff
;
font-size
:
12px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
move
;
/* 控制点 */
.ctrl-dot
{
position
:
absolute
;
width
:
4px
;
height
:
4px
;
border-radius
:
50%
;
background-color
:
#fff
;
}
}
</
style
>
src/components/DiyEditor/components/mobile/MenuSwiper/index.vue
View file @
1abff21e
...
@@ -28,7 +28,7 @@
...
@@ -28,7 +28,7 @@
<!-- 标题 -->
<!-- 标题 -->
<span
<span
v-if=
"property.layout === 'iconText'"
v-if=
"property.layout === 'iconText'"
class=
"text-1
4
px"
class=
"text-1
2
px"
:style=
"
{
:style=
"
{
color: item.titleColor,
color: item.titleColor,
height: `${TITLE_HEIGHT}px`,
height: `${TITLE_HEIGHT}px`,
...
@@ -51,7 +51,7 @@ const props = defineProps<{ property: MenuSwiperProperty }>()
...
@@ -51,7 +51,7 @@ const props = defineProps<{ property: MenuSwiperProperty }>()
// 标题的高度
// 标题的高度
const
TITLE_HEIGHT
=
20
const
TITLE_HEIGHT
=
20
// 图标的高度
// 图标的高度
const
ICON_SIZE
=
50
const
ICON_SIZE
=
42
// 垂直间距:一行上下的间距
// 垂直间距:一行上下的间距
const
SPACE_Y
=
16
const
SPACE_Y
=
16
...
...
src/components/DiyEditor/components/mobile/MenuSwiper/property.vue
View file @
1abff21e
...
@@ -23,7 +23,7 @@
...
@@ -23,7 +23,7 @@
</el-form-item>
</el-form-item>
<el-card
header=
"菜单设置"
class=
"property-group"
shadow=
"never"
>
<el-card
header=
"菜单设置"
class=
"property-group"
shadow=
"never"
>
<Draggable
v-model=
"formData.list"
:empty-item=
"cloneDeep(EMPTY_MENU_SWIPER_ITEM_PROPERTY"
>
<Draggable
v-model=
"formData.list"
:empty-item=
"cloneDeep(EMPTY_MENU_SWIPER_ITEM_PROPERTY
)
"
>
<template
#
default=
"
{ element }">
<template
#
default=
"
{ element }">
<el-form-item
label=
"图标"
prop=
"iconUrl"
>
<el-form-item
label=
"图标"
prop=
"iconUrl"
>
<UploadImg
v-model=
"element.iconUrl"
height=
"80px"
width=
"80px"
>
<UploadImg
v-model=
"element.iconUrl"
height=
"80px"
width=
"80px"
>
...
...
src/components/DiyEditor/components/mobile/TitleBar/config.ts
View file @
1abff21e
import
{
DiyComponent
}
from
'@/components/DiyEditor/util'
import
{
ComponentStyle
,
DiyComponent
}
from
'@/components/DiyEditor/util'
/** 标题栏属性 */
/** 标题栏属性 */
export
interface
TitleBarProperty
{
export
interface
TitleBarProperty
{
// 背景图
bgImgUrl
:
string
// 偏移
marginLeft
:
number
// 显示位置
textAlign
:
'left'
|
'center'
// 主标题
// 主标题
title
:
string
title
:
string
// 副标题
// 副标题
...
@@ -12,18 +18,12 @@ export interface TitleBarProperty {
...
@@ -12,18 +18,12 @@ export interface TitleBarProperty {
descriptionSize
:
number
descriptionSize
:
number
// 标题粗细
// 标题粗细
titleWeight
:
number
titleWeight
:
number
// 显示位置
position
:
'left'
|
'center'
// 描述粗细
// 描述粗细
descriptionWeight
:
number
descriptionWeight
:
number
// 标题颜色
// 标题颜色
titleColor
:
string
titleColor
:
string
// 描述颜色
// 描述颜色
descriptionColor
:
string
descriptionColor
:
string
// 背景颜色
backgroundColor
:
string
// 底部分割线
showBottomBorder
:
false
// 查看更多
// 查看更多
more
:
{
more
:
{
// 是否显示查看更多
// 是否显示查看更多
...
@@ -35,6 +35,8 @@ export interface TitleBarProperty {
...
@@ -35,6 +35,8 @@ export interface TitleBarProperty {
// 链接
// 链接
url
:
string
url
:
string
}
}
// 组件样式
style
:
ComponentStyle
}
}
// 定义组件
// 定义组件
...
@@ -48,18 +50,20 @@ export const component = {
...
@@ -48,18 +50,20 @@ export const component = {
titleSize
:
16
,
titleSize
:
16
,
descriptionSize
:
12
,
descriptionSize
:
12
,
titleWeight
:
400
,
titleWeight
:
400
,
positio
n
:
'left'
,
textAlig
n
:
'left'
,
descriptionWeight
:
200
,
descriptionWeight
:
200
,
titleColor
:
'rgba(50, 50, 51, 10)'
,
titleColor
:
'rgba(50, 50, 51, 10)'
,
descriptionColor
:
'rgba(150, 151, 153, 10)'
,
descriptionColor
:
'rgba(150, 151, 153, 10)'
,
backgroundColor
:
'rgba(255, 255, 255, 10)'
,
showBottomBorder
:
false
,
more
:
{
more
:
{
//查看更多
//查看更多
show
:
false
,
show
:
false
,
type
:
'icon'
,
type
:
'icon'
,
text
:
'查看更多'
,
text
:
'查看更多'
,
url
:
''
url
:
''
}
},
style
:
{
bgType
:
'color'
,
bgColor
:
'#fff'
}
as
ComponentStyle
}
}
}
as
DiyComponent
<
TitleBarProperty
>
}
as
DiyComponent
<
TitleBarProperty
>
src/components/DiyEditor/components/mobile/TitleBar/index.vue
View file @
1abff21e
<
template
>
<
template
>
<div
<div
class=
"title-bar"
>
class=
"title-bar"
<el-image
v-if=
"property.bgImgUrl"
:src=
"property.bgImgUrl"
fit=
"cover"
class=
"w-full"
/>
:style=
"
{
<div
class=
"absolute left-0 top-0 w-full"
>
background: property.backgroundColor,
borderBottom: property.showBottomBorder ? '1px solid #F9F9F9' : '1px solid #fff'
}"
>
<div>
<!-- 标题 -->
<!-- 标题 -->
<div
<div
:style=
"
{
:style=
"
{
fontSize: `${property.titleSize}px`,
fontSize: `${property.titleSize}px`,
fontWeight: property.titleWeight,
fontWeight: property.titleWeight,
color: property.titleColor,
color: property.titleColor,
textAlign: property.
positio
n
textAlign: property.
textAlig
n
}"
}"
v-if="property.title"
v-if="property.title"
>
>
...
@@ -25,7 +20,7 @@
...
@@ -25,7 +20,7 @@
fontSize: `${property.descriptionSize}px`,
fontSize: `${property.descriptionSize}px`,
fontWeight: property.descriptionWeight,
fontWeight: property.descriptionWeight,
color: property.descriptionColor,
color: property.descriptionColor,
textAlign: property.
positio
n
textAlign: property.
textAlig
n
}"
}"
class="m-t-8px"
class="m-t-8px"
v-if="property.description"
v-if="property.description"
...
@@ -38,10 +33,10 @@
...
@@ -38,10 +33,10 @@
class=
"more"
class=
"more"
v-show=
"property.more.show"
v-show=
"property.more.show"
:style=
"
{
:style=
"
{
color: property.
more.type === 'text' ? '#38f' : ''
color: property.
descriptionColor
}"
}"
>
>
{{
property
.
more
.
type
===
'icon'
?
''
:
property
.
more
.
text
}}
<span
v-if=
"property.more.type !== 'icon'"
>
{{
property
.
more
.
text
}}
</span>
<Icon
icon=
"ep:arrow-right"
v-if=
"property.more.type !== 'text'"
/>
<Icon
icon=
"ep:arrow-right"
v-if=
"property.more.type !== 'text'"
/>
</div>
</div>
</div>
</div>
...
@@ -59,8 +54,6 @@ defineProps<{ property: TitleBarProperty }>()
...
@@ -59,8 +54,6 @@ defineProps<{ property: TitleBarProperty }>()
position
:
relative
;
position
:
relative
;
width
:
100%
;
width
:
100%
;
min-height
:
20px
;
min-height
:
20px
;
padding
:
8px
16px
;
border
:
2px
solid
#fff
;
box-sizing
:
border-box
;
box-sizing
:
border-box
;
/* 更多 */
/* 更多 */
...
...
src/components/DiyEditor/components/mobile/TitleBar/property.vue
View file @
1abff21e
<
template
>
<
template
>
<
section
class=
"title-bar
"
>
<
ComponentContainerProperty
v-model=
"formData.style
"
>
<el-form
label-width=
"85px"
:model=
"formData"
:rules=
"rules"
>
<el-form
label-width=
"85px"
:model=
"formData"
:rules=
"rules"
>
<el-form-item
label=
"主标题"
prop=
"title"
>
<el-card
header=
"风格"
class=
"property-group"
shadow=
"never"
>
<el-input
<el-form-item
label=
"背景图片"
prop=
"bgImgUrl"
>
v-model=
"formData.title"
<UploadImg
v-model=
"formData.bgImgUrl"
width=
"100%"
height=
"40px"
>
placeholder=
"请输入主标题"
<template
#
tip
>
建议尺寸 750*80
</
template
>
show-word-limit
</UploadImg>
maxlength=
"20"
/>
</el-form-item>
<el-form-item
label=
"副标题"
prop=
"description"
>
<el-input
type=
"textarea"
v-model=
"formData.description"
placeholder=
"请输入副标题"
maxlength=
"50"
show-word-limit
/>
</el-form-item>
</el-form-item>
<el-form-item
label=
"显示位置"
prop=
"positio
n"
>
<el-form-item
label=
"标题位置"
prop=
"textAlig
n"
>
<el-radio-group
v-model=
"formData!.positio
n"
>
<el-radio-group
v-model=
"formData!.textAlig
n"
>
<el-tooltip
content=
"居左"
placement=
"top"
>
<el-tooltip
content=
"居左"
placement=
"top"
>
<el-radio-button
label=
"left"
>
<el-radio-button
label=
"left"
>
<Icon
icon=
"ant-design:align-left-outlined"
/>
<Icon
icon=
"ant-design:align-left-outlined"
/>
...
@@ -32,19 +21,26 @@
...
@@ -32,19 +21,26 @@
</el-tooltip>
</el-tooltip>
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<el-form-item
label=
"标题大小"
prop=
"titleSize"
>
</el-card>
<el-slider
v-model=
"formData.titleSize"
:max=
"60"
:min=
"10"
show-input
input-size=
"small"
/>
<el-card
header=
"主标题"
class=
"property-group"
shadow=
"never"
>
<el-form-item
label=
"文字"
prop=
"title"
label-width=
"40px"
>
<InputWithColor
v-model=
"formData.title"
v-model:color=
"formData.titleColor"
show-word-limit
maxlength=
"20"
/>
</el-form-item>
</el-form-item>
<el-form-item
label=
"副标题大小"
prop=
"descriptionSize
"
>
<el-form-item
label=
"大小"
prop=
"titleSize"
label-width=
"40px
"
>
<el-slider
<el-slider
v-model=
"formData.description
Size"
v-model=
"formData.title
Size"
:max=
"60"
:max=
"60"
:min=
"10"
:min=
"10"
show-input
show-input
input-size=
"small"
input-size=
"small"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label=
"标题粗细"
prop=
"titleWeight
"
>
<el-form-item
label=
"粗细"
prop=
"titleWeight"
label-width=
"40px
"
>
<el-slider
<el-slider
v-model=
"formData.titleWeight"
v-model=
"formData.titleWeight"
:min=
"100"
:min=
"100"
...
@@ -54,7 +50,26 @@
...
@@ -54,7 +50,26 @@
input-size=
"small"
input-size=
"small"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label=
"副标题粗细"
prop=
"descriptionWeight"
>
</el-card>
<el-card
header=
"副标题"
class=
"property-group"
shadow=
"never"
>
<el-form-item
label=
"文字"
prop=
"description"
label-width=
"40px"
>
<InputWithColor
v-model=
"formData.description"
v-model:color=
"formData.descriptionColor"
show-word-limit
maxlength=
"50"
/>
</el-form-item>
<el-form-item
label=
"大小"
prop=
"descriptionSize"
label-width=
"40px"
>
<el-slider
v-model=
"formData.descriptionSize"
:max=
"60"
:min=
"10"
show-input
input-size=
"small"
/>
</el-form-item>
<el-form-item
label=
"粗细"
prop=
"descriptionWeight"
label-width=
"40px"
>
<el-slider
<el-slider
v-model=
"formData.descriptionWeight"
v-model=
"formData.descriptionWeight"
:min=
"100"
:min=
"100"
...
@@ -64,22 +79,12 @@
...
@@ -64,22 +79,12 @@
input-size=
"small"
input-size=
"small"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label=
"标题颜色"
prop=
"titleColor"
>
</el-card>
<ColorInput
v-model=
"formData.titleColor"
/>
<el-card
header=
"查看更多"
class=
"property-group"
shadow=
"never"
>
</el-form-item>
<el-form-item
label=
"是否显示"
prop=
"more.show"
>
<el-form-item
label=
"副标题颜色"
prop=
"descriptionColor"
>
<ColorInput
v-model=
"formData.descriptionColor"
/>
</el-form-item>
<el-form-item
label=
"背景颜色"
prop=
"backgroundColor"
>
<ColorInput
v-model=
"formData.backgroundColor"
/>
</el-form-item>
<el-form-item
label=
"底部分割线"
prop=
"showBottomBorder"
>
<el-switch
v-model=
"formData!.showBottomBorder"
/>
</el-form-item>
<el-form-item
label=
"查看更多"
prop=
"more.show"
>
<el-checkbox
v-model=
"formData.more.show"
/>
<el-checkbox
v-model=
"formData.more.show"
/>
</el-form-item>
</el-form-item>
<!-- 更多
样式选择 -->
<!-- 更多按钮的
样式选择 -->
<
template
v-if=
"formData.more.show"
>
<
template
v-if=
"formData.more.show"
>
<el-form-item
label=
"样式"
prop=
"more.type"
>
<el-form-item
label=
"样式"
prop=
"more.type"
>
<el-radio-group
v-model=
"formData.more.type"
>
<el-radio-group
v-model=
"formData.more.type"
>
...
@@ -95,8 +100,9 @@
...
@@ -95,8 +100,9 @@
<AppLinkInput
v-model=
"formData.more.url"
/>
<AppLinkInput
v-model=
"formData.more.url"
/>
</el-form-item>
</el-form-item>
</
template
>
</
template
>
</el-card>
</el-form>
</el-form>
</
section
>
</
ComponentContainerProperty
>
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
TitleBarProperty
}
from
'./config'
import
{
TitleBarProperty
}
from
'./config'
...
...
src/components/DiyEditor/util.ts
View file @
1abff21e
...
@@ -124,7 +124,15 @@ export const PAGE_LIBS = [
...
@@ -124,7 +124,15 @@ export const PAGE_LIBS = [
{
{
name
:
'图文组件'
,
name
:
'图文组件'
,
extended
:
true
,
extended
:
true
,
components
:
[
'ImageBar'
,
'Carousel'
,
'TitleBar'
,
'VideoPlayer'
,
'Divider'
,
'MagicCube'
]
components
:
[
'ImageBar'
,
'Carousel'
,
'TitleBar'
,
'VideoPlayer'
,
'Divider'
,
'MagicCube'
,
'HotZone'
]
},
},
{
name
:
'商品组件'
,
extended
:
true
,
components
:
[
'ProductCard'
,
'ProductList'
]
},
{
name
:
'商品组件'
,
extended
:
true
,
components
:
[
'ProductCard'
,
'ProductList'
]
},
{
{
...
...
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