houzhongjian
2024-08-08 820397e43a0b64d35c6d31d2a55475061438593b
提交 | 用户 | 时间
820397 1 <template>
H 2   <Dialog v-model="dialogVisible" title="设置热区" width="780" @close="handleClose">
3     <div ref="container" class="relative h-full w-750px">
4       <el-image :src="imgUrl" class="pointer-events-none h-full w-750px select-none" />
5       <div
6         v-for="(item, hotZoneIndex) in formData"
7         :key="hotZoneIndex"
8         class="hot-zone"
9         :style="{
10           width: `${item.width}px`,
11           height: `${item.height}px`,
12           top: `${item.top}px`,
13           left: `${item.left}px`
14         }"
15         @mousedown="handleMove(item, $event)"
16         @dblclick="handleShowAppLinkDialog(item)"
17       >
18         <span class="pointer-events-none select-none">{{ item.name || '双击选择链接' }}</span>
19         <Icon icon="ep:close" class="delete" :size="14" @click="handleRemove(item)" />
20
21         <!-- 8个控制点 -->
22         <span
23           class="ctrl-dot"
24           v-for="(dot, dotIndex) in CONTROL_DOT_LIST"
25           :key="dotIndex"
26           :style="dot.style"
27           @mousedown="handleResize(item, dot, $event)"
28         ></span>
29       </div>
30     </div>
31     <template #footer>
32       <el-button @click="handleAdd" type="primary" plain>
33         <Icon icon="ep:plus" class="mr-5px" />
34         添加热区
35       </el-button>
36       <el-button @click="handleSubmit" type="primary" plain>
37         <Icon icon="ep:check" class="mr-5px" />
38         确定
39       </el-button>
40     </template>
41   </Dialog>
42   <AppLinkSelectDialog ref="appLinkDialogRef" @app-link-change="handleAppLinkChange" />
43 </template>
44
45 <script setup lang="ts">
46 import { HotZoneItemProperty } from '@/components/DiyEditor/components/mobile/HotZone/config'
47 import { array, string } from 'vue-types'
48 import {
49   CONTROL_DOT_LIST,
50   CONTROL_TYPE_ENUM,
51   ControlDot,
52   HOT_ZONE_MIN_SIZE,
53   useDraggable,
54   zoomIn,
55   zoomOut
56 } from './controller'
57 import { AppLink } from '@/components/AppLinkInput/data'
58 import { remove } from 'lodash-es'
59
60 /** 热区编辑对话框 */
61 defineOptions({ name: 'HotZoneEditDialog' })
62
63 // 定义属性
64 const props = defineProps({
65   modelValue: array<HotZoneItemProperty>(),
66   imgUrl: string().def('')
67 })
68 const emit = defineEmits(['update:modelValue'])
69 const formData = ref<HotZoneItemProperty[]>([])
70
71 // 弹窗的是否显示
72 const dialogVisible = ref(false)
73 // 打开弹窗
74 const open = () => {
75   // 放大
76   formData.value = zoomIn(props.modelValue)
77   dialogVisible.value = true
78 }
79 // 提供 open 方法,用于打开弹窗
80 defineExpose({ open })
81
82 // 热区容器
83 const container = ref<HTMLDivElement>()
84
85 // 增加热区
86 const handleAdd = () => {
87   formData.value.push({
88     width: HOT_ZONE_MIN_SIZE,
89     height: HOT_ZONE_MIN_SIZE,
90     top: 0,
91     left: 0
92   } as HotZoneItemProperty)
93 }
94 // 删除热区
95 const handleRemove = (hotZone: HotZoneItemProperty) => {
96   remove(formData.value, hotZone)
97 }
98
99 // 移动热区
100 const handleMove = (item: HotZoneItemProperty, e: MouseEvent) => {
101   useDraggable(item, e, (left, top, _, __, moveWidth, moveHeight) => {
102     setLeft(item, left + moveWidth)
103     setTop(item, top + moveHeight)
104   })
105 }
106
107 // 调整热区大小、位置
108 const handleResize = (item: HotZoneItemProperty, ctrlDot: ControlDot, e: MouseEvent) => {
109   useDraggable(item, e, (left, top, width, height, moveWidth, moveHeight) => {
110     ctrlDot.types.forEach((type) => {
111       switch (type) {
112         case CONTROL_TYPE_ENUM.LEFT:
113           setLeft(item, left + moveWidth)
114           break
115         case CONTROL_TYPE_ENUM.TOP:
116           setTop(item, top + moveHeight)
117           break
118         case CONTROL_TYPE_ENUM.WIDTH:
119           {
120             // 上移时,高度为减少
121             const direction = ctrlDot.types.includes(CONTROL_TYPE_ENUM.LEFT) ? -1 : 1
122             setWidth(item, width + moveWidth * direction)
123           }
124           break
125         case CONTROL_TYPE_ENUM.HEIGHT:
126           {
127             // 左移时,宽度为减少
128             const direction = ctrlDot.types.includes(CONTROL_TYPE_ENUM.TOP) ? -1 : 1
129             setHeight(item, height + moveHeight * direction)
130           }
131           break
132       }
133     })
134   })
135 }
136
137 // 设置X轴坐标
138 const setLeft = (item: HotZoneItemProperty, left: number) => {
139   // 不能超出容器
140   if (left >= 0 && left <= container.value!.offsetWidth - item.width) {
141     item.left = left
142   }
143 }
144 // 设置Y轴坐标
145 const setTop = (item: HotZoneItemProperty, top: number) => {
146   // 不能超出容器
147   if (top >= 0 && top <= container.value!.offsetHeight - item.height) {
148     item.top = top
149   }
150 }
151 // 设置宽度
152 const setWidth = (item: HotZoneItemProperty, width: number) => {
153   // 不能小于最小宽度 && 不能超出容器右边
154   if (width >= HOT_ZONE_MIN_SIZE && item.left + width <= container.value!.offsetWidth) {
155     item.width = width
156   }
157 }
158 // 设置高度
159 const setHeight = (item: HotZoneItemProperty, height: number) => {
160   // 不能小于最小高度 && 不能超出容器底部
161   if (height >= HOT_ZONE_MIN_SIZE && item.top + height <= container.value!.offsetHeight) {
162     item.height = height
163   }
164 }
165
166 // 处理对话框关闭
167 const handleSubmit = () => {
168   // 会自动触发handleClose
169   dialogVisible.value = false
170 }
171
172 // 处理对话框关闭
173 const handleClose = () => {
174   // 缩小
175   const list = zoomOut(formData.value)
176   emit('update:modelValue', list)
177 }
178
179 const activeHotZone = ref<HotZoneItemProperty>()
180 const appLinkDialogRef = ref()
181 const handleShowAppLinkDialog = (hotZone: HotZoneItemProperty) => {
182   activeHotZone.value = hotZone
183   appLinkDialogRef.value.open(hotZone.url)
184 }
185 const handleAppLinkChange = (appLink: AppLink) => {
186   if (!appLink || !activeHotZone.value) return
187   activeHotZone.value.name = appLink.name
188   activeHotZone.value.url = appLink.path
189 }
190 </script>
191
192 <style scoped lang="scss">
193 .hot-zone {
194   position: absolute;
195   background: var(--el-color-primary-light-7);
196   opacity: 0.8;
197   border: 1px solid var(--el-color-primary);
198   color: var(--el-color-primary);
199   font-size: 16px;
200   display: flex;
201   align-items: center;
202   justify-content: center;
203   cursor: move;
204   z-index: 10;
205
206   /* 控制点 */
207   .ctrl-dot {
208     position: absolute;
209     width: 8px;
210     height: 8px;
211     border-radius: 50%;
212     border: inherit;
213     background-color: #fff;
214     z-index: 11;
215   }
216
217   .delete {
218     display: none;
219     position: absolute;
220     top: 0;
221     right: 0;
222     padding: 2px 2px 6px 6px;
223     background-color: var(--el-color-primary);
224     border-radius: 0 0 0 80%;
225     cursor: pointer;
226     color: #fff;
227     text-align: right;
228   }
229
230   &:hover {
231     .delete {
232       display: block;
233     }
234   }
235 }
236 </style>