commit
						1abff21e56
					
				| 
						 | 
				
			
			@ -29,10 +29,11 @@
 | 
			
		|||
            :key="appLinkIndex"
 | 
			
		||||
            :content="appLink.path"
 | 
			
		||||
            placement="bottom"
 | 
			
		||||
            :show-after="300"
 | 
			
		||||
          >
 | 
			
		||||
            <el-button
 | 
			
		||||
              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)"
 | 
			
		||||
            >
 | 
			
		||||
              {{ appLink.name }}
 | 
			
		||||
| 
						 | 
				
			
			@ -63,7 +64,7 @@
 | 
			
		|||
  </Dialog>
 | 
			
		||||
</template>
 | 
			
		||||
<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 { split } from 'lodash-es'
 | 
			
		||||
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)
 | 
			
		||||
// 选中的 APP 链接
 | 
			
		||||
const activeAppLink = ref('')
 | 
			
		||||
const activeAppLink = ref({} as AppLink)
 | 
			
		||||
 | 
			
		||||
/** 打开弹窗 */
 | 
			
		||||
const dialogVisible = ref(false)
 | 
			
		||||
const open = (link: string) => {
 | 
			
		||||
  activeAppLink.value = link
 | 
			
		||||
  activeAppLink.value.path = link
 | 
			
		||||
  dialogVisible.value = true
 | 
			
		||||
 | 
			
		||||
  // 滚动到当前的链接
 | 
			
		||||
  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) {
 | 
			
		||||
    // 使用 nextTick 的原因:可能 Dom 还没生成,导致滚动失败
 | 
			
		||||
| 
						 | 
				
			
			@ -94,9 +101,9 @@ const open = (link: string) => {
 | 
			
		|||
defineExpose({ open })
 | 
			
		||||
 | 
			
		||||
// 处理 APP 链接选中
 | 
			
		||||
const handleAppLinkSelected = (appLink: any) => {
 | 
			
		||||
  if (!isSameLink(appLink.path, activeAppLink.value)) {
 | 
			
		||||
    activeAppLink.value = appLink.path
 | 
			
		||||
const handleAppLinkSelected = (appLink: AppLink) => {
 | 
			
		||||
  if (!isSameLink(appLink.path, activeAppLink.value.path)) {
 | 
			
		||||
    activeAppLink.value = appLink
 | 
			
		||||
  }
 | 
			
		||||
  switch (appLink.type) {
 | 
			
		||||
    case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST:
 | 
			
		||||
| 
						 | 
				
			
			@ -104,7 +111,7 @@ const handleAppLinkSelected = (appLink: any) => {
 | 
			
		|||
      detailSelectDialog.value.type = appLink.type
 | 
			
		||||
      // 返显
 | 
			
		||||
      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
 | 
			
		||||
    default:
 | 
			
		||||
      break
 | 
			
		||||
| 
						 | 
				
			
			@ -114,10 +121,12 @@ const handleAppLinkSelected = (appLink: any) => {
 | 
			
		|||
// 处理绑定值更新
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
  change: [link: string]
 | 
			
		||||
  appLinkChange: [appLink: AppLink]
 | 
			
		||||
}>()
 | 
			
		||||
const handleSubmit = () => {
 | 
			
		||||
  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 滚动条的位置
 | 
			
		||||
 */
 | 
			
		||||
const handleScroll = ({ scrollTop }: { scrollTop: number }) => {
 | 
			
		||||
  const titleEl = groupTitleRefs.value.find((titleEl) => {
 | 
			
		||||
  const titleEl = groupTitleRefs.value.find((titleEl: HTMLInputElement) => {
 | 
			
		||||
    // 获取标题的位置信息
 | 
			
		||||
    const { offsetHeight, offsetTop } = titleEl
 | 
			
		||||
    // 判断标题是否在可视范围内
 | 
			
		||||
| 
						 | 
				
			
			@ -146,7 +155,7 @@ const linkScrollbar = ref<ScrollbarInstance>()
 | 
			
		|||
// 处理分组选中
 | 
			
		||||
const handleGroupSelected = (group: string) => {
 | 
			
		||||
  activeGroup.value = group
 | 
			
		||||
  const titleRef = groupTitleRefs.value.find((item) => item.textContent === group)
 | 
			
		||||
  const titleRef = groupTitleRefs.value.find((item: HTMLInputElement) => item.textContent === group)
 | 
			
		||||
  if (titleRef) {
 | 
			
		||||
    // 滚动分组标题
 | 
			
		||||
    linkScrollbar.value?.setScrollTop(titleRef.offsetTop)
 | 
			
		||||
| 
						 | 
				
			
			@ -160,8 +169,8 @@ const groupBtnRefs = ref<ButtonInstance[]>([])
 | 
			
		|||
// 自动滚动分组按钮,确保分组按钮保持在可视区域内
 | 
			
		||||
const scrollToGroupBtn = (group: string) => {
 | 
			
		||||
  const groupBtn = groupBtnRefs.value
 | 
			
		||||
    .map((btn) => btn['ref'])
 | 
			
		||||
    .find((ref) => ref.textContent === group)
 | 
			
		||||
    .map((btn: ButtonInstance) => btn['ref'])
 | 
			
		||||
    .find((ref: Node) => ref.textContent === group)
 | 
			
		||||
  if (groupBtn) {
 | 
			
		||||
    groupScrollbar.value?.setScrollTop(groupBtn.offsetTop)
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -184,11 +193,11 @@ const detailSelectDialog = ref<{
 | 
			
		|||
})
 | 
			
		||||
// 处理详情选择
 | 
			
		||||
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 参数
 | 
			
		||||
  url.searchParams.set('id', `${id}`)
 | 
			
		||||
  // 排除域名
 | 
			
		||||
  activeAppLink.value = `${url.pathname}${url.search}`
 | 
			
		||||
  activeAppLink.value.path = `${url.pathname}${url.search}`
 | 
			
		||||
  // 关闭对话框
 | 
			
		||||
  detailSelectDialog.value.visible = false
 | 
			
		||||
  // 重置 id
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 链接类型(需要特殊处理,例如商品详情)
 | 
			
		||||
export const enum APP_LINK_TYPE_ENUM {
 | 
			
		||||
  // 拼团活动
 | 
			
		||||
| 
						 | 
				
			
			@ -243,4 +260,4 @@ export const APP_LINK_GROUP_LIST = [
 | 
			
		|||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
] as AppLinkGroup[]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,7 +37,7 @@ const emit = defineEmits<{
 | 
			
		|||
  'update:modelValue': [link: string]
 | 
			
		||||
}>()
 | 
			
		||||
watch(
 | 
			
		||||
  () => appLink,
 | 
			
		||||
  () => appLink.value,
 | 
			
		||||
  () => emit('update:modelValue', appLink.value)
 | 
			
		||||
)
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +28,7 @@
 | 
			
		|||
          <!-- 标题 -->
 | 
			
		||||
          <span
 | 
			
		||||
            v-if="property.layout === 'iconText'"
 | 
			
		||||
            class="text-14px"
 | 
			
		||||
            class="text-12px"
 | 
			
		||||
            :style="{
 | 
			
		||||
              color: item.titleColor,
 | 
			
		||||
              height: `${TITLE_HEIGHT}px`,
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +51,7 @@ const props = defineProps<{ property: MenuSwiperProperty }>()
 | 
			
		|||
// 标题的高度
 | 
			
		||||
const TITLE_HEIGHT = 20
 | 
			
		||||
// 图标的高度
 | 
			
		||||
const ICON_SIZE = 50
 | 
			
		||||
const ICON_SIZE = 42
 | 
			
		||||
// 垂直间距:一行上下的间距
 | 
			
		||||
const SPACE_Y = 16
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,7 +23,7 @@
 | 
			
		|||
      </el-form-item>
 | 
			
		||||
 | 
			
		||||
      <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 }">
 | 
			
		||||
            <el-form-item label="图标" prop="iconUrl">
 | 
			
		||||
              <UploadImg v-model="element.iconUrl" height="80px" width="80px">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,13 @@
 | 
			
		|||
import { DiyComponent } from '@/components/DiyEditor/util'
 | 
			
		||||
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
 | 
			
		||||
 | 
			
		||||
/** 标题栏属性 */
 | 
			
		||||
export interface TitleBarProperty {
 | 
			
		||||
  // 背景图
 | 
			
		||||
  bgImgUrl: string
 | 
			
		||||
  // 偏移
 | 
			
		||||
  marginLeft: number
 | 
			
		||||
  // 显示位置
 | 
			
		||||
  textAlign: 'left' | 'center'
 | 
			
		||||
  // 主标题
 | 
			
		||||
  title: string
 | 
			
		||||
  // 副标题
 | 
			
		||||
| 
						 | 
				
			
			@ -12,18 +18,12 @@ export interface TitleBarProperty {
 | 
			
		|||
  descriptionSize: number
 | 
			
		||||
  // 标题粗细
 | 
			
		||||
  titleWeight: number
 | 
			
		||||
  // 显示位置
 | 
			
		||||
  position: 'left' | 'center'
 | 
			
		||||
  // 描述粗细
 | 
			
		||||
  descriptionWeight: number
 | 
			
		||||
  // 标题颜色
 | 
			
		||||
  titleColor: string
 | 
			
		||||
  // 描述颜色
 | 
			
		||||
  descriptionColor: string
 | 
			
		||||
  // 背景颜色
 | 
			
		||||
  backgroundColor: string
 | 
			
		||||
  // 底部分割线
 | 
			
		||||
  showBottomBorder: false
 | 
			
		||||
  // 查看更多
 | 
			
		||||
  more: {
 | 
			
		||||
    // 是否显示查看更多
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +35,8 @@ export interface TitleBarProperty {
 | 
			
		|||
    // 链接
 | 
			
		||||
    url: string
 | 
			
		||||
  }
 | 
			
		||||
  // 组件样式
 | 
			
		||||
  style: ComponentStyle
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 定义组件
 | 
			
		||||
| 
						 | 
				
			
			@ -48,18 +50,20 @@ export const component = {
 | 
			
		|||
    titleSize: 16,
 | 
			
		||||
    descriptionSize: 12,
 | 
			
		||||
    titleWeight: 400,
 | 
			
		||||
    position: 'left',
 | 
			
		||||
    textAlign: 'left',
 | 
			
		||||
    descriptionWeight: 200,
 | 
			
		||||
    titleColor: 'rgba(50, 50, 51, 10)',
 | 
			
		||||
    descriptionColor: 'rgba(150, 151, 153, 10)',
 | 
			
		||||
    backgroundColor: 'rgba(255, 255, 255, 10)',
 | 
			
		||||
    showBottomBorder: false,
 | 
			
		||||
    more: {
 | 
			
		||||
      //查看更多
 | 
			
		||||
      show: false,
 | 
			
		||||
      type: 'icon',
 | 
			
		||||
      text: '查看更多',
 | 
			
		||||
      url: ''
 | 
			
		||||
    }
 | 
			
		||||
    },
 | 
			
		||||
    style: {
 | 
			
		||||
      bgType: 'color',
 | 
			
		||||
      bgColor: '#fff'
 | 
			
		||||
    } as ComponentStyle
 | 
			
		||||
  }
 | 
			
		||||
} as DiyComponent<TitleBarProperty>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,19 +1,14 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    class="title-bar"
 | 
			
		||||
    :style="{
 | 
			
		||||
      background: property.backgroundColor,
 | 
			
		||||
      borderBottom: property.showBottomBorder ? '1px solid #F9F9F9' : '1px solid #fff'
 | 
			
		||||
    }"
 | 
			
		||||
  >
 | 
			
		||||
    <div>
 | 
			
		||||
  <div class="title-bar">
 | 
			
		||||
    <el-image v-if="property.bgImgUrl" :src="property.bgImgUrl" fit="cover" class="w-full" />
 | 
			
		||||
    <div class="absolute left-0 top-0 w-full">
 | 
			
		||||
      <!-- 标题 -->
 | 
			
		||||
      <div
 | 
			
		||||
        :style="{
 | 
			
		||||
          fontSize: `${property.titleSize}px`,
 | 
			
		||||
          fontWeight: property.titleWeight,
 | 
			
		||||
          color: property.titleColor,
 | 
			
		||||
          textAlign: property.position
 | 
			
		||||
          textAlign: property.textAlign
 | 
			
		||||
        }"
 | 
			
		||||
        v-if="property.title"
 | 
			
		||||
      >
 | 
			
		||||
| 
						 | 
				
			
			@ -25,7 +20,7 @@
 | 
			
		|||
          fontSize: `${property.descriptionSize}px`,
 | 
			
		||||
          fontWeight: property.descriptionWeight,
 | 
			
		||||
          color: property.descriptionColor,
 | 
			
		||||
          textAlign: property.position
 | 
			
		||||
          textAlign: property.textAlign
 | 
			
		||||
        }"
 | 
			
		||||
        class="m-t-8px"
 | 
			
		||||
        v-if="property.description"
 | 
			
		||||
| 
						 | 
				
			
			@ -38,10 +33,10 @@
 | 
			
		|||
      class="more"
 | 
			
		||||
      v-show="property.more.show"
 | 
			
		||||
      :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'" />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -59,8 +54,6 @@ defineProps<{ property: TitleBarProperty }>()
 | 
			
		|||
  position: relative;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  min-height: 20px;
 | 
			
		||||
  padding: 8px 16px;
 | 
			
		||||
  border: 2px solid #fff;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
 | 
			
		||||
  /* 更多 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,102 +1,108 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <section class="title-bar">
 | 
			
		||||
  <ComponentContainerProperty v-model="formData.style">
 | 
			
		||||
    <el-form label-width="85px" :model="formData" :rules="rules">
 | 
			
		||||
      <el-form-item label="主标题" prop="title">
 | 
			
		||||
        <el-input
 | 
			
		||||
          v-model="formData.title"
 | 
			
		||||
          placeholder="请输入主标题"
 | 
			
		||||
          show-word-limit
 | 
			
		||||
          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 label="显示位置" prop="position">
 | 
			
		||||
        <el-radio-group v-model="formData!.position">
 | 
			
		||||
          <el-tooltip content="居左" placement="top">
 | 
			
		||||
            <el-radio-button label="left">
 | 
			
		||||
              <Icon icon="ant-design:align-left-outlined" />
 | 
			
		||||
            </el-radio-button>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
          <el-tooltip content="居中" placement="top">
 | 
			
		||||
            <el-radio-button label="center">
 | 
			
		||||
              <Icon icon="ant-design:align-center-outlined" />
 | 
			
		||||
            </el-radio-button>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </el-radio-group>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="标题大小" prop="titleSize">
 | 
			
		||||
        <el-slider v-model="formData.titleSize" :max="60" :min="10" show-input input-size="small" />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="副标题大小" prop="descriptionSize">
 | 
			
		||||
        <el-slider
 | 
			
		||||
          v-model="formData.descriptionSize"
 | 
			
		||||
          :max="60"
 | 
			
		||||
          :min="10"
 | 
			
		||||
          show-input
 | 
			
		||||
          input-size="small"
 | 
			
		||||
        />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="标题粗细" prop="titleWeight">
 | 
			
		||||
        <el-slider
 | 
			
		||||
          v-model="formData.titleWeight"
 | 
			
		||||
          :min="100"
 | 
			
		||||
          :max="900"
 | 
			
		||||
          :step="100"
 | 
			
		||||
          show-input
 | 
			
		||||
          input-size="small"
 | 
			
		||||
        />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="副标题粗细" prop="descriptionWeight">
 | 
			
		||||
        <el-slider
 | 
			
		||||
          v-model="formData.descriptionWeight"
 | 
			
		||||
          :min="100"
 | 
			
		||||
          :max="900"
 | 
			
		||||
          :step="100"
 | 
			
		||||
          show-input
 | 
			
		||||
          input-size="small"
 | 
			
		||||
        />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="标题颜色" prop="titleColor">
 | 
			
		||||
        <ColorInput v-model="formData.titleColor" />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <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-form-item>
 | 
			
		||||
      <!-- 更多样式选择 -->
 | 
			
		||||
      <template v-if="formData.more.show">
 | 
			
		||||
        <el-form-item label="样式" prop="more.type">
 | 
			
		||||
          <el-radio-group v-model="formData.more.type">
 | 
			
		||||
            <el-radio label="text">文字</el-radio>
 | 
			
		||||
            <el-radio label="icon">图标</el-radio>
 | 
			
		||||
            <el-radio label="all">文字+图标</el-radio>
 | 
			
		||||
      <el-card header="风格" class="property-group" shadow="never">
 | 
			
		||||
        <el-form-item label="背景图片" prop="bgImgUrl">
 | 
			
		||||
          <UploadImg v-model="formData.bgImgUrl" width="100%" height="40px">
 | 
			
		||||
            <template #tip>建议尺寸 750*80</template>
 | 
			
		||||
          </UploadImg>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="标题位置" prop="textAlign">
 | 
			
		||||
          <el-radio-group v-model="formData!.textAlign">
 | 
			
		||||
            <el-tooltip content="居左" placement="top">
 | 
			
		||||
              <el-radio-button label="left">
 | 
			
		||||
                <Icon icon="ant-design:align-left-outlined" />
 | 
			
		||||
              </el-radio-button>
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
            <el-tooltip content="居中" placement="top">
 | 
			
		||||
              <el-radio-button label="center">
 | 
			
		||||
                <Icon icon="ant-design:align-center-outlined" />
 | 
			
		||||
              </el-radio-button>
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
          </el-radio-group>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="更多文字" prop="more.text" v-show="formData.more.type !== 'icon'">
 | 
			
		||||
          <el-input v-model="formData.more.text" />
 | 
			
		||||
      </el-card>
 | 
			
		||||
      <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 label="跳转链接" prop="more.url">
 | 
			
		||||
          <AppLinkInput v-model="formData.more.url" />
 | 
			
		||||
        <el-form-item label="大小" prop="titleSize" label-width="40px">
 | 
			
		||||
          <el-slider
 | 
			
		||||
            v-model="formData.titleSize"
 | 
			
		||||
            :max="60"
 | 
			
		||||
            :min="10"
 | 
			
		||||
            show-input
 | 
			
		||||
            input-size="small"
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </template>
 | 
			
		||||
        <el-form-item label="粗细" prop="titleWeight" label-width="40px">
 | 
			
		||||
          <el-slider
 | 
			
		||||
            v-model="formData.titleWeight"
 | 
			
		||||
            :min="100"
 | 
			
		||||
            :max="900"
 | 
			
		||||
            :step="100"
 | 
			
		||||
            show-input
 | 
			
		||||
            input-size="small"
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </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
 | 
			
		||||
            v-model="formData.descriptionWeight"
 | 
			
		||||
            :min="100"
 | 
			
		||||
            :max="900"
 | 
			
		||||
            :step="100"
 | 
			
		||||
            show-input
 | 
			
		||||
            input-size="small"
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-card>
 | 
			
		||||
      <el-card header="查看更多" class="property-group" shadow="never">
 | 
			
		||||
        <el-form-item label="是否显示" prop="more.show">
 | 
			
		||||
          <el-checkbox v-model="formData.more.show" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <!-- 更多按钮的 样式选择 -->
 | 
			
		||||
        <template v-if="formData.more.show">
 | 
			
		||||
          <el-form-item label="样式" prop="more.type">
 | 
			
		||||
            <el-radio-group v-model="formData.more.type">
 | 
			
		||||
              <el-radio label="text">文字</el-radio>
 | 
			
		||||
              <el-radio label="icon">图标</el-radio>
 | 
			
		||||
              <el-radio label="all">文字+图标</el-radio>
 | 
			
		||||
            </el-radio-group>
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
          <el-form-item label="更多文字" prop="more.text" v-show="formData.more.type !== 'icon'">
 | 
			
		||||
            <el-input v-model="formData.more.text" />
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
          <el-form-item label="跳转链接" prop="more.url">
 | 
			
		||||
            <AppLinkInput v-model="formData.more.url" />
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
        </template>
 | 
			
		||||
      </el-card>
 | 
			
		||||
    </el-form>
 | 
			
		||||
  </section>
 | 
			
		||||
  </ComponentContainerProperty>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { TitleBarProperty } from './config'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -124,7 +124,15 @@ export const PAGE_LIBS = [
 | 
			
		|||
  {
 | 
			
		||||
    name: '图文组件',
 | 
			
		||||
    extended: true,
 | 
			
		||||
    components: ['ImageBar', 'Carousel', 'TitleBar', 'VideoPlayer', 'Divider', 'MagicCube']
 | 
			
		||||
    components: [
 | 
			
		||||
      'ImageBar',
 | 
			
		||||
      'Carousel',
 | 
			
		||||
      'TitleBar',
 | 
			
		||||
      'VideoPlayer',
 | 
			
		||||
      'Divider',
 | 
			
		||||
      'MagicCube',
 | 
			
		||||
      'HotZone'
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  { name: '商品组件', extended: true, components: ['ProductCard', 'ProductList'] },
 | 
			
		||||
  {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue