Skip to content

Commit 26a0827

Browse files
YunaiVgitee-org
authored andcommitted
!345 商城装修
Merge pull request !345 from 疯狂的世界/dev
2 parents 605f906 + edfdee8 commit 26a0827

File tree

8 files changed

+339
-1
lines changed

8 files changed

+339
-1
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { DiyComponent } from '@/components/DiyEditor/util'
2+
3+
// 悬浮按钮属性
4+
export interface FloatingActionButtonProperty {
5+
// 展开方向
6+
direction: 'horizontal' | 'vertical'
7+
// 是否显示文字
8+
showText: boolean
9+
// 按钮列表
10+
list: FloatingActionButtonItemProperty[]
11+
}
12+
13+
// 悬浮按钮项属性
14+
export interface FloatingActionButtonItemProperty {
15+
// 图片地址
16+
imgUrl: string
17+
// 跳转连接
18+
url: string
19+
// 文字
20+
text: string
21+
// 文字颜色
22+
textColor: string
23+
}
24+
25+
// 定义组件
26+
export const component = {
27+
id: 'FloatingActionButton',
28+
name: '悬浮按钮',
29+
icon: 'tabler:float-right',
30+
position: 'fixed',
31+
property: {
32+
direction: 'vertical',
33+
showText: true,
34+
list: [{ textColor: '#fff' }]
35+
}
36+
} as DiyComponent<FloatingActionButtonProperty>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<template>
2+
<div
3+
:class="[
4+
'absolute bottom-32px right-[calc(50%-375px/2+32px)] flex z-12 gap-12px items-center',
5+
{
6+
'flex-row': property.direction === 'horizontal',
7+
'flex-col': property.direction === 'vertical'
8+
}
9+
]"
10+
>
11+
<template v-if="expanded">
12+
<div
13+
v-for="(item, index) in property.list"
14+
:key="index"
15+
class="flex flex-col items-center"
16+
@click="handleActive(index)"
17+
>
18+
<el-image :src="item.imgUrl" fit="contain" class="h-27px w-27px">
19+
<template #error>
20+
<div class="h-full w-full flex items-center justify-center">
21+
<Icon icon="ep:picture" :color="item.textColor" />
22+
</div>
23+
</template>
24+
</el-image>
25+
<span v-if="property.showText" class="mt-4px text-12px" :style="{ color: item.textColor }">
26+
{{ item.text }}
27+
</span>
28+
</div>
29+
</template>
30+
<!-- todo: @owen 使用APP主题色 -->
31+
<el-button type="primary" size="large" circle @click="handleToggleFab">
32+
<Icon icon="ep:plus" :class="['fab-icon', { active: expanded }]" />
33+
</el-button>
34+
</div>
35+
<!-- 模态背景:展开时显示,点击后折叠 -->
36+
<div v-if="expanded" class="modal-bg" @click="handleToggleFab"></div>
37+
</template>
38+
<script setup lang="ts">
39+
import { FloatingActionButtonProperty } from './config'
40+
41+
/** 悬浮按钮 */
42+
defineOptions({ name: 'FloatingActionButton' })
43+
// 定义属性
44+
defineProps<{ property: FloatingActionButtonProperty }>()
45+
46+
// 是否展开
47+
const expanded = ref(true)
48+
// 处理展开/折叠
49+
const handleToggleFab = () => {
50+
expanded.value = !expanded.value
51+
}
52+
</script>
53+
54+
<style scoped lang="scss">
55+
/* 模态背景 */
56+
.modal-bg {
57+
position: absolute;
58+
left: calc(50% - 375px / 2);
59+
top: 0;
60+
z-index: 11;
61+
width: 375px;
62+
height: 100%;
63+
background-color: rgba(#000000, 0.4);
64+
}
65+
66+
.fab-icon {
67+
transform: rotate(0deg);
68+
transition: transform 0.3s;
69+
70+
&.active {
71+
transform: rotate(135deg);
72+
}
73+
}
74+
</style>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<template>
2+
<el-form label-width="80px" :model="formData">
3+
<el-card header="按钮配置" class="property-group" shadow="never">
4+
<el-form-item label="展开方向" prop="direction">
5+
<el-radio-group v-model="formData.direction">
6+
<el-radio label="vertical">垂直</el-radio>
7+
<el-radio label="horizontal">水平</el-radio>
8+
</el-radio-group>
9+
</el-form-item>
10+
<el-form-item label="显示文字" prop="showText">
11+
<el-switch v-model="formData.showText" />
12+
</el-form-item>
13+
</el-card>
14+
<el-card header="按钮列表" class="property-group" shadow="never">
15+
<Draggable v-model="formData.list" :empty-item="{ textColor: '#fff' }">
16+
<template #default="{ element, index }">
17+
<el-form-item label="图标" :prop="`list[${index}].imgUrl`">
18+
<UploadImg v-model="element.imgUrl" height="56px" width="56px" />
19+
</el-form-item>
20+
<el-form-item label="文字" :prop="`list[${index}].text`">
21+
<InputWithColor v-model="element.text" v-model:color="element.textColor" />
22+
</el-form-item>
23+
<el-form-item label="跳转链接" :prop="`list[${index}].url`">
24+
<AppLinkInput v-model="element.url" />
25+
</el-form-item>
26+
</template>
27+
</Draggable>
28+
</el-card>
29+
</el-form>
30+
</template>
31+
32+
<script setup lang="ts">
33+
import { FloatingActionButtonProperty } from './config'
34+
import { usePropertyForm } from '@/components/DiyEditor/util'
35+
36+
// 悬浮按钮属性面板
37+
defineOptions({ name: 'FloatingActionButtonProperty' })
38+
39+
const props = defineProps<{ modelValue: FloatingActionButtonProperty }>()
40+
const emit = defineEmits(['update:modelValue'])
41+
const { formData } = usePropertyForm(props.modelValue, emit)
42+
</script>
43+
44+
<style scoped lang="scss"></style>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { DiyComponent } from '@/components/DiyEditor/util'
2+
3+
/** 弹窗广告属性 */
4+
export interface PopoverProperty {
5+
list: PopoverItemProperty[]
6+
}
7+
8+
export interface PopoverItemProperty {
9+
// 图片地址
10+
imgUrl: string
11+
// 跳转连接
12+
url: string
13+
// 显示类型:仅显示一次、每次启动都会显示
14+
showType: 'once' | 'always'
15+
}
16+
17+
// 定义组件
18+
export const component = {
19+
id: 'Popover',
20+
name: '弹窗广告',
21+
icon: 'carbon:popup',
22+
position: 'fixed',
23+
property: {
24+
list: [{ showType: 'once' }]
25+
}
26+
} as DiyComponent<PopoverProperty>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<template>
2+
<div
3+
v-for="(item, index) in property.list"
4+
:key="index"
5+
class="absolute bottom-50% right-50% h-454px w-292px border-1px border-gray border-rounded-4px border-solid bg-white p-1px"
6+
:style="{
7+
zIndex: 100 + index + (activeIndex === index ? 100 : 0),
8+
marginRight: `${-146 - index * 20}px`,
9+
marginBottom: `${-227 - index * 20}px`
10+
}"
11+
@click="handleActive(index)"
12+
>
13+
<el-image :src="item.imgUrl" fit="contain" class="h-full w-full">
14+
<template #error>
15+
<div class="h-full w-full flex items-center justify-center">
16+
<Icon icon="ep:picture" />
17+
</div>
18+
</template>
19+
</el-image>
20+
<div class="absolute right-1 top-1 text-12px">{{ index + 1 }}</div>
21+
</div>
22+
</template>
23+
<script setup lang="ts">
24+
import { PopoverProperty } from './config'
25+
26+
/** 弹窗广告 */
27+
defineOptions({ name: 'Popover' })
28+
// 定义属性
29+
defineProps<{ property: PopoverProperty }>()
30+
31+
// 处理选中
32+
const activeIndex = ref(0)
33+
const handleActive = (index: number) => {
34+
activeIndex.value = index
35+
}
36+
</script>
37+
38+
<style scoped lang="scss"></style>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<template>
2+
<el-form label-width="80px" :model="formData">
3+
<Draggable v-model="formData.list" :empty-item="{ showType: 'once' }">
4+
<template #default="{ element, index }">
5+
<el-form-item label="图片" :prop="`list[${index}].imgUrl`">
6+
<UploadImg v-model="element.imgUrl" height="56px" width="56px" />
7+
</el-form-item>
8+
<el-form-item label="跳转链接" :prop="`list[${index}].url`">
9+
<AppLinkInput v-model="element.url" />
10+
</el-form-item>
11+
<el-form-item label="显示次数" :prop="`list[${index}].showType`">
12+
<el-radio-group v-model="element.showType">
13+
<el-tooltip content="只显示一次,下次打开时不显示" placement="bottom">
14+
<el-radio label="once">一次</el-radio>
15+
</el-tooltip>
16+
<el-tooltip content="每次打开时都会显示" placement="bottom">
17+
<el-radio label="always">不限</el-radio>
18+
</el-tooltip>
19+
</el-radio-group>
20+
</el-form-item>
21+
</template>
22+
</Draggable>
23+
</el-form>
24+
</template>
25+
26+
<script setup lang="ts">
27+
import { PopoverProperty } from './config'
28+
import { usePropertyForm } from '@/components/DiyEditor/util'
29+
30+
// 弹窗广告属性面板
31+
defineOptions({ name: 'PopoverProperty' })
32+
33+
const props = defineProps<{ modelValue: PopoverProperty }>()
34+
const emit = defineEmits(['update:modelValue'])
35+
const { formData } = usePropertyForm(props.modelValue, emit)
36+
</script>
37+
38+
<style scoped lang="scss"></style>

src/components/DiyEditor/index.vue

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@
4747
class="cursor-pointer!"
4848
/>
4949
</div>
50+
<!-- 绝对定位的组件:例如 弹窗、浮动按钮等 -->
51+
<div
52+
v-for="(component, index) in pageComponents"
53+
:key="index"
54+
@click="handleComponentSelected(component, index)"
55+
>
56+
<component
57+
v-if="component.position === 'fixed' && selectedComponent?.uid === component.uid"
58+
:is="component.id"
59+
:property="component.property"
60+
/>
61+
</div>
5062
<!-- 手机页面编辑区域 -->
5163
<el-scrollbar
5264
height="100%"
@@ -70,6 +82,7 @@
7082
>
7183
<template #item="{ element, index }">
7284
<ComponentContainer
85+
v-if="!element.position || element.position === 'center'"
7386
:component="element"
7487
:active="selectedComponentIndex === index"
7588
:can-move-up="index > 0"
@@ -91,6 +104,33 @@
91104
@click="handleTabBarSelected"
92105
/>
93106
</div>
107+
<!-- 固定布局的组件 操作按钮区 -->
108+
<div class="fixed-component-action-group">
109+
<el-tag
110+
v-if="showPageConfig"
111+
size="large"
112+
:effect="selectedComponent?.uid === pageConfigComponent.uid ? 'dark' : 'plain'"
113+
:type="selectedComponent?.uid === pageConfigComponent.uid ? '' : 'info'"
114+
@click="handleComponentSelected(pageConfigComponent)"
115+
>
116+
<Icon :icon="pageConfigComponent.icon" :size="12" />
117+
<span>{{ pageConfigComponent.name }}</span>
118+
</el-tag>
119+
<template v-for="(component, index) in pageComponents" :key="index">
120+
<el-tag
121+
v-if="component.position === 'fixed'"
122+
size="large"
123+
closable
124+
:effect="selectedComponent?.uid === component.uid ? 'dark' : 'plain'"
125+
:type="selectedComponent?.uid === component.uid ? '' : 'info'"
126+
@click="handleComponentSelected(component)"
127+
@close="handleDeleteComponent(index)"
128+
>
129+
<Icon :icon="component.icon" :size="12" />
130+
<span>{{ component.name }}</span>
131+
</el-tag>
132+
</template>
133+
</div>
94134
</div>
95135
<!-- 右侧属性面板 -->
96136
<el-aside class="editor-right" width="350px" v-if="selectedComponent?.property">
@@ -485,6 +525,31 @@ $toolbar-height: 42px;
485525
}
486526
}
487527
}
528+
529+
/* 固定布局的组件 操作按钮区 */
530+
.fixed-component-action-group {
531+
position: absolute;
532+
top: 0;
533+
right: 16px;
534+
display: flex;
535+
flex-direction: column;
536+
gap: 8px;
537+
538+
:deep(.el-tag) {
539+
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1);
540+
border: none;
541+
.el-tag__content {
542+
width: 100%;
543+
display: flex;
544+
align-items: center;
545+
justify-content: flex-start;
546+
547+
.el-icon {
548+
margin-right: 4px;
549+
}
550+
}
551+
}
552+
}
488553
}
489554
}
490555
}

src/components/DiyEditor/util.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ export interface DiyComponent<T> {
1313
name: string
1414
// 组件图标
1515
icon: string
16+
/*
17+
组件位置:
18+
top: 固定于手机顶部,例如 顶部的导航栏
19+
bottom: 固定于手机底部,例如 底部的菜单导航栏
20+
center: 位于手机中心,每个组件占一行,顺序向下排列
21+
空:同center
22+
fixed: 由组件自己决定位置,如弹窗位于手机中心、浮动按钮一般位于手机右下角
23+
*/
24+
position: 'top' | 'bottom' | 'center' | '' | 'fixed'
1625
// 组件属性
1726
property: T
1827
}
@@ -102,7 +111,15 @@ export const PAGE_LIBS = [
102111
{
103112
name: '基础组件',
104113
extended: true,
105-
components: ['SearchBar', 'NoticeBar', 'MenuSwiper', 'MenuGrid', 'MenuList']
114+
components: [
115+
'SearchBar',
116+
'NoticeBar',
117+
'MenuSwiper',
118+
'MenuGrid',
119+
'MenuList',
120+
'Popover',
121+
'FloatingActionButton'
122+
]
106123
},
107124
{
108125
name: '图文组件',

0 commit comments

Comments
 (0)