国军放假自动汇整回报网页

前言

待过军事训练役的人肯定有着假日还要忙着回报休假状况,而回报由於还是在line里面,要马就是要麻烦班头整理,要马就是在那边卡来卡去,实在是麻烦至极,於是我趁中间的假日开始思考什麽样的流程可以优化这个操作呢,最直观想到的当然是linebot,让每个人在回覆时下达指令,後端帮忙整理,最後再用特定指令叫出来,但实际写出来给邻兵试用後,发现全文字介面以及死板的指令操作造成相当大的负面回应,於是开始往具有图形介面的googleSheet编辑,再利用lineBot调用googleSheetAPI的方案走。结果遇到由於共编手机板需要下载,以及看起来弱弱的、......等原因导致推广失败。最後决定使用网页做为载体,提供单纯的入口以及图形化与特化的操作,终於成功推广,以下进行技术说明

环境架构与技术

後端环境 前端环境 後端框架 前端框架 资料库 资料库介面
node line浏览器 express boostrap mongodb mongoClient(mongoDB原生)

使用者操作流程

https://ithelp.ithome.com.tw/upload/images/20201219/20131164es4onCHNhx.png

实际画面

设定页

班级专属页

设定班级成员范例图

班级代号规则如文字说明,仅连支援中文,支持三个回报时间,进入班级页时会自动挑选下一个时间,下方按照希望呈现的顺序输入成员号码及叙述文字,最後案创建即可,不支援修改,所以创建者创建需一步到位,不然得由管理者於资料库中进行手动删除。

创建成功後取得专属网址

输入代号按进入,确定呈现内容无误即可复制该网页网址到line定为公告。

复制网址

技术说明

引用express框架


利用Visual Studio 2019 community 引用Express4框架

加载额外套件以及架构预设设定变更

加载额外套件

  1. 右键
  2. 安装mongodb(mongoClient)

架构预设设定变更

  1. 画面渲染引擎为ejs,页面副档名设为html,预设为别的,在这边进行修改
// view engine setup
app.engine('.html', require('ejs').__express)
app.set('views', path.join(__dirname, 'views')); //注意path要require一下
app.set('view engine', 'html')
  1. 最後的app.js(要改的只有上面一步和删除用不到的路由,其他都是visual studio自己生成的)
'use strict';
var debug = require('debug');
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');

var app = express();

// view engine setup
app.engine('.html', require('ejs').__express)
app.set('views', path.join(__dirname, 'views')); //注意path要require一下
app.set('view engine', 'html')

// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
//// catch 404 and forward to error handler
//app.use(function (req, res, next) {
//    var err = new Error('Not Found');
//    err.status = 404;
//    next(err);
//});

//// error handlers

//// development error handler
//// will print stacktrace
//if (app.get('env') === 'development') {
//    app.use(function (err, req, res, next) {
//        res.status(err.status || 500);
//        res.render('error', {
//            message: err.message,
//            error: err
//        });
//    });
//}

//// production error handler
//// no stacktraces leaked to user
//app.use(function (err, req, res, next) {
//    res.status(err.status || 500);
//    res.render('error', {
//        message: err.message,
//        error: {}
//    });
//});

app.set('port', process.env.PORT || 1451);

var server = app.listen(app.get('port'), function () {
    debug('Express server listening on port ' + server.address().port);
    console.log(process.env.PORT || 1451);
});

档案架构



  • routes
    • index.js
  • views
    • error.html
    • index.html
    • set.html
  • app.js

後端路由(index.js)

资料库

建立一个库来储存所有班级资料

  • db: armyUsers
    • collections: 班级代号(按照上面提过的命名规则)
      • element:成员座号=>ex:30(在此设定前方的0都要省略,所以不会有030)
      • element:成员叙述=>ex:31030 王大明(学号+空格+姓名)

每个班级有一个库可以储存每次回报的内容

  • dbs:班级代号(按照上面提过的命名规则)
    • collections: 年\月\日 时(ex:109/10/10 11时回报)
      • elements:回报内容=>30:1000 在家睡觉(座号:在做的事)

API

  1. 网站进入点
    共有2句API提供给使用者进入设定页或班级专属页
//the URL that we can connect to this Web(door of this Web)
//进入班级专属页
router.get("/index/:token", function (req, res) {
    res.render("index", { token: req.params.token });
});
//进入设定页
router.get("/", function (req, res) {
    res.render("set", {});
});

班级专属页为了使一句API提供给多个班级,使用了动态路由的技巧
2. 创建新班级

router.post("/buildClass", function (req, res) {
    //name:collection name, data: the elements in collections, it is a string can be split by <-> and <_>
    const name = req.body.name;
    const data = req.body.data;//num_include-num_include-..........-num_include

    MongoClient.connect(url, function (err, client) {
        if (err) throw err;
        IsExistCollection(name, client)
            .then(bool => insertClassData(name, data, client))
            .then(bool => res.end("success"))
            .catch(bool => res.end("error"))
            .finally(bool => client.close())
    });
});
//check if this class have exist
function IsExistCollection(name, client) {
    return new Promise((resolve, reject) => {
        var db = client.db(dbUsers);
        db.listCollections({ name: name })
            .next(function (err, collinfo) {
                err ? reject(false) : (collinfo ? reject(true) : resolve(false));
            });
    });
}
//record all users in the class
function insertClassData(name, data, client) {
    return new Promise((resolve, reject) => {
        var table = client.db(dbUsers).collection(name);
        //split "data"
        var numList = [];
        var includeList = [];
        data.split("-").forEach(element => {
            numList.push(element.split("_")[0]);
            includeList.push(element.split("_")[1]);
        });
        var jsonList = [];
        for (var i in numList) {
            var json = {};
            json["num"] = numList[i];
            json["include"] = includeList[i];
            jsonList.push(json);
        }
        table.insertMany(jsonList, function (err, result) {
            err ? reject(false) : resolve(true);
        })
    });
}

该API接受两个参数,前者为获取该创建班级的代号,後者为获取班级所有使用者的号码以及叙述(格式於上方注解中有说明)
做法是先检查有没有已创建该班级,可以藉由检查armyUsers内有没有collection名子为该班及代号来完成。
下一步是把第二个参数拆成jsonList插入资料库,collection名为班级代号。
3. 个人进行回报

router.post("/send", function (req, res) {
    const token = req.body.token;//each class have it's own db to save data,db's name is it's token
    const when = req.body.when;
    const who = req.body.who;
    const what = req.body.what;

    MongoClient.connect(url, function (err, client) {
        if (err) throw err;
        //console.log("Connected successfully to server");
        const db = client.db(token);
        const collection = db.collection(when);
        // Insert some documents
        collection.updateOne({ num: who }, { $set: { num: who, include: what } }, { upsert: true }, function (err, result) {
            if (err) res.send("error");
            else {
                client.close();
                res.send("success");
            }
        });
    });
});

该API接受以下4个参数

  • 班级代号
  • 回报时间节点
  • 回报者座号
  • 回报内容

做法是对名称为'班级代号'的DB,名称为'回报时间节点'的collection,更新一个document,内容含'回报者座号'及'回报内容'
而且要设为更新,让使用者可以进行修改,upsert要设为true,这样没有得更新时才能改为插入。
4. 刷新班级看板

router.post("/refresh", function (req, res) {
    const token = req.body.token;//each class have it's own db to save data,db's name is it's token
    const when = req.body.when;
    //console.log(req.body);
    MongoClient.connect(url, function (err, client) {
        if (err) throw "error";
        getUsers(token, client)
            .then(pkg => getResponse(pkg, token, when, client))
            .then(re => res.send(re))
            .catch(error => res.send(error))
            .finally(re => client.close())
    });
});

function getUsers(token, client) {
    return new Promise((resolve, reject) => {
        var table = client.db(dbUsers).collection(token);
        table.find({}).sort({ _id : 1 }).toArray(function (err, result) {
            err ? reject({ result: "connect error" }) : resolve(result);
        })
    });
}

function getResponse(pkg,token,when,client) {
    return new Promise((resolve, reject) => {
        var table = client.db(token).collection(when);
        table.find({}).toArray(function (err, result) {
            if (err)
                reject("connect error");
            else {
                var json = {};
                for (var i in result) json[result[i].num] = result[i].include;
                var str = "";
                for (var i in pkg)
                    str += "\n" + pkg[i].include + " : " + (json[pkg[i].num] != null ? json[pkg[i].num] : '<strong style="background-color: gray;">尚未回覆</strong>');
                //console.log(reply(token, when, result.length, str));
                //console.log("reply");
                resolve(reply(token.split('~'), when, result.length, str));
            }
        })
    });
}

function reply(token,when, length, str) {
    return (
        when +
        "\n" + decodeURI(token[1]).toString() + "连训员 第" + token[2] + "班\n今日看诊人员:共0员\n发烧人员:共0员\n应到:" + token[3] + "员 \n实到:" +
        length +
        "员" +
        str
    );
}

这句API接受班级代号与时间节点两个参数,
作法是先到armyUsers找到名称为班级代号的collection取得班级所有设定资料
,再到名称为'班级代号'的DB,名称为'回报时间节点'的collection取得该班该时间节点的回报讯息
组合这两个资讯进行排序再回传结果字串。

完整後端(源码)

要自己加入资料库连结
'use strict';
var express = require('express');
var router = express.Router();
const MongoClient = require("mongodb").MongoClient;
// Connection URL
//local mongoDB URL
//const url = "mongodb://localhost:27017";
//cloud mongoDB URL
const url = "这里要放云端mongoDB的连结URL"
// Database Name
const dbUsers = "armyUsers";

//the URL that we can connect to this Web(door of this Web)
router.get("/index/:token", function (req, res) {
    res.render("index", { token: req.params.token });
});
router.get("/", function (req, res) {
    res.render("set", {});
});
/******************************************************
post: buildClass ,use it to build a collection which can let Web know who are in the class
db:armyUsers collection: (营)~(连)~(班)~(人数)~(第一时间)~(第二时间)~(第三时间)=>班级编号 element: num=>30, include=>31030 林小明
token: ex:3~步一~10~17~11~14~19 => (营)~(连)~(班)~(人数)~(第一时间)~(第二时间)~(第三时间)
*******************************************************/
router.post("/buildClass", function (req, res) {
    //name:collection name, data: the elements in collections, it is a string can be split by <-> and <_>
    const name = req.body.name;
    const data = req.body.data;//num_include-num_include-..........-num_include

    MongoClient.connect(url, function (err, client) {
        if (err) throw err;
        IsExistCollection(name, client)
            .then(bool => insertClassData(name, data, client))
            .then(bool => res.end("success"))
            .catch(bool => res.end("error"))
    });
});
//check if this class have exist
function IsExistCollection(name, client) {
    return new Promise((resolve, reject) => {
        var db = client.db(dbUsers);
        db.listCollections({ name: name })
            .next(function (err, collinfo) {
                err ? reject(false) : (collinfo ? reject(true) : resolve(false));
            });
    });
}
//record all users in the class
function insertClassData(name, data, client) {
    return new Promise((resolve, reject) => {
        var table = client.db(dbUsers).collection(name);
        //split "data"
        var numList = [];
        var includeList = [];
        data.split("-").forEach(element => {
            numList.push(element.split("_")[0]);
            includeList.push(element.split("_")[1]);
        });
        var jsonList = [];
        for (var i in numList) {
            var json = {};
            json["num"] = numList[i];
            json["include"] = includeList[i];
            jsonList.push(json);
        }
        table.insertMany(jsonList, function (err, result) {
            err ? reject(false) : resolve(true);
        })
    });
}
/*****************************************
 post:send Record => 'when'? 'who' do "what"
 db:army collection:109/XX/XX XX点回报 element: num=>30,include=>1000在家睡觉
  *****************************************/

router.post("/send", function (req, res) {
    const token = req.body.token;//each class have it's own db to save data,db's name is it's token
    const when = req.body.when;
    const who = req.body.who;
    const what = req.body.what;

    MongoClient.connect(url, function (err, client) {
        if (err) throw err;
        //console.log("Connected successfully to server");
        const db = client.db(token);
        const collection = db.collection(when);
        // Insert some documents
        collection.updateOne({ num: who }, { $set: { num: who, include: what } }, { upsert: true }, function (err, result) {
            if (err) res.send("error");
            else {
                client.close();
                res.send("success");
            }
        });
    });
});
/*****************************************
 post:refresh => use token to find db, and use when to get goal, finally,return it
  *****************************************/
//date + "\n一连训员 第2班\n今日看诊人员:共0员\n发烧人员:共0员\n应到:16员 \n实到:" + result.length + "员" + str
router.post("/refresh", function (req, res) {
    const token = req.body.token;//each class have it's own db to save data,db's name is it's token
    const when = req.body.when;
    //console.log(req.body);
    MongoClient.connect(url, function (err, client) {
        if (err) throw "error";
        getUsers(token, client)
            .then(pkg => getResponse(pkg, token, when, client))
            .then(re => res.send(re))
            .catch(error => res.send(error));
    });
});

function getUsers(token, client) {
    return new Promise((resolve, reject) => {
        var table = client.db(dbUsers).collection(token);
        table.find({}).toArray(function (err, result) {
            err ? reject({ result: "connect error" }) : resolve(result);
        })
    });
}

function getResponse(pkg,token,when,client) {
    return new Promise((resolve, reject) => {
        var table = client.db(token).collection(when);
        table.find({}).toArray(function (err, result) {
            if (err)
                reject("connect error");
            else {
                var json = {};
                for (var i in result) json[result[i].num] = result[i].include;
                var str = "";
                for (var i in pkg)
                    str += "\n" + pkg[i].include + " : " + (json[pkg[i].num] != null ? json[pkg[i].num] : '<strong style="background-color: gray;">尚未回覆</strong>');
                //console.log(reply(token, when, result.length, str));
                //console.log("reply");
                resolve(reply(token.split('~'), when, result.length, str).replace());
            }
        })
    });
}

function reply(token,when, length, str) {
    return (
        when +
        "\n" + decodeURI(token[1]).toString() + "连训员 第" + token[2] + "班\n今日看诊人员:共0员\n发烧人员:共0员\n应到:" + token[3] + "员 \n实到:" +
        length +
        "员" +
        str
    );
}

module.exports = router;

前端

因为较为简单不进行一个一个的说明仅对重点做叙述

设定页(set.html)

禁止网页放大

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

encodeURI,decodeURI

$.post("/buildClass", { name: encodeURI($("#newClassToken").val()), data: getData() }, function (result) {
由於'连'要支援中文,但之後藉由动态路由会出现再网址,要避免错误,於是利用encodeURI来进行转换,於後端(index.js)最下面的reply函数中有decodeURI把其在输出时转换回中文。

输入server网域

在最下方的函数,进行页面跳转,要加入服务器的网域名

源码
<!DOCTYPE html>
<html>

<head>
    <title>放假回报</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</head>

<body>
    <header>
        <div name="Title" class="jumbotron  mb-0 ">
            <div class="text-center  align-self-center">
                <h1>放假回报</h1>
            </div>
        </div>
    </header>
    <div class="container" style="font-family:Microsoft JhengHei;font-size:100%">
        <div id="input" style="text-align:center">
            <label>请输入你的班级代号</label>
            <input id="classToken" type="text" />
            <input id="jumpPage" type="button" value="进入班级回报版" />
            <br>
            <hr />
            <label>新增班级回报版</label>
            <br>
            <p>班级代号规则 -> ex:3~步一~10~17~11~14~19 => (营)~(连)~(班)~(人数)~(第一时间)~(第二时间)~(第三时间)。</p>
            <input type="text" id="newClassToken" placeholder="请输入班级代号"><br>
            <label>请按照顺序输入班级内所有成员的座号(左)及资讯(右)</label><br>
            <div style="display:none">01.<input id="n1" type="text" placeholder="ex:1"/><input type="text" id="s1" placeholder="ex:31001 王大明"><br></div>
            <div style="display:none">02.<input id="n2" type="text" placeholder="ex:2" /><input type="text" id="s2" placeholder="ex:31002 王大明"><br></div>
            <div style="display:none">03.<input id="n3" type="text" placeholder="ex:3" /><input type="text" id="s3" placeholder="ex:31003 王大明"><br></div>
            <div style="display:none">04.<input id="n4" type="text" placeholder="ex:4" /><input type="text" id="s4" placeholder="ex:31004 王大明"><br></div>
            <div style="display:none">05.<input id="n5" type="text" placeholder="ex:5" /><input type="text" id="s5" placeholder="ex:31005 王大明"><br></div>
            <div style="display:none">06.<input id="n6" type="text" placeholder="ex:6" /><input type="text" id="s6" placeholder="ex:31006 王大明"><br></div>
            <div style="display:none"> 07.<input id="n7" type="text" placeholder="ex:7" /><input type="text" id="s7" placeholder="ex:31007 王大明"><br></div>
            <div style="display:none">08.<input id="n8" type="text" placeholder="ex:8" /><input type="text" id="s8" placeholder="ex:31008 王大明"><br></div>
            <div style="display:none">09.<input id="n9" type="text" placeholder="ex:9" /><input type="text" id="s9" placeholder="ex:31009 王大明"><br></div>
            <div style="display:none"> 10.<input id="n10" type="text" placeholder="ex:10" /><input type="text" id="s10" placeholder="ex:31010 王大明"><br></div>
            <div style="display:none">11.<input id="n11" type="text" placeholder="ex:11" /><input type="text" id="s11" placeholder="ex:31011 王大明"><br></div>
            <div style="display:none">12.<input id="n12" type="text" placeholder="ex:12" /><input type="text" id="s12" placeholder="ex:31012 王大明"><br></div>
            <div style="display:none">13.<input id="n13" type="text" placeholder="ex:13" /><input type="text" id="s13" placeholder="ex:31013 王大明"><br></div>
            <div style="display:none">14.<input id="n14" type="text" placeholder="ex:14" /><input type="text" id="s14" placeholder="ex:31014 王大明"><br></div>
            <div style="display:none">15.<input id="n15" type="text" placeholder="ex:15" /><input type="text" id="s15" placeholder="ex:31015 王大明"><br></div>
            <div style="display:none">16.<input id="n16" type="text" placeholder="ex:16" /><input type="text" id="s16" placeholder="ex:31016 王大明"><br></div>
            <div style="display:none">17.<input id="n17" type="text" placeholder="ex:17" /><input type="text" id="s17" placeholder="ex:31017 王大明"><br></div>
            <div style="display:none">18.<input id="n18" type="text" placeholder="ex:18" /><input type="text" id="s18" placeholder="ex:31018 王大明"><br></div>
            <div style="display:none">19.<input id="n19" type="text" placeholder="ex:19" /><input type="text" id="s19" placeholder="ex:31019 王大明"><br></div>
            <div style="display:none">20.<input id="n20" type="text" placeholder="ex:20" /><input type="text" id="s20" placeholder="ex:31020 王大明"><br></div>
            <br>
            <input id="PushUser" type="button" value="增加成员" />
            <input id="PopUser" type="button" value="减少成员" />
            <br /><br />
            <button id="buildClass" class="btn btn-success">创建班级</button>
        </div>
    </div>
    <br>
    <br>
    <script>
        var count = 16;
        function getData() {
            var str = $("#n1").val() + "_" + $("#s1").val();
            for (var i = 2; i <= count; i++)
                str += "-" + $("#n" + i).val() + "_" + $("#s" + i).val();
            return str;
        }
        $(document).ready(function () {
            for (var i = count; i >= 1; i--)
                $("#s" + i).parent("div").show();
            $("#PushUser").click(function () {
                if (count < 20)
                    $("#s" + (++count)).parent("div").show();
                else
                    alert("20人为班级人数的极限");
            });
            $("#PopUser").click(function () {
                if (count > 2)
                    $("#s" + (count--)).parent("div").hide();
                else
                    alert("2人为班级人数的最小值");
            });
            $("#buildClass").click(function () {
                if ($("#newClassToken").val() != null) {
                    $.post("/buildClass", { name: encodeURI($("#newClassToken").val()), data: getData() }, function (result) {
                        alert(result);
                    })
                } else
                    alert("请填入课程代号");
            });
            $("#jumpPage").click(function () {
                location.href =  "这里输入服务器网域名" +"/index/" + $("#classToken").val();
                //location.href = "http://127.0.0.1:1337/index/" + encodeURI($("#classToken").val());
            });
        });
    </script>
</body>

</html>

班级页(index.html)

复制按钮
function Copy(str) {
                //创建一个textarea标签,由於该网页API操作仅可对此标签进行
                var clip_area = document.createElement('textarea');
                //把内容放入标签
                clip_area.textContent = str;
                //新增标签至实际网页
                document.body.appendChild(clip_area);
                //选取该标签
                clip_area.select();
                //执行复制指令
                document.execCommand('copy');
                //移除标签
                clip_area.remove();
                alert("已复制好,可黏贴");
            }
源码
<!DOCTYPE html>
<html>

<head>
    <title>放假回报</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</head>

<body>
    <header>
        <div name="Title" class="jumbotron  mb-0 ">
            <div class="text-center  align-self-center">
                <h1>放假回报</h1>
            </div>
        </div>
    </header>
    <div class="container" style="font-family:Microsoft JhengHei;font-size:100%">
        <div id="input" style="text-align:center">
            <label>请输入你的学号末两码</label>
            <input type="number" id="number">
            <br>
            <label>回报时间</label>
            <select id="select">
                <option id="option1" value=""></option>
                <option id="option2" value=""></option>
                <option id="option3" value=""></option>
            </select>
            <br>
            <input type="text" id="text" placeholder="请输入回报内容">
            <button id="send">传送</button>
            <pre id="include"></pre>
            <button id="duplicate">复制</button>
            <button id="refresh">刷新</button>
        </div>
    </div>
    <script>
        $(document).ready(function () {
            var token = encodeURI('<%=token%>');
            //console.log(token);
            var dt = new Date();
            var now = dt.getHours();
            var select = document.getElementById("select");
            const times = token.split('~');
            $("#option1").val(times[4] + "时回报");
            $("#option2").val(times[5] + "时回报");
            $("#option3").val(times[6] + "时回报");
            $("#option1").html(times[4] + "时回报");
            $("#option2").html(times[5] + "时回报");
            $("#option3").html(times[6] + "时回报");
            if (now <= parseInt(times[4]) + 1)
                select.options[0].selected = true;
            else if (now <= parseInt(times[5]) + 1)
                select.options[1].selected = true;
            else
                select.options[2].selected = true;
            if (localStorage.getItem("num"))
                $("#number").val(localStorage.getItem("num"));
            $.post("/refresh", { token: token, when: (parseInt(dt.getFullYear()) - 1911).toString() + "/" + (parseInt(dt.getMonth()) + 1).toString() + "/" + dt.getDate().toString() + " " + $("select").val() }, function (result) {
                $("pre").html(result);
            })
            $("#send").click(function () {
                if (parseInt($("#number").val()) >= 0) {
                    $.post("/send", { token: token, when: (parseInt(dt.getFullYear()) - 1911).toString() + "/" + (parseInt(dt.getMonth()) + 1).toString() + "/" + dt.getDate().toString() + " " + $("select").val(), who: parseInt($("#number").val()).toString(), what: $("#text").val() }, function (result) {
                        $.post("/refresh", { token: token, when: (parseInt(dt.getFullYear()) - 1911).toString() + "/" + (parseInt(dt.getMonth()) + 1).toString() + "/" + dt.getDate().toString() + " " + $("select").val() }, function (result) {
                            $("pre").html(result);
                            $("#text").val('');
                        })
                    })
                    localStorage.setItem("num", $("#number").val().toString());
                } else {
                    alert('请检查你的学号是否输入正确');
                }
            });
            $("#refresh").click(function () {
                $.post("/refresh", { token: token, when: (parseInt(dt.getFullYear()) - 1911).toString() + "/" + (parseInt(dt.getMonth()) + 1).toString() + "/" + dt.getDate().toString() + " " + $("select").val() }, function (result) {
                    $("pre").html(result);
                })
            });
            function Copy(str) {
                var clip_area = document.createElement('textarea');
                clip_area.textContent = str;
                document.body.appendChild(clip_area);
                clip_area.select();
                document.execCommand('copy');
                clip_area.remove();
                alert("已复制好,可黏贴");
            }
            $("#duplicate").click(function () {
                Copy($("pre").html().replace(/<[^>]+>/g, ""));
            })
            $("select").change(function () {
                $.post("/refresh", { token: token, when: (parseInt(dt.getFullYear()) - 1911).toString() + "/" + (parseInt(dt.getMonth()) + 1).toString() + "/" + dt.getDate().toString() + " " + $("select").val() }, function (result) {
                    $("pre").html(result);
                })
            });
        })
    </script>
</body>

</html>

建构

若要实际发布此网站,在引用架构,编写所有档案後还不够还有资料库与服务器的问题,不过由於不是本篇重点,所以我将简单带过。

资料库

资料库我是使用MongoDB Altis,帐号申办简单,以本专案来说也有相当够用的免费存储空间。

服务器

我个人用过GCP(google clooud platform)在刚使用有一定额度的免费,但个人经验一下就用完了,而且使用复杂度相对较高。
其他就是各种云服务器。
不过以我个人而言最喜欢使用的方案是安装在个人可连接外网的机器里,用pm2发布。

後话

git连结:
https://github.com/leon123858/soldiers_response_system/tree/main/Web/Web
若想直接使用,记得在index.js加入资料库连结网址,在set.html加入服务器网域才可以顺利运作,在上方内容源码区都有用中文补在该插入的地方。
此外
若讲述不好欢迎建议或补充,
若讲述有误欢迎指正。


<<:  【元件如何正确使用 ?】元件耦合性三大原则 : ADP、SDP、SAP

>>:  Gulp 合并来自 npm 的 Javascript的资源 DAY96

DAY 30『 从相簿选取照片( 有裁剪照片功能 ) 』ImagePicker - Part2

在 @IBAction 里 令 vc 为 UIImagePickerController let v...

Web应用测试工具-Skipfish

Skipfish 是一个主动的Web应用程序安全测试工具 透过执行递归爬网和基於字典的探测 易於使用...

每日挑战,从Javascript面试题目了解一些你可能忽略的概念 - Day24

tags: ItIron2021 Javascript 前言 对点进来的你说一下,恭喜你撑到今天! ...

Day 16 分享一下研究 Compose UI 到目前的心得

今年的疫情蛮严重的,希望大家都过得安好,希望疫情快点过去, 能回到一些线下技术聚会的时光~ 祝福大家...

Day 22- Google Apps Script 线上文件更新

咦?To-Do-List 怎麽突然结束了!? 恩…主要是最近接到了不少新的任务,而我想,To Do ...