年会抽奖程序:200行HTML+JavaScript写个桌面程序

需求分析

  1. 多轮抽奖,每轮只有3个环节:展示奖品图,人名闪动,停止闪动确定中奖名单
  2. 中奖分级,例如试用期员工不能中二等奖或以上
  3. 每轮抽奖的中奖人数不同。每个人只能中一次奖
  4. 可临时加场,现场输入奖品名、数量。额外窗口输入,避免被观众看到修改过程。
  5. 本地记录每轮的奖品和中奖名单
  6. 全屏显示。不确定现场的屏幕分辨率,故核心部分固定1024*768,居中显示;背景拉伸铺满全屏。

(本程序仅适合懂点编程知识的人用,非商业化,不讲易用性)
demo

技术选型

搞桌面程序第一时间就想到了这几个框架:Java SwingPython TkinterC++ QtC# WPF。虽然都可行,但感觉开发不够便捷。而且谁知道年会现场那台电脑有没有对应的运行时库。

最后经同事给的灵感想到了用JavaScript做,选定了node-webkit,即nw.js。没有选electron是它需要搭开发环境。我只想用个文本编辑器快点搞出来而已……

既然连开发环境都懒得搭,那自然要应用VueReactAngular的话会比较麻烦。实际上也没必要,小学生才用牛刀杀鸡。

代码开源

开源在 https://github.com/hursing/raffle 。文末会贴一下当前的版本,但以github上的为准。

使用方法

启动

先在GitHub download或clone整份代码。然后到 https://nwjs.io/ 下载node-webkit,解压。

Windows的启动方法:把代码的整个目录拖动到解压出来的nw.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:人员名单与可中奖等级,等级数字越小表示可中更大的奖。中奖后此文件会被修改,删除已中奖的人。示例ini中人名也带有数字是方便验证等级机制生效而已,实际不需要。

PS:由于我是在mac上搞的,所以文件保存是以LF换行的。在Windows用记事本来打开的话会不显示换行,因为不是CRLF。文件编码都是utf-8。可以下载个notepad++来编辑。

TODO

  • 启动的时候“设置窗口大小和位置”会造成闪动,可以做得体验好点,虽然没必要
  • 更多的可动态设置项
  • 启动方式还是有点别扭,可打包一下程序

代码

程序步骤说明:

  1. 调整窗口大小和位置
  2. 读取配置文件,得到人员名单和抽奖轮次信息
  3. 进入第1轮。通过按键4和空格进入下个环节
  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>
相关推荐
©️2020 CSDN 皮肤主题: 代码科技 设计师:Amelia_0503 返回首页