import { defineStore } from 'pinia' import * as api from '@/api' import { normalizeReasonTags, getReasonLabels } from '@/config/smoke-reasons' export const useLogsStore = defineStore('logs', { state: () => ({ logs: [], // 记录列表 total: 0, // 总条数 page: 1, // 当前页 pageSize: 20, // 每页数量 hasMore: true, // 是否有更多 loading: false, // 加载状态 refreshing: false, // 刷新状态 queryType: 'all' // 当前筛选类型 }), getters: { // 按日期分组 groupedByDate: (state) => { const groups = {} state.logs.forEach(log => { const date = log.smoke_time?.split('T')[0] || '' if (!groups[date]) { groups[date] = [] } groups[date].push(log) }) return groups }, // 抽烟记录数量 smokeCount: (state) => { return state.logs.filter(log => normalizeLogType(log) === 'smoke').length }, // 忍住记录数量 resistedCount: (state) => { return state.logs.filter(log => normalizeLogType(log) === 'resisted').length }, // 格式化记录列表(按时间倒序,最新的在前) formattedLogs: (state) => { if (!state.logs || state.logs.length === 0) { return [] } // 获取时间戳的辅助函数(统一处理 smoke_at / smoke_time / createtime) const getTime = (log) => { if (log.smoke_at) { return new Date(log.smoke_at).getTime() } if (log.smoke_time) { return new Date(log.smoke_time).getTime() } if (log.createtime) { return typeof log.createtime === 'number' ? log.createtime * 1000 : new Date(log.createtime).getTime() } return 0 } // 先按时间正序(最早在前)计算「距上次抽烟」的时间间隔, // 再按时间倒序用于页面展示,保证间隔只和上一次「抽烟」记录有关 const logsAsc = [...state.logs].sort((a, b) => { const timeA = getTime(a) const timeB = getTime(b) return timeA - timeB }) const intervalById = new Map() let lastSmokeTime = null logsAsc.forEach((log) => { const type = normalizeLogType(log) const currentTime = getTime(log) let interval = '' // 已存在「上次抽烟」时间,计算与其的间隔 if (lastSmokeTime !== null && currentTime > lastSmokeTime) { const diff = currentTime - lastSmokeTime const hours = Math.floor(diff / (1000 * 60 * 60)) const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)) if (hours > 0) { interval = `${hours}小时${minutes}分` } else if (minutes > 0) { interval = `${minutes}分钟` } else { interval = '刚刚' } } intervalById.set(log.id, interval) // 仅当当前记录是「抽烟」时,更新「上次抽烟时间」 if (type === 'smoke' && currentTime > 0) { lastSmokeTime = currentTime } }) // 再按时间倒序排序用于展示 const sortedLogs = [...state.logs].sort((a, b) => { const timeA = getTime(a) const timeB = getTime(b) return timeB - timeA // 倒序:最新的在前 }) return sortedLogs.map((log) => { const type = normalizeLogType(log) const interval = intervalById.get(log.id) || '' // 获取显示日期(用本地日期,避免 UTC 导致差一天) let displayDate = '' if (log.smoke_time) { displayDate = log.smoke_time.split('T')[0] } else if (log.createtime) { const date = typeof log.createtime === 'number' ? new Date(log.createtime * 1000) : new Date(log.createtime) const y = date.getFullYear() const m = String(date.getMonth() + 1).padStart(2, '0') const d = String(date.getDate()).padStart(2, '0') displayDate = `${y}-${m}-${d}` } return { ...log, type, reasonTags: normalizeReasonTags(log.reason_tags), reasonLabels: getReasonLabels(log.reason_tags, type), interval, displayTime: formatLogTime(log.smoke_at || log.smoke_time || log.createtime), displayDate } }) } }, actions: { // 获取记录列表 async fetchLogs(refresh = false, type) { if (this.loading) return this.loading = true if (refresh) { this.refreshing = true this.page = 1 this.logs = [] this.queryType = type || 'all' } try { const res = await api.getLogs({ page: this.page, page_size: this.pageSize, type: this.queryType }) if (res.data) { let newLogs = res.data.items || [] // 按时间倒序排序(最新的在前) newLogs = newLogs.sort((a, b) => { const timeA = new Date(a.smoke_at || a.smoke_time || (a.createtime ? a.createtime * 1000 : 0)).getTime() const timeB = new Date(b.smoke_at || b.smoke_time || (b.createtime ? b.createtime * 1000 : 0)).getTime() return timeB - timeA }) if (refresh) { this.logs = newLogs } else { // 合并并去重(按 id) const existingIds = new Set(this.logs.map(log => log.id)) const uniqueNewLogs = newLogs.filter(log => !existingIds.has(log.id)) this.logs = [...this.logs, ...uniqueNewLogs] // 再次排序确保顺序 this.logs.sort((a, b) => { const timeA = new Date(a.smoke_at || a.smoke_time || (a.createtime ? a.createtime * 1000 : 0)).getTime() const timeB = new Date(b.smoke_at || b.smoke_time || (b.createtime ? b.createtime * 1000 : 0)).getTime() return timeB - timeA }) } this.total = res.data.total || 0 this.hasMore = newLogs.length >= this.pageSize } } catch (e) { console.error('fetchLogs error:', e) uni.showToast({ title: '加载失败', icon: 'none' }) } finally { this.loading = false this.refreshing = false } }, // 加载更多 async loadMore() { if (!this.hasMore || this.loading) return this.page++ await this.fetchLogs(false) }, // 删除记录 async deleteLog(id) { try { await api.deleteLog(id) // 乐观更新:先从列表中移除 const index = this.logs.findIndex(log => log.id === id) if (index > -1) { this.logs.splice(index, 1) this.total-- } uni.showToast({ title: '删除成功', icon: 'success' }) return true } catch (e) { console.error('deleteLog error:', e) uni.showToast({ title: '删除失败', icon: 'none' }) // 失败时刷新列表恢复数据 await this.fetchLogs(true) return false } }, // 更新记录 async updateLog(id, data) { try { await api.updateLog(id, data) // 更新本地数据 const index = this.logs.findIndex(log => log.id === id) if (index > -1) { this.logs[index] = { ...this.logs[index], ...data } } uni.showToast({ title: '更新成功', icon: 'success' }) return true } catch (e) { console.error('updateLog error:', e) uni.showToast({ title: '更新失败', icon: 'none' }) return false } }, // 清空列表 clearLogs() { this.logs = [] this.total = 0 this.page = 1 this.hasMore = true this.queryType = 'all' } } }) // 辅助函数:格式化时间 function formatLogTime(timeStr) { if (!timeStr) return '--:--' let date if (typeof timeStr === 'number') { // 如果是时间戳(秒) date = new Date(timeStr * 1000) } else if (typeof timeStr === 'string') { // 如果是字符串 date = new Date(timeStr) } else { return '--:--' } // 检查日期是否有效 if (isNaN(date.getTime())) { return '--:--' } const hours = String(date.getHours()).padStart(2, '0') const minutes = String(date.getMinutes()).padStart(2, '0') return `${hours}:${minutes}` } function normalizeLogType(log) { const rawType = log?.type if (typeof rawType === 'string') { const value = rawType.toLowerCase() if (value === 'resisted' || value === 'resist') return 'resisted' if (value === 'smoke' || value === 'log_smoke') return 'smoke' } if (typeof rawType === 'number') { if (rawType === 0) return 'resisted' if (rawType === 1) return 'smoke' } if (log?.num === 0) return 'resisted' return 'smoke' }