Files
EmailBill/Web/src/App.vue
孙诚 ad21d20751
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 19s
Docker Build & Deploy / Deploy to Production (push) Successful in 13s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
feat: 更新未读消息计数的刷新频率,优化消息视图;添加分类标签显示功能,增强预算卡片的可读性
2026-01-10 23:01:02 +08:00

247 lines
6.2 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>
<van-config-provider :theme="theme" class="app-provider">
<div class="app-root">
<RouterView />
<van-tabbar v-show="showTabbar" v-model="active">
<van-tabbar-item name="ccalendar" icon="notes" to="/calendar">
日历
</van-tabbar-item>
<van-tabbar-item name="statistics" icon="chart-trending-o" to="/" @click="handleTabClick('/statistics')">
统计
</van-tabbar-item>
<van-tabbar-item
name="balance"
icon="balance-list"
:to="messageStore.unreadCount > 0 ? '/balance?tab=message' : '/balance'"
:badge="messageStore.unreadCount || null"
@click="handleTabClick('/balance')"
>
账单
</van-tabbar-item>
<van-tabbar-item name="budget" icon="bill-o" to="/budget" @click="handleTabClick('/budget')">
预算
</van-tabbar-item>
<van-tabbar-item name="setting" icon="setting" to="/setting">
设置
</van-tabbar-item>
</van-tabbar>
<GlobalAddBill v-if="isShowAddBill" @success="handleAddTransactionSuccess"/>
<div v-if="needRefresh" class="update-toast" @click="updateServiceWorker">
<van-icon name="upgrade" class="update-icon" />
<span>新版本可用点击刷新</span>
</div>
</div>
</van-config-provider>
</template>
<script setup>
import { RouterView, useRoute } from 'vue-router'
import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
import { useMessageStore } from '@/stores/message'
import GlobalAddBill from '@/components/Global/GlobalAddBill.vue'
import { needRefresh, updateServiceWorker } from './registerServiceWorker'
import '@/styles/common.css'
const messageStore = useMessageStore()
const updateVh = () => {
const vh = window.innerHeight
document.documentElement.style.setProperty('--vh', `${vh}px`)
}
// 修复 PWA 模式下键盘收起页面不回弹的问题
const handleFocusOut = () => {
if (/(iPhone|iPad|iPod|iOS|Android)/i.test(navigator.userAgent)) {
// 延迟一小段时间执行,确保键盘收起动作已开始
setTimeout(() => {
// 强制回到顶部
window.scrollTo(0, 0)
// 同时也触发一次高度更新
updateVh()
}, 100)
}
}
onMounted(() => {
updateVh()
window.addEventListener('resize', updateVh)
if (window.visualViewport) {
window.visualViewport.addEventListener('resize', updateVh)
}
// 注册全局失去焦点监听
document.addEventListener('focusout', handleFocusOut)
})
onUnmounted(() => {
window.removeEventListener('resize', updateVh)
if (window.visualViewport) {
window.visualViewport.removeEventListener('resize', updateVh)
}
// 销毁监听
document.removeEventListener('focusout', handleFocusOut)
})
const route = useRoute()
// 根据路由判断是否显示Tabbar
const showTabbar = computed(() => {
return route.path === '/' ||
route.path === '/calendar' ||
route.path === '/message' ||
route.path === '/setting' ||
route.path === '/balance' ||
route.path === '/budget'
})
const active = ref('')
const theme = ref('light')
// 检测系统深色模式
const updateTheme = () => {
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches
theme.value = isDark ? 'dark' : 'light'
}
// 监听系统主题变化
let mediaQuery
onMounted(() => {
updateTheme()
mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
mediaQuery.addEventListener('change', updateTheme)
setActive(route.path)
})
setInterval(() => {
messageStore.updateUnreadCount()
}, 60 * 1000) // 每60秒更新一次未读消息数
// 监听路由变化调整
watch(() => route.path, (newPath) => {
setActive(newPath)
messageStore.updateUnreadCount()
})
const setActive = (path) => {
active.value = (() => {
switch (path) {
case '/calendar':
return 'ccalendar'
case '/balance':
case '/message':
return 'balance'
case '/setting':
return 'setting'
case '/budget':
return 'budget'
default:
return 'statistics'
}
})()
console.log(active.value, path)
}
const isShowAddBill = computed(() => {
return route.path === '/'
|| route.path === '/calendar'
|| route.path === '/balance'
|| route.path === '/message'
})
onUnmounted(() => {
if (mediaQuery) {
mediaQuery.removeEventListener('change', updateTheme)
}
})
// 处理tab点击如果点击当前页面则滚动到顶部
const handleTabClick = (path) => {
if (route.path === path) {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
}
const handleAddTransactionSuccess = () => {
// 当添加交易成功时,通知当前页面刷新数据
const event = new Event('transactions-changed')
window.dispatchEvent(event)
}
</script>
<style scoped>
.app-provider {
/* 使用准确的视口高度 CSS 变量 */
height: var(--vh, 100vh);
width: 100%;
}
.app-root {
height: 100%;
width: 100%;
position: relative;
padding-top: max(0px, calc(env(safe-area-inset-top, 0px) * 0.75));
box-sizing: border-box;
overflow: hidden;
}
/* TabBar 固定在底部 */
:deep(.van-tabbar) {
position: fixed !important;
bottom: 0 !important;
left: 0;
right: 0;
/* 重置所有 padding然后只添加安全区域 */
padding: 0 !important;
padding-bottom: env(safe-area-inset-bottom, 0px) !important;
height: auto !important;
min-height: 50px !important;
box-sizing: content-box !important;
}
/* 确保 TabBar 项目居中 */
:deep(.van-tabbar-item) {
padding: 0 !important;
height: 50px !important;
}
.debug-overlay {
position: fixed;
top: 0;
left: 0;
z-index: 9999;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px;
font-size: 12px;
pointer-events: auto;
}
.update-toast {
position: fixed;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
background-color: var(--van-primary-color);
color: white;
padding: 10px 20px;
border-radius: 24px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 2000;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
}
.update-toast:active {
transform: translateX(-50%) scale(0.95);
}
.update-icon {
font-size: 18px;
}
</style>