1.项目所用技术栈
2.效果图
3.源码
Index.ets(登录页)
登陆时让前端访问数据库中已经存好的账号密码,如果可以查询到数据库中的数据,则账号密码正确,登录成功,否则登录失败。
import axios from '@ohos/axios'
import router from '@ohos.router'
@Entry
@Component
struct Index {
// 上传数据
@State zhanghao: string = ''
@State mima: string = ''
@State zhanghao_find:string =''
@State mima_find:string =''
build() {
Column() {
Text('淼学签到系统')
.margin({top:70})
.fontWeight(FontWeight.Bold)
.fontSize(30)
Image('./components/img/appimg.jpg')
.width(200)
.height(200)
.borderRadius(20)
.margin({top:50,bottom:20})
// 账号登录
Row(){
Image('./components/img/zhanghao.png')
.width(20)
.height(20)
TextInput({placeholder:'账号(学号)'})
.backgroundColor('#00FFFFFF')
.width('75%')
.onChange(value =>{
console.log(value)
this.zhanghao_find = value
})
}
.borderRadius(12)
.borderWidth(1)
.borderColor('#E4DFDF')
.padding(5)
.margin({top:10})
Row(){
Image('./components/img/mima.png')
.width(20)
.height(20)
TextInput({placeholder:'密码(学号后6位)'})
.backgroundColor('#00FFFFFF')
.width('75%')
.onChange(value =>{
console.log(value)
this.mima_find = value
})
}
.borderRadius(12)
.borderWidth(1)
.borderColor('#E4DFDF')
.padding(5)
.margin({top:10})
Button('登录',{type:ButtonType.Normal,stateEffect:true})
.borderRadius(10)
.margin({top:40})
.width(250)
.height(55)
.onClick(()=>{
axios({
method: "get",
url: 'http://localhost:3000/users/find1/'+this.zhanghao_find+ '/' + this.mima_find,
}).then(res => {
console.info('result:' + JSON.stringify(res.data));
// 获取data数组中的第一个元素
const firstData = res.data.data[0];
// 获取zhanghao字段的值
const juese = firstData.juese;
console.log('zhanghaoValue:', juese);
const zhanghao = firstData.zhanghao;
// 获取data数组中的第一个元素
// 获取zhanghao字段的值
router.pushUrl({
url: 'pages/App_one',
params: {
juese,
zhanghao
}
})
}).catch(error => {
console.error(error);
})
})
}
.width('100%')
.height('100%')
}
}
App_one.ets(具体功能页)
根据上一个登录页面的跳转,该页面拿到跳转传递过来的参数,根据与数据库中的“juese”字段相对比,如果角色为管理者,则功能页的内容切换为管理者的功能内容,如果角色为学生,则显示学生的页面内容。
import { Header } from '../components/Toubu'
import router from '@ohos.router'
import axios from '@ohos/axios'
@Entry
@Component
struct App_one {
@State zhanghao_find:string =''
@State Ondata: object = router.getParams()
// 统计总共的学生数
@State students:string = '0'
// 统计已签到的学生数
@State students_ok:string = '0'
// 查询已经签到的信息
@State items:Array<Object> = []
// 判断登录的角色
@State Switch:string = ''
controller: TextClockController = new TextClockController();
@State accumulateTime: number = 0
aboutToAppear(){
this.zhanghao_find = this.Ondata?.['zhanghao']
console.log('qweqweqwe',JSON.stringify(this.zhanghao_find))
if(this.Ondata?.['juese'] == 'Administrator'){
// 查询学生总数
axios({
method: "get",
url: 'http://localhost:3000/users/count/student',
}).then(res=>{
console.info('result:' + JSON.stringify(res.data.count));
this.students = JSON.stringify(res.data.count)
// 获取已签到的人数
axios({
method: "get",
url: 'http://localhost:3000/qiandaos/count/student_ok',
}).then(res=>{
console.info('result:' + JSON.stringify(res.data.count));
this.students_ok = JSON.stringify(res.data.count)
// 查询所有签到者的账号
axios({
method: "get",
url: 'http://localhost:3000/qiandaos/find_all',
}).then(res=>{
console.info('result:' + JSON.stringify(res.data.data));
this.items = res.data.data
})
})
})
}
}
build() {
Column() {
Header()
.margin(20)
if(this.Ondata?.['juese'] == 'Administrator'){
Text('管理员页面')
.fontSize(20)
Row(){
Text('今日已签到人数:')
Text(this.students_ok + '/' +this.students)
}
.borderRadius(5)
.borderWidth(1)
.borderColor('#E4DFDF')
.padding(10)
.margin({top:10})
Scroll(){
Column() {
ForEach(this.items, (item) => {
Row() {
Image($r('app.media.icon'))
.alt($r('app.media.icon')) // 使用alt,在网络图片加载成功前使用占位图
.width(30)
.height(30)
Text(item.zhanghao)
.fontWeight(800)
.margin({ left: 20 })
Text(item.date_times)
.fontWeight(800)
.margin({ left: 20 })
}
.margin({top:10})
})
}
}
.height(200)
.scrollable(ScrollDirection.Vertical) // 滚动方向纵向
.scrollBar(BarState.On) // 滚动条常驻显示
.scrollBarColor(Color.Gray) // 滚动条颜色
.scrollBarWidth(10) // 滚动条宽度
.edgeEffect(EdgeEffect.Spring) // 滚动到边沿后回弹
}else {
Text('学生页面')
TextClock({ timeZoneOffset: -8, controller: this.controller })
.backgroundColor('#00FFFFFF')
.borderRadius(12)
.borderWidth(1)
.borderColor('#E4DFDF')
.padding(10)
.margin({top:10})
.format('yyyy/M/d/h/m/s')
.onDateChange((value: number) => {
this.accumulateTime = value
})
.margin(20)
.fontSize(20)
Button("马上签到")
.borderRadius(10)
.margin({top:40})
.width(250)
.height(55)
.onClick(() => {
axios({
method: "get",
url: 'http://localhost:3000/qiandaos/find/time/'+this.zhanghao_find,
}).then(res=>{
console.info('result:' + JSON.stringify(res.data.message));
let zhanghao = res.data.message
if (zhanghao == 'None' ){
// 停止文本时钟
this.controller.stop()
// 传递给后端
axios({
method: "post",
url: 'http://localhost:3000/qiandaos/time',
data:{
juese:'student',
zhanghao:this.Ondata?.['zhanghao']
}
}).then(res => {
console.info('result1111:');
}).catch(error => {
console.error(error);
})
}else {
AlertDialog.show(
{
title: '今天已经签到,请勿多次签到',
message: '请勿多次签到',
autoCancel: true,
alignment: DialogAlignment.Bottom,
offset: { dx: 0, dy: -20 },
gridCount: 3,
confirm: {
value: 'OK',
action: () => {
console.info('Button-clicking callback')
}
},
cancel: () => {
console.info('Closed callbacks')
}
}
)
}
})
})
}
}
.width('100%')
}
onPageShow(){
console.log(JSON.stringify(this.Ondata?.['zhanghao']))
}
}
后端node.js文件架构
主要代码:
db.js
负责创建数据库中数据表的结构,并连接数据库,为数据表中的键值创建模型。
const mongoose = require('mongoose')
//连接mongodb数据库
mongoose.connect("mongodb://localhost:27017/DoList")
.then(() => {
console.log("数据库连接成功!")
})
.catch((err) => {
console.log("数据库连接失败!", err)
})
// 创建表用户表
const Users = new mongoose.Schema({
zhanghao: {
type: String,
},
mima: {
type: String
},
//此处是为了测试vue动态路由
juese:{
type: String
}
})
// 创建签到表
const Qiandao = new mongoose.Schema({
zhanghao: {
type: String,
},
date_times: {
type: String
},
juese: {
type: String
}
})
const users = mongoose.model("Users", Users);
const qiandao = mongoose.model("Qiandao", Qiandao);
module.exports = {
users,
qiandao,
}
index.js
后端主入口程序,引用自定义组件进行进一步的模块封装。
// index.js
const express = require('express');
const cors = require('cors'); // 导入cors模块
const app = express();
const userApi = require('./user_ctrl/user_api');
const qiandaoApi = require('./qiandao_ctrl/qiandao_api');
const dingshi = require('./dingshi/dingshi'); // 引入定时任务文件
app.use(cors()); // 使用cors中间件处理跨域请求
app.use('/users', userApi);
app.use('/qiandaos', qiandaoApi);
app.listen(3000, () => {
console.log('server running');
});
user_api.js
负责使用者用户登录的信息的判断。
// user_api.js
const express = require('express');
const router = express.Router();
const { users } = require('../db');
router.use(express.urlencoded({ extended: true }));
router.use(express.json());
// 账号登录
router.get("/find1/:zhanghao/:mima", async (req, res) => {
try {
const zhanghao = req.params.zhanghao;
const mima = req.params.mima;
// 使用 find 查询所有匹配指定 的数据记录
const results = await users.find({ zhanghao, mima });
if (results.length > 0) {
// 如果找到匹配的记录,则返回所有匹配的记录
res.json({ data: results, message: "登录成功!" });
} else {
res.status(404).json({ message: "未找到匹配的记录" });
}
} catch (error) {
res.status(500).json({ message: "服务器内部错误" });
}
});
router.get("/count/student", async (req, res) => {
try {
// 使用 countDocuments 查询 juese 字段为 "student" 的数据数量
const count = await users.countDocuments({ juese: "student" });
// 返回查询结果
res.json({ count: count, message: "查询成功!" });
} catch (error) {
// 如果发生错误,返回500错误
res.status(500).json({ message: "服务器内部错误" });
}
});
module.exports = router;
qiandao_api.js
负责学生签到的时间查询、全部查询、账号查询等。
// user_api.js
const express = require('express');
const router = express.Router();
const { qiandao } = require('../db');
router.use(express.urlencoded({ extended: true }));
router.use(express.json());
router.get("/count/student_ok", async (req, res) => {
try {
// 使用 countDocuments 查询 juese 字段为 "student" 的数据数量
const count = await qiandao.countDocuments({ juese: "student" });
// 返回查询结果
res.json({ count: count, message: "查询成功!" });
} catch (error) {
// 如果发生错误,返回500错误
res.status(500).json({ message: "服务器内部错误" });
}
});
// 全部查询
router.get("/find_all", async (req, res) => { // Corrected function signature
try {
const results = await qiandao.find();
if (results.length > 0) {
// 如果找到匹配的记录,则返回所有匹配的记录
res.json({ data: results, message: "成功" });
} else {
res.status(404).json({ message: "未找到匹配的记录" });
}
} catch (error) {
res.status(500).json({ message: "服务器内部错误" });
}
});
// 签到时间
router.post("/time", async (req, res) => {
try {
const { zhanghao ,juese } = req.body;
// if(date_time == true){
await qiandao.create({
juese,
zhanghao,
date_times:new Date().toLocaleString()
});
// }
res.send("success");
} catch (error) {
res.send(error, "error");
}
});
router.get("/find/time/:zhanghao", async (req, res) => {
try {
const zhanghao = req.params.zhanghao;
// 使用 find 查询所有匹配指定 name 的数据记录
const results = await qiandao.find({ zhanghao });
if (results.length > 0) {
// 如果找到匹配的记录,则返回所有匹配的记录
res.json({ data: results, message: "签到成功!" });
} else {
res.json({ data: results, message: "None" });
}
} catch (error) {
res.status(500).json({ message: "服务器内部错误" });
}
});
module.exports = router;
dingshi.js
定时器的作用负责每天凌晨0点,刷新diandaos数据表,确保第二天可以正常的使用。
// dingshi.js
const cron = require('node-cron');
const { qiandao } = require('../db'); // 导入数据库模型
// 每天凌晨0点0分执行任务
cron.schedule('0 0 * * *', async () => {
try {
// 清空签到表
await qiandao.deleteMany({});
console.log('签到表数据已清空');
} catch (error) {
console.error('清空数据表时出错:', error);
}
}, {
scheduled: true,
timezone: "Asia/Shanghai" // 设置时区为上海时间
});
console.log('定时任务已启动');