考勤管理系统
文章目录
1. 项目概述
1.1 项目名称
考勤管理系统
1.2 项目背景
随着社会的发展和企业规模的扩大,对于员工考勤的管理变得愈加重要。考勤管理系统旨在帮助企业轻松记录和管理员工的出勤情况,提高工作效率。
1.3 开发工具
Visual Studio 2022
2. 需求分析
2.1 功能需求
-
员工
- 注册账号
- 打卡
- 请假
- 签退
- (自己)考勤记录的打印
-
管理员
- 员工的增添
- 打印员工信息
- 删除某一员工
- 修改员工信息
- 请假的批准,驳回,结束假期等
-
其他功能需求
- 文件的存入,存出(实现存储信息的持久性)
- 账户系统(实现功能的模块化设计)
2.2 非功能需求
-
容错性高
考虑到用户的错误输入,有有效应对措施
-
易用性需求:
用户界面友好: 设计直观、易用的用户界面,以提高用户体验。
-
系统稳定性
合理开辟使用释放空间,防止内存泄漏,调用野指针,解引用空指针等情况存在
-
可扩展性需求
考虑未来可能的需求变化,确保系统能够方便地扩展和修改。
-
代码可读性
系统中变量均采用易懂的英文单词或缩写,并有适量注释,方便阅读
编写清晰,功能模块化,易于理解
3. 系统设计
3.1 系统架构
功能一览
3.1.1 模块划分
考勤管理系统采用简单的三层架构,包括用户界面层、业务逻辑层和数据存储层。
-
节点展示
typedef struct StuffTime { time_t checkInTime;//存储打卡开始时间 time_t checkOutTime;//存储打卡结束时间 int checkIn;//存储有效打卡数 int checkOut;//存储有效签退数 }STime; typedef struct PersonData { char account[50];//账号 char password[20];//密码 char user[20];//用户名 STime time;//存储时间 int leave;//员工的假期状态(假期中,申请请假中,请假被驳回) int judge;//判断是员工还是管理员 }PData; typedef PData InfoDate; typedef struct PersonInfo//节点 { InfoDate data;//信息 struct PersonInfo* next;//下一个的地址 }PInfo;
其中typedef PData InfoDate;后再将数据存入节点原因有下:
1.让节点结构看着清晰简洁,存储数据和下一个节点的地址
2.一开始调试时我是typedef int InfoDate;这样干的,便于调试,调试完成后再将改回结构体
-
用户界面层: 负责与用户进行交互,提供命令行界面,包括签到、请假申请等功能。
交互界面
void Staff(PInfo** head) { system("cls"); loadStaff(*head);//载入数据 int temp = 1; char option; do { system("cls"); printf("**************************\n"); printf("* 请输入选项 *\n"); printf("**************************\n"); printf("* *\n"); printf("* 1.创建账号 *\n"); printf("* 2.登录账号 *\n"); printf("* 0.返回选择界面 *\n"); printf("* *\n"); printf("**************************\n"); option = getch(); system("cls"); switch (option) { case '1': { PushBack(*head, 0); break; } case '2': { PInfo* current = StuffAccount(*head); system("cls"); if (current != NULL) { int tem = 1; do { system("cls"); MenuStuff(); char op = getch(); switch (op) { case '1': { system("cls"); printf("打卡\n"); CheckIntime(head, current); system("pause"); break; } case '2': { system("cls"); printf("请假\n"); RequestLeave(head, current); system("pause"); break; } case '3': { system("cls"); printf("签退\n"); CheckOutTime(head, current); system("pause"); break; } case '4': { system("cls"); printf("考勤记录"); Attdend(head, current); system("pause"); break; } case '0': { system("cls"); printf("退出登录"); tem = 0; break; } default: { system("cls"); printf("输入错误请重新输入:\n"); system("pause"); } } } while (tem); } else { printf("登陆失败"); } break; } case '0': { printf("程序退出\n"); temp = 0; break; } default: printf("输入错误,请重新输入\n"); break; } } while (temp); SaveStaff(*head); }
- 整体采用do while循环和switch语句,实现程序的重复运行
- 使用非标准函数getch();方便用户交互
注册界面
void PushBack(PInfo* head, int judge) { loadStaff(head); PInfo* tem = (PInfo*)malloc(sizeof(PInfo)); if (!tem) { printf("内存分配失败\n"); return; } InfoDate PersonData; printf("请输入账户:\n"); scanf("%s", PersonData.account); printf("请输入用户名:\n"); scanf("%s", PersonData.user); PInfo* current = head; while (current != NULL) { if (strcmp(current->data.account, PersonData.account) == 0) { printf("账户名已存在\n"); system("pause"); return; } if (strcmp(current->data.user, PersonData.user) == 0) { printf("用户名已存在\n"); system("pause"); return; } current = current->next; }//遍历链表,防止存在相同用户名或者账户名 printf("请输入密码:\n"); scanf("%s", PersonData.password); tem->next = NULL; tem->data.leave = 0; tem->data.time.checkIn = 0; tem->data.time.checkOut = 0; tem->data.judge = judge; strcpy(tem->data.account, PersonData.account); strcpy(tem->data.password, PersonData.password); strcpy(tem->data.user, PersonData.user);//初始化 current = head; while (current->next != NULL) { current = current->next; } current->next = tem;//将新数据存在最后一个节点的末尾,实现尾增 SaveStaff(head);//保存数据 printf("注册成功"); system("pause"); }
- 开始开辟空间失败,直接返回,不会访问无效地址
- 创建用户时,利用循环有效防止相同用户名,账户名存在
- 注册界面公用,其中的参数judge如果是1,就是注册管理员,如果是0,就是注册员工
登录界面
PInfo* StuffAccount(PInfo* head) { char c; int op = 1; while (op) { system("cls"); printf("请输入你的员工账号:\n"); char Account[20]; scanf("%s", Account); printf("请输入密码:\n"); char Password[20]; scanf("%s", Password); PInfo* current = head; while (current->next != NULL) { current = current->next; if (strcmp(current->data.account, Account) == 0) if (strcmp(current->data.password, Password) == 0) if (current->data.judge == 0)//判断是否为员工 return current;//遍历,判断节点中是否存在账号密码都存在的数据,如果存在,返回这个节点 } int a = 1; printf("账号或密码存在错误\n"); while (a)//如果没找到,可以进入循环,方便退出或者再次寻找 { printf("**************************\n"); printf("* 请选择 *\n"); printf("**************************\n"); printf("* *\n"); printf("* 1.重新输入 *\n"); printf("* 0.退出程序 *\n"); printf("* *\n"); printf("**************************\n"); c = getch(); switch (c) { case '1': { a = 0; break; } case '0': { op = 0; a = 0; break; } default: { system("cls"); printf("输入错误,请重新输入\n"); break; } } } } return NULL;//没找到返回空指针 }
- 用户可以选择返回,防止陷入死循环
- 直接返回节点,登录成功后方便后续使用
打卡签退界面
void CheckIntime(PInfo* head, PInfo* current)//打卡 { loadStaff(head); PInfo* cur = current;//存储当前已经登陆的节点 PInfo* tem = head;//存储头节点,用于最后的信息存储 cur->data.time.checkInTime = time(NULL);//存储当前时间 struct tm checkInTime = *localtime(&cur->data.time.checkInTime); // 显示打卡时间 char timeStr[100]; strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", &checkInTime);//将当前时间转换成字符格式 printf("%s的打卡时间是: %s\n", cur->data.user, timeStr); // 判断是否在8:00之前打卡 if (checkInTime.tm_hour < 8) { printf("打卡成功!\n"); cur->data.time.checkIn++;//如果打卡成功,打卡成功数加1 } else { printf("打卡失败,未在8:00前打卡。\n"); } SaveStaff(head); }
void CheckOutTime(PInfo* head, PInfo* current)//签退 { loadStaff(head); PInfo* cur = current; PInfo* tem = head; cur->data.time.checkOutTime = time(NULL); struct tm checkInTime = *localtime(&cur->data.time.checkOutTime); // 显示签退时间 char timeStr[100]; strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", &checkInTime); printf("%s的签退时间是: %s\n", cur->data.user, timeStr); // 判断是否在8:00之前签退 if (checkInTime.tm_hour > 8) { printf("签退成功!\n"); cur->data.time.checkOut++; } else { printf("打卡签退,未在8:00前签退。\n"); } SaveStaff(head); }
- 打卡与当前系统时间联系起来,实现功能上的运用
- 签退功能与打卡功能类似
考勤记录的打印(自己)
void Attdend(PInfo* head, PInfo* cur)//cur是登录的节点 { printf("%s的有效打卡数为%d\n", cur->data.user, cur->data.time.checkIn); printf("%s的有效签退数为%d\n", cur->data.user, cur->data.time.checkOut); }
考勤记录的打印
请假界面
void RequestLeave(PInfo* head, PInfo* current) { loadStaff(head); PInfo* cur = current; if (cur->data.leave == 0)//data.leave存储的数值是当前登录者的假期状态 { printf("%s申请请假\n", cur->data.account); cur->data.leave = 1; } else if (cur->data.leave == 1) { printf("请假进行中,请耐心等待\n"); } else if (cur->data.leave == 2)//如果没有批准请假,自动将其状态变为未请假状态 { printf("您的请假没有被批准,如需请假,请重新申请\n"); cur->data.leave == 0; } else printf("申请成功,正在休假"); SaveStaff(head); }
和管理员功能搭配起来,管理员的批准请假负责修改其状态
-
管理界面层:
管理员交互界面
void Admin(PInfo** head) { loadStaff(*head); int temp = 1; char option; do { system("cls"); printf("**************************\n"); printf("* 请输入选项 *\n"); printf("**************************\n"); printf("* *\n"); printf("* 1.注册账号 *\n"); printf("* 2.登录账号 *\n"); printf("* 0.返回选择界面 *\n"); printf("* *\n"); printf("**************************\n"); option = getch(); switch (option) { case '1': { PushBack(*head, 1); break; } case '2': { if (AdminAccount(*head)) { int tem = 1; char op; do { system("cls"); MenuAdmin(); op = getch(); switch (op) { case '1': { system("cls"); printf("增加员工\n"); PushBack(*head, 0); break; } case '2': { system("cls"); printf("查看员工信息\n"); PrintData(*head); system("pause"); break; }case '3': { system("cls"); PopMid(*head); system("pause"); break; }case '4': { system("cls"); printf("修改员工信息\n"); UpdateStaffInfo(*head); system("pause"); break; }case '5': { system("cls"); printf("请假批准\n"); approveLeaveRequest(*head); system("pause"); break; }case '0': { system("cls"); printf("退出登录\n"); tem = 0; break; } default: { system("cls"); printf("输入错误请重新输入:\n"); system("pause"); break; } } } while (tem); } break; } case '0': { printf("程序退出\n"); temp = 0; break; } default: printf("输入错误,请重新输入\n");