AttendanceView.vue
5.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
<template>
<div class="attendance-container">
<el-card shadow="never" class="search-card">
<el-form :inline="true" :model="searchForm" size="small">
<el-form-item label="工种">
<el-select v-model="searchForm.workType" placeholder="全部工种" clearable>
<el-option label="保洁" value="CLEANING" />
<el-option label="保安" value="SECURITY" />
<el-option label="工程维修" value="ENGINEERING" />
</el-select>
</el-form-item>
<el-form-item label="人员">
<el-select v-model="searchForm.userId" placeholder="全部人员" filterable clearable>
<el-option v-for="user in userList" :key="user.user_id"
:label="user.name" :value="user.user_id" />
</el-select>
</el-form-item>
<el-form-item label="打卡类型">
<el-select v-model="searchForm.punchType" placeholder="全部" clearable>
<el-option label="上班" value="ON" />
<el-option label="下班" value="OFF" />
</el-select>
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker v-model="dateRange" type="datetimerange"
range-separator="至" start-placeholder="开始" end-placeholder="结束"
format="yyyy-MM-dd HH:mm:ss" value-format="yyyy-MM-dd HH:mm:ss" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch" icon="el-icon-search">查询</el-button>
<el-button @click="handleExport" icon="el-icon-download">导出</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never">
<div slot="header"><span>打卡记录</span></div>
<el-table :data="tableData" border stripe size="small" v-loading="loading">
<el-table-column prop="user_name" label="姓名" width="100" />
<el-table-column prop="work_type" label="工种" width="100">
<template slot-scope="scope">{{ workTypeLabel(scope.row.work_type) }}</template>
</el-table-column>
<el-table-column prop="punch_type" label="打卡类型" width="100">
<template slot-scope="scope">
<el-tag :type="scope.row.punch_type === 'ON' ? 'success' : 'warning'" size="small">
{{ scope.row.punch_type === 'ON' ? '上班' : '下班' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="punch_time" label="打卡时间" width="180" />
<el-table-column prop="punch_address" label="打卡地点" min-width="200" />
<el-table-column prop="longitude" label="经度" width="120" />
<el-table-column prop="latitude" label="纬度" width="120" />
</el-table>
<el-pagination
style="margin-top: 16px; text-align: right;"
@size-change="handleSizeChange" @current-change="handlePageChange"
:current-page="pagination.page" :page-sizes="[10, 20, 50, 100]"
:page-size="pagination.row" :total="pagination.total"
layout="total, sizes, prev, pager, next, jumper" />
</el-card>
</div>
</template>
<script>
import { queryAttendanceRecords } from '@/api/property/propertyApi'
import { queryPropertyUsers } from '@/api/property/propertyApi'
export default {
name: 'AttendanceView',
data() {
return {
searchForm: { workType: '', userId: '', punchType: '' },
dateRange: [],
userList: [],
tableData: [],
loading: false,
pagination: { page: 1, row: 20, total: 0 }
}
},
mounted() { this.loadUserList(); this.handleSearch() },
methods: {
workTypeLabel(type) {
const map = { CLEANING: '保洁', SECURITY: '保安', ENGINEERING: '工程维修' }
return map[type] || type
},
async loadUserList() {
try {
const res = await queryPropertyUsers({ page: 1, row: 200 })
this.userList = res.data && res.data.data || []
} catch (e) { console.error(e) }
},
async handleSearch() {
this.loading = true
try {
const params = {
page: this.pagination.page,
row: this.pagination.row,
...this.searchForm
}
// 移除空值
Object.keys(params).forEach(k => { if (!params[k]) delete params[k] })
if (this.dateRange && this.dateRange.length === 2) {
params.startTime = this.dateRange[0]
params.endTime = this.dateRange[1]
}
const res = await queryAttendanceRecords(params)
this.tableData = res.data && res.data.data || []
this.pagination.total = res.data && res.data.total || 0
} catch (e) {
this.$message.error('查询失败')
} finally { this.loading = false }
},
handleExport() {
// 导出打卡记录为CSV
if (!this.tableData.length) { this.$message.warning('无数据可导出'); return }
const headers = ['姓名', '工种', '打卡类型', '打卡时间', '打卡地点', '经度', '纬度']
const rows = this.tableData.map(r => [
r.user_name, this.workTypeLabel(r.work_type),
r.punch_type === 'ON' ? '上班' : '下班', r.punch_time,
r.punch_address, r.longitude, r.latitude
])
let csv = '' + headers.join(',') + '\n'
rows.forEach(row => { csv += row.join(',') + '\n' })
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = '打卡记录_' + new Date().toISOString().slice(0, 10) + '.csv'
link.click()
this.$message.success('导出成功')
},
handleSizeChange(val) { this.pagination.row = val; this.handleSearch() },
handlePageChange(val) { this.pagination.page = val; this.handleSearch() }
}
}
</script>
<style scoped>
.attendance-container { padding: 16px; }
.search-card { margin-bottom: 16px; }
</style>