9441e28e
刘淇
考勤打卡
|
1
|
<template>
|
bc215493
刘淇
考勤记录
|
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<view class="attendance-page">
<!-- 顶部统计卡片 -->
<view class="stat-card">
<view class="stat-item">
<view class="stat-label">本月打卡</view>
<view class="stat-value">{{ statData.checkInDays }}</view>
</view>
<view class="stat-item">
<view class="stat-label">正常天数</view>
<view class="stat-value">{{ statData.normalDays }}</view>
</view>
<view class="stat-item">
<view class="stat-label">异常天数</view>
<view class="stat-value">{{ statData.errorDays }}</view>
</view>
|
9441e28e
刘淇
考勤打卡
|
17
|
</view>
|
bc215493
刘淇
考勤记录
|
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
<!-- 手写内联日历,无弹窗 -->
<view class="calendar-wrap">
<view class="calendar-header">
<!-- 左右横向排列:上一月 | 年月标题 | 下一月 -->
<view class="calendar-btn" @click="prevMonth">上一月</view>
<view class="calendar-title">{{ currentYear }}年{{ currentMonth }}月</view>
<!-- 当前是最新月份时,隐藏下个月按钮 -->
<view v-if="canNext" class="calendar-btn" @click="nextMonth">下一月</view>
<view v-else class="calendar-btn empty-btn"></view>
</view>
<!-- 星期头部 -->
<view class="week-row">
<view class="week-item" v-for="w in weekList" :key="w">{{ w }}</view>
</view>
<!-- 日期网格 -->
<view class="day-row" v-for="(row, rIdx) in dayRows" :key="rIdx">
<view class="day-item" v-for="(day, dIdx) in row" :key="dIdx" :class="{
|
9441e28e
刘淇
考勤打卡
|
35
36
|
'day-item--other': day.otherMonth,
'day-item--active': day.dateStr === selectDate,
|
bc215493
刘淇
考勤记录
|
37
38
39
40
|
}" @click="onDayClick(day)">
<view class="day-text">{{ day.day }}</view>
<view v-if="day.dot" class="day-dot" :style="{ backgroundColor: day.dotColor }"></view>
</view>
|
9441e28e
刘淇
考勤打卡
|
41
42
|
</view>
</view>
|
bc215493
刘淇
考勤记录
|
43
44
45
46
|
<!-- 打卡详情区域 -->
<view class="detail-card">
<view v-if="!currentAttendance">
<view class="empty-text">暂无考勤记录</view>
|
9441e28e
刘淇
考勤打卡
|
47
|
</view>
|
bc215493
刘淇
考勤记录
|
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
<view v-else>
<view class="detail-row">
<view class="detail-title">上班打卡时间:</view>
<view class="detail-text">{{ currentAttendance.onDutyTime || '缺少打卡记录' }}</view>
</view>
<view class="detail-row">
<view class="detail-title">上班打卡地点:</view>
<view class="detail-text">{{ currentAttendance.onDutyAddress || '-' }}</view>
</view>
<view class="divider"></view>
<view class="detail-row">
<view class="detail-title">下班打卡时间:</view>
<view class="detail-text">{{ currentAttendance.offDutyTime || '缺少打卡记录' }}</view>
</view>
<view class="detail-row">
<view class="detail-title">下班打卡地点:</view>
<view class="detail-text">{{ currentAttendance.offDutyAddress || '-' }}</view>
</view>
<!-- 可选:展示异常原因 -->
<view v-if="currentAttendance.abnormalReason" class="detail-row">
<view class="detail-title">异常原因:</view>
<view class="detail-text" style="color:#f56c6c">{{ currentAttendance.abnormalReason }}</view>
</view>
|
9441e28e
刘淇
考勤打卡
|
71
72
|
</view>
</view>
|
bc215493
刘淇
考勤记录
|
73
|
</view>
|
9441e28e
刘淇
考勤打卡
|
74
|
</template>
|
9441e28e
刘淇
考勤打卡
|
75
|
<script setup lang="ts">
|
bc215493
刘淇
考勤记录
|
76
77
78
|
import { ref, onMounted, computed, watch } from 'vue';
import { attendanceDaily, dayDetail } from '@/api/user'
const weekList = ['日', '一', '二', '三', '四', '五', '六'];
|
9441e28e
刘淇
考勤打卡
|
79
80
81
82
83
84
85
|
const today = new Date();
const todayTs = today.getTime();
// 当前显示月份
const currentYear = ref(today.getFullYear());
const currentMonth = ref(today.getMonth() + 1);
const selectDate = ref('');
const dayRows = ref<any[]>([]);
|
9441e28e
刘淇
考勤打卡
|
86
87
|
// 是否允许点击下一月:判断当前展示月份是否小于本月
const canNext = computed(() => {
|
bc215493
刘淇
考勤记录
|
88
|
const showMonthDate = new Date(currentYear.value, currentMonth.value - 1, 1);
|
9441e28e
刘淇
考勤打卡
|
89
90
91
92
|
const nowMonthDate = new Date(today.getFullYear(), today.getMonth(), 1);
return showMonthDate < nowMonthDate;
});
|
bc215493
刘淇
考勤记录
|
93
|
// 统计数据
|
9441e28e
刘淇
考勤打卡
|
94
|
const statData = ref({
|
bc215493
刘淇
考勤记录
|
95
96
97
|
checkInDays: 0,
normalDays: 0,
errorDays: 0
|
9441e28e
刘淇
考勤打卡
|
98
|
});
|
bc215493
刘淇
考勤记录
|
99
100
|
// 当月考勤打点字典 {dateStr: {dot:string, abnormalReason:string}}
const monthDayMap = ref<Record<string, { dot: string | null, abnormalReason: string | null }>>({});
|
9441e28e
刘淇
考勤打卡
|
101
|
|
bc215493
刘淇
考勤记录
|
102
103
|
// 选中日期详情
const currentAttendance = ref<any>(null);
|
9441e28e
刘淇
考勤打卡
|
104
|
|
bc215493
刘淇
考勤记录
|
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
// 拉取月度考勤汇总(stat + 当月日期打点)
const getMonthAttendance = async () => {
try {
const month = `${currentYear.value}-${String(currentMonth.value).padStart(2, '0')}`;
const res = await attendanceDaily({ month });
// 赋值顶部统计
statData.value.checkInDays = res.stat.clockDays;
statData.value.normalDays = res.stat.normalDays;
statData.value.errorDays = res.stat.abnormalDays;
// 构建日期映射字典
const map: Record<string, { dot: string | null, abnormalReason: string | null }> = {};
res.days.forEach((item: any) => {
map[item.date] = {
dot: item.dot,
abnormalReason: item.abnormalReason
};
})
monthDayMap.value = map;
// 重新渲染日历
renderMonth();
} catch (err) {
console.error('获取月度考勤失败', err);
uni.showToast({ title: '加载月度考勤失败', icon: 'none' });
|
9441e28e
刘淇
考勤打卡
|
128
|
}
|
bc215493
刘淇
考勤记录
|
129
|
};
|
9441e28e
刘淇
考勤打卡
|
130
131
132
133
134
135
136
137
138
139
140
141
142
|
// 生成月份日期网格
const renderMonth = () => {
const year = currentYear.value;
const month = currentMonth.value;
// 当月第一天
const firstDay = new Date(year, month - 1, 1);
// 当月最后一天
const lastDay = new Date(year, month, 0);
// 上个月最后一天
const prevLastDay = new Date(year, month - 1, 0).getDate();
// 当月第一天星期 0周日~6周六
const firstWeek = firstDay.getDay();
|
9441e28e
刘淇
考勤打卡
|
143
144
145
|
const rows: any[] = [];
let row: any[] = [];
// 填充上月尾部
|
bc215493
刘淇
考勤记录
|
146
|
for (let i = 0; i < firstWeek; i++) {
|
9441e28e
刘淇
考勤打卡
|
147
148
149
150
|
const dayNum = prevLastDay - firstWeek + i + 1;
row.push({
day: dayNum,
otherMonth: true,
|
bc215493
刘淇
考勤记录
|
151
|
dateStr: `${year}-${String(month - 1).padStart(2, '0')}-${String(dayNum).padStart(2, '0')}`
|
9441e28e
刘淇
考勤打卡
|
152
153
154
|
})
}
// 填充当月日期
|
bc215493
刘淇
考勤记录
|
155
156
157
158
|
for (let d = 1; d <= lastDay.getDate(); d++) {
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
const mark = monthDayMap.value[dateStr];
const item: any = {
|
9441e28e
刘淇
考勤打卡
|
159
160
161
162
163
164
|
day: d,
otherMonth: false,
dateStr,
dot: false,
dotColor: ''
};
|
bc215493
刘淇
考勤记录
|
165
|
if (mark?.dot) {
|
9441e28e
刘淇
考勤打卡
|
166
|
item.dot = true;
|
bc215493
刘淇
考勤记录
|
167
168
169
170
171
|
if (mark.dot === 'abnormal') {
item.dotColor = '#f56c6c';
} else if (mark.dot === 'partial') {
item.dotColor = '#cccccc';
}
|
9441e28e
刘淇
考勤打卡
|
172
173
|
}
row.push(item);
|
bc215493
刘淇
考勤记录
|
174
|
if (row.length === 7) {
|
9441e28e
刘淇
考勤打卡
|
175
176
177
178
179
180
|
rows.push(row);
row = [];
}
}
// 填充下月开头
let nextDay = 1;
|
bc215493
刘淇
考勤记录
|
181
|
while (row.length < 7) {
|
9441e28e
刘淇
考勤打卡
|
182
183
|
row.push({
day: nextDay,
|
bc215493
刘淇
考勤记录
|
184
185
|
otherMonth: true,
dateStr: `${year}-${String(month + 1).padStart(2, '0')}-${String(nextDay).padStart(2, '0')}`
|
9441e28e
刘淇
考勤打卡
|
186
187
188
189
190
191
192
193
194
|
})
nextDay++;
}
rows.push(row);
dayRows.value = rows;
};
// 上一月
const prevMonth = () => {
|
bc215493
刘淇
考勤记录
|
195
|
if (currentMonth.value === 1) {
|
9441e28e
刘淇
考勤打卡
|
196
197
|
currentYear.value--;
currentMonth.value = 12;
|
bc215493
刘淇
考勤记录
|
198
|
} else {
|
9441e28e
刘淇
考勤打卡
|
199
200
|
currentMonth.value--;
}
|
bc215493
刘淇
考勤记录
|
201
|
getMonthAttendance();
|
9441e28e
刘淇
考勤打卡
|
202
203
204
205
206
|
};
// 下一月
const nextMonth = () => {
let nextY = currentYear.value;
let nextM = currentMonth.value + 1;
|
bc215493
刘淇
考勤记录
|
207
|
if (nextM > 12) {
|
9441e28e
刘淇
考勤打卡
|
208
|
nextY++;
|
bc215493
刘淇
考勤记录
|
209
|
nextM = 1;
|
9441e28e
刘淇
考勤打卡
|
210
211
212
|
}
currentYear.value = nextY;
currentMonth.value = nextM;
|
bc215493
刘淇
考勤记录
|
213
|
getMonthAttendance();
|
9441e28e
刘淇
考勤打卡
|
214
215
|
};
|
bc215493
刘淇
考勤记录
|
216
217
|
// 点击日期:获取当日详情
const onDayClick = async (day: any) => {
|
9441e28e
刘淇
考勤打卡
|
218
|
// 禁止点击其他月份
|
bc215493
刘淇
考勤记录
|
219
|
if (day.otherMonth) return;
|
9441e28e
刘淇
考勤打卡
|
220
|
const selectDay = new Date(day.dateStr);
|
bc215493
刘淇
考勤记录
|
221
|
if (selectDay > today) {
|
9441e28e
刘淇
考勤打卡
|
222
|
currentAttendance.value = null;
|
bc215493
刘淇
考勤记录
|
223
|
selectDate.value = day.dateStr;
|
9441e28e
刘淇
考勤打卡
|
224
225
226
|
return;
}
selectDate.value = day.dateStr;
|
bc215493
刘淇
考勤记录
|
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
|
try {
const res = await dayDetail({ date: day.dateStr });
// 取第一条记录,大多数场景单条记录
const record = res.records?.[0] ?? null;
if (record) {
currentAttendance.value = {
...record,
abnormalReason: res.abnormalReason
};
} else {
currentAttendance.value = null;
}
} catch (err) {
console.error('获取当日考勤详情失败', err);
uni.showToast({ title: '加载当日考勤失败', icon: 'none' });
currentAttendance.value = null;
}
|
9441e28e
刘淇
考勤打卡
|
244
245
|
};
|
bc215493
刘淇
考勤记录
|
246
247
248
|
// 页面初始化
onMounted(async () => {
const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
|
9441e28e
刘淇
考勤打卡
|
249
|
selectDate.value = todayStr;
|
bc215493
刘淇
考勤记录
|
250
251
252
|
await getMonthAttendance();
// 默认选中今天,加载当日详情
await onDayClick({ dateStr: todayStr, otherMonth: false });
|
9441e28e
刘淇
考勤打卡
|
253
|
});
|
9441e28e
刘淇
考勤打卡
|
254
|
</script>
|
9441e28e
刘淇
考勤打卡
|
255
|
<style scoped lang="scss">
|
bc215493
刘淇
考勤记录
|
256
|
.attendance-page {
|
9441e28e
刘淇
考勤打卡
|
257
258
259
260
|
padding: 20rpx;
background-color: #f5f5f7;
min-height: 100vh;
}
|
bc215493
刘淇
考勤记录
|
261
262
|
.stat-card {
|
9441e28e
刘淇
考勤打卡
|
263
264
265
|
display: flex;
background-color: #3c9cff;
border-radius: 16rpx;
|
bc215493
刘淇
考勤记录
|
266
267
268
269
270
271
272
273
274
275
276
|
padding: 30rpx 0;
margin-bottom: 20rpx;
.stat-item {
flex: 1;
text-align: center;
color: #fff;
.stat-label {
font-size: 28rpx;
opacity: 0.9;
|
9441e28e
刘淇
考勤打卡
|
277
|
}
|
bc215493
刘淇
考勤记录
|
278
279
280
281
282
|
.stat-value {
font-size: 36rpx;
margin-top: 10rpx;
font-weight: bold;
|
9441e28e
刘淇
考勤打卡
|
283
284
285
|
}
}
}
|
bc215493
刘淇
考勤记录
|
286
287
288
289
290
291
|
.calendar-wrap {
background: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 20rpx;
|
9441e28e
刘淇
考勤打卡
|
292
|
}
|
bc215493
刘淇
考勤记录
|
293
294
295
|
.calendar-header {
display: flex;
|
9441e28e
刘淇
考勤打卡
|
296
|
justify-content: space-between;
|
bc215493
刘淇
考勤记录
|
297
298
|
align-items: center;
margin-bottom: 20rpx;
|
9441e28e
刘淇
考勤打卡
|
299
|
}
|
bc215493
刘淇
考勤记录
|
300
301
302
303
|
.calendar-title {
font-size: 32rpx;
font-weight: bold;
|
9441e28e
刘淇
考勤打卡
|
304
|
}
|
bc215493
刘淇
考勤记录
|
305
306
307
308
309
310
|
.calendar-btn {
padding: 8rpx 16rpx;
border: 1rpx solid #eee;
border-radius: 8rpx;
font-size: 26rpx;
|
9441e28e
刘淇
考勤打卡
|
311
|
}
|
bc215493
刘淇
考勤记录
|
312
313
314
315
|
.empty-btn {
border-color: transparent;
color: transparent;
|
9441e28e
刘淇
考勤打卡
|
316
|
}
|
bc215493
刘淇
考勤记录
|
317
318
319
320
321
322
323
324
325
326
|
.week-row {
display: flex;
.week-item {
flex: 1;
text-align: center;
font-size: 26rpx;
color: #666;
padding: 10rpx 0;
|
9441e28e
刘淇
考勤打卡
|
327
328
|
}
}
|
bc215493
刘淇
考勤记录
|
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
|
.day-row {
display: flex;
.day-item {
flex: 1;
min-height: 70rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 8rpx 0;
position: relative;
.day-text {
font-size: 28rpx;
|
9441e28e
刘淇
考勤打卡
|
345
|
}
|
bc215493
刘淇
考勤记录
|
346
347
348
349
350
351
352
|
.day-dot {
position: absolute;
bottom: 8rpx;
width: 10rpx;
height: 10rpx;
border-radius: 50%;
|
9441e28e
刘淇
考勤打卡
|
353
354
|
}
}
|
bc215493
刘淇
考勤记录
|
355
356
357
|
.day-item--other {
color: #ccc;
|
9441e28e
刘淇
考勤打卡
|
358
|
}
|
bc215493
刘淇
考勤记录
|
359
360
361
|
.day-item--active {
color: #f00;
|
9441e28e
刘淇
考勤打卡
|
362
363
|
}
}
|
bc215493
刘淇
考勤记录
|
364
365
366
367
368
369
370
371
372
373
374
|
.detail-card {
background: #fff;
border-radius: 12rpx;
padding: 30rpx;
.empty-text {
text-align: center;
color: #999;
font-size: 30rpx;
padding: 40rpx 0;
|
9441e28e
刘淇
考勤打卡
|
375
|
}
|
bc215493
刘淇
考勤记录
|
376
377
378
379
380
381
382
383
384
|
.detail-row {
margin-bottom: 20rpx;
display: flex;
.detail-title {
font-size: 30rpx;
color: #333;
width: 220rpx;
|
9441e28e
刘淇
考勤打卡
|
385
|
}
|
bc215493
刘淇
考勤记录
|
386
387
388
389
390
|
.detail-text {
flex: 1;
font-size: 30rpx;
color: #666;
|
9441e28e
刘淇
考勤打卡
|
391
392
|
}
}
|
bc215493
刘淇
考勤记录
|
393
394
395
396
397
|
.divider {
height: 1px;
background: #eee;
margin: 30rpx 0;
|
9441e28e
刘淇
考勤打卡
|
398
399
400
|
}
}
</style>
|