Files
oa_app/pages/tabs/clock.vue
T

130 lines
5.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page edge">
<fy-header title="打卡" />
<fy-scroll class="edge-scroll" dock @refresh="reload">
<view v-if="!canPunch" class="deny">当前账号没有打卡权限</view>
<view v-else class="body">
<view class="clock-box">
<text class="now">{{ now }}</text>
<text class="shift">班次 {{ rules.workStart }}{{ rules.workEnd }}{{ locName ? ' · ' + locName : '' }}</text>
</view>
<view class="grid">
<view class="stat card">
<text class="lab">上班</text>
<text class="val" :class="{ ok: inRec }">{{ inRec ? inRec.time + ' 已打卡' : '未打卡' }}</text>
<text class="tiny">{{ inRec ? (inRec.status || '正常') : '待打卡' }}</text>
</view>
<view class="stat card">
<text class="lab">下班</text>
<text class="val" :class="{ ok: outRec }">{{ outRec ? outRec.time + ' 已打卡' : '未打卡' }}</text>
<text class="tiny">{{ outRec ? (outRec.status || '正常') : '待打卡' }}</text>
</view>
</view>
<wd-button type="primary" block size="large" :loading="punching" @click="doPunch">{{ outRec ? '更新打卡' : '打卡' }}</wd-button>
<text class="geo">将使用手机定位校验打卡范围</text>
<view class="quick">
<text class="q" @click="goKind('card')">补卡</text>
<text class="q" @click="goKind('leave')">请假</text>
<text class="q" @click="goKind('overtime')">加班</text>
<text class="q" @click="goKind('outing')">外出</text>
</view>
<view class="sec">
<text class="st">今日及近期记录</text>
</view>
<view class="card">
<view v-if="!records.length" class="empty">暂无记录</view>
<view v-for="r in records" :key="r.id || r.time" class="rec">
<view>
<text class="rt">{{ r.title || r.kind || '打卡' }} {{ r.time || r.punchTime || '' }}</text>
<text class="rl">{{ r.location || r.address || '' }}</text>
</view>
<text class="ok">{{ r.status || '正常' }}</text>
</view>
</view>
</view>
</fy-scroll>
</view>
</template>
<script>
import FyHeader from '../../components/fy-header.vue'
import FyScroll from '../../components/fy-scroll.vue'
import { store } from '../../utils/store.js'
import { clockText, toast } from '../../utils/format.js'
export default {
components: { FyHeader, FyScroll },
data() {
return { now: clockText(), timer: 0, punching: false, off: null }
},
computed: {
canPunch() { return store.can('personal', 'punch') },
rules() { return store.attendRules },
records() { return store.attendance || [] },
locName() {
const locs = this.rules.locations || []
return locs[0] ? (locs[0].name || locs[0].title || '') : ''
},
inRec() {
return this.records.find((r) => /上班|in|on/i.test(String(r.title || r.kind || r.type || '')))
},
outRec() {
return this.records.find((r) => /下班|out/i.test(String(r.title || r.kind || r.type || '')))
}
},
onShow() {
if (store.phase !== 'app') uni.reLaunch({ url: '/pages/login/login' })
store.refreshAttendance()
this.timer = setInterval(() => { this.now = clockText() }, 1000)
this.off = store.on(() => this.$forceUpdate())
},
onHide() {
if (this.timer) clearInterval(this.timer)
if (this.off) this.off()
},
methods: {
async doPunch() {
if (this.punching) return
this.punching = true
try {
const msg = await store.punch()
toast(msg, 'success')
} catch (e) {
toast(e.message || '打卡失败')
} finally {
this.punching = false
}
},
reload() { store.refreshAttendance() },
goKind(kind) {
if (!store.can('personal', 'apply')) return toast('没有发起申请权限')
uni.navigateTo({ url: '/pages/apply/form?kind=' + kind })
}
}
}
</script>
<style scoped>
.body { padding: 16rpx 24rpx 40rpx; }
.clock-box { background: #fff; border-radius: 24rpx; padding: 48rpx 0 40rpx; text-align: center; border: 1rpx solid rgba(17,24,39,0.06); }
.now { display: block; font-size: 72rpx; font-weight: 650; letter-spacing: 2rpx; color: #111827; }
.shift { font-size: 22rpx; color: #4B5563; }
.grid { display: flex; gap: 16rpx; margin: 20rpx 0; }
.stat { flex: 1; padding: 24rpx; }
.lab { font-size: 26rpx; font-weight: 600; display: block; }
.val { font-size: 24rpx; color: #4B5563; display: block; margin-top: 12rpx; }
.val.ok { color: #16A34A; font-weight: 650; }
.tiny { font-size: 20rpx; color: #6B7280; }
.geo { display: block; text-align: center; font-size: 22rpx; color: #4B5563; margin: 16rpx 0 24rpx; }
.body :deep(.wd-button) { margin-top: 8rpx; }
.quick { display: flex; gap: 12rpx; }
.q { flex: 1; height: 72rpx; background: #fff; border-radius: 12rpx; text-align: center; line-height: 72rpx; font-size: 24rpx; }
.sec { margin: 32rpx 0 12rpx; font-size: 28rpx; font-weight: 600; }
.rec { display: flex; justify-content: space-between; align-items: center; padding: 24rpx 28rpx; border-bottom: 1rpx solid #E8ECF2; }
.rt { display: block; font-size: 28rpx; font-weight: 600; }
.rl { display: block; font-size: 22rpx; color: #6B7280; }
.ok { color: #16A34A; font-size: 24rpx; }
.empty { padding: 40rpx; text-align: center; color: #6B7280; }
.deny { padding: 120rpx 40rpx; text-align: center; color: #6B7280; }
</style>