需求分析
- 多輪抽獎,每輪只有3個環節:展示獎品圖,人名閃動,停止閃動確定中獎名單
- 中獎分級,例如試用期員工不能中二等獎或以上
- 每輪抽獎的中獎人數不同。每個人只能中一次獎
- 可臨時加場,現場輸入獎品名、數量。額外窗口輸入,避免被觀衆看到修改過程。
- 本地記錄每輪的獎品和中獎名單
- 全屏顯示。不確定現場的屏幕分辨率,故核心部分固定1024*768,居中顯示;背景拉伸鋪滿全屏。
技術選型
搞桌面程序第一時間就想到了這幾個框架:Java Swing
、Python Tkinter
、C++ Qt
、C# WPF
。雖然都會,但感覺還是不夠便捷。而且鬼知道年會現場那臺電腦有沒有對應的運行時庫。
最後經同事給的靈感想到了用JavaScript做,選定了node-webkit
。沒有選electron
是它需要搭開發環境。
既然連開發環境都懶得搭,那自然也用不了Vue
、React
、Angular
。實際上也沒必要,小學生才用牛刀殺雞。
代碼開源
開源在 https://github.com/hursing/raffle 。文末會貼一下當前的版本,但以github上的爲準。
使用方法
啓動
Windows的啓動方法:到 https://nwjs.io/ 下載node-webkit
,解壓出來,把代碼的整個目錄拖動到nwjs.exe
上。
其它操作系統按官方說明做:
cd /path/to/your/app
/path/to/nw .
/path/to/nw
is the binary file of NW.js. On Windows, it’s nw.exe
; On Linux, it’s nw
; On Mac, it’s nwjs.app/Contents/MacOS/nwjs
.
按鍵
f
:切換全屏4
:下一步。進入下一輪抽獎的展示獎品圖片、進入名單滾動。- 空格:立刻停止名單滾動。即確定中獎人員。
8
:重新加載配置文件。主要用於臨場增加獎項1
:上一步,用來看看上個獎項的情況
核心文件說明
index.html
:所有代碼都在這steps.json
:流程配置文件,應該一看就懂。中獎後此文件會被修改,包含中獎名單。如果需要加獎項,不用退出程序,編輯完這個文件後按8
就能重新加載配置,繼續抽。names.ini
:人員名單與可中獎等級,等級數字越小表示可中更大的獎。中獎後此文件會被修改,刪除已中獎的人
TODO
- 啓動的時候設置窗口大小和位置會閃動,可以做得體驗好點,雖然沒必要
- 更多的可動態設置項
- 啓動方式還是有點彆扭,可打包一下程序
代碼
程序步驟說明:
- 調整窗口大小和位置
- 讀取配置文件,得到人員名單和抽獎輪次信息
- 進入第1輪。通過按鍵
4
和空格進入下個環節 - 用state變量來記錄狀態:展示圖片、滾動名單、顯示中獎名單
html的部分:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
var win = nw.Window.get()
win.resizeTo(1024, 768)
win.moveTo(0, 0)
</script>
<style type="text/css">
* {
margin: 0;
padding: 0;
}
html, body {
width: 100%;
height: 100%;
}
body {
text-align: center;
background: url("./bg.png") no-repeat;
overflow: hidden;
background-size: 100% 100%;
font-weight: bold;
color: #D40000;
}
#container {
min-width: 1000px;
min-height: 700px;
}
#title {
font-size: 100px;
margin-top: 80px;
}
#disc {
font-size: 40px;
margin: 10px 0;
}
#image {
margin-top: 20px;
max-height: 280px;
border: 1px solid #E23540FF;
border-radius: 20px;
}
#list {
margin: 0 auto;
max-width: 800px;
}
#list span {
display: inline-block;
width: 160px;
font-size: 36px;
margin-top: 8px;
}
</style>
</head>
<body>
<div id="container">
<div id="title">XX公司年會</div>
<div id="disc">獎品描述</div>
<img id="image" />
<div id="list"></div>
</div>
<script>
var fs = require('fs')
var steps = null
var step = 0
var names = null
var state = ''
var disc = document.getElementById('disc')
var image = document.getElementById('image')
var list = document.getElementById('list')
function reloadConf(func) {
fs.readFile('names.ini', 'utf8', function(err, data) {
names = data.split('\n').map(x => x.split(','))
})
fs.readFile('./steps.json', 'utf8', function(err, data) {
steps = eval(data)
if (func) func()
})
}
function saveConf(func) {
fs.writeFile('./steps.json', JSON.stringify(steps), function(err) {
if (err) {
alert(err)
}
})
fs.writeFile('./names.ini', names.map(x => x.join(',')).join('\n'), function(err) {
if (err) {
alert(err)
}
})
}
function showPic(data) {
disc.innerHTML = data.disc
image.src = data.image
image.style.display = 'inline'
list.style.display = 'none'
while (list.hasChildNodes()) {
list.removeChild(list.firstChild)
}
}
function showBlink(data) {
disc.innerHTML = data.disc
image.style.display = 'none'
list.style.display = 'block'
var spans = []
for (var i = 0; i < data.count; ++i) {
var span = document.createElement('span')
list.appendChild(span)
spans.push(span)
}
function doBlink() {
if (state == 'showBlink') {
names.sort(function() {
return 0.5 - Math.random()
})
for (var i = 0; i < data.count; ++i) {
spans[i].innerHTML = names[i][0]
}
window.requestAnimationFrame(doBlink)
}
}
window.requestAnimationFrame(doBlink)
}
function showList(data) {
disc.innerHTML = data.disc
image.style.display = 'none'
list.style.display = 'block'
while (list.hasChildNodes()) {
list.removeChild(list.firstChild)
}
for (var i = 0; i < data.list.length; ++i) {
var span = document.createElement('span')
span.innerHTML = data.list[i]
list.appendChild(span)
}
}
function nextStep() {
var data = steps[step]
if (state == 'showPic') {
data.list = data.list || []
if (data.list.length > 0) {
state = 'showList'
showList(data)
} else {
state = 'showBlink'
showBlink(data)
}
} else if (state == 'showBlink') {
if (data.list.length > 0) {
state = 'showList'
showList(data)
}
} else if (state == 'showList') {
if (step < (steps.length - 1)) {
++step
state = ''
nextStep()
}
} else {
state = 'showPic'
showPic(data)
}
}
function previousStep() {
if (step > 0) {
--step
}
state = ''
nextStep()
}
function drawPrize() {
if (state == 'showBlink') {
var data = steps[step]
names.sort(function (a, b) {
if (a[1] <= data.level && b[1] > data.level) {
return -1
}
return 0
})
var luck = names.splice(0, data.count)
data.list = luck.map(x => x[0])
saveConf()
nextStep()
}
}
document.addEventListener('keydown', function(e) {
e=e||window.event
if (e.keyCode == 56) {
// 8
reloadConf()
} else if (e.keyCode == 52) {
// 4
nextStep()
} else if (e.keyCode == 49) {
// 1
previousStep()
} else if (e.keyCode == 32) {
// 空格
drawPrize()
} else if (e.keyCode == 70) {
// f
win.toggleFullscreen()
}
})
reloadConf(nextStep)
</script>
</bdoy>
</html>