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
82aed175
authored
Nov 09, 2023
by
owen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
营销:适配商城装修组件【菜单导航】
parent
ac424936
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
344 additions
and
44 deletions
+344
-44
src/components/ColorInput/index.vue
+2
-22
src/components/DiyEditor/components/ComponentContainer.vue
+21
-10
src/components/DiyEditor/components/mobile/MenuGrid/config.ts
+1
-1
src/components/DiyEditor/components/mobile/MenuSwiper/config.ts
+66
-0
src/components/DiyEditor/components/mobile/MenuSwiper/index.vue
+119
-0
src/components/DiyEditor/components/mobile/MenuSwiper/property.vue
+106
-0
src/components/DiyEditor/util.ts
+6
-10
src/components/InputWithColor/index.vue
+2
-1
src/utils/color.ts
+21
-0
No files found.
src/components/ColorInput/index.vue
View file @
82aed175
<
template
>
<
template
>
<el-input
v-model=
"color"
>
<el-input
v-model=
"color"
>
<template
#
prepend
>
<template
#
prepend
>
<el-color-picker
v-model=
"color"
:predefine=
"COLORS"
/>
<el-color-picker
v-model=
"color"
:predefine=
"
PREDEFINE_
COLORS"
/>
</
template
>
</
template
>
</el-input>
</el-input>
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
PREDEFINE_COLORS
}
from
'@/utils/color'
// 颜色输入框
// 颜色输入框
defineOptions
({
name
:
'ColorInput'
})
defineOptions
({
name
:
'ColorInput'
})
// 预设颜色
const
COLORS
=
[
'#ff4500'
,
'#ff8c00'
,
'#ffd700'
,
'#90ee90'
,
'#00ced1'
,
'#1e90ff'
,
'#c71585'
,
'#409EFF'
,
'#909399'
,
'#C0C4CC'
,
'#b7390b'
,
'#ff7800'
,
'#fad400'
,
'#5b8c5f'
,
'#00babd'
,
'#1f73c3'
,
'#711f57'
]
const
props
=
defineProps
({
const
props
=
defineProps
({
modelValue
:
propTypes
.
string
.
def
(
''
)
modelValue
:
propTypes
.
string
.
def
(
''
)
})
})
...
...
src/components/DiyEditor/components/ComponentContainer.vue
View file @
82aed175
<
template
>
<
template
>
<div
:class=
"['component',
{ active: active }]">
<div
:class=
"['component',
{ active: active }]">
<div
<div
class=
"component-inner"
:style=
"
{
:style=
"
{
...style
...style
}"
}"
...
@@ -130,23 +131,19 @@ $toolbar-position: -55px;
...
@@ -130,23 +131,19 @@ $toolbar-position: -55px;
.component
{
.component
{
position
:
relative
;
position
:
relative
;
cursor
:
move
;
cursor
:
move
;
.component-inner
{
position
:
relative
;
z-index
:
1
;
}
.component-wrap
{
.component-wrap
{
z-index
:
0
;
pointer-events
:
none
;
display
:
block
;
display
:
block
;
position
:
absolute
;
position
:
absolute
;
left
:
-
$
active-border-width
;
left
:
-
$
active-border-width
;
top
:
0
;
top
:
0
;
width
:
100%
;
width
:
100%
;
height
:
100%
;
height
:
100%
;
/* 鼠标放到组件上时 */
&:hover
{
border
:
$
hover-border-width
dashed
var
(
--el-color-primary
);
box-shadow
:
0
0
5px
0
rgba
(
24
,
144
,
255
,
0.3
);
.component-name
{
/* 防止加了边框之后,位置移动 */
left
:
$
name-position
-
$
hover-border-width
;
top
:
$
hover-border-width
;
}
}
/* 左侧:组件名称 */
/* 左侧:组件名称 */
.component-name
{
.component-name
{
display
:
block
;
display
:
block
;
...
@@ -199,6 +196,7 @@ $toolbar-position: -55px;
...
@@ -199,6 +196,7 @@ $toolbar-position: -55px;
margin-bottom
:
4px
;
margin-bottom
:
4px
;
.component-wrap
{
.component-wrap
{
z-index
:
2
;
border
:
$
active-border-width
solid
var
(
--el-color-primary
)
!important
;
border
:
$
active-border-width
solid
var
(
--el-color-primary
)
!important
;
box-shadow
:
0
0
10px
0
rgba
(
24
,
144
,
255
,
0.3
);
box-shadow
:
0
0
10px
0
rgba
(
24
,
144
,
255
,
0.3
);
margin-bottom
:
$
active-border-width
+
$
active-border-width
;
margin-bottom
:
$
active-border-width
+
$
active-border-width
;
...
@@ -218,5 +216,18 @@ $toolbar-position: -55px;
...
@@ -218,5 +216,18 @@ $toolbar-position: -55px;
}
}
}
}
}
}
/* 鼠标放到组件上时 */
&
:hover
{
.component-wrap
{
z-index
:
2
;
border
:
$
hover-border-width
dashed
var
(
--el-color-primary
);
box-shadow
:
0
0
5px
0
rgba
(
24
,
144
,
255
,
0.3
);
.component-name
{
/* 防止加了边框之后,位置移动 */
left
:
$
name-position
-
$
hover-border-width
;
top
:
$
hover-border-width
;
}
}
}
}
}
</
style
>
</
style
>
src/components/DiyEditor/components/mobile/MenuGrid/config.ts
View file @
82aed175
...
@@ -53,7 +53,7 @@ export const EMPTY_MENU_GRID_ITEM_PROPERTY = {
...
@@ -53,7 +53,7 @@ export const EMPTY_MENU_GRID_ITEM_PROPERTY = {
export
const
component
=
{
export
const
component
=
{
id
:
'MenuGrid'
,
id
:
'MenuGrid'
,
name
:
'宫格导航'
,
name
:
'宫格导航'
,
icon
:
'
fa-solid:list
'
,
icon
:
'
bi:grid-3x3-gap
'
,
property
:
{
property
:
{
column
:
3
,
column
:
3
,
list
:
[
cloneDeep
(
EMPTY_MENU_GRID_ITEM_PROPERTY
)],
list
:
[
cloneDeep
(
EMPTY_MENU_GRID_ITEM_PROPERTY
)],
...
...
src/components/DiyEditor/components/mobile/MenuSwiper/config.ts
0 → 100644
View file @
82aed175
import
{
ComponentStyle
,
DiyComponent
}
from
'@/components/DiyEditor/util'
import
{
cloneDeep
}
from
'lodash-es'
/** 菜单导航属性 */
export
interface
MenuSwiperProperty
{
// 布局: 图标+文字 | 图标
layout
:
'iconText'
|
'icon'
// 行数
row
:
number
// 列数
column
:
number
// 导航菜单列表
list
:
MenuSwiperItemProperty
[]
// 组件样式
style
:
ComponentStyle
}
/** 菜单导航项目属性 */
export
interface
MenuSwiperItemProperty
{
// 图标链接
iconUrl
:
string
// 标题
title
:
string
// 标题颜色
titleColor
:
string
// 链接
url
:
string
// 角标
badge
:
{
// 是否显示
show
:
boolean
// 角标文字
text
:
string
// 角标文字颜色
textColor
:
string
// 角标背景颜色
bgColor
:
string
}
}
export
const
EMPTY_MENU_SWIPER_ITEM_PROPERTY
=
{
title
:
'标题'
,
titleColor
:
'#333'
,
badge
:
{
show
:
false
,
textColor
:
'#fff'
,
bgColor
:
'#FF6000'
}
}
as
MenuSwiperItemProperty
// 定义组件
export
const
component
=
{
id
:
'MenuSwiper'
,
name
:
'菜单导航'
,
icon
:
'bi:grid-3x2-gap'
,
property
:
{
layout
:
'iconText'
,
row
:
1
,
column
:
3
,
list
:
[
cloneDeep
(
EMPTY_MENU_SWIPER_ITEM_PROPERTY
)],
style
:
{
bgType
:
'color'
,
bgColor
:
'#fff'
,
marginBottom
:
8
}
as
ComponentStyle
}
}
as
DiyComponent
<
MenuSwiperProperty
>
src/components/DiyEditor/components/mobile/MenuSwiper/index.vue
0 → 100644
View file @
82aed175
<
template
>
<el-carousel
:height=
"`$
{carouselHeight}px`"
:autoplay="false"
arrow="hover"
indicator-position="outside"
>
<el-carousel-item
v-for=
"(page, pageIndex) in pages"
:key=
"pageIndex"
>
<div
class=
"flex flex-row flex-wrap"
>
<div
v-for=
"(item, index) in page"
:key=
"index"
class=
"relative flex flex-col items-center justify-center"
:style=
"
{ width: columnWidth, height: `${rowHeight}px` }"
>
<!-- 图标 + 角标 -->
<div
class=
"relative"
:class=
"`h-$
{ICON_SIZE}px w-${ICON_SIZE}px`">
<!-- 右上角角标 -->
<span
v-if=
"item.badge?.show"
class=
"absolute right--10px top--10px z-1 h-20px rounded-10px p-x-6px text-center text-12px leading-20px"
:style=
"
{ color: item.badge.textColor, backgroundColor: item.badge.bgColor }"
>
{{
item
.
badge
.
text
}}
</span>
<el-image
v-if=
"item.iconUrl"
:src=
"item.iconUrl"
class=
"h-full w-full"
/>
</div>
<!-- 标题 -->
<span
v-if=
"property.layout === 'iconText'"
class=
"text-14px"
:style=
"
{
color: item.titleColor,
height: `${TITLE_HEIGHT}px`,
lineHeight: `${TITLE_HEIGHT}px`
}"
>
{{
item
.
title
}}
</span>
</div>
</div>
</el-carousel-item>
</el-carousel>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
MenuSwiperProperty
,
MenuSwiperItemProperty
}
from
'./config'
/** 菜单导航 */
defineOptions
({
name
:
'MenuSwiper'
})
const
props
=
defineProps
<
{
property
:
MenuSwiperProperty
}
>
()
// 标题的高度
const
TITLE_HEIGHT
=
20
// 图标的高度
const
ICON_SIZE
=
50
// 垂直间距:一行上下的间距
const
SPACE_Y
=
16
// 分页
const
pages
=
ref
<
MenuSwiperItemProperty
[][]
>
([])
// 轮播图高度
const
carouselHeight
=
ref
(
0
)
// 行高
const
rowHeight
=
ref
(
0
)
// 列宽
const
columnWidth
=
ref
(
''
)
watch
(
()
=>
props
.
property
,
()
=>
{
// 计算列宽:每一列的百分比
columnWidth
.
value
=
`
${
100
*
(
1
/
props
.
property
.
column
)}
%`
// 计算行高:图标 + 文字(仅显示图片时为0) + 垂直间距 * 2
rowHeight
.
value
=
(
props
.
property
.
layout
===
'iconText'
?
ICON_SIZE
+
TITLE_HEIGHT
:
ICON_SIZE
)
+
SPACE_Y
*
2
// 计算轮播的高度:行数 * 行高
carouselHeight
.
value
=
props
.
property
.
row
*
rowHeight
.
value
// 每页数量:行数 * 列数
const
pageSize
=
props
.
property
.
row
*
props
.
property
.
column
// 清空分页
pages
.
value
=
[]
// 每一页的菜单
let
pageItems
:
MenuSwiperItemProperty
[]
=
[]
for
(
const
item
of
props
.
property
.
list
)
{
// 本页满员,新建下一页
if
(
pageItems
.
length
===
pageSize
)
{
pageItems
=
[]
}
// 增加一页
if
(
pageItems
.
length
===
0
)
{
pages
.
value
.
push
(
pageItems
)
}
// 本页增加一个
pageItems
.
push
(
item
)
}
},
{
immediate
:
true
,
deep
:
true
}
)
</
script
>
<
style
lang=
"scss"
>
//
重写指示器样式,与
APP
保持一致
:root
{
.el-carousel__indicator
{
padding-top
:
0
;
padding-bottom
:
0
;
.el-carousel__button
{
--el-carousel-indicator-height
:
6px
;
--el-carousel-indicator-width
:
6px
;
--el-carousel-indicator-out-color
:
#ff6000
;
border-radius
:
6px
;
}
}
.el-carousel__indicator.is-active
{
.el-carousel__button
{
--el-carousel-indicator-width
:
12px
;
}
}
}
</
style
>
src/components/DiyEditor/components/mobile/MenuSwiper/property.vue
0 → 100644
View file @
82aed175
<
template
>
<ComponentContainerProperty
v-model=
"formData.style"
>
<!-- 表单 -->
<el-form
label-width=
"80px"
:model=
"formData"
class=
"m-t-8px"
>
<el-form-item
label=
"布局"
prop=
"layout"
>
<el-radio-group
v-model=
"formData.layout"
>
<el-radio
label=
"iconText"
>
图标+文字
</el-radio>
<el-radio
label=
"icon"
>
仅图标
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label=
"行数"
prop=
"row"
>
<el-radio-group
v-model=
"formData.row"
>
<el-radio
:label=
"1"
>
1行
</el-radio>
<el-radio
:label=
"2"
>
2行
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label=
"列数"
prop=
"column"
>
<el-radio-group
v-model=
"formData.column"
>
<el-radio
:label=
"3"
>
3列
</el-radio>
<el-radio
:label=
"4"
>
4列
</el-radio>
<el-radio
:label=
"5"
>
5列
</el-radio>
</el-radio-group>
</el-form-item>
<el-text
tag=
"p"
>
菜单设置
</el-text>
<el-text
type=
"info"
size=
"small"
>
拖动左侧的小圆点可以调整顺序
</el-text>
<template
v-if=
"formData.list.length"
>
<VueDraggable
class=
"m-t-8px"
:list=
"formData.list"
item-key=
"index"
handle=
".drag-icon"
:forceFallback=
"true"
:animation=
"200"
>
<template
#
item=
"
{ element, index }">
<div
class=
"mb-4px flex flex-col gap-4px rounded bg-gray-100 p-8px"
>
<div
class=
"flex flex-row justify-between"
>
<Icon
icon=
"ic:round-drag-indicator"
class=
"drag-icon cursor-move"
/>
<Icon
icon=
"ep:delete"
class=
"text-red-500"
@
click=
"handleDeleteMenu(index)"
/>
</div>
<el-form-item
label=
"图标"
prop=
"iconUrl"
>
<UploadImg
v-model=
"element.iconUrl"
height=
"80px"
width=
"80px"
>
<template
#
tip
>
建议尺寸:98 * 98
</
template
>
</UploadImg>
</el-form-item>
<el-form-item
label=
"标题"
prop=
"title"
>
<InputWithColor
v-model=
"element.title"
v-model:color=
"element.titleColor"
/>
</el-form-item>
<el-form-item
label=
"链接"
prop=
"url"
>
<el-input
v-model=
"element.url"
/>
</el-form-item>
<el-form-item
label=
"显示角标"
prop=
"badge.show"
>
<el-switch
v-model=
"element.badge.show"
/>
</el-form-item>
<
template
v-if=
"element.badge.show"
>
<el-form-item
label=
"角标内容"
prop=
"badge.text"
>
<InputWithColor
v-model=
"element.badge.text"
v-model:color=
"element.badge.textColor"
/>
</el-form-item>
<el-form-item
label=
"背景颜色"
prop=
"badge.bgColor"
>
<ColorInput
v-model=
"element.badge.bgColor"
/>
</el-form-item>
</
template
>
</div>
</template>
</VueDraggable>
</template>
<el-form-item
label-width=
"0"
>
<el-button
@
click=
"handleAddMenu"
type=
"primary"
plain
class=
"m-t-8px w-full"
>
<Icon
icon=
"ep:plus"
class=
"mr-5px"
/>
添加菜单
</el-button>
</el-form-item>
</el-form>
</ComponentContainerProperty>
</template>
<
script
setup
lang=
"ts"
>
import
VueDraggable
from
'vuedraggable'
import
{
usePropertyForm
}
from
'@/components/DiyEditor/util'
import
{
EMPTY_MENU_SWIPER_ITEM_PROPERTY
,
MenuSwiperProperty
}
from
'@/components/DiyEditor/components/mobile/MenuSwiper/config'
import
{
cloneDeep
}
from
'lodash-es'
/** 菜单导航属性面板 */
defineOptions
({
name
:
'MenuSwiperProperty'
})
const
props
=
defineProps
<
{
modelValue
:
MenuSwiperProperty
}
>
()
const
emit
=
defineEmits
([
'update:modelValue'
])
const
{
formData
}
=
usePropertyForm
(
props
.
modelValue
,
emit
)
/* 添加菜单 */
const
handleAddMenu
=
()
=>
{
formData
.
value
.
list
.
push
(
cloneDeep
(
EMPTY_MENU_SWIPER_ITEM_PROPERTY
))
}
/* 删除菜单 */
const
handleDeleteMenu
=
(
index
:
number
)
=>
{
formData
.
value
.
list
.
splice
(
index
,
1
)
}
</
script
>
<
style
scoped
lang=
"scss"
></
style
>
src/components/DiyEditor/util.ts
View file @
82aed175
...
@@ -100,17 +100,13 @@ export const PAGE_LIBS = [
...
@@ -100,17 +100,13 @@ export const PAGE_LIBS = [
{
{
name
:
'基础组件'
,
name
:
'基础组件'
,
extended
:
true
,
extended
:
true
,
components
:
[
components
:
[
'SearchBar'
,
'NoticeBar'
,
'MenuSwiper'
,
'MenuGrid'
,
'MenuList'
]
'SearchBar'
,
},
'NoticeBar'
,
{
'MenuSwiper'
,
name
:
'图文组件'
,
'MenuGrid'
,
extended
:
true
,
'MenuList'
,
components
:
[
'ImageBar'
,
'Carousel'
,
'TitleBar'
,
'VideoPlayer'
,
'Divider'
]
'Divider'
,
'TitleBar'
]
},
},
{
name
:
'图文组件'
,
extended
:
true
,
components
:
[
'ImageBar'
,
'Carousel'
,
'VideoPlayer'
]
},
{
name
:
'商品组件'
,
extended
:
true
,
components
:
[
'ProductCard'
]
},
{
name
:
'商品组件'
,
extended
:
true
,
components
:
[
'ProductCard'
]
},
{
{
name
:
'会员组件'
,
name
:
'会员组件'
,
...
...
src/components/InputWithColor/index.vue
View file @
82aed175
<
template
>
<
template
>
<el-input
v-model=
"valueRef"
v-bind=
"$attrs"
>
<el-input
v-model=
"valueRef"
v-bind=
"$attrs"
>
<template
#
append
>
<template
#
append
>
<el-color-picker
v-model=
"colorRef"
:predefine=
"COLORS"
/>
<el-color-picker
v-model=
"colorRef"
:predefine=
"
PREDEFINE_
COLORS"
/>
</
template
>
</
template
>
</el-input>
</el-input>
</template>
</template>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
PREDEFINE_COLORS
}
from
'@/utils/color'
/**
/**
* 带颜色选择器输入框
* 带颜色选择器输入框
...
...
src/utils/color.ts
View file @
82aed175
...
@@ -151,3 +151,24 @@ const subtractLight = (color: string, amount: number) => {
...
@@ -151,3 +151,24 @@ const subtractLight = (color: string, amount: number) => {
const
c
=
cc
<
0
?
0
:
cc
const
c
=
cc
<
0
?
0
:
cc
return
c
.
toString
(
16
).
length
>
1
?
c
.
toString
(
16
)
:
`0
${
c
.
toString
(
16
)}
`
return
c
.
toString
(
16
).
length
>
1
?
c
.
toString
(
16
)
:
`0
${
c
.
toString
(
16
)}
`
}
}
// 预设颜色
export
const
PREDEFINE_COLORS
=
[
'#ff4500'
,
'#ff8c00'
,
'#ffd700'
,
'#90ee90'
,
'#00ced1'
,
'#1e90ff'
,
'#c71585'
,
'#409EFF'
,
'#909399'
,
'#C0C4CC'
,
'#b7390b'
,
'#ff7800'
,
'#fad400'
,
'#5b8c5f'
,
'#00babd'
,
'#1f73c3'
,
'#711f57'
]
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