|
@@ -1,37 +1,45 @@
|
|
<template>
|
|
<template>
|
|
<div id="index">
|
|
<div id="index">
|
|
- <list-frame title="考勤管理" @query="onsearch" :total="total" :needFilter="false" :needAdd="false">
|
|
|
|
- <el-form :inline="true" size="mini">
|
|
|
|
- <el-form-item label="期">
|
|
|
|
- <el-select v-model="form.termid" placeholder="请选择期数" @change="getClasses">
|
|
|
|
- <el-option v-for="(i, index) in termList" :key="index" :label="`第${i.term}期`" :value="i._id"></el-option>
|
|
|
|
- </el-select>
|
|
|
|
- </el-form-item>
|
|
|
|
- <el-form-item label="班级">
|
|
|
|
- <el-select v-model="form.classid" placeholder="请先选择班级">
|
|
|
|
- <el-option v-for="(i, index) in classList" :key="index" :label="i.name" :value="i._id"></el-option>
|
|
|
|
- </el-select>
|
|
|
|
- </el-form-item>
|
|
|
|
- <el-form-item label="学生姓名">
|
|
|
|
- <el-input v-model="form.name" placeholder="请输入学生姓名"></el-input>
|
|
|
|
- </el-form-item>
|
|
|
|
- <el-form-item label="">
|
|
|
|
- <el-button type="primary" size="mini" @click="onsearch()"> 查询</el-button>
|
|
|
|
- </el-form-item>
|
|
|
|
- </el-form>
|
|
|
|
- <data-table :fields="fields" :data="list"> </data-table>
|
|
|
|
|
|
+ <list-frame title="考勤管理" :total="total" :needFilter="false" :needAdd="false" :needPag="false">
|
|
|
|
+ <el-table :data="list" border stripe height="600px" v-if="headList.length > 0" v-loading="loading" :cell-style="cellStyle">
|
|
|
|
+ <el-table-column align="center" v-for="(h, hindex) in headList" :key="`col-${hindex}`" :label="h.label" :prop="h.prop">
|
|
|
|
+ <template v-if="h.prop !== 'name'">
|
|
|
|
+ <el-table-column align="center" label="上午">
|
|
|
|
+ <template v-slot="{ row }">
|
|
|
|
+ {{ getTableContent(row, h.label, 'am') }}
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column align="center" label="下午">
|
|
|
|
+ <template v-slot="{ row }">
|
|
|
|
+ {{ getTableContent(row, h.label, 'pm') }}
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column align="center" label="寝室">
|
|
|
|
+ <template v-slot="{ row }">
|
|
|
|
+ {{ getTableContent(row, h.label, 'bd') }}
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ </el-table>
|
|
|
|
+ <el-card v-else>
|
|
|
|
+ <el-row type="flex" align="middle" style="height:600px; font-size:18px; text-align:center">
|
|
|
|
+ <el-col :span="24">请选择班级后进行查询</el-col>
|
|
|
|
+ </el-row>
|
|
|
|
+ </el-card>
|
|
</list-frame>
|
|
</list-frame>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</template>
|
|
<script>
|
|
<script>
|
|
|
|
+const moment = require('moment');
|
|
import _ from 'lodash';
|
|
import _ from 'lodash';
|
|
import listFrame from '@frame/layout/admin/list-frame';
|
|
import listFrame from '@frame/layout/admin/list-frame';
|
|
-import dataTable from '@frame/components/data-table';
|
|
|
|
import { mapState, createNamespacedHelpers } from 'vuex';
|
|
import { mapState, createNamespacedHelpers } from 'vuex';
|
|
const { mapActions: attendance } = createNamespacedHelpers('attendance');
|
|
const { mapActions: attendance } = createNamespacedHelpers('attendance');
|
|
const { mapActions: student } = createNamespacedHelpers('student');
|
|
const { mapActions: student } = createNamespacedHelpers('student');
|
|
const { mapActions: trainplan } = createNamespacedHelpers('trainplan');
|
|
const { mapActions: trainplan } = createNamespacedHelpers('trainplan');
|
|
const { mapActions: classes } = createNamespacedHelpers('classes');
|
|
const { mapActions: classes } = createNamespacedHelpers('classes');
|
|
|
|
+const { mapActions: leave } = createNamespacedHelpers('leave');
|
|
|
|
|
|
export default {
|
|
export default {
|
|
metaInfo: { title: '考勤管理' },
|
|
metaInfo: { title: '考勤管理' },
|
|
@@ -39,9 +47,9 @@ export default {
|
|
props: {},
|
|
props: {},
|
|
components: {
|
|
components: {
|
|
listFrame,
|
|
listFrame,
|
|
- dataTable,
|
|
|
|
},
|
|
},
|
|
data: () => ({
|
|
data: () => ({
|
|
|
|
+ loading: true,
|
|
classList: [],
|
|
classList: [],
|
|
batchList: [],
|
|
batchList: [],
|
|
termList: [],
|
|
termList: [],
|
|
@@ -54,7 +62,7 @@ export default {
|
|
label: '类型',
|
|
label: '类型',
|
|
prop: 'type',
|
|
prop: 'type',
|
|
format: item => {
|
|
format: item => {
|
|
- return item === '0' ? '上课考勤' : item === '1' ? '上课考勤' : '';
|
|
|
|
|
|
+ return item === '0' ? '上课考勤' : item === '1' ? '寝室考勤' : '';
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
{
|
|
@@ -66,72 +74,291 @@ export default {
|
|
},
|
|
},
|
|
],
|
|
],
|
|
form: {},
|
|
form: {},
|
|
|
|
+ headList: [],
|
|
|
|
+ attendList: [],
|
|
|
|
+ leaveList: [],
|
|
|
|
+ options: undefined,
|
|
list: [],
|
|
list: [],
|
|
total: 0,
|
|
total: 0,
|
|
}),
|
|
}),
|
|
- created() {
|
|
|
|
- this.searchinfo();
|
|
|
|
- },
|
|
|
|
|
|
+ created() {},
|
|
computed: { ...mapState(['user', 'defaultOption']) },
|
|
computed: { ...mapState(['user', 'defaultOption']) },
|
|
methods: {
|
|
methods: {
|
|
...attendance(['query', 'delete']),
|
|
...attendance(['query', 'delete']),
|
|
...student({ stuquery: 'query' }),
|
|
...student({ stuquery: 'query' }),
|
|
...trainplan({ planfetch: 'fetch' }),
|
|
...trainplan({ planfetch: 'fetch' }),
|
|
- ...classes({ classesquery: 'query' }),
|
|
|
|
- async searchinfo() {
|
|
|
|
- const res = await this.planfetch(this.defaultOption.planid);
|
|
|
|
- let terms = res.data.termnum;
|
|
|
|
- this.$set(this, `termList`, terms);
|
|
|
|
- if (this.defaultOption.termid) {
|
|
|
|
- this.form.termid = this.defaultOption.termid;
|
|
|
|
- this.getClasses(this.form.termid);
|
|
|
|
- }
|
|
|
|
|
|
+ ...classes({ classesquery: 'query', getClass: 'fetch' }),
|
|
|
|
+ ...leave({ studentLeave: 'query' }),
|
|
|
|
+ async search() {
|
|
|
|
+ this.loading = true;
|
|
|
|
+ // 首先整理表头,如果没有选择班级,就是说,要看这期的考勤,需要将这期的日期都列出来坐表头
|
|
|
|
+ const headColumn = await this.getColumn();
|
|
|
|
+ if (!headColumn) return;
|
|
|
|
+ this.$set(this, `headList`, headColumn);
|
|
|
|
+ // 找到相关信息
|
|
|
|
+ await this.getStudent();
|
|
|
|
+ await this.getAttendance();
|
|
|
|
+ await this.getLeave();
|
|
|
|
+ this.loading = false;
|
|
},
|
|
},
|
|
- getBatch(termid) {
|
|
|
|
- let batchs = this.termList.filter(f => f._id === termid);
|
|
|
|
- if (batchs.length > 0) {
|
|
|
|
- let { batchnum } = batchs[0];
|
|
|
|
- this.$set(this, `batchList`, batchnum);
|
|
|
|
- }
|
|
|
|
|
|
+ // 获取表格内容,
|
|
|
|
+ getTableContent(...data) {
|
|
|
|
+ const res = this.getPersonAttend(...data);
|
|
|
|
+ if (!res) return;
|
|
|
|
+ const { type } = res;
|
|
|
|
+ const { word } = this.chageWord(type);
|
|
|
|
+ return word;
|
|
},
|
|
},
|
|
- async getClasses(termid) {
|
|
|
|
- const res = await this.classesquery({ termid });
|
|
|
|
- var arr = res.data.filter(item => item.type == '0');
|
|
|
|
- this.$set(this, `classList`, arr);
|
|
|
|
|
|
+ // 表格颜色识别
|
|
|
|
+ cellStyle({ row, column, rowIndex, columnIndex }) {
|
|
|
|
+ // 除去第一列,学生姓名列
|
|
|
|
+ if (columnIndex <= 0) return;
|
|
|
|
+ // 公式, columnIndex / 3 = x ...y
|
|
|
|
+ // x为哪天,直接在表头数组取出
|
|
|
|
+ // y的值有 0,1,2:0=>寝室;1=>上午;2=>下午
|
|
|
|
+ let di = _.floor(columnIndex / 3);
|
|
|
|
+ let ti = columnIndex % 3;
|
|
|
|
+ if (ti != 0) di = di + 1;
|
|
|
|
+ let day = this.headList[di];
|
|
|
|
+ if (day) day = day.label;
|
|
|
|
+ let type = 'bd';
|
|
|
|
+ if (ti == 1) type = 'am';
|
|
|
|
+ else if (ti == 2) type = 'pm';
|
|
|
|
+ const res = this.getPersonAttend(row, day, type);
|
|
|
|
+ if (!res) return;
|
|
|
|
+ const { type: rtype } = res;
|
|
|
|
+ const { color } = this.chageWord(rtype);
|
|
|
|
+ if (color) return { background: color };
|
|
},
|
|
},
|
|
- async onsearch({ skip = 0, limit = 10, ...info } = {}) {
|
|
|
|
- const res = await this.query({ termid: this.form.termid, classid: this.form.classid, ...info });
|
|
|
|
- const newdatas = [];
|
|
|
|
- if (this.form.name) {
|
|
|
|
- var arr = res.data.filter(item => item.stuname == this.form.name);
|
|
|
|
- for (const data of arr) {
|
|
|
|
- for (const attend of data.attend) {
|
|
|
|
- let newdata = { stuname: data.stuname, ...attend };
|
|
|
|
- newdatas.push(newdata);
|
|
|
|
- }
|
|
|
|
|
|
+ /**
|
|
|
|
+ * 获取表格内容的通用方法
|
|
|
|
+ * @param stu 学生信息
|
|
|
|
+ * @param date 日期
|
|
|
|
+ * @param type 类型: am=>上午;pm=>下午;bd=>寝室(晚上)
|
|
|
|
+ * @description 因为 文字 与 颜色 都可以根据这个方法取对应的值,所以把这个方法单抽出来
|
|
|
|
+ * @property toReturn.type 返回结果: ok:正常签到;late:迟到;nosign:没签到;leave:请假;exit:退出
|
|
|
|
+ */
|
|
|
|
+ getPersonAttend(stu, date, type) {
|
|
|
|
+ let toReturn = {};
|
|
|
|
+ // 先找有没有请假/退出,退出直接返回GG
|
|
|
|
+ const { id: studentid } = stu;
|
|
|
|
+ const les = this.leaveList.filter(f => f.studentid === studentid && f.status === '1');
|
|
|
|
+ // 查看是否有退出且审核的记录
|
|
|
|
+ const isExit = les.find(f => f.type == '1');
|
|
|
|
+ if (isExit) {
|
|
|
|
+ toReturn = { type: 'exit' };
|
|
|
|
+ return toReturn;
|
|
|
|
+ }
|
|
|
|
+ // 查看请假在不在这个时间里
|
|
|
|
+ // 先获得考勤要求时间
|
|
|
|
+ const { start, end } = this.getThisDayAttendTime(date, type);
|
|
|
|
+ if (!start || !end) return;
|
|
|
|
+ // 在请假里找,如果有符合条件的请假信息,说明本单元格内容是请假范围内的
|
|
|
|
+ const lr = les.find(l => {
|
|
|
|
+ const { starttime, endtime } = l;
|
|
|
|
+ if (starttime && endtime) {
|
|
|
|
+ const r1 = moment(starttime).isSameOrBefore(end);
|
|
|
|
+ const r2 = moment(endtime).isAfter(start);
|
|
|
|
+ return r1 && r2;
|
|
}
|
|
}
|
|
|
|
+ });
|
|
|
|
+ if (lr) {
|
|
|
|
+ toReturn = { type: 'leave' };
|
|
|
|
+ return toReturn;
|
|
|
|
+ }
|
|
|
|
+ // 下面就该找考勤了,分为:签没签到=>签到了=>迟没迟到:1,没签到,2正常到位,3迟到
|
|
|
|
+ const stuAtt = this.attendList.find(f => f.studentid === studentid);
|
|
|
|
+ if (!stuAtt) {
|
|
|
|
+ toReturn = { type: 'nosign' };
|
|
|
|
+ return toReturn;
|
|
|
|
+ }
|
|
|
|
+ const { attend } = stuAtt;
|
|
|
|
+ if (!_.isArray(attend)) {
|
|
|
|
+ toReturn = { type: 'nosign' };
|
|
|
|
+ return toReturn;
|
|
|
|
+ }
|
|
|
|
+ // 过滤出该天的签到记录
|
|
|
|
+ let thisday = attend.filter(f => f.date === date);
|
|
|
|
+ if (thisday.length <= 0) {
|
|
|
|
+ toReturn = { type: 'nosign' };
|
|
|
|
+ return toReturn;
|
|
|
|
+ }
|
|
|
|
+ // 找到该天,该签到时间
|
|
|
|
+ // TODO这里做的不是很好,数据关系不强,没法定下哪个记录就是上午,哪个记录就是下午,只能靠时间决定
|
|
|
|
+ // 应该以下个检查开始时间为节点去过滤,am使用pm的开始时间为节点,pm使用bd的开始时间为节点,bd使用自己的开始时间往后查
|
|
|
|
+ thisday = thisday.map(i => ({ ...i, toCompare: `${i.date} ${i.time}` }));
|
|
|
|
+ thisday = _.orderBy(thisday, ['toCompare'], ['asc']);
|
|
|
|
+ let attTime;
|
|
|
|
+ const { pm_start, bd_start } = this.defaultOption;
|
|
|
|
+ if (type === 'am') attTime = `${date} ${pm_start}`;
|
|
|
|
+ else attTime = `${date} ${bd_start}`;
|
|
|
|
+ // 找到在上述3个时间点前的记录,然后看记录是迟到还是正常到位
|
|
|
|
+ const r = thisday.find(f => {
|
|
|
|
+ if (type === 'am' || type === 'pm') return moment(f.toCompare).isSameOrBefore(attTime);
|
|
|
|
+ else return moment(f.toCompare).isSameOrAfter(attTime);
|
|
|
|
+ });
|
|
|
|
+ if (!r) {
|
|
|
|
+ toReturn = { type: 'nosign' };
|
|
|
|
+ return toReturn;
|
|
|
|
+ }
|
|
|
|
+ const { status } = r;
|
|
|
|
+ if (status == '1') {
|
|
|
|
+ toReturn = { type: 'ok' };
|
|
} else {
|
|
} else {
|
|
- for (const data of res.data) {
|
|
|
|
- for (const attend of data.attend) {
|
|
|
|
- let newdata = { stuname: data.stuname, ...attend };
|
|
|
|
- newdatas.push(newdata);
|
|
|
|
- }
|
|
|
|
|
|
+ toReturn = { type: 'late' };
|
|
|
|
+ }
|
|
|
|
+ return toReturn;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 获取对应签到时间
|
|
|
|
+ getThisDayAttendTime(date, type) {
|
|
|
|
+ let start = _.get(this.defaultOption, `${type}_start`);
|
|
|
|
+ let end = _.get(this.defaultOption, `${type}_end`);
|
|
|
|
+ if (!start || !end) {
|
|
|
|
+ console.warn('未找到系统设置的考勤时间');
|
|
|
|
+ return {};
|
|
|
|
+ }
|
|
|
|
+ start = `${date} ${start}`;
|
|
|
|
+ end = `${date} ${end}`;
|
|
|
|
+ return { start, end };
|
|
|
|
+ },
|
|
|
|
+ /**
|
|
|
|
+ * 获取表头,使用form中的termid和classid制作表头
|
|
|
|
+ */
|
|
|
|
+ async getColumn() {
|
|
|
|
+ let head = [{ label: '学生', prop: 'name' }];
|
|
|
|
+ const { termid, classid } = this.defaultOption;
|
|
|
|
+ if (classid) {
|
|
|
|
+ // 有classid,就一定有classList否则数据也不能出现
|
|
|
|
+ const cla = this.classList.find(f => f.id === classid);
|
|
|
|
+ const claRes = await this.getClass(classid);
|
|
|
|
+ if (!this.$checkRes(claRes)) {
|
|
|
|
+ this.tip('数据错误: 未找到指定班级!', 'error');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ const { startdate, enddate } = claRes.data;
|
|
|
|
+ if (!(startdate && enddate)) {
|
|
|
|
+ this.tip('数据错误: 未找到开始,结束日期', 'error');
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
|
|
+ let dayList = this.getDayList(startdate, enddate);
|
|
|
|
+ return [...head, ...dayList];
|
|
|
|
+ } else {
|
|
|
|
+ this.tip('请选择班级', 'warning');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ /**
|
|
|
|
+ * 获取天列表
|
|
|
|
+ * @param start 开始日期
|
|
|
|
+ * @param end 结束日期
|
|
|
|
+ */
|
|
|
|
+ getDayList(start, end) {
|
|
|
|
+ const day = moment(end).diff(moment(start), 'days');
|
|
|
|
+ let arr = [];
|
|
|
|
+ for (let d = 0; d <= day; d++) {
|
|
|
|
+ const ad = moment(start)
|
|
|
|
+ .add(d, 'days')
|
|
|
|
+ .format('YYYY-MM-DD');
|
|
|
|
+ let obj = { label: ad, prop: `day${d}` };
|
|
|
|
+ arr.push(obj);
|
|
|
|
+ }
|
|
|
|
+ return arr;
|
|
|
|
+ },
|
|
|
|
+ // 获取学生
|
|
|
|
+ async getStudent() {
|
|
|
|
+ const { classid } = this.defaultOption;
|
|
|
|
+ if (!classid) {
|
|
|
|
+ this.tip('请选择班级', 'warning');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ const res = await this.stuquery({ classid });
|
|
|
|
+ if (this.$checkRes(res)) {
|
|
|
|
+ this.$set(this, 'list', res.data);
|
|
}
|
|
}
|
|
|
|
+ },
|
|
|
|
+ // 获取考勤
|
|
|
|
+ async getAttendance() {
|
|
|
|
+ const { classid } = this.defaultOption;
|
|
|
|
+ if (!classid) {
|
|
|
|
+ this.tip('请选择班级', 'warning');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ const res = await this.query({ classid });
|
|
if (this.$checkRes(res)) {
|
|
if (this.$checkRes(res)) {
|
|
- this.$set(this, `total`, newdatas.length);
|
|
|
|
- let _newdatas = newdatas.splice(skip, skip + limit);
|
|
|
|
- this.$set(this, `list`, _newdatas);
|
|
|
|
|
|
+ this.$set(this, 'attendList', res.data);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
|
|
+ // 获取请假
|
|
|
|
+ async getLeave() {
|
|
|
|
+ const { classid } = this.defaultOption;
|
|
|
|
+ if (!classid) {
|
|
|
|
+ this.tip('请选择班级', 'warning');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ const res = await this.studentLeave({ classid });
|
|
|
|
+ if (this.$checkRes(res)) {
|
|
|
|
+ this.$set(this, 'leaveList', res.data);
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ /**
|
|
|
|
+ * 提示通用,本页面测试使用
|
|
|
|
+ * @param message 提示信息
|
|
|
|
+ * @param type 提示类型 success/error/warning/info
|
|
|
|
+ * @param duration 持续时间,默认3000;0为不消失
|
|
|
|
+ */
|
|
|
|
+ tip(message, type, duration = 3000) {
|
|
|
|
+ this.$message({ type, message, duration });
|
|
|
|
+ },
|
|
|
|
+ chageWord(type) {
|
|
|
|
+ let word;
|
|
|
|
+ let color;
|
|
|
|
+ switch (type) {
|
|
|
|
+ case 'ok':
|
|
|
|
+ word = '签到';
|
|
|
|
+ color = '#00E500';
|
|
|
|
+ break;
|
|
|
|
+ case 'late':
|
|
|
|
+ word = '迟到';
|
|
|
|
+ color = '#F56C6C';
|
|
|
|
+ break;
|
|
|
|
+ case 'nosign':
|
|
|
|
+ word = '';
|
|
|
|
+ break;
|
|
|
|
+ case 'leave':
|
|
|
|
+ word = '请假';
|
|
|
|
+ color = '#30DFF4';
|
|
|
|
+ break;
|
|
|
|
+ case 'exit':
|
|
|
|
+ word = '退出';
|
|
|
|
+ color = '#909399';
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ word = '';
|
|
|
|
+ color = '';
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ return { word, color };
|
|
|
|
+ },
|
|
},
|
|
},
|
|
watch: {
|
|
watch: {
|
|
defaultOption: {
|
|
defaultOption: {
|
|
|
|
+ immediate: true,
|
|
|
|
+ deep: true,
|
|
handler(val) {
|
|
handler(val) {
|
|
- this.form.termid = this.defaultOption.termid;
|
|
|
|
- this.getBatch(this.form.termid);
|
|
|
|
|
|
+ if (!_.get(this, 'options')) {
|
|
|
|
+ this.$set(this, `options`, _.cloneDeep(val));
|
|
|
|
+ this.search();
|
|
|
|
+ } else {
|
|
|
|
+ let nid = _.get(val, 'classid');
|
|
|
|
+ let oid = _.get(this.options, 'classid');
|
|
|
|
+ if (nid && !_.isEqual(nid, oid)) {
|
|
|
|
+ this.$set(this, `options`, _.cloneDeep(val));
|
|
|
|
+ this.search();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
},
|
|
},
|
|
- deep: true,
|
|
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
};
|