vr-uniapp/src/components/common/magic-cube/index.vue

312 lines
8.8 KiB
Vue

<template>
<div>
<div class="decorate-cube">
<ul v-for="(n, index) in densityNum" :key="index" class="cube-col">
<li v-for="(i, index1) in densityNum" :key="index1" class="cube-item" :style="{ width: cubeCellWidth + 'px', height: cubeCellHeight + 'px' }" :data-x="n" :data-y="i" @click="onClickCubeItem($event)" @mouseenter="onEnterCubeItem($event)">
<div :class="['w h item', { 'item-selecting': isSelecting(n, i), 'item-selected': isSelected(n, i) }]">
<icon name="add" color="9" :style="{ 'line-height': cubeCellHeight + 'px' }"></icon>
</div>
</li>
</ul>
<div v-for="(item, index) in selectedList" :key="index" :class="['cube-selected', {'cube-selected_active': selected_active == index }]" :style="selected_style(item)" @click="selected_click(index)">
<div v-if="selected_active == index && props.flag" class="cube-del" @click.stop="on_selected_del(index)">
<icon name="close" color="f" size="8"></icon>
</div>
<template v-if="!isEmpty(item.img[0]) && props.type == 'img'">
<image-empty v-model="item.img[0]"></image-empty>
</template>
<template v-else>
<div class="cube-selected-text">
{{ Math.round((750 / densityNum) * (item.end.y - item.start.y + 1)) }}
x
{{ Math.round((750 / densityNum) * (item.end.x - item.start.x + 1)) }}
像素
<template v-if="props.type == 'data'">
<div class="mt-12">共有3个商品</div>
</template>
</div>
</template>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { isEmpty } from 'lodash';
interface CubeItem {
start: {
x: number;
y: number;
};
end: {
x: number;
y: number;
};
img: uploadList[],
img_list?: any,
data_list?: any
}
interface Props {
flag: boolean;
list: CubeItem[];
type: string;
cubeWidth: number;
cubeHeight: number;
}
const props = withDefaults(defineProps<Props>(), {
list: () => [],
flag: false,
type: 'img',
cubeWidth: 390,
cubeHeight: 390,
});
const selected_active = ref(0);
//#region 容器大小变更
const density = ref('4');
//#endregion
const selectingItem = reactive<any>({
tempStart: null,
tempEnd: null,
start: null,
end: null,
});
const selectedList = ref(props.list);
//单元魔方宽度。
const cubeCellWidth = computed(() => props.cubeWidth / parseInt(density.value));
//密度值。
const densityNum = computed(() => parseInt(density.value));
//单元魔方高度。
const cubeCellHeight = computed(() => props.cubeHeight / parseInt(density.value));
const emits = defineEmits(['selected_click']);
// 判断选择的内容的长度是否发生变化
watch(() => selectedList.value.length, (val) => {
if (val > 1) {
selected_active.value = val - 1;
} else {
selected_active.value = 0;
}
emits('selected_click', selected_active.value);
}, {deep: true});
const updateSelecting = () => {
//获取开始和结束之间的所有魔方单元。
const tempStart = selectingItem.tempStart;
const tempEnd = selectingItem.tempEnd;
selectingItem.start = {
x: Math.min(tempStart.x, tempEnd.x),
y: Math.min(tempStart.y, tempEnd.y),
};
selectingItem.end = {
x: Math.max(tempStart.x, tempEnd.x),
y: Math.max(tempStart.y, tempEnd.y),
};
};
//清除正在选择的。
const clearSelecting = () => {
selectingItem.tempStart = null;
selectingItem.tempEnd = null;
selectingItem.start = null;
selectingItem.end = null;
};
const coordFromCubeEvent = (event: any) => {
var el = event.currentTarget;
var x = el.getAttribute('data-x');
var y = el.getAttribute('data-y');
return { x: x, y: y };
};
const isContain = (x: number, y: number, item: CubeItem) => {
return item.start.x <= x && x <= item.end.x && item.start.y <= y && y <= item.end.y;
};
//魔方点击事件。
const onClickCubeItem = (event: any) => {
let domclass = event.currentTarget.getAttribute('class');
if (-1 !== domclass.indexOf('item-selected')) {
return;
}
let coord = coordFromCubeEvent(event);
if (null == selectingItem.tempStart) {
selectingItem.tempStart = coord;
selectingItem.tempEnd = coord;
selectingItem.start = coord;
selectingItem.end = coord;
return;
}
selectingItem.tempEnd = coord;
updateSelecting();
//加入选中的。
let selectedItem = {
start: selectingItem.start,
end: selectingItem.end,
img: [],
};
selectedList.value.push(selectedItem);
clearSelecting();
};
const onEnterCubeItem = (event: any) => {
if (selectingItem.tempStart) {
var coord = coordFromCubeEvent(event);
selectingItem.tempEnd = coord;
updateSelecting();
}
};
// 删除当前选中的内容
const on_selected_del = (index: number) => {
clearSelecting();
// splice() 会先从原数组中添加/删除项目 然后返回被删除的项目。
selectedList.value.splice(index, 1);
};
//判断是否正在选择
const isSelecting = (x: number, y: number) => {
const item = selectingItem;
if (item.tempStart) {
return isContain(x, y, item);
}
return false;
};
//判断是否已经选择。
const isSelected = (x: number, y: number) => {
for (var i = 0; i < selectedList.value.length; i++) {
if (isContain(x, y, selectedList.value[i])) {
return true;
}
}
return false;
};
//计算选中层的宽度。
const getSelectedWidth = (item: CubeItem) => {
return (item.end.x - item.start.x + 1) * cubeCellWidth.value;
};
//计算选中层的高度。
const getSelectedHeight = (item: CubeItem) => {
return (item.end.y - item.start.y + 1) * cubeCellHeight.value;
};
//计算选中层的右边距离。
const getSelectedTop = (item: CubeItem) => {
return (item.start.y - 1) * cubeCellHeight.value;
};
//计算选中层的左边距离。
const getSelectedLeft = (item: CubeItem) => {
return (item.start.x - 1) * cubeCellWidth.value;
};
// 生成的样式
const selected_style = (item: CubeItem) => {
return `width: ${ getSelectedWidth(item) }px; height: ${ getSelectedHeight(item) }px; top: ${ getSelectedTop(item) }px; left: ${ getSelectedLeft(item) }px;`
}
// 选中的点击事件
const selected_click = (index: number) => {
selected_active.value = index;
emits('selected_click', index);
};
const tips = () => {
console.log(selectedList.value[selected_active.value].img_list);
console.log(selectedList.value[selected_active.value].data_list);
};
</script>
<style lang="scss" scoped>
.decorate-cube {
position: relative;
.cube-col {
float: left;
list-style: none;
padding: 0;
margin: 0;
}
.cube-item {
background: #f5f5f5;
cursor: pointer;
text-align: center;
box-sizing: border-box;
position: relative;
.item {
border: 1px solid #fff;
border-top: 0;
}
.item-selecting {
background: #e0edff;
position: absolute;
z-index: 99999;
}
.item-selected {
background: #e0edff;
}
}
.cube-item:first-child {
.item {
border-top: 0;
}
}
.cube-item:last-child {
.item {
border-bottom: 0;
}
}
.cube-col:first-child {
.cube-item .item {
border-left: 0;
}
}
.cube-col:last-of-type {
.cube-item .item {
border-right: 0;
}
}
.cube-selected-text {
font-size: 12px;
width: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
.cube-selected {
position: absolute;
background-color: #eee;
border: 1px solid #fff;
text-align: center;
color: $cr-primary;
cursor: pointer;
box-sizing: border-box;
z-index: 2;
}
.cube-selected_active {
border: 1px solid $cr-primary;
}
.cube-del {
background: $cr-primary;
position: absolute;
top: 0;
right: 0;
height: 1.6rem;
width: 1.6rem;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
}
:deep(.el-image) {
height: 100%;
width: 100%;
background-color: #fff;
.el-image__inner {
object-fit: cover;
}
.image-slot img {
width: 6rem;
}
}
}
</style>