营销:适配商城装修组件【菜单导航】

This commit is contained in:
owen 2023-11-09 09:18:46 +08:00
parent ac42493659
commit 82aed175ab
9 changed files with 344 additions and 44 deletions

View File

@ -1,38 +1,18 @@
<template>
<el-input v-model="color">
<template #prepend>
<el-color-picker v-model="color" :predefine="COLORS" />
<el-color-picker v-model="color" :predefine="PREDEFINE_COLORS" />
</template>
</el-input>
</template>
<script setup lang="ts">
import { propTypes } from '@/utils/propTypes'
import { PREDEFINE_COLORS } from '@/utils/color'
//
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({
modelValue: propTypes.string.def('')
})

View File

@ -1,6 +1,7 @@
<template>
<div :class="['component', { active: active }]">
<div
class="component-inner"
:style="{
...style
}"
@ -130,23 +131,19 @@ $toolbar-position: -55px;
.component {
position: relative;
cursor: move;
.component-inner {
position: relative;
z-index: 1;
}
.component-wrap {
z-index: 0;
pointer-events: none;
display: block;
position: absolute;
left: -$active-border-width;
top: 0;
width: 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 {
display: block;
@ -199,6 +196,7 @@ $toolbar-position: -55px;
margin-bottom: 4px;
.component-wrap {
z-index: 2;
border: $active-border-width solid var(--el-color-primary) !important;
box-shadow: 0 0 10px 0 rgba(24, 144, 255, 0.3);
margin-bottom: $active-border-width + $active-border-width;
@ -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>

View File

@ -53,7 +53,7 @@ export const EMPTY_MENU_GRID_ITEM_PROPERTY = {
export const component = {
id: 'MenuGrid',
name: '宫格导航',
icon: 'fa-solid:list',
icon: 'bi:grid-3x3-gap',
property: {
column: 3,
list: [cloneDeep(EMPTY_MENU_GRID_ITEM_PROPERTY)],

View File

@ -0,0 +1,66 @@
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>

View File

@ -0,0 +1,119 @@
<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>

View File

@ -0,0 +1,106 @@
<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>

View File

@ -100,17 +100,13 @@ export const PAGE_LIBS = [
{
name: '基础组件',
extended: true,
components: [
'SearchBar',
'NoticeBar',
'MenuSwiper',
'MenuGrid',
'MenuList',
'Divider',
'TitleBar'
]
components: ['SearchBar', 'NoticeBar', 'MenuSwiper', 'MenuGrid', 'MenuList']
},
{
name: '图文组件',
extended: true,
components: ['ImageBar', 'Carousel', 'TitleBar', 'VideoPlayer', 'Divider']
},
{ name: '图文组件', extended: true, components: ['ImageBar', 'Carousel', 'VideoPlayer'] },
{ name: '商品组件', extended: true, components: ['ProductCard'] },
{
name: '会员组件',

View File

@ -1,13 +1,14 @@
<template>
<el-input v-model="valueRef" v-bind="$attrs">
<template #append>
<el-color-picker v-model="colorRef" :predefine="COLORS" />
<el-color-picker v-model="colorRef" :predefine="PREDEFINE_COLORS" />
</template>
</el-input>
</template>
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import { PREDEFINE_COLORS } from '@/utils/color'
/**
* 带颜色选择器输入框

View File

@ -151,3 +151,24 @@ const subtractLight = (color: string, amount: number) => {
const c = cc < 0 ? 0 : cc
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'
]