京东抽奖案例
0.设计思路
项目呢是模仿的王者荣耀抽奖界面的一部分。。。
1.目录结构
|-- app.js
|-- router.js 路由接口文件
|-- controllers 控制器
|-- models
|-- node_modules 第三方包
|-- package.json 包描述文件
|-- package-lock.json 第三方包版本锁定文件(npm 5 以后才有)
|-- public 公共的静态资源
|-- README.md 项目说明文档
|-- routes
|-- views 存储视图目录
2.项目需求
本项目包含用户注册、登录以及抽奖等功能:
-
用户注册功能
a. 提供手机号验证码注册功能: 用户输入手机号,对应手机号接收验证码
b.输入完成手机号和验证码之后进入下一步,允许用户输入密码以及确认密码:密码要求12~16位的数字、大小写字母以及符号
c. 注册成功后显示用户抽奖界面(注: 页面不刷新)
-
用户登录功能
a. 提供手机号和密码、手机号和验证码两种方式登录
b. 判断如果用户没有注册的话,直接跳转用户注册界面(注: 页面不刷新)
c. 登录成功后显示用户抽奖界面(注: 页面不刷新)
-
用户抽奖功能
a. 每个用户—次拥有三次抽奖机会
b. 抽奖方式采用转盘方式抽奖:不同奖项的得奖率不同(转盘抽奖可参考百度)
c. 三次抽奖完成后在界面显示三次抽奖汇总结果
3.路由设计
由于要求是页面不跳转,所以应该是只写一个路由即可
路径 | 方法 | get参数 | post参数 | 是否需要登录 | 备注 |
---|---|---|---|---|---|
/ | GET | 渲染首页 |
4.项目构建
-
一、先把静态页面写好
-
HTML完整代码(views/index.html)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>京东抽奖</title> <link rel="stylesheet" href="../public/css/style.css" /> <link rel="stylesheet" href="../public/css/login.css" /> <link rel="stylesheet" href="../public/css/main.css" /> </head> <body> <!-- 注册 --> <!-- <form action="#" class="register"> <input type="text" name="username" id="username" /> <input type="text" name="password" id="password" /> <input type="submit" /> </form> --> <!-- 登录 --> <div class="loginBox"> <h2>Login</h2> <form action="#" method="" id="login"> <div class="item"> <input type="text" name="phone" id="phone" required /> <label for="">Phone</label> </div> <div class="item"> <input type="password" name="password" id="password" required /> <label for="">PassWord</label> </div> <button class="btn"> submit <span></span> <span></span> <span></span> <span></span> <input type="submit" value="" id="submit" /> </button> </form> </div> <!-- 共有一圈奖品,是8个 中间是一个抽奖键 点击抽奖之后抽奖按键消失,随后弹出3项奖品内容,这是一个蒙层 有再次抽奖按钮,和确定按钮 点击再次抽奖按钮可以再次抽奖,点击确定按钮完成抽奖 --> <table> <tr> <td class="prize prize_0" name="阿古朵"> <img class="jpg_ jpg_0" src="https://cdn.jsdelivr.net/gh/extheor/images/Ajax%E5%9B%BE%E7%89%87/jpg_1.jpg" alt="" /> </td> <td class="prize prize_1" name="萌芽"> <img class="jpg_ jpg_1" src="https://cdn.jsdelivr.net/gh/extheor/images/Ajax%E5%9B%BE%E7%89%87/jpg_2.jpg" alt="" /> </td> <td class="prize prize_2" name="蒙恬"> <img class="jpg_ jpg_2" src="https://cdn.jsdelivr.net/gh/extheor/images/Ajax%E5%9B%BE%E7%89%87/jpg_3.jpg" alt="" /> </td> </tr> <tr> <td class="prize prize_7" name="华硕笔记本电脑"> <img class="jpg_ jpg_7" src="https://cdn.jsdelivr.net/gh/extheor/images/Ajax%E5%9B%BE%E7%89%87/jpg_4.jpg" alt="" /> </td> <td class="button">抽奖</td> <td class="prize prize_3" name="运动鞋一双"> <img class="jpg_ jpg_3" src="https://cdn.jsdelivr.net/gh/extheor/images/Ajax%E5%9B%BE%E7%89%87/jpg_5.jpg" alt="" /> </td> </tr> <tr> <td class="prize prize_6" name="摄像机"> <img class="jpg_ jpg_6" src="https://cdn.jsdelivr.net/gh/extheor/images/Ajax%E5%9B%BE%E7%89%87/jpg_6.jpg" alt="" /> </td> <td class="prize prize_5" name="联想笔记本电脑"> <img class="jpg_ jpg_5" src="https://cdn.jsdelivr.net/gh/extheor/images/Ajax%E5%9B%BE%E7%89%87/jpg_7.jpg" alt="" /> </td> <td class="prize prize_4" name="谢谢惠顾"> <!-- 谢谢惠顾 --> <img class="jpg_ jpg_4" src="https://cdn.jsdelivr.net/gh/extheor/images/Ajax%E5%9B%BE%E7%89%87/jpg_8.jpg" alt="" /> </td> </tr> </table> <!-- 遮挡抽奖功能的div --> <div class="coverLottery"></div> <!-- 抽奖结果页面 --> <div class="lotteryResult"> <!-- 抽奖结果显示框 --> <div class="msgbox"></div> </div> <!-- 抽奖结果背景 --> <div class="main"></div> <script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/jquery/1.9.1/jquery.min.js" ></script> <script src="../node_modules/axios/dist/axios.js"></script> <script src="../public/js/axios_default.js"></script> <script src="../public/js/main.js"></script> <script src="../public/js/login.js"></script> <script src="../public/js/index.js"></script> </body> </html>
-
首页CSS代码(style.css)
* { margin: 0; padding: 0; box-sizing: border-box; } html, body { height: 100%; overflow: hidden; } input, button { background: transparent; border: none; outline: none; } body { height: 100vh; background: linear-gradient(#141e30, #243b55); display: flex; justify-content: center; align-items: center; font-size: 16px; color: #03e9f4; } /* 抽奖样式 */ table { width: 300px; height: 300px; /* margin: 50px auto; */ text-align: center; border-radius: 10px; position: absolute; left: 40%; top: 30%; display: none; } td { /* border: 1px solid #900; */ color: #fff; border-radius: 10px; } .prize { background: #fff; } .button { cursor: pointer; } .jpg_ { width: 100px; height: 100px; border-radius: 10px; opacity: 0.5; } .coverLottery { height: 200px; width: 200px; /* background: red; */ /* margin: -307px auto; */ position: absolute; left: 45%; top: 35%; display: none; } .lotteryResult { width: 800px; height: 500px; background: #325c8e; margin: 25px auto; border-radius: 20px; position: relative; display: none; } .lotteryResult > .jpg_ { width: 100px; height: 100px; position: absolute; top: 150px; } .lotteryResult > img:nth-child(2) { left: 100px; } .lotteryResult > img:nth-child(3) { left: 350px; } .lotteryResult > img:nth-child(4) { left: 600px; } .lotteryResult > .msgbox { width: 500px; height: 200px; background: #000; color: rgb(170, 170, 73) !important; position: absolute; left: 140px; top: 260px; border-radius: 20px; text-align: center; padding-top: 50px; font-size: 24px; color: #fff; /* display: none; */ } /* 注册样式 */
-
登录页CSS样式(login.css)
/* 登录样式 */ .loginBox { width: 450px; height: 400px; background: rgba(0, 0, 0, 0.5); box-shadow: 0px 15px 25px 0 rgba(0, 0, 0, 0.6); padding: 40px; } h2 { text-align: center; color: #fff; margin-bottom: 30px; font-weight: 800; } .item { height: 45px; border-bottom: 1px solid #fff; margin-bottom: 40px; position: relative; } .item input { width: 100%; height: 100%; color: #fff; padding-top: 20px; } .item input:focus + label, .item input:valid + label { color: #fff; font-size: 12px; top: 0; } .item label { position: absolute; left: 0; top: 12px; transition: all 0.5s; } .btn { padding: 10px 20px; margin-top: 30px; color: #03e9f4; text-transform: uppercase; letter-spacing: 2px; position: relative; overflow: hidden; transition: all 0.2s; } .btn:hover { background: #03e9f4; border-radius: 5px; color: #fff; box-shadow: 0 0 5px 0 #03e9f4, 0 0 25px 0 #03e9f4, 0 0 50px 0 #03e9f4, 0 0 100px 0 #03e9f4; } .btn > span { position: absolute; } .btn > span:nth-child(1) { width: 100%; height: 2px; background: -webkit-linear-gradient(left, transparent, #03e9f4); left: -100%; top: 0; animation: line1 1s infinite; } #submit { width: 108px; height: 38px; position: absolute; left: 0; top: 0; } @keyframes line1 { 50%, 100% { left: 100%; } } .btn > span:nth-child(2) { width: 2px; height: 100%; background: -webkit-linear-gradient(top, transparent, #03e9f4); right: 0%; top: -100%; animation: line2 1s 0.25s infinite; } @keyframes line2 { 50%, 100% { top: 100%; } } .btn > span:nth-child(3) { width: 100%; height: 2px; background: -webkit-linear-gradient(right, transparent, #03e9f4); right: -100%; bottom: 0; animation: line3 1s 0.5s infinite; } @keyframes line3 { 50%, 100% { right: 100%; } } .btn > span:nth-child(4) { width: 2px; height: 100%; background: -webkit-linear-gradient(bottom, transparent, #03e9f4); left: 0%; bottom: -100%; animation: line4 1s 0.75s infinite; } @keyframes line4 { 50%, 100% { bottom: 100%; } }
-
奖品结果背景特效CSS样式(main.css)
.main { width: 8px; height: 8px; position: absolute; left: 50%; top: 61%; transform: translate(-50%, -50%); perspective: 800px; display: none; } .main i { width: 8px; height: 8px; border-radius: 50%; background: rgba(255, 255, 255, 0.5); box-shadow: 0 0 10px 0 white; position: absolute; animation: run 3s ease-in-out infinite; } .main i:nth-child(1) { transform: rotate(0deg) translateX(80px); } .main i:nth-child(2) { transform: rotate(12deg) translateX(80px); } .main i:nth-child(3) { transform: rotate(24deg) translateX(80px); } @keyframes run { 0% { opacity: 0; } 10% { opacity: 1; } 100% { opacity: 1; transform: translate3d(0, 0, 560px); } }
然后就可以看到,我们的静态页面已经写好了,接下来可以开始写JS逻辑代码了
-
首页的JS代码(index.js)
$(function () { // 全局计时器 var drawTimer = null; // 全局步数 var drawStep = -1; // 全局缓动计时 var easeTime = 2; // 全局随机奖品(最后停止的位置) var stopPosition = 1; // 点击按钮的次数 var clickNum = 0; // 当我点击按钮时,开始抽奖 $(".button").click(function () { // 点击抽奖之后,禁用抽奖功能 $(".coverLottery").css("display", "block"); // console.log(clickNum); // 声明一个 1~8 的数组,写重复的数来提高此数的概率值 var arr = [1, 2, 3, 4, 5, 6, 7, 8]; // 0 ~ arr.length-1 的随机数 var randomNum = Math.floor(Math.random() * arr.length); stopPosition = arr[randomNum]; // console.log(stopPosition); // easeTime * 100 毫秒后开始抽奖 drawTimer = setTimeout(drawRun, easeTime * 100); // 抽三次奖,停止抽奖 if (clickNum >= 3) { // 清除计时器,停止抽奖 clearTimeout(drawTimer); // 显示那个中奖页面 $(".lotteryResult").css("display", "block"); // 让奖品不透明 $(".lotteryResult").children().css("opacity", "1"); // 显示背景特效 $(".main").css("display", "block"); } clickNum++; }); // 抽奖游戏滚动函数 function drawRun() { // 抽奖游戏滚动 if (drawStep >= 40 + stopPosition) { // prize为奖品序号(序号从0开始) var prize = drawStep % 8; $(".jpg_" + prize).css("opacity", "1"); // 重置drawStep,为下一次的抽奖做准备 // 让下一次抽奖的位置从上一次抽奖结束的位置开始走 drawStep = stopPosition; // 以防万一,写上 easeTime = 2 easeTime = 2; // 提示消息 $(".prize_" + prize).each(function (index, value) { var prizeName = value.getAttribute("name"); msg(prizeName); }); var img = $(".prize_" + prize) .children() .clone(); $(".lotteryResult").append(img); // 清除计时器 clearTimeout(drawTimer); // 返回结果 $(".button").trigger("click"); return; } if (drawStep >= 36 + stopPosition) { // 最后四个的时候开始缓动 easeTime += 1; } else { if (easeTime <= 2) { easeTime = 2; } easeTime--; } drawStep++; // 使每个方格在变成不透明之后再变成半透明 $(".jpg_").each(function (index, value) { // console.log(value); $(this).css("opacity", "0.5"); }); // 每次抽奖时的不透明效果 $(".jpg_" + (drawStep % 8)).css("opacity", "1"); // 延迟递归 // 每 easeTime * 70 的时间走一格 drawTimer = setTimeout(drawRun, easeTime * 70); } // 消息提示函数 function msg(prizeName) { var say = "恭喜您抽中了奖品" + prizeName; var $pElement = $(`<p>${say}<p>`); $(".msgbox").append($pElement); } });
-
登录页的JS代码(login.js)
-
首先需要写一个json文件来存储用户信息
{ "username": "zhangsan", "phone": "17692414892", "password": "123456", "msg": "登录成功" }
$("#login").bind("submit", function (event) { // 阻止默认行为 event.preventDefault(); // 表单序列化 - 根据表单默认同步提交获取数据的方式 var data = $("#login").serialize(); console.log(data); // 通过异步交互提交表单 // 因为我在 axios_default.js 中写了baseURL,所以可以直接写/data/login.json axios.get("/data/login.json").then(function (response) { console.log(response); // 获取到json文件中的phone和password的值 var phoneJson = response.phone; var passwordJson = response.password; // console.log(phoneJson, passwordJson); // 获取到用户输入的phone和password的值 var phoneInput = $("#phone").val(); var passwordInput = $("#password").val(); // console.log(phoneInput, passwordInput); // 如果用户输入的 phone和password都一样,那么跳转到抽奖页面(页面不刷新) if (phoneInput === phoneJson && passwordInput === passwordJson) { // 登录页面隐藏 $(".loginBox").css("display", "none"); // 抽奖页面显示 $("table").css("display", "block"); } else { // 提示错误信息,可以改成页面 alert("手机号或密码错误"); } }); });
-
-
中奖结果背景特效JS代码
var main = document.getElementsByClassName("main")[0]; for (var x = 0; x < 60; x++) { var i = document.createElement("i"); main.appendChild(i); } var is = document.getElementsByTagName("i"); for (var i = 0; i < is.length; i++) { is[i].style.transform = `rotate(${i * 12}deg) translateX(80px)`; is[i].style.animationDelay = `${i * 0.05}s`; }
-
-
二、写后台代码
-
首先先创建一个服务器
- 安装第三方库
npm i axios
npm i express
npm i ejs
router.js - 把所有的路由都放在这个文件里面了
// 引入express第三方模块,用来写服务器 var express = require("express"); // router路由对象 var router = express.Router(); // 写路由 router.get("/", function (req, res) { res.render("index"); }); router.get("/login", function (req, res) { res.send("666"); }); module.exports = router;
app.js代码
// 引入express第三方模块,用来写服务器 var express = require("express"); // 引入path内置模块,用来获取文件路径 var path = require("path"); // 引入router.js文件(自己写的路由文件) var router = require("./router"); // 创建一个服务器 var app = express(); // 模板引擎设置(ejs模板引擎) app.set("view engine", "html"); app.set("views", `${__dirname}/views`); app.engine("html", require("ejs").renderFile); // 用ejs模板渲染html // console.log("__dirname: ", __dirname); // 把文件暴露出去,使每个文件都能访问到暴露出去的文件 // 使用"/public"作为前缀来加载public文件夹下的文件 app.use("/public", express.static(path.join(__dirname, "./public/"))); // 使用"/node_modules"作为前缀来加载node_modules文件夹下的文件 app.use( "/node_modules", express.static(path.join(__dirname, "./node_modules/")) ); // 使用"/data"作为前缀来加载data文件夹下的文件 app.use("/data", express.static(path.join(__dirname, "./data/"))); // 把路由挂载到 app 中 //router路由对象中的路由都会匹配到 app.use(router); // 监听3000端口 app.listen(3000, function () { console.log("服务器开启成功了 -- 3000"); });
-
我写了一个配置默认的设置的文件,可以在
axios.defaults.xxx
中进行配置
axios_default.js文件
// 默认配置 if (typeof axios !== "undefined") { axios.defaults.baseURL = "http://127.0.0.1:3000"; axios.defaults.withCredentials = true; //=> 允许跨域请求 axios.defaults.transformRequest = (data) => { let str = ``; if (data && typeof data === "object") { for (let attr in data) { if (data.hasOwnProperty(attr)) { str += `${attr}=${data[attr]}&`; } } } return str.substring(0, str.length - 1); }; axios.defaults.headers["Contet-Type"] = "x-www-form-urlencoded"; axios.interceptors.response.use((result) => result.data); }
在浏览器输入
127.0.0.1:3000
,可以发现,我们已经可以成功启动服务了,并且能够访问到我写的网页了 -