虽然每个学校情况不同,但如果许多学生有需求,开展晚自习的可能性就很高。
因此,我们学校仍然有晚自习。
晚自习的名字是独书三道。
到目前为止,晚自习的考勤检查一直是手工完成的。

学生进入自习室前,需要在画有座位图的考勤表上填写自己的学号和姓名,然后负责自习的老师携带考勤表在自习室内巡查,确认学生名单。
于是,晚自习前,学生们常常在考勤表前排队写名字。
学期末,还需要拥有参加晚自习的学生的统计数据。
这一切都是手工完成的。
所以,我决定进行电子化,并在一个学期内成功地运行了这个系统。
1. 想法

我提取出了晚自习考勤的重要关键点。
- 必须能以简单便捷的方式进行考勤。
- 必须允许提交后进行修改。
- 需要负责老师的确认。
我认为,QR码的谷歌问卷链接是满足上述三项的方案。
在每个座位上制作包含查询字符串的调查问卷QR码,学生通过扫码提交问卷。
这样汇总的数据利用电子表格的Apps Script在特定时间按日期逐行添加。
这样,负责老师也可以在手机上查看和编辑考勤表。
此外,还可以汇总学期末学生的参与度。
现在让我们按照该过程进行设置。
2. 制作晚自习的问卷和电子表格
在谷歌问卷中,内容只包括座位号、学号和姓名。
输入的内容应尽可能简洁。

在右上角有一个获取预先填写链接的选项。
进入该链接。

由于我们学校的晚自习没有指定座位,所以学号和姓名会留空,仅预先填写座位号。
点击复制链接,可以得到如下地址。

这里viewform?后面的部分是查询。
查询字符串在问号后以key和value对的形式传递。
我创建了一个地址,其中座位号为23,学号为20366,姓名为“袋鼠”。
https://docs.google.com/forms/d/e/1FAIpQLScXgTn9UK5HETv3fCipGiRMl-et9IezB1OSZp2r9-Nj8b54Fg/viewform?usp=pp_url
&entry.1738725719=23 // 座位号
&entry.7330080=20366 // 学号
&entry.1354978010=%EC%BA%A5%EA%B1%B0%EB%A3%A8 // 姓名
因此,我们需要生成的地址如下。
访问该地址时

现在创建该问卷的电子表格并填写表格。

3. 设置电子表格

首先进入表格,会有一个问卷响应表。
这里将按时间顺序累积问卷响应。

中间考勤表的作用是整理问卷响应。
这里是最重要的地方。
首先使用query函数获取时间戳为当天的数据。
=QUERY('问卷响应表1'!$A:$D,"SELECT B,C,D WHERE A<DATE'"&YEAR('自主学习室'!C1)&"-"&MONTH('自主学习室'!C1)&"-"&DAY('自主学习室'!C1)+1&"' AND A>=DATE'"&YEAR('自主学习室'!C1)&"-"&MONTH('自主学习室'!C1)&"-"&DAY('自主学习室'!C1)&"' ORDER BY A",1)
这样获取后可以想到几个问题。
- 当学生更换座位时,不同座位的名字重复
- 同一座位上有两个学生重名
因此使用unique函数和xlookup函数解决此问题。

首先在“学号”和“姓名”列中使用=UNIQUE(F:G)获取唯一值。
在“座位号”标签中使用=XLOOKUP(J2,F:F,E:E,,,-1)获取每个学号最后一次提交的值。

最后,确定座位号上坐的学生。
=XLOOKUP($A2,$I:$I,J:J,"",,-1)从提交的座位中获取最后的值。
这样数据整理就完成了。
现在可以根据座位号创建多个表格。

自主学习室表格是为教师显示的表格。
使用电子表格函数today()显示今天的日期。
创建一个与座位对应的座位表,并从中间考勤表中调用符合条件的学生学号和姓名。
利用条件样式将坐过的座位更改为绿色。

最后是出勤统计和检查的表格。
用于参与晚自习学生的统计。
如果晚自习参与超过2/3,可以记录在学生活动记录中。
这里需要设置Apps Script。
4. 设置Apps Script

在顶部菜单的扩展程序中可以找到一个Apps Script标签。
进入此处可以用javascript形式给表格配置命令。

在Excel中需要学习Visual Basic来制作程序,而在电子表格中仅用javascript就可以实现所需功能,真是方便。
在代码编辑区输入以下代码。
函数名称为addRowDaily,当然其它名称也可以。
详细说明在注释中。
function addRowDaily() {
// 创建一个调用表格的对象
let checkSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("出勤统计");
let data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("中间考勤表");
// 用作列索引的日期信息
let date = new Date()
let month = date.getMonth() + 1;
let day = date.getDate();
let time = date.getHours();
// 获取需要插入的行和最后的行。
let lastColumn = 3
let rowRange = checkSheet.getLastRow()
// 在中间插入列,列名是月,日,时间,都是复选框形式
checkSheet.insertColumnAfter(lastColumn-1);
checkSheet.getRange(1, lastColumn).setValue(`${month}月 ${day}日 ${time}时`);
checkSheet.getRange(2, lastColumn, rowRange-1, 1).insertCheckboxes();
// 以map形式存储名字所在的行信息。学号为key,行号为value。
let studentMap = new Map();
checkSheet.getRange("A1:B6").getValues().forEach((row, idx) => {
studentMap.set(row[0], idx+1)
});
// 遍历有出勤的行,获取有值的行的学号。在map中以学号为key进行查找,将其所在行设置为[true]。
let checkedStudents = data.getRange("A2:C64").getValues();
checkedStudents.forEach((rows) => {
let seatNumbers = rows[0];
let studentIdNumber = rows[1];
let studentName = rows[2];
if (studentIdNumber ===""){
return
};
checkSheet.getRange(studentMap.get(studentIdNumber), lastColumn).setValue([true]);
})
}
// 监控单元格更改。当出勤统计表格的A11单元格更改时,执行函数
function onEdit(e){
let rg = e.range;
if (rg.getA1Notation() === "A11" && rg.isChecked() && rg.getSheet().getName() === "出勤统计"){
addRowDaily();
rg.uncheck();
};
}

最后为函数执行指定触发器。
如果想知道为何要用复选框作为按钮,请参考stackoverflow。
现在点击测试会弹出一个奇怪的窗口。
点击审查权限。

出现“未经验证的应用程序”警告,但这毕竟是我自己编写的代码,所以不需要特别检查。
进入高级设置,点击最下面的链接。

然后返回表格,可以看到已经添加了一行。

在A11单元格中指定触发器,因此在此单元格中创建一个用于执行函数的复选框。
然后试着点击复选框。

点击复选框时,可以看到函数执行后复选框被取消选中。
大致的框架已经建立,现在开始制作QR码吧。
5. 制作QR码
QR码是用python的qrcode制作的。
如果没有库,则使用pip安装qrcode。
pip install qrcode
qrcode的使用方法非常简单直观。
一行make和save就能生成QR码。
import qrcode
checkqr = qrcode.make('https://naver.com')
checkqr.save('./qr.png')
自从知道了这个库,我就只用python来生成QR码。
无论如何,由于我们要为座位号制作QR码,所以在for循环中生成QR码。
用之前输入的地址。
import qrcode
for i in range(1,44):
url = f"https://docs.google.com/forms/d/e/1FAIpQLScXgTn9UK5HETv3fCipGiRMl-et9IezB1OSZp2r9-Nj8b54Fg/viewform?usp=pp_url&entry.1738725719={i}"
qr = qrcode.make(url)
qr.save(f"./qr/{i}号.png")
这样制作出所需座位号范围的QR码。
但若要使用,还需要具备一定的格式。
需要有关于如何使用QR码、如何考勤等的说明。
这可以借助pywin32解决。

如前所述,在座位号和图片插入位置预先创建占位符。
然后执行以下代码。
import win32com.client as win32
import os
import time
hwpdir = os.getcwd()
hwpfiles = hwpdir + "/2.hwpx"
maxnum = 43 # 所需座位号
hwp = win32.gencache.EnsureDispatch('HWPFrame.HwpObject')
hwp.RegisterModule("FilePathCheckDLL", "FilePathCheckerModule")
hwp.Open(hwpfiles)
field_list = [i for i in hwp.GetFieldList().split('\x02')]
time.sleep(0.5)
hwp.MovePos(3)
hwp.Run('SelectAll')
hwp.Run('Copy')
hwp.MovePos(3)
for i in range(0,maxnum):
hwp.MovePos(3)
hwp.Run('Paste')
hwp.MovePos(3)
for page in range(0,maxnum):
hwp.PutFieldText(f'座位号{{{{{page}}}}}', page+1)
hwp.MoveToField(f'QR码{{{{{page}}}}}')
hwp.InsertPicture(f'{hwpdir}/2qr/{page+1}号.png', True, 1, False, False, 0, 60, 60)
al
hwp.SaveAs(hwpfiles+".hwpx")
hwp.Quit()
这样就可以一次性完成附于所需编号上的韩文文档。
直接用A4纸打印的话会太大,建议使用页面分割打印。

6. 使用体验

我在4月提出了这种方法,并一直使用到7月。
因为有739人的问卷结果,所以与社团学生们一起分析了参与晚自习学生的趋势和座位偏好统计。
第二学期未使用的原因是1至3年级晚自习合并后名单变更。
此外,由于我不是负责人员,不容易干涉其他年级的运营工作。

明年计划将格式优化后推荐给负责人。
今年使用效果不错,明年继续使用的可能性也很大。
希望学校的许多方面都能实现自动化。
댓글을 불러오는 중...