chore: v1.1.0 - 5项功能优化

This commit is contained in:
2026-05-24 18:28:48 +08:00
parent cd4fcfefa2
commit 84fd33fce1
9 changed files with 3045 additions and 71 deletions

View File

@@ -194,12 +194,46 @@
}
.name-list .name-tag {
display: inline-block;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
margin: 2px;
border-radius: 4px;
background: rgba(255,255,255,0.06);
color: var(--text-secondary);
position: relative;
}
.name-list .name-tag .delete-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
font-size: 11px;
line-height: 1;
color: #888;
background: transparent;
border: none;
border-radius: 50%;
cursor: pointer;
transition: all 0.2s ease;
padding: 0;
font-weight: bold;
}
.name-list .name-tag .delete-btn:hover {
color: #ff4444;
background: rgba(255, 68, 68, 0.15);
}
.name-list .name-tag .delete-btn:active {
transform: scale(0.85);
}
.name-list.disabled .name-tag .delete-btn {
display: none;
}
/* ===== Mode Switch ===== */
@@ -499,6 +533,274 @@
background: rgba(255,255,255,0.08);
}
/* ===== Probability Settings ===== */
.probability-hint {
font-size: 12px;
color: var(--text-secondary);
margin-bottom: 12px;
line-height: 1.5;
}
.probability-list {
max-height: 300px;
overflow-y: auto;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 8px;
margin-bottom: 8px;
}
.probability-row {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.probability-row:last-child {
border-bottom: none;
}
.probability-row .person-name {
flex: 0 0 80px;
font-size: 14px;
color: var(--text-primary);
}
.probability-row .weight-input {
width: 56px;
padding: 4px 6px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 4px;
background: var(--bg-secondary);
color: var(--text-primary);
text-align: center;
font-size: 13px;
font-family: inherit;
outline: none;
transition: var(--transition);
}
.probability-row .weight-input:focus {
border-color: var(--accent);
}
.weight-quick-btns {
display: flex;
gap: 4px;
flex-wrap: wrap;
}
.weight-quick-btns button {
padding: 2px 8px;
font-size: 12px;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 4px;
background: var(--bg-card);
color: var(--text-secondary);
cursor: pointer;
transition: var(--transition);
font-family: inherit;
}
.weight-quick-btns button:hover {
background: var(--accent);
color: #fff;
border-color: var(--accent);
}
.weight-quick-btns button.active {
background: var(--accent);
color: #fff;
border-color: var(--accent);
}
.probability-actions {
display: flex;
gap: 8px;
margin-top: 12px;
}
.probability-actions button {
flex: 1;
background: var(--accent);
color: #fff;
border: none;
padding: 8px 16px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: var(--transition);
font-family: inherit;
}
.probability-actions button:hover {
background: var(--accent-hover);
}
.probability-actions button.secondary {
background: var(--bg-card);
color: var(--text-primary);
border: 1px solid rgba(255,255,255,0.12);
}
.probability-actions button.secondary:hover {
background: rgba(255,255,255,0.08);
}
/* ===== Winner Popup ===== */
.winner-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.75);
z-index: 10001;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
}
.winner-overlay.hidden {
display: none;
}
.winner-modal {
background: var(--bg-secondary);
border-radius: 20px;
padding: 40px 50px;
text-align: center;
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.5);
animation: winnerPopIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
max-width: 90vw;
}
@keyframes winnerPopIn {
0% {
opacity: 0;
transform: scale(0.5) translateY(30px);
}
100% {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.winner-modal .winner-close {
position: absolute;
top: 12px;
right: 16px;
font-size: 24px;
color: var(--text-secondary);
background: none;
border: none;
cursor: pointer;
transition: var(--transition);
padding: 4px 8px;
border-radius: 50%;
font-family: inherit;
}
.winner-modal .winner-close:hover {
color: var(--accent);
background: rgba(255,255,255,0.08);
}
.winner-modal .winner-congrats {
font-size: 28px;
font-weight: 700;
color: #FFD700;
margin-bottom: 20px;
text-shadow: 0 0 20px rgba(255, 215, 0, 0.4);
}
.winner-modal .winner-avatar-large {
width: 120px;
height: 120px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 20px;
font-size: 40px;
font-weight: 700;
color: #fff;
text-shadow: 0 2px 6px rgba(0,0,0,0.3);
border: 4px solid rgba(255,255,255,0.2);
box-shadow: 0 0 30px rgba(255, 215, 0, 0.3);
}
.winner-modal .winner-name-large {
font-size: 32px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 8px;
}
.winner-modal .winner-label {
font-size: 14px;
color: var(--text-secondary);
}
/* ===== Horizontal Scroll (Slot Machine) ===== */
.scroll-container {
width: 100%;
overflow: hidden;
position: relative;
height: 40px;
margin: 8px 0;
}
.scroll-container::before,
.scroll-container::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
width: 40px;
z-index: 2;
pointer-events: none;
}
.scroll-container::before {
left: 0;
background: linear-gradient(to right, var(--bg-secondary), transparent);
}
.scroll-container::after {
right: 0;
background: linear-gradient(to left, var(--bg-secondary), transparent);
}
.scroll-track {
display: flex;
align-items: center;
height: 100%;
will-change: transform;
}
.scroll-item {
flex-shrink: 0;
padding: 0 16px;
font-size: 22px;
font-weight: 600;
color: var(--text-primary);
white-space: nowrap;
opacity: 0.5;
transition: opacity 0.1s;
}
.scroll-item.active {
opacity: 1;
color: var(--accent);
}
/* ===== Scrollbar ===== */
::-webkit-scrollbar {
width: 6px;
@@ -568,6 +870,9 @@
<div id="avatarDisplay" class="avatar-display">
<div id="avatarText" class="avatar-text">?</div>
</div>
<div id="scrollContainer" class="scroll-container hidden">
<div id="scrollTrack" class="scroll-track"></div>
</div>
<div id="rollName" class="roll-name">等待开始</div>
</div>
@@ -603,6 +908,15 @@
<div id="mustWinChecklist" class="checklist"></div>
<button id="btnConfirmMustWin">确认</button>
</div>
<div class="backdoor-section">
<h3>🎲 概率设置</h3>
<p class="probability-hint">权重值 0-100数值越大被抽中的概率越高。默认权重为 1设为 0 则不参与抽奖。</p>
<div id="probabilityList" class="probability-list"></div>
<div class="probability-actions">
<button id="btnResetAllWeights" class="secondary">重置全部为 1</button>
<button id="btnConfirmProbabilities">确认</button>
</div>
</div>
<div class="backdoor-section">
<h3>排除名单</h3>
<textarea id="excludeInput" placeholder="输入排除名字(逗号分隔)..."></textarea>
@@ -613,6 +927,17 @@
</div>
</div>
<!-- 中奖弹窗(默认隐藏) -->
<div id="winnerOverlay" class="winner-overlay hidden">
<div class="winner-modal">
<button id="winnerClose" class="winner-close"></button>
<div class="winner-congrats">🎉 恭喜中奖!🎉</div>
<div id="winnerAvatarLarge" class="winner-avatar-large">?</div>
<div id="winnerNameLarge" class="winner-name-large"></div>
<div class="winner-label">本次幸运儿</div>
</div>
</div>
</div>
<script>
@@ -621,7 +946,7 @@
// ===== 共享状态 =====
const state = {
pool: [], // 当前抽奖池:[{ id, name, color, avatar }]
pool: [], // 当前抽奖池:[{ id, name, color, avatar, weight }]
history: [], // 历史记录(内存,页面关闭清空)
totalImported: 0, // 累计导入总人数
@@ -635,6 +960,7 @@
enabled: false,
mustWinList: [], // 必中名单(名字字符串数组)
excludeList: [], // 排除名单(名字字符串数组)
probabilities: {}, // { name: weight },权重值 0-100
},
// 动画
@@ -759,7 +1085,8 @@
id: Utils.generateId(),
name: name,
color: Utils.hashColor(name),
avatar: Utils.getAvatarText(name)
avatar: Utils.getAvatarText(name),
weight: 1 // v1.1.0: 默认权重 1
};
},
@@ -779,28 +1106,84 @@
var container = document.getElementById('nameList');
if (!container) return;
container.innerHTML = '';
// 抽奖中禁用删除
if (state.isDrawing) {
container.classList.add('disabled');
} else {
container.classList.remove('disabled');
}
var fragment = document.createDocumentFragment();
for (var i = 0; i < state.pool.length; i++) {
var person = state.pool[i];
var span = document.createElement('span');
span.className = 'name-tag';
span.textContent = state.pool[i].name;
var nameText = document.createTextNode(person.name);
span.appendChild(nameText);
// 删除按钮
var delBtn = document.createElement('button');
delBtn.className = 'delete-btn';
delBtn.textContent = '×';
delBtn.title = '删除 ' + person.name;
delBtn.setAttribute('data-name', person.name);
if (state.isDrawing) {
delBtn.disabled = true;
}
span.appendChild(delBtn);
fragment.appendChild(span);
}
container.appendChild(fragment);
},
/** 按名字删除人员v1.1.0 新增) */
removeByName: function(name) {
if (state.isDrawing) return; // 抽奖中禁止删除
// 从池中移除
var idx = -1;
for (var i = 0; i < state.pool.length; i++) {
if (state.pool[i].name === name) {
idx = i;
break;
}
}
if (idx === -1) return;
state.pool.splice(idx, 1);
// 同步清理必中名单
var mwIdx = state.backdoor.mustWinList.indexOf(name);
if (mwIdx !== -1) state.backdoor.mustWinList.splice(mwIdx, 1);
// 同步清理排除名单
var exIdx = state.backdoor.excludeList.indexOf(name);
if (exIdx !== -1) state.backdoor.excludeList.splice(exIdx, 1);
// 同步清理概率配置
delete state.backdoor.probabilities[name];
this.updatePoolCount();
this.renderNameList();
}
};
// ===== 抽奖引擎模块 =====
const LotteryEngine = {
/** 根据模式和后门设置,从池中选出中奖人 */
/** 根据模式和后门设置,从池中选出中奖人v1.1.0 加权随机) */
pickOne: function() {
// 1. 候选池 = state.pool 副本
var candidates = state.pool.slice();
// 2. 过滤排除名单
// 2. 过滤排除名单 + 权重为 0 的人
var self = this;
candidates = this.applyExclude(candidates);
candidates = candidates.filter(function(person) {
var w = self.getWeight(person.name);
return w > 0;
});
// 3. 若必中名单非空,取交集
// 3. 若必中名单非空,取交集(最高优先级,等概率)
if (state.backdoor.mustWinList.length > 0) {
var intersection = [];
for (var i = 0; i < candidates.length; i++) {
@@ -812,13 +1195,49 @@
}
}
if (intersection.length > 0) {
candidates = intersection;
return intersection[Math.floor(Math.random() * intersection.length)];
}
}
// 4. 从候选池随机选一人
// 4. 加权随机(无必中名单或必中名单不匹配时)
if (candidates.length === 0) return null;
return candidates[Math.floor(Math.random() * candidates.length)];
var totalWeight = 0;
for (var k = 0; k < candidates.length; k++) {
totalWeight += self.getWeight(candidates[k].name);
}
if (totalWeight === 0) {
// 兜底:所有人权重都为 0 时等概率
return candidates[Math.floor(Math.random() * candidates.length)];
}
// 加权随机选择
var r = Math.random() * totalWeight;
var cumulative = 0;
for (var m = 0; m < candidates.length; m++) {
cumulative += self.getWeight(candidates[m].name);
if (r < cumulative) {
return candidates[m];
}
}
// 兜底
return candidates[candidates.length - 1];
},
/** 获取某人权重v1.1.0 */
getWeight: function(name) {
// 优先从 probabilities 读取,其次 person.weight默认 1
if (state.backdoor.probabilities.hasOwnProperty(name)) {
return state.backdoor.probabilities[name];
}
for (var i = 0; i < state.pool.length; i++) {
if (state.pool[i].name === name) {
return state.pool[i].weight || 1;
}
}
return 1;
},
/** 过滤掉排除名单中的人 */
@@ -855,42 +1274,95 @@
rAFId: null,
lastTickTime: 0,
/** 名字滚动动画:快速切换 → 3秒后减速 → 停止在 winner */
/** 名字滚动动画:水平老虎机效果 → 减速 → 停止在 winner */
startRollAnimation: function(winnerPerson, callback) {
var self = this;
var startTime = performance.now();
var duration = 3500; // 总时长 3.5 秒
var lastTickTime = 0;
var rollNameEl = document.getElementById('rollName');
var avatarTextEl = document.getElementById('avatarText');
var avatarDisplayEl = document.getElementById('avatarDisplay');
var scrollContainerEl = document.getElementById('scrollContainer');
var scrollTrackEl = document.getElementById('scrollTrack');
// 移除 highlight
// 移除 highlight
rollNameEl.classList.remove('highlight');
// 构建滚动轨道:重复多份名单以产生连续滚动效果
var allNames = [];
for (var i = 0; i < 8; i++) {
for (var j = 0; j < state.pool.length; j++) {
allNames.push(state.pool[j]);
}
}
// 清空并填充轨道
scrollTrackEl.innerHTML = '';
scrollTrackEl.style.transform = 'translateX(0px)';
for (var k = 0; k < allNames.length; k++) {
var item = document.createElement('span');
item.className = 'scroll-item';
item.textContent = allNames[k].name;
scrollTrackEl.appendChild(item);
}
// 显示滚动容器,隐藏静态名字
scrollContainerEl.classList.remove('hidden');
rollNameEl.textContent = '';
// 测量轨道总宽度
var totalWidth = scrollTrackEl.scrollWidth;
var containerWidth = scrollContainerEl.clientWidth;
// 缓动函数
function easeOutCubic(t) {
return 1 - Math.pow(1 - t, 3);
}
var lastItemIndex = -1;
function tick(now) {
var elapsed = now - startTime;
var progress = Math.min(elapsed / duration, 1);
var easedProgress = easeOutCubic(progress);
// 缓动:前 85% 快速切换,后 15% 减速
var easeProgress = self.easeOutCubic(progress);
var interval = 50 + easeProgress * 400; // 50ms → 450ms
// 总位移 = 轨道宽度 - 容器宽度(滚动到末尾附近)
var maxScroll = totalWidth - containerWidth;
// 最终停在 winner 附近
var targetScroll = maxScroll * 0.85;
var currentScroll = targetScroll * easedProgress;
if (elapsed - lastTickTime >= interval) {
// 获取当前滚动候选
var candidate = self.getRollCandidate(winnerPerson, progress);
if (candidate) {
rollNameEl.textContent = candidate.name;
avatarTextEl.textContent = candidate.avatar;
avatarDisplayEl.style.background = candidate.color;
scrollTrackEl.style.transform = 'translateX(' + (-currentScroll) + 'px)';
// 高亮当前可见的中间项
var items = scrollTrackEl.querySelectorAll('.scroll-item');
var centerOffset = containerWidth / 2 + currentScroll;
var itemWidth = 100; // 估算每个名字宽度
var currentIndex = Math.floor(centerOffset / itemWidth) % items.length;
if (currentIndex !== lastItemIndex && currentIndex >= 0 && currentIndex < items.length) {
// 清除旧高亮
for (var m = 0; m < items.length; m++) {
items[m].classList.remove('active');
}
lastTickTime = elapsed;
items[currentIndex].classList.add('active');
// 更新头像和名字
var personIdx = currentIndex % state.pool.length;
var currentPerson = state.pool[personIdx];
if (currentPerson) {
rollNameEl.textContent = currentPerson.name;
avatarTextEl.textContent = currentPerson.avatar;
avatarDisplayEl.style.background = currentPerson.color;
}
lastItemIndex = currentIndex;
}
if (progress < 1) {
self.rAFId = requestAnimationFrame(tick);
} else {
// 最终停在 winner,添加 highlight
// 最终停在 winner
scrollContainerEl.classList.add('hidden');
rollNameEl.textContent = winnerPerson.name;
avatarTextEl.textContent = winnerPerson.avatar;
avatarDisplayEl.style.background = winnerPerson.color;
@@ -924,14 +1396,40 @@
return state.pool[Math.floor(Math.random() * state.pool.length)];
},
/** Canvas 烟花粒子效果 */
/** 显示中奖弹窗v1.1.0 新增) */
showWinnerPopup: function(winnerPerson) {
var overlay = document.getElementById('winnerOverlay');
var avatarLarge = document.getElementById('winnerAvatarLarge');
var nameLarge = document.getElementById('winnerNameLarge');
avatarLarge.textContent = winnerPerson.avatar;
avatarLarge.style.background = winnerPerson.color;
nameLarge.textContent = winnerPerson.name;
overlay.classList.remove('hidden');
},
/** 关闭中奖弹窗 */
hideWinnerPopup: function() {
var overlay = document.getElementById('winnerOverlay');
overlay.classList.add('hidden');
},
/** Canvas 烟花粒子效果v1.1.0 改进:弹窗周围爆炸 + 5秒 + 更密集) */
fireFireworks: function() {
var canvas = document.getElementById('fireworksCanvas');
var ctx = canvas.getContext('2d');
var running = true;
var fireworks = [];
var spawnTimer = 0;
var endTime = performance.now() + 4000; // 持续 4
var endTime = performance.now() + 5000; // 持续 5
// 获取弹窗位置作为爆炸中心
var popupEl = document.getElementById('winnerOverlay');
var popupRect = popupEl.getBoundingClientRect();
var centerX = popupRect.left + popupRect.width / 2;
var centerY = popupRect.top + popupRect.height / 2;
var spreadRadius = Math.max(popupRect.width, popupRect.height) * 0.8;
// 调整 canvas 尺寸
function resize() {
@@ -943,17 +1441,17 @@
// 粒子类
function Particle(x, y, color) {
var angle = Math.random() * Math.PI * 2;
var speed = 2 + Math.random() * 6;
var speed = 2 + Math.random() * 8;
this.x = x;
this.y = y;
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
this.color = color;
this.alpha = 1;
this.size = 2 + Math.random() * 3;
this.life = 60 + Math.random() * 40;
this.size = 2 + Math.random() * 4;
this.life = 80 + Math.random() * 50;
this.maxLife = this.life;
this.gravity = 0.05;
this.gravity = 0.04;
this.friction = 0.98;
}
@@ -977,34 +1475,25 @@
ctx.restore();
};
// 烟花类
// 烟花类v1.1.0:无上升阶段,直接爆炸)
function Firework(targetX, targetY, colors) {
this.x = targetX;
this.y = canvas.height;
this.targetX = targetX;
this.targetY = targetY;
this.colors = colors;
this.particles = [];
this.state = 'rising';
this.speed = 4 + Math.random() * 3;
this.state = 'exploding';
// 初始爆炸生成更多粒子
for (var i = 0; i < 100 + Math.floor(Math.random() * 40); i++) {
var color = colors[Math.floor(Math.random() * colors.length)];
this.particles.push(new Particle(targetX, targetY, color));
}
}
Firework.prototype.update = function() {
if (this.state === 'rising') {
this.y -= this.speed;
if (this.y <= this.targetY) {
this.state = 'exploding';
// 生成爆炸粒子
for (var i = 0; i < 60 + Math.floor(Math.random() * 20); i++) {
var color = this.colors[Math.floor(Math.random() * this.colors.length)];
this.particles.push(new Particle(this.targetX, this.targetY, color));
}
}
} else if (this.state === 'exploding') {
if (this.state === 'exploding') {
for (var j = 0; j < this.particles.length; j++) {
this.particles[j].update();
}
// 过滤死亡粒子
this.particles = this.particles.filter(function(p) { return p.life > 0; });
if (this.particles.length === 0) {
this.state = 'dead';
@@ -1013,14 +1502,7 @@
};
Firework.prototype.draw = function(ctx) {
if (this.state === 'rising') {
ctx.save();
ctx.fillStyle = this.colors[0];
ctx.beginPath();
ctx.arc(this.x, this.y, 3, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
} else if (this.state === 'exploding') {
if (this.state === 'exploding') {
for (var i = 0; i < this.particles.length; i++) {
this.particles[i].draw(ctx);
}
@@ -1054,11 +1536,17 @@
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 每 200-500ms 生成一朵烟花
// 每 150-400ms 生成一朵烟花
spawnTimer++;
if (spawnTimer > 4 + Math.random() * 6) {
var x = canvas.width * (0.2 + Math.random() * 0.6);
var y = canvas.height * (0.1 + Math.random() * 0.4);
if (spawnTimer > 3 + Math.random() * 5) {
// 在弹窗周围随机位置生成爆炸点
var angle = Math.random() * Math.PI * 2;
var radius = spreadRadius * (0.5 + Math.random() * 1.2);
var x = centerX + Math.cos(angle) * radius;
var y = centerY + Math.sin(angle) * radius;
// 限制在屏幕范围内
x = Math.max(50, Math.min(canvas.width - 50, x));
y = Math.max(50, Math.min(canvas.height - 50, y));
var colors = randomColorPalette();
fireworks.push(new Firework(x, y, colors));
spawnTimer = 0;
@@ -1188,6 +1676,124 @@
} else {
excludeChecklist.innerHTML = '<span style="color:var(--text-secondary);font-size:12px;">抽奖池为空</span>';
}
// v1.1.0: 渲染概率列表
this.renderProbabilityList();
},
/** 渲染概率设置列表v1.1.0 新增) */
renderProbabilityList: function() {
var list = document.getElementById('probabilityList');
if (!list) return;
list.innerHTML = '';
if (state.pool.length === 0) {
list.innerHTML = '<span style="color:var(--text-secondary);font-size:12px;">抽奖池为空</span>';
return;
}
for (var i = 0; i < state.pool.length; i++) {
var person = state.pool[i];
var row = document.createElement('div');
row.className = 'probability-row';
row.setAttribute('data-name', person.name);
// 获取当前权重
var currentWeight = 1;
if (state.backdoor.probabilities.hasOwnProperty(person.name)) {
currentWeight = state.backdoor.probabilities[person.name];
} else if (person.weight) {
currentWeight = person.weight;
}
// 人名
var nameSpan = document.createElement('span');
nameSpan.className = 'person-name';
nameSpan.textContent = person.name;
// 权重输入框
var weightInput = document.createElement('input');
weightInput.type = 'number';
weightInput.className = 'weight-input';
weightInput.min = 0;
weightInput.max = 100;
weightInput.value = currentWeight;
weightInput.setAttribute('data-name', person.name);
// 快捷按钮
var quickBtns = document.createElement('div');
quickBtns.className = 'weight-quick-btns';
var quickValues = [0, 1, 5, 10, 50, 100];
for (var q = 0; q < quickValues.length; q++) {
var w = quickValues[q];
var btn = document.createElement('button');
btn.type = 'button';
btn.textContent = w;
btn.setAttribute('data-weight', w);
if (w === currentWeight) {
btn.className = 'active';
}
(function(input, b, wVal) {
btn.addEventListener('click', function() {
input.value = wVal;
var siblings = quickBtns.querySelectorAll('button');
for (var s = 0; s < siblings.length; s++) {
siblings[s].className = '';
}
b.className = 'active';
});
})(weightInput, btn, w);
quickBtns.appendChild(btn);
}
// 输入框变化时更新快捷按钮高亮
weightInput.addEventListener('input', function() {
var v = parseInt(this.value) || 0;
var btns = quickBtns.querySelectorAll('button');
for (var b = 0; b < btns.length; b++) {
btns[b].className = (parseInt(btns[b].getAttribute('data-weight')) === v) ? 'active' : '';
}
});
row.appendChild(nameSpan);
row.appendChild(weightInput);
row.appendChild(quickBtns);
list.appendChild(row);
}
},
/** 保存概率设置v1.1.0 新增) */
saveProbabilities: function() {
var inputs = document.querySelectorAll('.weight-input');
var newProbs = {};
for (var i = 0; i < inputs.length; i++) {
var input = inputs[i];
var name = input.getAttribute('data-name');
var weight = Math.max(0, Math.min(100, parseInt(input.value) || 0));
if (weight !== 1) {
newProbs[name] = weight;
}
}
state.backdoor.probabilities = newProbs;
// 同步更新 pool 中每个人的 weight
for (var j = 0; j < state.pool.length; j++) {
var person = state.pool[j];
if (newProbs.hasOwnProperty(person.name)) {
person.weight = newProbs[person.name];
} else {
person.weight = 1;
}
}
},
/** 重置所有权重为 1v1.1.0 新增) */
resetAllWeights: function() {
state.backdoor.probabilities = {};
for (var i = 0; i < state.pool.length; i++) {
state.pool[i].weight = 1;
}
this.renderProbabilityList();
},
/** 确认必中名单 */
@@ -1255,7 +1861,12 @@
btnCloseBackdoor: document.getElementById('btnCloseBackdoor'),
btnConfirmMustWin: document.getElementById('btnConfirmMustWin'),
btnConfirmExclude: document.getElementById('btnConfirmExclude'),
fireworksCanvas: document.getElementById('fireworksCanvas')
fireworksCanvas: document.getElementById('fireworksCanvas'),
// v1.1.0 新增
btnConfirmProbabilities: document.getElementById('btnConfirmProbabilities'),
btnResetAllWeights: document.getElementById('btnResetAllWeights'),
winnerOverlay: document.getElementById('winnerOverlay'),
winnerClose: document.getElementById('winnerClose')
};
// ===== 事件绑定 =====
@@ -1319,6 +1930,39 @@
Backdoor.close();
}
});
// v1.1.0: 名单删除按钮(事件委托)
DOM.nameList.addEventListener('click', function(e) {
var btn = e.target.closest('.delete-btn');
if (btn && !btn.disabled) {
var name = btn.getAttribute('data-name');
if (name) {
NameManager.removeByName(name);
}
}
});
// v1.1.0: 概率设置确认
DOM.btnConfirmProbabilities.addEventListener('click', function() {
Backdoor.saveProbabilities();
});
// v1.1.0: 重置所有权重
DOM.btnResetAllWeights.addEventListener('click', function() {
Backdoor.resetAllWeights();
});
// v1.1.0: 关闭中奖弹窗
DOM.winnerClose.addEventListener('click', function() {
AnimationEngine.hideWinnerPopup();
});
// v1.1.0: 点击遮罩关闭中奖弹窗
DOM.winnerOverlay.addEventListener('click', function(e) {
if (e.target === DOM.winnerOverlay) {
AnimationEngine.hideWinnerPopup();
}
});
}
/** 设置抽奖按钮的点击/长按事件 */
@@ -1389,25 +2033,23 @@
state.isDrawing = true;
DOM.btnStart.disabled = true;
DOM.resultArea.classList.add('hidden');
NameManager.renderNameList(); // 禁用删除按钮
// 执行抽奖逻辑
var winner = LotteryEngine.pickOne();
if (!winner) {
state.isDrawing = false;
DOM.btnStart.disabled = false;
NameManager.renderNameList();
alert('无法选出中奖人,请检查名单或后门设置。');
return;
}
// 开始滚动动画
AnimationEngine.startRollAnimation(winner, function(finalWinner) {
// 动画结束,显示结果
// 动画结束
state.winner = finalWinner;
// 显示中奖结果
DOM.winnerName.textContent = finalWinner.name;
DOM.resultArea.classList.remove('hidden');
// 根据模式处理
if (state.mode === 'single') {
LotteryEngine.removeWinner(finalWinner.id);
@@ -1418,12 +2060,16 @@
// 记录历史
HistoryModule.addRecord(finalWinner);
// 播放烟花
// 显示中奖弹窗
AnimationEngine.showWinnerPopup(finalWinner);
// 播放烟花(围绕弹窗)
AnimationEngine.fireFireworks();
// 恢复按钮
state.isDrawing = false;
DOM.btnStart.disabled = false;
NameManager.renderNameList(); // 恢复删除按钮
});
}