AttendanceView.vue 5.85 KB
<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>