feat: 更新未读消息计数的刷新频率,优化消息视图;添加分类标签显示功能,增强预算卡片的可读性
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

This commit is contained in:
2026-01-10 23:01:02 +08:00
parent 171febcfb6
commit ad21d20751
6 changed files with 98 additions and 25 deletions

View File

@@ -113,11 +113,13 @@ onMounted(() => {
setInterval(() => {
messageStore.updateUnreadCount()
}, 30 * 1000) // 每30秒更新一次未读消息数
}, 60 * 1000) // 每60秒更新一次未读消息数
// 监听路由变化调整
watch(() => route.path, (newPath) => {
setActive(newPath)
messageStore.updateUnreadCount()
})
const setActive = (path) => {

View File

@@ -90,7 +90,7 @@
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { ref, onMounted, watch, nextTick } from 'vue'
import { showToast } from 'vant'
import dayjs from 'dayjs'
import ClassifySelector from '@/components/ClassifySelector.vue'
@@ -122,6 +122,7 @@ const form = ref({
})
const categoryName = ref('')
const isSyncing = ref(false)
// 弹窗控制
const showDatePicker = ref(false)
@@ -134,6 +135,7 @@ const currentTime = ref(dayjs().format('HH:mm').split(':'))
// 初始化数据
const initForm = async () => {
if (props.initialData) {
isSyncing.value = true
const { occurredAt, amount, reason, type, classify } = props.initialData
if (occurredAt) {
@@ -152,6 +154,10 @@ const initForm = async () => {
if (classify) {
categoryName.value = classify
}
nextTick(() => {
isSyncing.value = false
})
}
}
@@ -165,7 +171,9 @@ watch(() => props.initialData, () => {
}, { deep: true })
const handleTypeChange = (newType) => {
categoryName.value = ''
if (!isSyncing.value) {
categoryName.value = ''
}
}
const onConfirmDate = ({ selectedValues }) => {

View File

@@ -15,6 +15,9 @@
</van-tag>
</slot>
<h3 class="card-title">{{ budget.name }}</h3>
<span v-if="budget.selectedCategories?.length" class="card-subtitle">
({{ budget.selectedCategories.join('') }})
</span>
</div>
<van-icon name="arrow-down" class="expand-icon" />
</div>
@@ -85,6 +88,18 @@
</div>
<div class="budget-body">
<div v-if="budget.selectedCategories?.length" class="category-tags">
<van-tag
v-for="cat in budget.selectedCategories"
:key="cat"
size="mini"
class="category-tag"
plain
round
>
{{ cat }}
</van-tag>
</div>
<div class="amount-info">
<slot name="amount-info"></slot>
</div>
@@ -425,12 +440,29 @@ const timePercentage = computed(() => {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
flex: 1;
}
.card-title {
margin: 0;
font-size: 16px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 0;
max-width: 120px;
}
.card-subtitle {
font-size: 12px;
color: #969799;
font-weight: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
.header-actions {
@@ -438,6 +470,18 @@ const timePercentage = computed(() => {
gap: 8px;
}
.category-tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin: 8px 0 4px;
}
.category-tag {
opacity: 0.7;
font-size: 10px;
}
.amount-info {
display: flex;
justify-content: space-between;

View File

@@ -55,7 +55,7 @@
<van-field name="type" label="交易类型">
<template #input>
<van-radio-group v-model="editForm.type" direction="horizontal">
<van-radio-group v-model="editForm.type" direction="horizontal" @change="handleTypeChange">
<van-radio :name="0">支出</van-radio>
<van-radio :name="1">收入</van-radio>
<van-radio :name="2">不计</van-radio>
@@ -149,7 +149,7 @@
</template>
<script setup>
import { ref, reactive, watch, defineProps, defineEmits, computed } from 'vue'
import { ref, reactive, watch, defineProps, defineEmits, computed, nextTick } from 'vue'
import { showToast, showConfirmDialog } from 'vant'
import dayjs from 'dayjs'
import PopupContainer from '@/components/PopupContainer.vue'
@@ -171,6 +171,7 @@ const emit = defineEmits(['update:show', 'save'])
const visible = ref(false)
const submitting = ref(false)
const isSyncing = ref(false)
// 日期选择相关
const showDatePicker = ref(false)
@@ -201,6 +202,7 @@ watch(() => props.show, (newVal) => {
watch(() => props.transaction, (newVal) => {
if (newVal) {
isSyncing.value = true
// 填充编辑表单
editForm.id = newVal.id
editForm.reason = newVal.reason || ''
@@ -216,6 +218,11 @@ watch(() => props.transaction, (newVal) => {
currentDate.value = dt.format('YYYY-MM-DD').split('-')
currentTime.value = dt.format('HH:mm').split(':')
}
// 在下一个 tick 结束同步状态,确保 van-radio-group 的 @change 已触发完毕
nextTick(() => {
isSyncing.value = false
})
}
})
@@ -223,6 +230,13 @@ watch(visible, (newVal) => {
emit('update:show', newVal)
})
// 处理类型切换
const handleTypeChange = () => {
if (!isSyncing.value) {
editForm.classify = ''
}
}
// 处理日期确认
const onConfirmDate = ({ selectedValues }) => {
const dateStr = selectedValues.join('-')
@@ -241,12 +255,6 @@ const onConfirmTime = ({ selectedValues }) => {
showTimePicker.value = false
}
// 监听交易类型变化,重新加载分类
watch(() => editForm.type, (newVal) => {
// 清空已选的分类
editForm.classify = ''
})
const applySuggestion = () => {
if (props.transaction.unconfirmedClassify) {
editForm.classify = props.transaction.unconfirmedClassify

View File

@@ -25,7 +25,7 @@
:title="activeTabTitle"
:get-value-class="getValueClass"
/>
<div class="scroll-content">
<van-pull-refresh v-model="isRefreshing" class="scroll-content" @refresh="onRefresh">
<div class="budget-list">
<template v-if="expenseBudgets?.length > 0">
<van-swipe-cell v-for="budget in expenseBudgets" :key="budget.id">
@@ -66,7 +66,7 @@
<van-empty v-else description="暂无支出预算" />
</div>
<div style="height: calc(50px + env(safe-area-inset-bottom, 0px))"></div>
</div>
</van-pull-refresh>
</van-tab>
<van-tab title="收入" :name="BudgetCategory.Income">
@@ -76,7 +76,7 @@
:title="activeTabTitle"
:get-value-class="getValueClass"
/>
<div class="scroll-content">
<van-pull-refresh v-model="isRefreshing" class="scroll-content" @refresh="onRefresh">
<div class="budget-list">
<template v-if="incomeBudgets?.length > 0">
<van-swipe-cell v-for="budget in incomeBudgets" :key="budget.id">
@@ -117,11 +117,11 @@
<van-empty v-else description="暂无收入预算" />
</div>
<div style="height: calc(50px + env(safe-area-inset-bottom, 0px))"></div>
</div>
</van-pull-refresh>
</van-tab>
<van-tab title="存款" :name="BudgetCategory.Savings">
<div class="scroll-content" style="padding-top:4px">
<van-pull-refresh v-model="isRefreshing" class="scroll-content" style="padding-top:4px" @refresh="onRefresh">
<div class="budget-list">
<template v-if="savingsBudgets?.length > 0">
<BudgetCard
@@ -155,7 +155,7 @@
<van-empty v-else description="暂无存款计划" />
</div>
<div style="height: calc(50px + env(safe-area-inset-bottom, 0px))"></div>
</div>
</van-pull-refresh>
</van-tab>
</van-tabs>
@@ -183,6 +183,7 @@ import SavingsConfigPopup from '@/components/Budget/SavingsConfigPopup.vue'
const activeTab = ref(BudgetCategory.Expense)
const budgetEditRef = ref(null)
const savingsConfigRef = ref(null)
const isRefreshing = ref(false)
const expenseBudgets = ref([])
const incomeBudgets = ref([])
@@ -229,6 +230,16 @@ const fetchBudgetList = async () => {
}
}
const onRefresh = async () => {
try {
await Promise.all([fetchBudgetList(), fetchCategoryStats()])
} catch (err) {
console.error('刷新失败', err)
} finally {
isRefreshing.value = false
}
}
const fetchCategoryStats = async () => {
try {
const res = await getCategoryStats(activeTab.value)
@@ -364,6 +375,11 @@ const handleDelete = (budget) => {
margin: 0 12px 12px;
}
.scroll-content {
flex: 1;
overflow-y: auto;
}
.delete-button {
height: 100%;
}

View File

@@ -53,11 +53,11 @@
<div v-else class="detail-content">
{{ currentMessage.content }}
</div>
<div v-if="currentMessage.url" class="detail-footer" style="padding: 16px;">
<van-button type="primary" block @click="handleUrlJump(currentMessage.url)">
<template v-if="currentMessage.url && currentMessage.messageType === 1" #footer>
<van-button type="primary" block round @click="handleUrlJump(currentMessage.url)">
查看详情
</van-button>
</div>
</template>
</PopupContainer>
</div>
</template>
@@ -144,11 +144,6 @@ const viewDetail = async (item) => {
}
}
if (item.messageType === 1) {
handleUrlJump(item.url || item.content);
return;
}
currentMessage.value = item;
detailVisible.value = true;
};