Implement login functionality and UI updates across the application. Added silent login process in App.vue, updated styles for various components, and integrated smoke record dialog. Enhanced onboarding and profile pages with improved layouts and user experience. Updated manifest and configuration files for deployment. Added easycom configuration for component auto-import.
This commit is contained in:
@@ -7,3 +7,4 @@ export default pinia
|
||||
export * from './user'
|
||||
export * from './dashboard'
|
||||
export * from './profile'
|
||||
export * from './logs'
|
||||
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import * as api from '@/api'
|
||||
|
||||
export const useLogsStore = defineStore('logs', {
|
||||
state: () => ({
|
||||
logs: [], // 记录列表
|
||||
total: 0, // 总条数
|
||||
page: 1, // 当前页
|
||||
pageSize: 20, // 每页数量
|
||||
hasMore: true, // 是否有更多
|
||||
loading: false, // 加载状态
|
||||
refreshing: false // 刷新状态
|
||||
}),
|
||||
|
||||
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 => log.num > 0).length
|
||||
},
|
||||
|
||||
// 忍住记录数量
|
||||
resistedCount: (state) => {
|
||||
return state.logs.filter(log => log.num === 0 && log.level === 0).length
|
||||
},
|
||||
|
||||
// 格式化记录列表(按时间倒序,最新的在前)
|
||||
formattedLogs: (state) => {
|
||||
if (!state.logs || state.logs.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
// 获取时间戳的辅助函数
|
||||
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 sortedLogs = [...state.logs].sort((a, b) => {
|
||||
const timeA = getTime(a)
|
||||
const timeB = getTime(b)
|
||||
return timeB - timeA // 倒序:最新的在前
|
||||
})
|
||||
|
||||
return sortedLogs.map((log, index) => {
|
||||
const type = (log.level === 0 && log.num === 0) ? 'resisted' : 'smoke'
|
||||
|
||||
// 计算间隔时间:当前记录与上一条记录的间隔(上一条是 index-1,因为已倒序)
|
||||
let interval = ''
|
||||
if (index > 0) {
|
||||
const currentTime = getTime(log)
|
||||
const prevTime = getTime(sortedLogs[index - 1])
|
||||
const diff = prevTime - currentTime // 上一条时间 - 当前时间(因为已倒序)
|
||||
|
||||
if (diff > 0) {
|
||||
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 = '刚刚'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取显示日期
|
||||
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)
|
||||
displayDate = date.toISOString().split('T')[0]
|
||||
}
|
||||
|
||||
return {
|
||||
...log,
|
||||
type,
|
||||
interval,
|
||||
displayTime: formatLogTime(log.smoke_at || log.smoke_time || log.createtime),
|
||||
displayDate
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 获取记录列表
|
||||
async fetchLogs(refresh = false) {
|
||||
if (this.loading) return
|
||||
|
||||
this.loading = true
|
||||
if (refresh) {
|
||||
this.refreshing = true
|
||||
this.page = 1
|
||||
this.logs = []
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await api.getLogs({
|
||||
page: this.page,
|
||||
page_size: this.pageSize
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 辅助函数:格式化时间
|
||||
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}`
|
||||
}
|
||||
+5
-1
@@ -20,13 +20,17 @@ export const useProfileStore = defineStore('profile', {
|
||||
try {
|
||||
const res = await getProfile()
|
||||
this.exists = res.data.exists
|
||||
this.isCompleted = res.data.is_completed
|
||||
this.awakeMinutes = res.data.awake_minutes || 960
|
||||
this.baselineIntervalMinutes = res.data.baseline_interval_minutes || 60
|
||||
|
||||
if (res.data.profile) {
|
||||
this.profile = res.data.profile
|
||||
storage.set(PROFILE_KEY, res.data.profile)
|
||||
this.isCompleted = res.data.is_completed ||
|
||||
!!res.data.profile.onboarding_completed_at ||
|
||||
res.data.profile.baseline_cigs_per_day > 0
|
||||
} else {
|
||||
this.isCompleted = res.data.is_completed
|
||||
}
|
||||
|
||||
return res.data
|
||||
|
||||
Reference in New Issue
Block a user