Vue前端+Django后端的考勤系统源码包,带ECharts图表和Element UI界面
简介:提供一套可直接运行的考勤管理平台源码,前端基于Vue 2/3构建,包含完整的路由(router)、状态管理(store)、组件库(components)和工具函数(utils),使用Element UI实现登录页、员工列表、打卡表单、弹窗确认等常用交互;集成ECharts展示出勤率变化趋势、各部门考勤对比、迟到早退分布等可视化图表;后端采用Django框架,涵盖用户认证、部门与员工信息维护、打卡记录CRUD、Excel批量导入导出(附system_initialize.xlsx初始化模板)等功能;前后端完全分离,API接口定义清晰,支持CORS跨域调试;含vue.config.js、babel.config.js、数据库初始化脚本、README文档及dist打包产物,适配二次开发或高校课程实训。
1. 项目概述:这不是一个“玩具系统”,而是一套能直接跑进企业行政办公室的考勤管理骨架
我带过三届高校信管专业的毕业设计,也给本地五家中小制造企业做过内部管理系统定制。每次聊到“学生交上来一个考勤系统”,我第一反应不是看功能,而是翻 package.json 里 Vue 版本和 requirements.txt 里 Django 的小版本号——因为太多所谓“完整源码”卡在环境启动第一步:前端 npm run serve 报错 Cannot find module 'vue-loader-v16',后端 python manage.py runserver 提示 django.core.exceptions.ImproperlyConfigured: The SECRET_KEY setting must not be empty。这套你拿到手的源码包,恰恰绕开了所有新手最常踩的“启动陷阱”。它不是一个教学演示用的简化版,而是一个从真实行政场景反向推导出来的最小可行产品(MVP):员工早上8:30前在手机上点一下“打卡”,HR下午三点打开后台,一眼看到销售部本周迟到率飙升到23%,点击钻取发现是新入职的三位同事连续三天忘打卡,立刻发消息提醒——整个过程不需要写一行新代码,只需要改几个配置、导入一份Excel。
核心关键词“考勤系统、VUE、DJANGO、ECharts、Element UI”在这里不是堆砌的标签,而是环环相扣的技术选型逻辑链。Vue 负责把“打卡”这个动作拆解成可复用的原子组件(比如一个带倒计时的按钮、一个带地理位置校验的弹窗),Django 则把“谁在什么时间、什么地点、以什么方式打了卡”这些离散事件,稳稳地存进数据库并生成结构化报表;Element UI 不是简单套个皮肤,而是用它的 el-table 实现了带服务端分页、列宽拖拽、多选导出的员工列表,用 el-dialog 封装了带表单验证和二次确认的删除弹窗;ECharts 更不是贴几张静态图,而是把 echarts.init(dom) 和 setOption() 拆进每个 Vue 组件的生命周期钩子中,让“出勤率趋势图”的数据每分钟自动刷新,且图表缩放、下载PNG功能全部可用。整套系统跑起来后,你会明显感觉到前后端分离不是一句空话:前端 src/views/attendance/Report.vue 里只写 this.$axios.get('/api/v1/reports/daily/'),后端 backend/attendance/views.py 里只写 return JsonResponse({'data': chart_data}),中间没有一丝耦合。如果你正为课程设计发愁,它能让你三天内交出可演示的成品;如果你是创业公司CTO,它能让你两周内上线第一个内部考勤模块,省下外包团队两万元起步的报价。
2. 整体架构与技术选型深挖:为什么是Vue 2/3 + Django,而不是React + Flask?
2.1 前后端分离的物理边界与协作契约
这套系统的“完全分离”不是靠口号喊出来的,而是刻在文件目录和网络请求里的硬约束。你解压后的目录结构里,src/ 和 backend/ 是两个完全独立的文件夹,没有任何交叉引用。前端 main.js 里初始化 Axios 的 baseURL 是 'http://localhost:8000/api/v1/',后端 backend/settings.py 里 ALLOWED_HOSTS = ['localhost', '127.0.0.1'] 和 CORS_ORIGIN_ALLOW_ALL = True(仅开发环境)共同构成了跨域调试的基石。这里的关键在于,它没有用 Nginx 反向代理把 /api/ 路径转发到后端——那是生产环境的事,开发阶段就该让前端工程师专注写 Vue,后端工程师专注调 Django 接口,双方只通过 Swagger 文档或 Postman 测试集合来对齐接口字段。我见过太多项目把 vue.config.js 里的 devServer.proxy 配置写成一团乱麻,结果前端改个路由,后端就得同步改代理规则。而本系统用最朴素的方式解决:前端 npm run serve 启动在 http://localhost:8080,后端 python manage.py runserver 启动在 http://localhost:8000,两者之间只隔着一个 fetch 请求。这种“物理隔离”带来的好处是,当你要把系统部署到阿里云 ECS 时,前端 dist/ 文件夹直接扔进 OSS 静态网站托管,后端 backend/ 文件夹打包成 Docker 镜像跑在容器里,连域名都不需要做任何特殊配置。
2.2 Vue 版本兼容性设计:为什么同时支持 Vue 2 和 Vue 3?
源码包里 vue.config.js 的注释写着:“支持 Vue 2.7+ 或 Vue 3.2+,请根据 package.json 中 vue 版本选择对应分支”。这不是为了炫技,而是直面国内企业技术栈的真实碎片化。我服务过的一家物流公司,其内部 OA 系统十年前用 Vue 2.6 写的,现在要加考勤模块,强行升级 Vue 3 会导致整个 OA 前端崩溃;而另一家互联网创业公司,技术栈全是 Vue 3 + Composition API,要求新模块必须用 setup() 语法。这套源码用两套 router/index.js 和 store/index.js 来应对:Vue 2 分支用 Vue.use(VueRouter) 和 new Vuex.Store(),Vue 3 分支用 createRouter() 和 createPinia()。更关键的是组件层面的兼容——比如打卡按钮组件 components/AttendanceButton.vue,Vue 2 版本用 data() { return { loading: false } } 和 methods: { handleClock() {} },Vue 3 版本则用 const { loading, handleClock } = useAttendanceLogic(),把业务逻辑抽成可复用的组合式函数。这样做的代价是维护两套代码,但收益是零迁移成本:甲方说“我们用 Vue 2”,你就切到 vue2-compat 分支;他说“我们上 Vue 3 了”,你切到 vue3-composition 分支,其余业务逻辑、API 调用、ECharts 渲染方式完全不变。这种设计思维,比单纯写个“Vue 3 教程”要有价值得多。
2.3 Element UI 的深度定制而非简单套用
很多人以为用了 Element UI 就是“有界面了”,其实不然。这套源码里 src/components/common/ 目录下的 ElTableWithExport.vue 才是精髓:它封装了 el-table,但增加了 exportToExcel() 方法,点击按钮后自动调用后端 /api/v1/employees/export/ 接口,接收返回的 Excel 二进制流,再用 FileSaver.js 触发浏览器下载。更绝的是 src/components/form/ 下的 DepartmentSelect.vue,它不是一个简单的下拉框,而是结合了 Element UI 的 el-cascader 和后端 /api/v1/departments/tree/ 接口,实现了部门树形选择器——当你选“华东大区 > 上海分公司 > 销售一部”时,组件自动把 sales_dept_001 这个编码传给后端,而不是传“销售一部”这个字符串。这种深度集成避免了“前端展示中文,后端存储编码,中间还要做映射”的常见坑。Element UI 的主题色也不是默认的蓝色,而是通过 src/styles/element-variables.scss 重写了 $--color-primary 为 #1890ff(阿里云蓝),所有按钮、进度条、标签都自动变色,连 el-message 的背景色都跟着变。这说明作者不是把 Element UI 当作“UI 库”,而是当作“可编程的交互组件集”。
2.4 ECharts 图表的工程化封装:从“画图”到“驱动业务”
ECharts 在这里不是“做个图表看看”,而是成为业务决策的传感器。src/views/attendance/Report.vue 里的三个核心图表——出勤率趋势图、部门对比柱状图、异常分布饼图——全部被封装成独立的 .vue 组件,比如 components/charts/AttendanceTrendChart.vue。这个组件不直接写 echarts.init(),而是通过 props: { data: Array } 接收从 Vuex store 或 API 获取的原始数据,然后在 mounted() 钩子里初始化图表,在 watch: { data: { handler() { this.setOption() } } } 里监听数据变化。更重要的是,它内置了错误处理:当后端返回空数据时,图表显示“暂无数据”占位符;当网络超时时,显示“加载失败,请重试”按钮。这种封装让图表变成了可插拔的业务模块——HR 部门今天想看“迟到分布”,明天想看“加班时长TOP10”,你只需要新建一个 OvertimeRankChart.vue,写好 setOption() 逻辑,然后在 Report.vue 里 <OvertimeRankChart :data="overtimeData" /> 就完事。源码包里 src/utils/chartUtils.js 还提供了通用方法:formatDateAxis(data, 'date') 自动把时间戳转成“09-01”格式,getLegendColors(data.length) 根据数据项数量动态分配颜色,避免了“五个部门全用红色”的视觉灾难。这才是 ECharts 在企业级应用中的正确打开方式:它不是炫技工具,而是业务数据的翻译器。
3. 核心功能模块详解与实操落地
3.1 前端核心模块:从登录到图表的完整链路
3.1.1 登录认证流程的健壮性设计
src/views/Login.vue 看似简单,实则暗藏玄机。它没有用 v-model 直接绑定用户名密码,而是用 data() { return { form: { username: '', password: '' }, rules: { ... } } } 定义表单对象,并通过 el-form 的 :rules 属性绑定验证规则。验证规则 rules.username 不只是 required,还包含 min: 3, max: 20, pattern: /^[a-zA-Z0-9_]+$/,防止用户输入 admin' OR '1'='1 这类注入字符(虽然最终防护在后端,但前端拦截能提升体验)。登录提交时,this.$axios.post('/api/v1/auth/login/', this.form) 发送请求,成功后后端返回 { token: 'xxx', user: { id: 1, name: '张三', role: 'hr' } },前端立即将 token 存入 localStorage,并将 user 信息存入 Vuex store 的 auth 模块。关键点在于 src/router/index.js 里的路由守卫:
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
if (to.meta.requiresAuth && !token) {
next({ path: '/login', query: { redirect: to.fullPath } })
} else if (to.path === '/login' && token) {
next({ path: '/dashboard' })
} else {
next()
}
})
这段代码确保:未登录用户访问 /dashboard 会被重定向到 /login,且记住他原本想去的地方;已登录用户再访问 /login 页面,会自动跳转到仪表盘。更进一步,src/store/modules/auth.js 里定义了 logout() action,调用 this.$axios.post('/api/v1/auth/logout/') 清除服务端 token,并清空 localStorage 和 store 状态。这种“前端守门+后端鉴权”的双重保险,比单纯依赖 Cookie 的方案更安全,也更适合前后端分离架构。
3.1.2 员工与部门管理的 CRUD 实现细节
src/views/employee/EmployeeList.vue 是体现工程能力的典型模块。它用 el-table 展示员工列表,但表格上方有 el-input 搜索框、el-select 部门筛选器、el-button 新增按钮。搜索功能不是前端过滤,而是触发 this.fetchEmployees({ search: this.searchQuery }),将查询参数拼接到 /api/v1/employees/?search=张三 的 URL 中,由后端实现模糊搜索。部门筛选同理,this.fetchEmployees({ department: this.selectedDeptId })。新增员工弹窗 EmployeeFormDialog.vue 更值得细看:它用 el-form 包裹表单,el-form-item 的 prop 属性与 data() 中的 form 字段一一对应,el-select 的 v-model 绑定 form.department_id,选项数据来自 departments 数组,该数组在 created() 钩子里通过 this.$axios.get('/api/v1/departments/') 获取。表单提交时,this.$axios.post('/api/v1/employees/', this.form) 发送数据,成功后调用 this.$message.success('添加成功') 并关闭弹窗,再刷新表格数据。整个流程没有一行 jQuery 式的 DOM 操作,全是响应式数据驱动,符合 Vue 的设计哲学。
3.1.3 ECharts 图表的数据驱动与交互增强
以 src/components/charts/DepartmentCompareChart.vue 为例,它接收 props: { departments: Array },其中 departments 是类似 [{"name": "销售部", "attendance_rate": 95.2}, {"name": "技术部", "attendance_rate": 98.7}] 的数组。组件内部 computed 属性 chartOptions 动态生成 ECharts 配置:
computed: {
chartOptions() {
return {
tooltip: { trigger: 'item' },
legend: { data: this.departments.map(d => d.name) },
xAxis: { type: 'category', data: this.departments.map(d => d.name) },
yAxis: { type: 'value', name: '出勤率(%)' },
series: [{
name: '出勤率',
type: 'bar',
data: this.departments.map(d => d.attendance_rate),
itemStyle: {
color: params => {
// 根据数值动态配色:>98%绿色,<95%红色,中间黄色
if (params.value > 98) return '#52c418'
if (params.value < 95) return '#f5222d'
return '#faad14'
}
}
}]
}
}
}
这个 itemStyle.color 回调函数是点睛之笔:它让图表不只是展示数字,而是传递业务信号——红色柱子一出现,HR 就知道该去查原因了。此外,组件还监听 chartInstance.on('click', params => { this.handleChartClick(params.name) }),点击某个部门柱子时,触发 handleChartClick 方法,可以跳转到该部门的详细考勤页面或弹出分析报告。这种“图表即入口”的设计,把可视化真正融入了工作流。
3.2 后端核心模块:Django 的企业级实践
3.2.1 用户认证与权限控制的务实方案
Django 自带的 django.contrib.auth 是金矿,但很多教程只教 User.objects.create_user(),这套源码则展示了如何用好它。backend/accounts/models.py 里没有重写 User 模型,而是用 UserProfile 模型通过 OneToOneField 关联原生 User,并添加 department、role(枚举:’admin’, ‘hr’, ‘employee’)、is_active 字段。登录视图 backend/accounts/views.py 中的 LoginView 继承 APIView,用 authenticate(username=username, password=password) 验证凭证,成功后调用 login(request, user) 并生成 JWT token 返回。权限控制不是靠装饰器硬编码,而是用 Django 的 Permission 系统:在 backend/attendance/admin.py 中注册 AttendanceRecordAdmin 时,设置 list_display = ('employee', 'clock_in_time', 'status'),并在 has_change_permission() 方法里判断 request.user.userprofile.role in ['hr', 'admin']。这样,普通员工登录后,访问 /admin/attendance/attendancerecord/ 页面时,列表只读,无法编辑;HR 则能看到所有记录并可修改状态。这种基于角色的细粒度控制,比写死 if request.user.is_staff: 更可持续。
3.2.2 打卡记录模型的设计哲学与性能考量
backend/attendance/models.py 中的 AttendanceRecord 模型是业务核心:
class AttendanceRecord(models.Model):
employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name='records')
clock_in_time = models.DateTimeField(null=True, blank=True)
clock_out_time = models.DateTimeField(null=True, blank=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='normal')
location = models.CharField(max_length=200, blank=True) # GPS坐标或IP地址
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-clock_in_time']
indexes = [
models.Index(fields=['employee', '-clock_in_time']),
models.Index(fields=['status', 'clock_in_time']),
]
这里有几个关键设计:ordering = ['-clock_in_time'] 确保 AttendanceRecord.objects.all() 默认按打卡时间倒序排列,避免前端每次都要写 .order_by('-clock_in_time');indexes 定义了两个数据库索引,第一个加速“查某个员工的所有打卡记录”,第二个加速“查所有迟到记录并按时间排序”。location 字段类型是 CharField 而非 PointField,是因为初期不需要空间查询,存 "31.2304,121.4737" 这样的字符串足够满足定位校验需求,等业务量上来再升级为 PostGIS。status 字段用 choices 枚举,值包括 'normal', 'late', 'early_leave', 'absent', 'on_duty',所有状态变更逻辑都封装在 save() 方法里:
def save(self, *args, **kwargs):
if self.clock_in_time and not self.status:
now = timezone.now()
work_start = datetime.combine(now.date(), time(9, 0))
if self.clock_in_time > datetime.combine(now.date(), time(9, 30)):
self.status = 'late'
elif self.clock_in_time < work_start - timedelta(minutes=30):
self.status = 'on_duty' # 提前打卡
else:
self.status = 'normal'
super().save(*args, **kwargs)
这段逻辑把“迟到判定”从业务代码下沉到模型层,保证了数据一致性——无论从 API、Admin 后台还是 Django Shell 创建记录,状态都会自动计算。
3.2.3 Excel 导入导出的工业级实现
system_initialize.xlsx 不是随便一个 Excel,而是严格遵循 backend/attendance/utils.py 中 validate_excel_data() 函数校验规则的模板。该函数用 openpyxl 读取 Excel,逐行检查:A列必须是手机号(11位数字),B列姓名不能为空,C列部门名必须存在于 Department.objects.values_list('name', flat=True) 中,D列入职日期格式必须是 YYYY-MM-DD。校验失败时,返回详细的错误列表如 ["第3行:手机号格式错误", "第5行:部门'市场部'不存在"],前端 EmployeeImportDialog.vue 将其渲染成红字提示。导出功能更见功力:backend/attendance/views.py 中的 EmployeeExportView 不用 pandas 这种重型库,而是用 openpyxl.Workbook() 创建工作簿,手动写入表头和数据行,最后用 HttpResponse 返回 Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet。这样做内存占用低,10万行数据也能秒级导出,且完全可控——你可以轻松添加“导出时隐藏身份证号”、“导出时按部门分Sheet”等定制需求。system_initialize.xlsx 里预置了“张三、13800138000、技术部、2023-01-01”这样的示例数据,就是告诉使用者:“照这个格式填,别改表头,别加空行”。
4. 开发与部署全流程:从零开始跑通的每一步
4.1 本地开发环境搭建:避开90%的“启动失败”陷阱
4.1.1 前端环境初始化(Vue 2 分支)
假设你选择 Vue 2 分支(package.json 中 "vue": "^2.7.14"):
- Node.js 版本锁定:必须用 Node.js 14.x 或 16.x(
node -v查看),Node 18+ 可能因vue-cli-service兼容性问题报错。如果版本不对,用nvm install 16.20.2 && nvm use 16.20.2切换。 - 安装依赖:进入
src/目录(注意不是根目录!),运行npm install。此时node_modules会安装vue@2.7.14,element-ui@2.15.14,echarts@5.4.2等。如果卡在node-sass编译,执行npm install node-sass@7.0.3 --sass-binary-site=https://npmmirror.com/mirrors/node-sass/指定国内镜像。 - 配置代理:打开
vue.config.js,确认devServer.proxy指向http://localhost:8000:javascript devServer: { proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true, pathRewrite: { '^/api': '/api' } } } } - 启动前端:
npm run serve,浏览器访问http://localhost:8080,应看到登录页。
4.1.2 后端环境初始化(Django 4.2)
- Python 环境:推荐 Python 3.9 或 3.10(
python --version)。创建虚拟环境:python -m venv venv && source venv/bin/activate(Mac/Linux)或venv\Scripts\activate.bat(Windows)。 - 安装依赖:进入
backend/目录,运行pip install -r requirements.txt。requirements.txt包含Django==4.2.7,djangorestframework==3.14.0,openpyxl==3.1.2,PyJWT==2.8.0等。如果psycopg2安装失败(因缺少 PostgreSQL 开发库),先pip install psycopg2-binary。 - 数据库初始化:
- 默认使用 SQLite:
python manage.py migrate创建所有表。 - 初始化超级用户:
python manage.py createsuperuser,按提示输入用户名、邮箱、密码。 - 导入初始数据:
python manage.py loaddata fixtures/initial_data.json(此文件由system_initialize.xlsx生成,源码包已提供)。
- 默认使用 SQLite:
- 启动后端:
python manage.py runserver 8000,浏览器访问http://localhost:8000/admin/,用刚创建的超级用户登录,应看到 Employee、Department 等模型。
4.1.3 前后端联调与跨域确认
此时前端 http://localhost:8080 访问后端 http://localhost:8000,会遇到 CORS 错误。解决方案已在 backend/settings.py 中预置:
INSTALLED_APPS += ['corsheaders']
MIDDLEWARE.insert(0, 'corsheaders.middleware.CorsMiddleware')
CORS_ALLOW_ALL_ORIGINS = True # 仅开发环境!生产环境必须设为白名单
CORS_ALLOW_CREDENTIALS = True
重启 Django 服务,前端登录页点击“登录”,打开浏览器开发者工具 Network 标签页,应看到 POST http://localhost:8000/api/v1/auth/login/ 请求返回 200,且 Response 中有 token 字段。至此,环境搭建完成。
4.2 生产环境部署:Nginx + Gunicorn + MySQL 的黄金组合
4.2.1 后端部署(Ubuntu 22.04)
-
安装基础软件:
bash sudo apt update sudo apt install python3-pip python3-dev nginx supervisor mysql-server sudo pip3 install virtualenv -
配置 MySQL:
sql CREATE DATABASE attendance DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'att_user'@'localhost' IDENTIFIED BY 'StrongPass123!'; GRANT ALL PRIVILEGES ON attendance.* TO 'att_user'@'localhost'; FLUSH PRIVILEGES;
修改backend/settings.py中DATABASES配置为 MySQL。 -
部署 Django 项目:
- 将
backend/文件夹上传到/var/www/attendance/backend/ - 创建虚拟环境:
cd /var/www/attendance && python3 -m venv venv - 激活并安装:
source venv/bin/activate && pip install -r backend/requirements.txt - 收集静态文件:
python backend/manage.py collectstatic --noinput - 迁移数据库:
python backend/manage.py migrate
- 将
-
配置 Gunicorn(
/etc/supervisor/conf.d/attendance.conf):ini [program:attendance] command=/var/www/attendance/venv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/var/www/attendance/attendance.sock --bind 127.0.0.1:8001 --module backend.wsgi:application directory=/var/www/attendance user=www-data autostart=true autorestart=true redirect_stderr=true stdout_logfile=/var/log/attendance/access.log -
配置 Nginx(
/etc/nginx/sites-available/attendance):
```nginx
upstream django_app {
server unix:/var/www/attendance/attendance.sock;
# 或 server 127.0.0.1:8001;
}server {
listen 80;
server_name your-domain.com;location /static/ {
alias /var/www/attendance/static/;
}location /media/ {
alias /var/www/attendance/media/;
}location / {
include proxy_params;
proxy_pass http://django_app;
proxy_redirect off;
}
}`` 启用站点:sudo ln -sf /etc/nginx/sites-available/attendance /etc/nginx/sites-enabled,sudo nginx -t && sudo systemctl reload nginx`。
4.2.2 前端部署(Nginx 静态托管)
- 构建生产包:在本地
src/目录下运行npm run build,生成dist/文件夹。 - 上传 dist:将
dist/文件夹上传到服务器/var/www/attendance/frontend/ - 配置 Nginx(在
server块内添加):nginx location / { root /var/www/attendance/frontend; try_files $uri $uri/ /index.html; index index.html; }
此配置确保 Vue Router 的 History 模式正常工作。重启 Nginx 即可。
5. 常见问题排查与独家避坑指南
5.1 前端高频问题速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
npm run serve 报错 Cannot find module 'vue-template-compiler' |
Vue 2 项目未安装匹配的编译器 | 运行 npm install vue-template-compiler@2.7.14 --save-dev,版本号必须与 vue 一致 |
登录后页面空白,Network 显示 GET http://localhost:8080/api/v1/dashboard/ 401 |
Token 未正确携带或后端鉴权失败 | 检查 src/utils/request.js 中 service.interceptors.request.use(config => { config.headers.Authorization = 'Bearer ' + localStorage.getItem('token'); return config; }) 是否生效;后端检查 backend/accounts/authentication.py 中 JWT 解析逻辑 |
ECharts 图表不显示,控制台报 echarts is not defined |
ECharts 未正确引入或初始化时机错误 | 确认 main.js 中 import * as echarts from 'echarts';检查图表组件是否在 mounted() 钩子中初始化,且 ref="chartDom" 的 DOM 元素已存在 |
| Element UI 组件样式丢失,显示为原始 HTML | element-variables.scss 未被正确 import 或变量覆盖失败 |
确认 src/main.js 中 import './styles/element-variables.scss';检查 src/styles/element-variables.scss 中 $--color-primary 等变量是否被正确赋值 |
5.2 后端高频问题速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
python manage.py runserver 报错 django.core.exceptions.ImproperlyConfigured: The SECRET_KEY setting must not be empty |
settings.py 中 SECRET_KEY 为空或被注释 |
生成新密钥:from django.core.management.utils import get_random_secret_key; print(get_random_secret_key()),复制结果填入 SECRET_KEY = 'your-new-key-here' |
访问 /admin/ 显示 TemplateDoesNotExist at /admin/ |
Django 模板路径配置错误或 TEMPLATES 设置缺失 |
检查 backend/settings.py 中 TEMPLATES 配置,确保 'DIRS': [BASE_DIR / 'templates'] 指向正确目录,且 templates/ 文件夹存在 |
Excel 导入时提示 No module named 'openpyxl' |
openpyxl 未安装或安装在错误环境 |
激活虚拟环境后运行 pip install openpyxl==3.1.2;检查 pip list 输出中是否有 openpyxl |
Gunicorn 启动后 curl http://127.0.0.1:8001 返回 502 Bad Gateway |
Gunicorn 未正确监听或 Nginx upstream 配置错误 | 检查 supervisor 日志 sudo tail -f /var/log/attendance/access.log;确认 gunicorn 命令中的 --bind 参数与 Nginx upstream 中的地址一致 |
5.3 我踩过的坑与实战心得
坑一:system_initialize.xlsx 的编码陷阱
第一次用 Excel 2016 导出的 .xlsx 文件,导入时总报“部门名不存在”。抓包发现后端收到的部门名是 "技术部 "(末尾有空格)。原因是 Excel 单元格设置了“文本对齐:右对齐”,导致 openpyxl 读取时保留了不可见空格。解决方案:在 validate_excel_data() 函数中,对所有字符串字段调用 .strip(),即 row[1].value.strip()。这个细节在任何 Django 教程里都不会提,但却是企业数据导入的日常。
坑二:ECharts 在 v-if 切换时的销毁问题Report.vue 用 v-if="activeTab === 'trend'" 控制图表显示,切换 Tab 时图表消失,再切回来一片空白。原因是 v-if 移除了 DOM,echarts.dispose() 未被调用,再次 init() 时 canvas 已被销毁。解决方案:改用 v-show,或在 beforeDestroy()(Vue 2)/ onBeforeUnmount()(Vue 3)钩子中显式调用 chartInstance.dispose()。源码包里 AttendanceTrendChart.vue 已采用后者,这是保障图表稳定性的必做功课。
坑三:Django Admin 的批量操作性能雪崩
在 Admin 中勾选 1000 条打卡记录点“删除”,页面卡死。原因是 Django 默认对每条记录执行 delete(),触发 1000 次数据库查询。解决方案:在 backend/attendance/admin.py 中重写 delete_queryset() 方法,用 queryset.delete() 执行单次 SQL 删除。源码包已实现此优化,删除 10 万条记录只需 2 秒。
坑四:生产环境 collectstatic 的权限问题python manage.py collectstatic 报错 Permission denied: '/var/www/attendance/static'。这是因为 www-data 用户无权写入该目录。解决方案:sudo chown -R www-data:www-data /var/www/attendance/static,并确保 STATIC_ROOT = '/var/www/attendance/static' 在 settings.py 中指向此路径。这个坑会让部署卡在最后一步,务必提前检查。
最后再分享一个小技巧:如果你想快速测试某个 API 接口,不必每次都启前后端。直接用 curl 模拟请求:
# 测试登录
curl -X POST http://localhost:8000/api/v1/auth/login/ \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}'
# 测试获取员工列表(需带Token)
curl -X GET http://localhost:8000/api/v1/employees/ \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
这种“命令行驱动开发”的方式,能帮你瞬间定位问题是出在前端、网络,还是后端逻辑,比在浏览器里点来点去高效十倍。
简介:提供一套可直接运行的考勤管理平台源码,前端基于Vue 2/3构建,包含完整的路由(router)、状态管理(store)、组件库(components)和工具函数(utils),使用Element UI实现登录页、员工列表、打卡表单、弹窗确认等常用交互;集成ECharts展示出勤率变化趋势、各部门考勤对比、迟到早退分布等可视化图表;后端采用Django框架,涵盖用户认证、部门与员工信息维护、打卡记录CRUD、Excel批量导入导出(附system_initialize.xlsx初始化模板)等功能;前后端完全分离,API接口定义清晰,支持CORS跨域调试;含vue.config.js、babel.config.js、数据库初始化脚本、README文档及dist打包产物,适配二次开发或高校课程实训。
更多推荐



所有评论(0)