From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目
---
src/components/MagicCubeEditor/index.vue | 270 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 270 insertions(+), 0 deletions(-)
diff --git a/src/components/MagicCubeEditor/index.vue b/src/components/MagicCubeEditor/index.vue
new file mode 100644
index 0000000..6af4ca4
--- /dev/null
+++ b/src/components/MagicCubeEditor/index.vue
@@ -0,0 +1,270 @@
+<template>
+ <div class="relative">
+ <table class="cube-table">
+ <!-- 搴曞眰锛氶瓟鏂圭煩闃� -->
+ <tbody>
+ <tr v-for="(rowCubes, row) in cubes" :key="row">
+ <td
+ v-for="(cube, col) in rowCubes"
+ :key="col"
+ :class="['cube', { active: cube.active }]"
+ :style="{
+ width: `${cubeSize}px`,
+ height: `${cubeSize}px`
+ }"
+ @click="handleCubeClick(row, col)"
+ @mouseenter="handleCellHover(row, col)"
+ >
+ <Icon icon="ep-plus" />
+ </td>
+ </tr>
+ </tbody>
+ <!-- 椤跺眰锛氱儹鍖� -->
+ <div
+ v-for="(hotArea, index) in hotAreas"
+ :key="index"
+ class="hot-area"
+ :style="{
+ top: `${cubeSize * hotArea.top}px`,
+ left: `${cubeSize * hotArea.left}px`,
+ height: `${cubeSize * hotArea.height}px`,
+ width: `${cubeSize * hotArea.width}px`
+ }"
+ @click="handleHotAreaSelected(hotArea, index)"
+ @mouseover="exitHotAreaSelectMode"
+ >
+ <!-- 鍙充笂瑙掔儹鍖哄垹闄ゆ寜閽� -->
+ <div
+ v-if="selectedHotAreaIndex === index && hotArea.width && hotArea.height"
+ class="btn-delete"
+ @click="handleDeleteHotArea(index)"
+ >
+ <Icon icon="ep:circle-close-filled" />
+ </div>
+ <span v-if="hotArea.width">{{ `${hotArea.width}脳${hotArea.height}` }}</span>
+ </div>
+ </table>
+ </div>
+</template>
+<script lang="ts" setup>
+import { propTypes } from '@/utils/propTypes'
+import * as vueTypes from 'vue-types'
+import { Point, Rect, isContains, isOverlap, createRect } from './util'
+
+// 榄旀柟缂栬緫鍣�
+// 鏈変袱閮ㄥ垎缁勬垚锛�
+// 1. 榄旀柟鐭╅樀锛氫綅浜庡簳灞傦紝鐢辨柟鍧楃粍浠剁殑浜岀淮琛ㄦ牸锛岀敤浜庡垱寤虹儹鍖�
+// 鎿嶄綔鏂规硶锛�
+// 1.1 鐐瑰嚮鍏朵腑涓�涓柟鍧楀氨浼氳繘鍏ョ儹鍖洪�夋嫨妯″紡
+// 1.2 鍐嶆鐐瑰嚮鍙﹀涓�涓柟鍧楁椂锛岀粨鏉熺儹鍖洪�夋嫨妯″紡
+// 1.3 鍦ㄤ袱涓柟鍧椾腑闂寸殑鍖哄煙鍒涘缓鐑尯
+// 濡傛灉涓ゆ鐐瑰嚮鐨勯兘鏄悓涓�鏂瑰潡锛屽氨鍙垱寤轰竴涓牸瀛愮殑鐑尯
+// 2. 鐑尯锛氫綅浜庨《灞傦紝閲囩敤缁濆瀹氫綅锛岃鐩栧湪榄旀柟鐭╅樀涓婇潰銆�
+defineOptions({ name: 'MagicCubeEditor' })
+
+/**
+ * 鏂瑰潡
+ * @property active 鏄惁婵�娲�
+ */
+type Cube = Point & { active: boolean }
+
+// 瀹氫箟灞炴��
+const props = defineProps({
+ // 鐑尯鍒楄〃
+ modelValue: vueTypes.array<any>().isRequired,
+ // 琛屾暟锛岄粯璁� 4 琛�
+ rows: propTypes.number.def(4),
+ // 鍒楁暟锛岄粯璁� 4 鍒�
+ cols: propTypes.number.def(4),
+ // 鏂瑰潡澶у皬锛屽崟浣峱x锛岄粯璁�75px
+ cubeSize: propTypes.number.def(75)
+})
+
+// 榄旀柟鐭╅樀锛氭墍鏈夌殑鏂瑰潡
+const cubes = ref<Cube[][]>([])
+// 鐩戝惉琛屾暟銆佸垪鏁板彉鍖�
+watch(
+ () => [props.rows, props.cols],
+ () => {
+ // 娓呯┖榄旀柟
+ cubes.value = []
+ if (!props.rows || !props.cols) return
+
+ // 鍒濆鍖栭瓟鏂�
+ for (let row = 0; row < props.rows; row++) {
+ cubes.value[row] = []
+ for (let col = 0; col < props.cols; col++) {
+ cubes.value[row].push({ x: col, y: row, active: false })
+ }
+ }
+ },
+ { immediate: true }
+)
+
+// 鐑尯鍒楄〃
+const hotAreas = ref<Rect[]>([])
+// 鍒濆鍖栫儹鍖�
+watch(
+ () => props.modelValue,
+ () => (hotAreas.value = props.modelValue || []),
+ { immediate: true }
+)
+
+// 鐑尯璧峰鏂瑰潡
+const hotAreaBeginCube = ref<Cube>()
+// 鏄惁寮�鍚簡鐑尯閫夋嫨妯″紡
+const isHotAreaSelectMode = () => !!hotAreaBeginCube.value
+/**
+ * 澶勭悊榧犳爣鐐瑰嚮鏂瑰潡
+ *
+ * @param currentRow 褰撳墠琛屽彿
+ * @param currentCol 褰撳墠鍒楀彿
+ */
+const handleCubeClick = (currentRow: number, currentCol: number) => {
+ const currentCube = cubes.value[currentRow][currentCol]
+ // 鎯呭喌1锛氳繘鍏ョ儹鍖洪�夋嫨妯″紡
+ if (!isHotAreaSelectMode()) {
+ hotAreaBeginCube.value = currentCube
+ hotAreaBeginCube.value.active = true
+ return
+ }
+
+ // 鎯呭喌2锛氱粨鏉熺儹鍖洪�夋嫨妯″紡
+ hotAreas.value.push(createRect(hotAreaBeginCube.value!, currentCube))
+ // 缁撴潫鐑尯閫夋嫨妯″紡
+ exitHotAreaSelectMode()
+ // 鍒涘缓鍚庡氨閫変腑鐑尯
+ let hotAreaIndex = hotAreas.value.length - 1
+ handleHotAreaSelected(hotAreas.value[hotAreaIndex], hotAreaIndex)
+ // 鍙戦�佺儹鍖哄彉鍔ㄩ�氱煡
+ emitUpdateModelValue()
+}
+/**
+ * 澶勭悊榧犳爣缁忚繃鏂瑰潡
+ *
+ * @param currentRow 褰撳墠琛屽彿
+ * @param currentCol 褰撳墠鍒楀彿
+ */
+const handleCellHover = (currentRow: number, currentCol: number) => {
+ // 褰撳墠娌℃湁杩涘叆鐑尯閫夋嫨妯″紡
+ if (!isHotAreaSelectMode()) return
+
+ // 褰撳墠宸查�夌殑鍖哄煙
+ const currentSelectedArea = createRect(
+ hotAreaBeginCube.value!,
+ cubes.value[currentRow][currentCol]
+ )
+ // 鐑尯涓嶅厑璁搁噸鍙�
+ for (const hotArea of hotAreas.value) {
+ // 妫�鏌ユ槸鍚﹂噸鍙�
+ if (isOverlap(hotArea, currentSelectedArea)) {
+ // 缁撴潫鐑尯閫夋嫨妯″紡
+ exitHotAreaSelectMode()
+
+ return
+ }
+ }
+
+ // 婵�娲婚�変腑鍖哄煙鍐呴儴鐨勬柟鍧�
+ eachCube((_, __, cube) => {
+ cube.active = isContains(currentSelectedArea, cube)
+ })
+}
+/**
+ * 澶勭悊鐑尯鍒犻櫎
+ *
+ * @param index 鐑尯绱㈠紩
+ */
+const handleDeleteHotArea = (index: number) => {
+ hotAreas.value.splice(index, 1)
+ // 缁撴潫鐑尯閫夋嫨妯″紡
+ exitHotAreaSelectMode()
+ // 鍙戦�佺儹鍖哄彉鍔ㄩ�氱煡
+ emitUpdateModelValue()
+}
+
+// 鍙戦�佹ā鍨嬫洿鏂�
+const emit = defineEmits(['update:modelValue', 'hotAreaSelected'])
+// 鍙戦�佺儹鍖哄彉鍔ㄩ�氱煡
+const emitUpdateModelValue = () => emit('update:modelValue', hotAreas)
+
+// 鐑尯閫変腑
+const selectedHotAreaIndex = ref(0)
+const handleHotAreaSelected = (hotArea: Rect, index: number) => {
+ selectedHotAreaIndex.value = index
+ emit('hotAreaSelected', hotArea, index)
+}
+
+/**
+ * 缁撴潫鐑尯閫夋嫨妯″紡
+ */
+function exitHotAreaSelectMode() {
+ // 绉婚櫎鏂瑰潡婵�娲绘爣璁�
+ eachCube((_, __, cube) => {
+ if (cube.active) {
+ cube.active = false
+ }
+ })
+
+ // 娓呴櫎璧风偣
+ hotAreaBeginCube.value = undefined
+}
+
+/**
+ * 杩唬榄旀柟鐭╅樀
+ * @param callback 鍥炶皟
+ */
+const eachCube = (callback: (x: number, y: number, cube: Cube) => void) => {
+ for (let x = 0; x < cubes.value.length; x++) {
+ for (let y = 0; y < cubes.value[x].length; y++) {
+ callback(x, y, cubes.value[x][y])
+ }
+ }
+}
+</script>
+<style lang="scss" scoped>
+.cube-table {
+ position: relative;
+ border-spacing: 0;
+ border-collapse: collapse;
+
+ .cube {
+ border: 1px solid var(--el-border-color);
+ text-align: center;
+ color: var(--el-text-color-secondary);
+ cursor: pointer;
+ box-sizing: border-box;
+ &.active {
+ background: var(--el-color-primary-light-9);
+ }
+ }
+
+ .hot-area {
+ position: absolute;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid var(--el-color-primary);
+ background: var(--el-color-primary-light-8);
+ color: var(--el-color-primary);
+ box-sizing: border-box;
+ border-spacing: 0;
+ border-collapse: collapse;
+ cursor: pointer;
+
+ .btn-delete {
+ z-index: 1;
+ position: absolute;
+ top: -8px;
+ right: -8px;
+ height: 16px;
+ width: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background-color: #fff;
+ }
+ }
+}
+</style>
--
Gitblit v1.8.0