|
|
@ -1,20 +1,22 @@ |
|
|
|
<template> |
|
|
|
<div class="ball-grid" :style="gridStyle"> |
|
|
|
<div v-for="(isActive, index) in ballStates" :key="index" :class="['ball', { active: isActive }]" |
|
|
|
:style="ballStyle(index)" @click="handleBallClick(index)"> |
|
|
|
<div class="inner-circle" :style="innerCircleStyle(index)"></div> |
|
|
|
<div class="ball-area" :style="gridStyle"> |
|
|
|
<div class="ball-grid"> |
|
|
|
<div |
|
|
|
v-for="(ball, index) in data" |
|
|
|
:key="index" |
|
|
|
:class="['ball']" |
|
|
|
:style="ballStyle(index)" |
|
|
|
> |
|
|
|
<div class="inner-circle" > |
|
|
|
<div class="value-circle" :style="innerCircleStyle(index)"></div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script setup> |
|
|
|
import { |
|
|
|
computed, |
|
|
|
ref, |
|
|
|
watch, |
|
|
|
onMounted, |
|
|
|
onBeforeUnmount, |
|
|
|
} from 'vue' |
|
|
|
import { computed, ref, watch, onMounted, onBeforeUnmount } from 'vue' |
|
|
|
|
|
|
|
// 移液盘复用组件 |
|
|
|
|
|
|
@ -28,14 +30,6 @@ const props = defineProps({ |
|
|
|
type: Array, |
|
|
|
default: () => [], |
|
|
|
}, |
|
|
|
customColors: { |
|
|
|
type: Boolean, |
|
|
|
default: false, |
|
|
|
}, |
|
|
|
activated: { |
|
|
|
type: Number, |
|
|
|
default: 0, |
|
|
|
}, |
|
|
|
width: { |
|
|
|
type: String, |
|
|
|
default: '300px', |
|
|
@ -58,24 +52,13 @@ const props = defineProps({ |
|
|
|
}, |
|
|
|
}) |
|
|
|
|
|
|
|
// 发出自定义事件通知父组件 |
|
|
|
const emit = defineEmits(['deactivate']) |
|
|
|
|
|
|
|
const ballStates = ref(Array.from({ length: props.total }, () => false)) // 小球激活状态数组 |
|
|
|
const activatedQueue = ref([]) // 用来追踪激活顺序的队列,先进先出 |
|
|
|
let savedBallStates = [] |
|
|
|
let savedActivatedQueue = [] |
|
|
|
// const ballStates = ref(Array.from({ length: props.total }, () => false)) // 小球激活状态数组 |
|
|
|
|
|
|
|
// 计算网格样式 |
|
|
|
const gridStyle = computed(() => ({ |
|
|
|
width: props.width, |
|
|
|
height: props.height, |
|
|
|
display: 'grid', |
|
|
|
gridTemplateColumns: `repeat(${props.columns}, 1fr)`, |
|
|
|
gap: '4px', |
|
|
|
justifyContent: 'center', |
|
|
|
alignItems: 'center', |
|
|
|
backgroundColor: '#555', |
|
|
|
})) |
|
|
|
|
|
|
|
// 计算小球样式 |
|
|
@ -84,128 +67,62 @@ const ballStyle = (index) => { |
|
|
|
return { |
|
|
|
width: ballSize, |
|
|
|
height: ballSize, |
|
|
|
backgroundColor: 'white', |
|
|
|
borderRadius: '50%', |
|
|
|
display: 'flex', |
|
|
|
justifyContent: 'center', |
|
|
|
alignItems: 'center', |
|
|
|
cursor: 'pointer', |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 动态计算内部小球的颜色变化 |
|
|
|
const innerCircleStyle = (index) => { |
|
|
|
const ball = props.data[index] || {}; |
|
|
|
const isActive = props.customColors ? ball.isUse : ballStates.value[index]; |
|
|
|
const color = props.customColors ? (ball.color || 'lightgray') : props.activeColor; |
|
|
|
const ball = props.data[index] || {} |
|
|
|
const isActive = !!ball.isUse |
|
|
|
console.log(ball.num) |
|
|
|
return { |
|
|
|
width: '80%', |
|
|
|
height: '80%', |
|
|
|
backgroundColor: isActive ? color : props.innerColor, |
|
|
|
borderRadius: '50%', |
|
|
|
height: `${ball.num/25.0*100}%`, |
|
|
|
backgroundColor: isActive ? ball.color : props.innerColor, |
|
|
|
transition: 'background-color 0.3s ease', |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 初始化激活的小球 |
|
|
|
const initializeActivatedBalls = () => { |
|
|
|
for (let i = 0; i < props.activated; i++) { |
|
|
|
ballStates.value[i] = true |
|
|
|
activatedQueue.value.push(i) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 监听 `props.activated` 的变化,更新小球激活状态 |
|
|
|
watch( |
|
|
|
() => props.activated, |
|
|
|
(newVal, oldVal) => { |
|
|
|
// 增加激活小球数量 |
|
|
|
if (newVal > oldVal) { |
|
|
|
for (let i = 0; i < newVal - oldVal; i++) { |
|
|
|
const nextIndex = ballStates.value.findIndex((state) => state === false) |
|
|
|
if (nextIndex !== -1) { |
|
|
|
ballStates.value[nextIndex] = true |
|
|
|
activatedQueue.value.push(nextIndex) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// 减少激活小球数量 |
|
|
|
else if (newVal < oldVal) { |
|
|
|
for (let i = 0; i < oldVal - newVal; i++) { |
|
|
|
const toDeactivate = activatedQueue.value.shift() |
|
|
|
if (toDeactivate !== undefined) { |
|
|
|
ballStates.value[toDeactivate] = false |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
{ immediate: true }, // 在初次渲染时立即执行 |
|
|
|
) |
|
|
|
// 用于批量更新小球激活状态的函数 |
|
|
|
const updateActivationState = (toActivateCount) => { |
|
|
|
// 重置状态 |
|
|
|
ballStates.value = Array.from({ length: props.total }, () => false); |
|
|
|
activatedQueue.value = []; |
|
|
|
|
|
|
|
// 根据新的激活数更新状态和队列 |
|
|
|
for (let i = 0; i < toActivateCount; i++) { |
|
|
|
ballStates.value[i] = true; |
|
|
|
activatedQueue.value.push(i); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const handleBallClick = (index) => { |
|
|
|
// 确定点击的小球在激活队列中的位置 |
|
|
|
const queueIndex = activatedQueue.value.indexOf(index); |
|
|
|
|
|
|
|
if (queueIndex !== -1) { |
|
|
|
// 获取要取消激活的小球数量 |
|
|
|
const deactivatedCount = queueIndex + 1; |
|
|
|
|
|
|
|
// 更新 ballStates 仅对前 queueIndex + 1 个小球取消激活 |
|
|
|
for (let i = 0; i < deactivatedCount; i++) { |
|
|
|
const ballIndex = activatedQueue.value[i]; |
|
|
|
ballStates.value[ballIndex] = false; |
|
|
|
} |
|
|
|
|
|
|
|
// 从 activatedQueue 中移除取消激活的小球 |
|
|
|
activatedQueue.value.splice(0, deactivatedCount); |
|
|
|
|
|
|
|
// 发出事件,通知父组件剩余的激活小球数量 |
|
|
|
emit('deactivate', activatedQueue.value.length); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复状态 |
|
|
|
onMounted(() => { |
|
|
|
if (savedBallStates.length > 0) { |
|
|
|
ballStates.value = [...savedBallStates] |
|
|
|
activatedQueue.value = [...savedActivatedQueue] |
|
|
|
} else { |
|
|
|
initializeActivatedBalls() |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
// 保存状态 |
|
|
|
onBeforeUnmount(() => { |
|
|
|
savedBallStates = [...ballStates.value] |
|
|
|
savedActivatedQueue = [...activatedQueue.value] |
|
|
|
}) |
|
|
|
</script> |
|
|
|
|
|
|
|
<style scoped> |
|
|
|
.ball-area { |
|
|
|
position: relative; |
|
|
|
} |
|
|
|
.ball-grid { |
|
|
|
width: 100%; |
|
|
|
height: 100%; |
|
|
|
display: flex; |
|
|
|
background-color: #666; |
|
|
|
border-radius: 10px; |
|
|
|
width: fit-content; |
|
|
|
flex-wrap: wrap; |
|
|
|
column-gap: 4px; |
|
|
|
padding-left: 2px; |
|
|
|
} |
|
|
|
|
|
|
|
.ball { |
|
|
|
margin: 0 2px 2px 0; |
|
|
|
background-color: #fff; |
|
|
|
border-radius: 999px; |
|
|
|
display: flex; |
|
|
|
justify-content: center; |
|
|
|
align-items: center; |
|
|
|
transition: background-color 0.3s ease; |
|
|
|
transition: background-color 0.3s ease; |
|
|
|
} |
|
|
|
|
|
|
|
.inner-circle { |
|
|
|
position: relative; |
|
|
|
width: 80%; |
|
|
|
height: 80%; |
|
|
|
background-color: lightgray; |
|
|
|
border-radius: 999px; |
|
|
|
overflow: hidden; |
|
|
|
transition: background-color 0.3s ease; |
|
|
|
} |
|
|
|
</style> |
|
|
|
.inner-circle .value-circle { |
|
|
|
position: absolute; |
|
|
|
left: 0; |
|
|
|
right: 0; |
|
|
|
bottom: 0; |
|
|
|
} |
|
|
|
</style> |