!347 商城装修

Merge pull request !347 from 疯狂的世界/dev
This commit is contained in:
芋道源码 2023-12-15 15:37:01 +00:00 committed by Gitee
commit 1abff21e56
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
14 changed files with 702 additions and 139 deletions

View File

@ -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 @@
</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' })
// //
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) => {
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) => {
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) => {
// //
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[]>([])
* @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>()
// //
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[]>([])
// //
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<{
}) })
// //
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

View File

@ -1,3 +1,20 @@
// 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 = [
} }
] ]
} }
] ] as AppLinkGroup[]

View File

@ -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>

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,63 @@
<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: #409effbf;
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>

View File

@ -28,7 +28,7 @@
<!-- 标题 --> <!-- 标题 -->
<span <span
v-if="property.layout === 'iconText'" v-if="property.layout === 'iconText'"
class="text-14px" class="text-12px"
: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 }>()
// //
const TITLE_HEIGHT = 20 const TITLE_HEIGHT = 20
// //
const ICON_SIZE = 50 const ICON_SIZE = 42
// //
const SPACE_Y = 16 const SPACE_Y = 16

View File

@ -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">

View File

@ -1,7 +1,13 @@
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 {
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 {
// 链接 // 链接
url: string url: string
} }
// 组件样式
style: ComponentStyle
} }
// 定义组件 // 定义组件
@ -48,18 +50,20 @@ export const component = {
titleSize: 16, titleSize: 16,
descriptionSize: 12, descriptionSize: 12,
titleWeight: 400, titleWeight: 400,
position: 'left', textAlign: '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>

View File

@ -1,19 +1,14 @@
<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.position textAlign: property.textAlign
}" }"
v-if="property.title" v-if="property.title"
> >
@ -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.position textAlign: property.textAlign
}" }"
class="m-t-8px" class="m-t-8px"
v-if="property.description" v-if="property.description"
@ -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 }>()
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;
/* 更多 */ /* 更多 */

View File

@ -1,25 +1,14 @@
<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>
<el-form-item label="副标题" prop="description"> <el-form-item label="标题位置" prop="textAlign">
<el-input <el-radio-group v-model="formData!.textAlign">
type="textarea"
v-model="formData.description"
placeholder="请输入副标题"
maxlength="50"
show-word-limit
/>
</el-form-item>
<el-form-item label="显示位置" prop="position">
<el-radio-group v-model="formData!.position">
<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 @@
</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.descriptionSize" v-model="formData.titleSize"
: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 @@
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 @@
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 @@
<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'

View File

@ -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'] },
{ {