目录结构
python/
├── sql/
│ └── table.sql # 创建数据库及数据表
├── config/
│ └── __init__.py # 数据库和Flask配置
├── static/
│ ├── style.css # 样式文件
│ └── script.js # JavaScript脚本
├── templates/
│ └── index.html # 主页面模板
└── lucky_draw.py # 主应用程序
1.table.sql
sql
复制代码
table.sql
CREATE DATABASE lucky_draw;
USE lucky_draw;
CREATE TABLE participants (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
department VARCHAR(50),
employee_id VARCHAR(20),
join_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE winners (
id INT AUTO_INCREMENT PRIMARY KEY,
participant_id INT,
draw_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (participant_id) REFERENCES participants(id)
);
2.__init__.py
python
复制代码
DB_CONFIG = {
'host': 'localhost',
'user': 'your_username',
'password': 'your_password',
'database': 'lucky_draw'
}
3.style.css
css
复制代码
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
}
.container {
max-width: 1000px;
margin: 0 auto;
padding: 30px;
background-color: white;
border-radius: 15px;
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
}
header {
text-align: center;
margin-bottom: 40px;
}
h1 {
color: #2c3e50;
font-size: 2.5em;
margin-bottom: 10px;
}
.subtitle {
color: #7f8c8d;
font-size: 1.2em;
margin: 0;
}
h2 {
color: #34495e;
font-size: 1.5em;
margin-bottom: 20px;
}
.section {
margin: 30px 0;
padding: 25px;
border-radius: 10px;
background-color: #f8f9fa;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}
.draw-section {
background: linear-gradient(to right, #fff5f5, #fff0f0);
}
.form-inline {
display: flex;
justify-content: center;
}
.input-group {
display: flex;
align-items: center;
gap: 10px;
}
.input-group input {
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1em;
transition: all 0.3s ease;
}
.input-group input:focus {
border-color: #3498db;
outline: none;
box-shadow: 0 0 5px rgba(52,152,219,0.3);
}
.draw-controls {
display: flex;
flex-wrap: wrap;
gap: 20px;
align-items: center;
justify-content: center;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 5px;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1em;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background-color: #3498db;
color: white;
}
.btn-primary:hover {
background-color: #2980b9;
transform: translateY(-2px);
}
.btn-danger {
background-color: #e74c3c;
color: white;
}
.btn-danger:hover {
background-color: #c0392b;
}
.reset-form {
text-align: center;
margin-top: 20px;
}
.dashboard {
display: grid;
grid-template-columns: 1fr;
gap: 30px;
margin-top: 40px;
}
.name-list {
display: flex;
flex-wrap: wrap;
gap: 12px;
padding: 15px;
background-color: white;
border-radius: 8px;
min-height: 50px;
}
.name-tag {
padding: 8px 16px;
background-color: #f0f2f5;
border-radius: 20px;
font-size: 0.9em;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
}
.name-tag:hover {
transform: translateY(-2px);
}
.winner {
background: linear-gradient(45deg, #ffd700, #ffa500);
color: #000;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.alert {
padding: 15px 20px;
margin: 20px 0;
border-radius: 8px;
background-color: #d4edda;
color: #155724;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
animation: slideIn 0.5s ease;
}
.count {
font-size: 0.8em;
color: #666;
font-weight: normal;
}
footer {
text-align: center;
margin-top: 40px;
color: #7f8c8d;
}
@keyframes slideIn {
from {
transform: translateY(-20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@media (max-width: 768px) {
.container {
padding: 20px;
}
.draw-controls {
flex-direction: column;
}
.input-group {
width: 100%;
}
}
/* 深色模式 */
[data-theme="dark"] {
background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%);
}
[data-theme="dark"] .container {
background-color: #2c3e50;
color: #ecf0f1;
}
[data-theme="dark"] .section {
background-color: #34495e;
}
[data-theme="dark"] .draw-section {
background: linear-gradient(to right, #2c3e50, #34495e);
}
[data-theme="dark"] h1,
[data-theme="dark"] h2 {
color: #ecf0f1;
}
[data-theme="dark"] .name-tag {
background-color: #465c74;
color: #ecf0f1;
}
/* 主题切换开关 */
.theme-switch {
position: fixed;
top: 20px;
right: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
}
input:checked + .slider {
background-color: #2196F3;
}
input:checked + .slider:before {
transform: translateX(26px);
}
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
/* 抽奖动画 */
.lottery-animation {
margin: 20px 0;
padding: 20px;
text-align: center;
}
.lottery-box {
position: relative;
overflow: hidden;
display: inline-block;
padding: 30px 60px;
background: linear-gradient(45deg, #f1c40f, #f39c12);
border-radius: 15px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
animation: pulse 1.5s infinite;
}
.rolling-name-text {
font-size: 2.5em;
color: #fff;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
margin-bottom: 10px;
}
.rolling-dept-text {
font-size: 1.2em;
color: rgba(255, 255, 255, 0.9);
text-shadow: 1px 1px 2px rgba(0,0,0,0.2);
}
@keyframes pulse {
0% {
transform: scale(1);
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
50% {
transform: scale(1.05);
box-shadow: 0 8px 25px rgba(0,0,0,0.3);
}
100% {
transform: scale(1);
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
}
/* 优化中奖名单样式 */
.winners-section .name-tag {
font-size: 1.2em;
padding: 10px 20px;
background: linear-gradient(45deg, #ffd700, #ffa500);
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
.winners-section .name-tag:hover {
transform: translateY(-3px) rotate(3deg);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
/* 统计图表 */
.charts-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
.chart-wrapper {
width: 300px;
height: 300px;
}
/* 响应式调整 */
@media (max-width: 768px) {
.chart-wrapper {
width: 100%;
height: auto;
}
}
/* 中奖高亮效果 */
.winner-highlight {
animation: winner-glow 1s ease-in-out infinite alternate;
transform: scale(1.1);
transition: all 0.3s ease;
}
@keyframes winner-glow {
from {
box-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #e60073, 0 0 40px #e60073;
}
to {
box-shadow: 0 0 20px #fff, 0 0 30px #ff4da6, 0 0 40px #ff4da6, 0 0 50px #ff4da6;
}
}
/* 庆祝效果 */
.celebration {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.8);
animation: fadeIn 0.3s ease-out;
border-radius: 15px;
}
.celebration-content {
text-align: center;
animation: popIn 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.celebration-icon {
font-size: 4em;
margin-bottom: 10px;
animation: bounce 1s infinite;
}
.winner-name {
font-size: 2.5em;
color: #fff;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
margin-bottom: 5px;
}
.winner-dept {
font-size: 1.2em;
color: rgba(255, 255, 255, 0.9);
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes popIn {
0% {
transform: scale(0.3);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-20px);
}
}
4.script.js
javascript
复制代码
// 主题切换
const themeToggle = document.getElementById('theme-toggle');
const html = document.documentElement;
themeToggle.addEventListener('change', () => {
if (themeToggle.checked) {
html.setAttribute('data-theme', 'dark');
localStorage.setItem('theme', 'dark');
} else {
html.setAttribute('data-theme', 'light');
localStorage.setItem('theme', 'light');
}
});
// 加载保存的主题
const savedTheme = localStorage.getItem('theme') || 'light';
html.setAttribute('data-theme', savedTheme);
themeToggle.checked = savedTheme === 'dark';
// 抽奖动画
const startDrawBtn = document.getElementById('start-draw');
const drawForm = document.getElementById('draw-form');
const rollingName = document.getElementById('rolling-name');
const rollingSound = document.getElementById('rolling-sound');
const winnerSound = document.getElementById('winner-sound');
let isRolling = false;
function getAvailableParticipants() {
const excludeWinners = document.querySelector('input[name="exclude_winners"]').checked;
if (excludeWinners) {
return participants.filter(p => !p.is_winner);
}
return participants;
}
function showParticipant(participant) {
rollingName.innerHTML = `
<div class="rolling-name-text">${participant.name}</div>
<div class="rolling-dept-text">${participant.department}</div>
`;
}
function getRandomParticipant(available) {
const randomIndex = Math.floor(Math.random() * available.length);
return available[randomIndex];
}
// 随机抽取指定数量的参与者
function getRandomWinners(available, count) {
const shuffled = [...available].sort(() => 0.5 - Math.random());
return shuffled.slice(0, Math.min(count, available.length));
}
startDrawBtn.addEventListener('click', async () => {
if (isRolling) return;
const available = getAvailableParticipants();
if (available.length === 0) {
alert('没有合适的抽奖人选!');
return;
}
isRolling = true;
startDrawBtn.disabled = true;
rollingSound.currentTime = 0;
rollingSound.play();
// 先决定中奖者
const numWinners = parseInt(document.querySelector('input[name="num_winners"]').value);
const winners = getRandomWinners(available, numWinners);
const finalWinner = winners[0]; // 显示第一个中奖者的动画
// 开始动画
let duration = 3000; // 总持续时间
let interval = 50; // 初始间隔时间
let startTime = Date.now();
function roll() {
let currentTime = Date.now();
let elapsed = currentTime - startTime;
// 逐渐减慢滚动速度
interval = Math.min(500, 50 + (elapsed / duration) * 450);
// 最后一次显示实际中奖者
if (elapsed >= duration - interval) {
// 显示最终中奖者
showParticipant(finalWinner);
// 添加中奖效果
rollingName.classList.add('winner-highlight');
// 停止动画
rollingSound.pause();
winnerSound.play();
// 显示庆祝效果
showCelebration(finalWinner);
// 将中奖者ID添加到表单
const winnerIdsInput = document.createElement('input');
winnerIdsInput.type = 'hidden';
winnerIdsInput.name = 'winner_ids';
winnerIdsInput.value = winners.map(w => w.id).join(',');
drawForm.appendChild(winnerIdsInput);
// 延迟提交表单
setTimeout(() => {
rollingName.classList.remove('winner-highlight');
drawForm.submit();
}, 2000);
return;
}
// 随机显示参与者
showParticipant(getRandomParticipant(available));
if (elapsed < duration) {
setTimeout(roll, interval);
}
}
roll();
});
// 显示庆祝效果
function showCelebration(winner) {
// 创建庆祝动画容器
const celebration = document.createElement('div');
celebration.className = 'celebration';
// 添加中奖信息
celebration.innerHTML = `
<div class="celebration-content">
<div class="celebration-icon">🎉</div>
<div class="winner-name">${winner.name}</div>
<div class="winner-dept">${winner.department}</div>
</div>
`;
// 添加到页面
document.querySelector('.lottery-animation').appendChild(celebration);
// 2秒后移除庆祝效果
setTimeout(() => {
celebration.remove();
}, 2000);
}
// 数据可视化
const ctx = document.getElementById('winnersPieChart').getContext('2d');
new Chart(ctx, {
type: 'pie',
data: {
labels: ['已中奖', '未中奖'],
datasets: [{
data: [winners.length, participants.length - winners.length],
backgroundColor: [
'rgba(255, 206, 86, 0.8)',
'rgba(75, 192, 192, 0.8)'
]
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'bottom'
},
title: {
display: true,
text: '中奖情况统计'
}
}
}
});
// 添加动画效果
document.querySelectorAll('.name-tag').forEach(tag => {
tag.addEventListener('mouseover', () => {
tag.style.transform = 'scale(1.1) rotate(5deg)';
});
tag.addEventListener('mouseout', () => {
tag.style.transform = 'translateY(-2px)';
});
});
5.index.html
html
复制代码
<!DOCTYPE html>
<html data-theme="light">
<head>
<!-- 设置网页标题 -->
<title>年会抽奖系统</title>
<!-- 引入自定义的 CSS 样式文件,使用 Flask 的 url_for 函数生成静态文件的 URL -->
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<!-- 引入 Font Awesome 图标库,用于显示各种图标 -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<!-- 引入 Chart.js 库,用于绘制图表 -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<!-- 主题切换区域 -->
<div class="theme-switch">
<!-- 显示月亮图标,代表暗色模式 -->
<i class="fas fa-moon"></i>
<!-- 切换开关 -->
<label class="switch">
<!-- 复选框,用于切换主题 -->
<input type="checkbox" id="theme-toggle">
<!-- 开关滑块 -->
<span class="slider round"></span>
</label>
<!-- 显示太阳图标,代表亮色模式 -->
<i class="fas fa-sun"></i>
</div>
<div class="container">
<header>
<h1><i class="fas fa-gift"></i> 年会抽奖系统</h1>
<p class="subtitle">让我们看看谁是今天的幸运儿!</p>
</header>
<!-- 获取并显示 Flask 闪现消息 -->
{% with messages = get_flashed_messages() %}
<!-- 如果有闪现消息 -->
{% if messages %}
<!-- 遍历每条消息 -->
{% for message in messages %}
<!-- 显示消息的提示框 -->
<div class="alert">
<!-- 显示铃铛图标 -->
<i class="fas fa-bell"></i>
<!-- 显示消息内容 -->
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="section">
<h2><i class="fas fa-user-plus"></i> 添加参与者</h2>
<form method="POST" action="{{ url_for('add_participant') }}" class="form-inline">
<div class="input-group">
<input type="text" name="name" placeholder="姓名" required>
<input type="text" name="department" placeholder="部门" required>
<input type="text" name="employee_id" placeholder="工号" required>
<button type="submit" class="btn">
<i class="fas fa-plus"></i> 添加
</button>
</div>
</form>
</div>
<div class="section draw-section">
<h2><i class="fas fa-random"></i> 抽奖</h2>
<div id="lottery-animation" class="lottery-animation">
<div class="lottery-box">
<div class="lottery-name" id="rolling-name">准备开始</div>
</div>
</div>
<form id="draw-form" method="POST" action="{{ url_for('draw') }}" class="form-inline">
<div class="draw-controls">
<div class="input-group">
<label>抽取人数:</label>
<input type="number" name="num_winners" value="1" min="1" required>
</div>
<div class="checkbox-group">
<label>
<input type="checkbox" name="exclude_winners" value="true" checked>
<span>排除已中奖者</span>
</label>
</div>
<button type="button" class="btn btn-primary" id="start-draw">
<i class="fas fa-dice"></i> 开始抽奖
</button>
</div>
</form>
<form method="POST" action="{{ url_for('reset') }}" class="reset-form">
<button type="submit" class="btn btn-danger">
<i class="fas fa-redo"></i> 重置中奖记录
</button>
</form>
</div>
<div class="dashboard">
<div class="section participants-section">
<h2>
<i class="fas fa-users"></i>
参与者名单
<span class="count">({{ participants|length }}人)</span>
</h2>
{% for dept, members in participants|groupby('department') %}
<div class="department-group">
<h3>{{ dept }} ({{ members|length }}人)</h3>
<div class="name-list">
{% for p in members %}
<span class="name-tag {% if p.is_winner %}winner{% endif %}">
<i class="fas fa-user"></i>
{{ p.name }}
<small>{{ p.employee_id }}</small>
</span>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
<div class="section winners-section">
<h2>
<i class="fas fa-crown"></i>
中奖名单
<span class="count">({{ winners|length }}人)</span>
</h2>
<div class="name-list">
{% for winner in winners %}
<span class="name-tag winner">
<i class="fas fa-star"></i>
{{ winner.name }}
</span>
{% endfor %}
</div>
</div>
<div class="section stats-section">
<h2><i class="fas fa-chart-pie"></i> 数据统计</h2>
<div class="charts-container">
<div class="chart-wrapper">
<canvas id="winnersPieChart"></canvas>
</div>
<div class="chart-wrapper">
<canvas id="departmentChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<footer>
<p>祝大家好运!</p>
</footer>
<audio id="rolling-sound" preload="auto"></audio>
<audio id="winner-sound" preload="auto"></audio>
<script>
// 传递Python数据到JavaScript
const participants = {{ participants|tojson|safe }};
const winners = {{ winners|tojson|safe }};
</script>
<script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>
5.lucky_draw.py
python
复制代码
# 从 flask 模块导入必要的类和函数
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash
# 导入 mysql.connector 模块用于连接和操作 MySQL 数据库
import mysql.connector
# 导入 random 模块用于生成随机数,用于抽奖功能
import random
# 从 config 模块导入 DB_CONFIG,DB_CONFIG 存储了数据库连接信息
from config import DB_CONFIG # 创建配置文件存储数据库连接信息
# 创建 Flask 应用实例
app = Flask(__name__)
# 设置应用的密钥,用于会话管理和闪现消息
app.secret_key = 'your_secret_key'
# 定义 Database 类,用于管理数据库连接
class Database:
def __init__(self):
# 初始化数据库连接对象
self.conn = None
# 调用 connect 方法建立数据库连接
self.connect()
def connect(self):
try:
# 使用 DB_CONFIG 中的配置信息建立数据库连接
self.conn = mysql.connector.connect(**DB_CONFIG)
except Exception as e:
# 若连接失败,打印错误信息
print(f"数据库连接错误:{str(e)}")
def get_connection(self):
try:
# 检查数据库连接是否正常,若不正常则尝试重连,最多尝试 3 次,每次间隔 5 秒
self.conn.ping(reconnect=True, attempts=3, delay=5)
except:
# 若重连失败,重新调用 connect 方法建立连接
self.connect()
# 返回数据库连接对象
return self.conn
# 定义 LuckyDraw 类,封装抽奖系统的主要功能
class LuckyDraw:
def __init__(self):
# 创建 Database 类的实例,用于管理数据库连接
self.db = Database()
def get_all_participants(self):
"""获取所有参与者"""
# 获取数据库连接对象
conn = self.db.get_connection()
# 创建游标对象,设置返回结果为字典形式
cursor = conn.cursor(dictionary=True)
try:
# 执行 SQL 查询,获取所有参与者信息,并标记是否为中奖者
cursor.execute("""
SELECT p.*, CASE WHEN w.id IS NOT NULL THEN 1 ELSE 0 END as is_winner
FROM participants p
LEFT JOIN winners w ON p.id = w.participant_id
ORDER BY p.department, p.name
""")
# 获取查询结果
return cursor.fetchall()
except Exception as e:
# 若查询失败,打印错误信息
print(f"获取参与者失败:{str(e)}")
# 返回空列表
return []
finally:
# 关闭游标
cursor.close()
def get_winners(self):
"""获取所有中奖者"""
# 获取数据库连接对象
conn = self.db.get_connection()
# 创建游标对象,设置返回结果为字典形式
cursor = conn.cursor(dictionary=True)
try:
# 执行 SQL 查询,获取所有中奖者信息,并按抽奖时间降序排列
cursor.execute("""
SELECT p.*, w.draw_time
FROM winners w
JOIN participants p ON w.participant_id = p.id
ORDER BY w.draw_time DESC
""")
# 获取查询结果
return cursor.fetchall()
except Exception as e:
# 若查询失败,打印错误信息
print(f"获取中奖者失败:{str(e)}")
# 返回空列表
return []
finally:
# 关闭游标
cursor.close()
def add_participant(self, name, department, employee_id):
"""添加参与者"""
# 获取数据库连接对象
conn = self.db.get_connection()
# 创建游标对象
cursor = conn.cursor()
try:
# 执行 SQL 插入语句,将参与者信息插入到 participants 表中
cursor.execute("""
INSERT INTO participants (name, department, employee_id)
VALUES (%s, %s, %s)
""", (name, department, employee_id))
# 提交数据库事务
conn.commit()
# 返回添加成功标志
return True
except Exception as e:
# 若插入失败,打印错误信息
print(f"添加参与者失败:{str(e)}")
# 回滚数据库事务
conn.rollback()
# 返回添加失败标志
return False
finally:
# 关闭游标
cursor.close()
def draw(self, num_winners=1, exclude_winners=True):
"""抽奖"""
# 获取数据库连接对象
conn = self.db.get_connection()
# 创建游标对象,设置返回结果为字典形式
cursor = conn.cursor(dictionary=True)
try:
# 获取可抽奖的参与者
if exclude_winners:
# 若排除已中奖者,执行 SQL 查询,获取未中奖的参与者
cursor.execute("""
SELECT p.* FROM participants p
LEFT JOIN winners w ON p.id = w.participant_id
WHERE w.id IS NULL
""")
else:
# 若不排除已中奖者,执行 SQL 查询,获取所有参与者
cursor.execute("SELECT * FROM participants")
# 获取查询结果
available = cursor.fetchall()
if not available:
# 若没有可抽奖的参与者,返回空列表
return []
# 随机抽取
# 从可抽奖的参与者中随机抽取指定数量的中奖者
winners = random.sample(available, min(num_winners, len(available)))
# 记录中奖者
for winner in winners:
# 执行 SQL 插入语句,将中奖者信息插入到 winners 表中
cursor.execute("""
INSERT INTO winners (participant_id) VALUES (%s)
""", (winner['id'],))
# 提交数据库事务
conn.commit()
# 返回中奖者列表
return winners
except Exception as e:
# 若抽奖失败,打印错误信息
print(f"抽奖失败:{str(e)}")
# 回滚数据库事务
conn.rollback()
# 返回空列表
return []
finally:
# 关闭游标
cursor.close()
def reset_winners(self):
"""重置中奖记录"""
# 获取数据库连接对象
conn = self.db.get_connection()
# 创建游标对象
cursor = conn.cursor()
try:
# 执行 SQL 语句,清空 winners 表中的数据
cursor.execute("TRUNCATE TABLE winners")
# 提交数据库事务
conn.commit()
# 返回重置成功标志
return True
except Exception as e:
# 若重置失败,打印错误信息
print(f"重置中奖记录失败:{str(e)}")
# 回滚数据库事务
conn.rollback()
# 返回重置失败标志
return False
finally:
# 关闭游标
cursor.close()
# 创建抽奖系统实例
lucky_draw = LuckyDraw()
# 定义首页路由
@app.route('/')
def index():
"""首页"""
# 获取所有参与者信息
participants = lucky_draw.get_all_participants()
# 获取所有中奖者信息
winners = lucky_draw.get_winners()
# 按部门统计数据
department_stats = {}
for p in participants:
# 获取参与者所在部门
dept = p['department']
if dept not in department_stats:
# 若部门不在统计字典中,初始化该部门的统计信息
department_stats[dept] = {'total': 0, 'winners': 0}
# 该部门总人数加 1
department_stats[dept]['total'] += 1
if p['is_winner']:
# 若参与者为中奖者,该部门中奖人数加 1
department_stats[dept]['winners'] += 1
# 渲染 index.html 模板,并传递参与者、中奖者和部门统计信息
return render_template('index.html',
participants=participants,
winners=winners,
department_stats=department_stats)
# 定义添加参与者的路由,只接受 POST 请求
@app.route('/add_participant', methods=['POST'])
def add_participant():
"""添加参与者"""
# 获取表单提交的姓名,并去除前后空格
name = request.form.get('name', '').strip()
# 获取表单提交的部门,并去除前后空格
department = request.form.get('department', '').strip()
# 获取表单提交的员工编号,并去除前后空格
employee_id = request.form.get('employee_id', '').strip()
if name and department and employee_id:
# 若姓名、部门和员工编号都不为空
if lucky_draw.add_participant(name, department, employee_id):
# 若添加参与者成功,闪现成功消息
flash(f'成功添加参与者:{name}')
else:
# 若添加参与者失败,闪现失败消息
flash('添加参与者失败')
else:
# 若信息不完整,闪现提示消息
flash('请填写完整信息')
# 重定向到首页
return redirect(url_for('index'))
# 定义抽奖的路由,只接受 POST 请求
@app.route('/draw', methods=['POST'])
def draw():
"""进行抽奖"""
# 获取表单提交的中奖人数,默认为 1
num_winners = int(request.form.get('num_winners', 1))
# 获取表单提交的是否排除已中奖者的标志,默认为 true
exclude_winners = request.form.get('exclude_winners', 'true') == 'true'
# 获取表单提交的中奖者 ID 列表,以逗号分隔
winner_ids = request.form.get('winner_ids', '').split(',')
if winner_ids and winner_ids[0]:
# 若中奖者 ID 列表不为空
# 获取数据库连接对象
conn = lucky_draw.db.get_connection()
# 创建游标对象
cursor = conn.cursor()
try:
# 记录中奖者
for winner_id in winner_ids:
# 执行 SQL 插入语句,将中奖者信息插入到 winners 表中
cursor.execute("""
INSERT INTO winners (participant_id) VALUES (%s)
""", (int(winner_id),))
# 提交数据库事务
conn.commit()
# 获取中奖者信息
cursor.execute("""
SELECT name FROM participants
WHERE id IN (%s)
""" % ','.join(['%s'] * len(winner_ids)), tuple(map(int, winner_ids)))
# 获取中奖者姓名列表
winner_names = [row[0] for row in cursor.fetchall()]
# 闪现中奖消息
flash(f'恭喜中奖者:{", ".join(winner_names)}')
except Exception as e:
# 若记录中奖失败,打印错误信息
print(f"记录中奖失败:{str(e)}")
# 回滚数据库事务
conn.rollback()
# 闪现错误消息
flash('抽奖过程出现错误')
finally:
# 关闭游标
cursor.close()
else:
# 若没有合适的抽奖人选,闪现提示消息
flash('没有合适的抽奖人选')
# 重定向到首页
return redirect(url_for('index'))
# 定义重置中奖记录的路由,只接受 POST 请求
@app.route('/reset', methods=['POST'])
def reset():
"""重置中奖记录"""
if lucky_draw.reset_winners():
# 若重置中奖记录成功,闪现成功消息
flash('已重置所有中奖记录')
else:
# 若重置中奖记录失败,闪现失败消息
flash('重置失败')
# 重定向到首页
return redirect(url_for('index'))
# 若该脚本作为主程序运行
if __name__ == '__main__':
# 启动 Flask 应用,开启调试模式
app.run(debug=True)