feat: 考勤功能补全

1. 首页加载时检查工作状态(fetchWorkState)
2. MQTT type=4 工作状态推送处理(上班→亮屏,下班→关NFC+熄屏)
3. PunchStatus 增加打卡时间字段(onPunchTime/offPunchTime)
4. 考勤页显示打卡时间("已上班 07:02"/"已下班 17:05")
5. 首页注入 PunchApi/ScreenController/NfcController

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
dongliang
2026-04-28 19:30:29 +09:30
parent 967b001d46
commit b59fee4bb7
3 changed files with 72 additions and 5 deletions

View File

@@ -11,7 +11,11 @@ data class PunchStatus(
/** 上班打卡状态0=未上班, 1=已上班 */
@SerializedName("onPunchState") val onPunchState: Int = 0,
/** 下班打卡状态1=已下班, 其他=未下班 */
@SerializedName("offPunchState") val offPunchState: Int = 0
@SerializedName("offPunchState") val offPunchState: Int = 0,
/** 上班打卡时间API 可能返回,待验证字段名) */
@SerializedName("onPunchTime") val onPunchTime: String? = null,
/** 下班打卡时间 */
@SerializedName("offPunchTime") val offPunchTime: String? = null
) {
/** 是否已上班 */
val isOnDuty: Boolean get() = onPunchState == 1

View File

@@ -15,9 +15,13 @@ import com.xiaoqu.watch.data.prefs.UserPrefs
import com.xiaoqu.watch.databinding.FragmentHomeBinding
import com.xiaoqu.watch.event.AppEvent
import com.xiaoqu.watch.event.EventBus
import com.xiaoqu.watch.device.nfc.NfcController
import com.xiaoqu.watch.device.screen.ScreenController
import com.xiaoqu.watch.network.ApiResult
import com.xiaoqu.watch.network.api.PunchApi
import com.xiaoqu.watch.network.api.TaskApi
import com.xiaoqu.watch.network.safeApiCall
import org.json.JSONObject
import com.xiaoqu.watch.ui.common.BaseFragment
import com.xiaoqu.watch.ui.widget.StatusBarView
import com.xiaoqu.watch.util.DateUtil
@@ -39,6 +43,9 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
@Inject lateinit var userPrefs: UserPrefs
@Inject lateinit var eventBus: EventBus
@Inject lateinit var taskApi: TaskApi
@Inject lateinit var punchApi: PunchApi
@Inject lateinit var screenController: ScreenController
@Inject lateinit var nfcController: NfcController
// ===== 固定状态栏(不随 ViewPager 滑动) =====
private lateinit var statusBar: StatusBarView
@@ -93,6 +100,9 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
// 加载任务统计数据
fetchStatistics()
// 检查当前工作状态(考勤)
fetchWorkState()
// 监听 MQTT 事件
observeEvents()
@@ -251,9 +261,9 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
findNavController().navigate(R.id.action_home_to_bind)
}
4 -> {
// 工作状态变更
// 工作状态变更MQTT type=4/5 推送)
Timber.d("首页: 收到工作状态变更")
// TODO: 更新考勤状态,控制蓝牙扫描
handleWorkStateChange(event.rawJson)
}
}
}
@@ -273,6 +283,57 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
findNavController().navigate(R.id.action_home_to_taskList, bundle)
}
// ===== 工作状态(考勤联动) =====
/** 首页加载时检查当前工作状态 */
private fun fetchWorkState() {
viewLifecycleOwner.lifecycleScope.launch {
val result = safeApiCall { punchApi.getAttendance() }
if (result is ApiResult.Success && result.data != null) {
val status = result.data
val isWorking = status.isOnDuty && !status.isOffDuty
Timber.d("首页: 工作状态 isWorking=$isWorking (on=${status.onPunchState}, off=${status.offPunchState})")
// 发送工作状态事件(其他模块可监听)
eventBus.emit(AppEvent.WorkStateChanged(isWorking))
}
}
}
/**
* 处理 MQTT 工作状态变更type=4/5
* 服务端推送上班/下班状态
* v1.2.5: msg.action = 0(下班) / 1(上班)
*/
private fun handleWorkStateChange(rawJson: String) {
try {
val json = JSONObject(rawJson)
val action = json.optInt("action", -1)
val isWorking = action == 1
Timber.d("首页: MQTT 工作状态变更 action=$action isWorking=$isWorking")
// 发送工作状态事件
viewLifecycleOwner.lifecycleScope.launch {
eventBus.emit(AppEvent.WorkStateChanged(isWorking))
}
// 屏幕亮度联动
if (isWorking) {
screenController.turnOn()
} else {
// 下班 → 低耗电关闭NFC + 熄屏
nfcController.stopScan()
if (nfcController.isOpen()) nfcController.close()
screenController.turnOff()
}
// 刷新统计数据
fetchStatistics()
} catch (e: Exception) {
Timber.w(e, "首页: 解析工作状态变更异常")
}
}
/** 设置下拉手势 → 进入考勤打卡页(在状态栏区域检测) */
@android.annotation.SuppressLint("ClickableViewAccessibility")
private fun setupPullDownGesture() {

View File

@@ -125,7 +125,8 @@ class PunchFragment : BaseFragment<FragmentPunchBinding>() {
}
// 已上班 + 已下班
status.isOnDuty && status.isOffDuty -> {
binding.tvPunchStatus.text = "已下班"
val timeText = if (!status.offPunchTime.isNullOrEmpty()) "已下班 ${status.offPunchTime}" else "已下班"
binding.tvPunchStatus.text = timeText
binding.tvPunchStatus.setTextColor(requireContext().getColor(R.color.text_secondary))
binding.btnPunch.text = "下班打卡"
binding.btnPunch.setBackgroundColor(requireContext().getColor(R.color.primary))
@@ -136,7 +137,8 @@ class PunchFragment : BaseFragment<FragmentPunchBinding>() {
}
// 已上班 + 未下班
status.isOnDuty && !status.isOffDuty -> {
binding.tvPunchStatus.text = "已上班"
val timeText = if (!status.onPunchTime.isNullOrEmpty()) "已上班 ${status.onPunchTime}" else "已上班"
binding.tvPunchStatus.text = timeText
binding.tvPunchStatus.setTextColor(requireContext().getColor(R.color.success))
binding.btnPunch.text = "下班打卡"
binding.btnPunch.setBackgroundColor(requireContext().getColor(R.color.primary))