fix
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 22s
Docker Build & Deploy / Deploy to Production (push) Successful in 5s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 22s
Docker Build & Deploy / Deploy to Production (push) Successful in 5s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
This commit is contained in:
@@ -181,6 +181,10 @@ const props = defineProps({
|
||||
activeTab: {
|
||||
type: [Number, String],
|
||||
default: BudgetCategory.Expense
|
||||
},
|
||||
selectedDate: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -294,33 +298,81 @@ const updateSingleGauge = (chart, data, isExpense) => {
|
||||
]
|
||||
}
|
||||
|
||||
chart.setOption(option)
|
||||
chart.setOption(option, true)
|
||||
}
|
||||
|
||||
const disposeBudgetCharts = () => {
|
||||
varianceChart?.dispose()
|
||||
varianceChart = null
|
||||
burndownChart?.dispose()
|
||||
burndownChart = null
|
||||
yearBurndownChart?.dispose()
|
||||
yearBurndownChart = null
|
||||
}
|
||||
|
||||
const updateCharts = () => {
|
||||
const isExpense = props.activeTab === BudgetCategory.Expense
|
||||
updateSingleGauge(monthGaugeChart, props.overallStats.month, isExpense)
|
||||
updateSingleGauge(yearGaugeChart, props.overallStats.year, isExpense)
|
||||
|
||||
// 仪表盘总是存在的
|
||||
if (!monthGaugeChart && monthGaugeRef.value) {
|
||||
monthGaugeChart = echarts.init(monthGaugeRef.value)
|
||||
}
|
||||
if (monthGaugeChart) {
|
||||
updateSingleGauge(monthGaugeChart, props.overallStats.month, isExpense)
|
||||
}
|
||||
|
||||
if (!yearGaugeChart && yearGaugeRef.value) {
|
||||
yearGaugeChart = echarts.init(yearGaugeRef.value)
|
||||
}
|
||||
if (yearGaugeChart) {
|
||||
updateSingleGauge(yearGaugeChart, props.overallStats.year, isExpense)
|
||||
}
|
||||
|
||||
if (props.budgets.length > 0) {
|
||||
// Update Variance Chart
|
||||
if (!varianceChart && varianceChartRef.value) {
|
||||
varianceChart = echarts.init(varianceChartRef.value)
|
||||
}
|
||||
if (varianceChart) {
|
||||
updateVarianceChart(varianceChart, props.budgets)
|
||||
}
|
||||
// 等待 v-if 相关的 DOM 更新
|
||||
nextTick(() => {
|
||||
// 偏差分析图
|
||||
if (varianceChartRef.value) {
|
||||
const existing = echarts.getInstanceByDom(varianceChartRef.value)
|
||||
if (existing) {
|
||||
varianceChart = existing
|
||||
} else {
|
||||
varianceChart?.dispose()
|
||||
varianceChart = echarts.init(varianceChartRef.value)
|
||||
}
|
||||
updateVarianceChart(varianceChart, props.budgets)
|
||||
varianceChart.resize()
|
||||
}
|
||||
|
||||
// 更新燃尽图/积累图
|
||||
if (!burndownChart && burndownChartRef.value) {
|
||||
burndownChart = echarts.init(burndownChartRef.value)
|
||||
}
|
||||
updateBurndownChart()
|
||||
// 月度燃尽图
|
||||
if (burndownChartRef.value) {
|
||||
const existing = echarts.getInstanceByDom(burndownChartRef.value)
|
||||
if (existing) {
|
||||
burndownChart = existing
|
||||
} else {
|
||||
burndownChart?.dispose()
|
||||
burndownChart = echarts.init(burndownChartRef.value)
|
||||
}
|
||||
updateBurndownChart()
|
||||
burndownChart.resize()
|
||||
}
|
||||
|
||||
if (!yearBurndownChart && yearBurndownChartRef.value) {
|
||||
yearBurndownChart = echarts.init(yearBurndownChartRef.value)
|
||||
}
|
||||
updateYearBurndownChart()
|
||||
// 年度燃尽图
|
||||
if (yearBurndownChartRef.value) {
|
||||
const existing = echarts.getInstanceByDom(yearBurndownChartRef.value)
|
||||
if (existing) {
|
||||
yearBurndownChart = existing
|
||||
} else {
|
||||
yearBurndownChart?.dispose()
|
||||
yearBurndownChart = echarts.init(yearBurndownChartRef.value)
|
||||
}
|
||||
updateYearBurndownChart()
|
||||
yearBurndownChart.resize()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 预算数据为空,DOM 已移除,清理实例
|
||||
disposeBudgetCharts()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,7 +499,7 @@ const updateVarianceChart = (chart, budgets) => {
|
||||
]
|
||||
}
|
||||
|
||||
chart.setOption(option)
|
||||
chart.setOption(option, true)
|
||||
}
|
||||
|
||||
const calculateChartHeight = (budgets) => {
|
||||
@@ -462,12 +514,18 @@ const calculateChartHeight = (budgets) => {
|
||||
const updateBurndownChart = () => {
|
||||
if (!burndownChart) { return }
|
||||
|
||||
// 获取当前月份的日期
|
||||
const today = new Date()
|
||||
const year = today.getFullYear()
|
||||
const month = today.getMonth()
|
||||
// 使用传入的所选日期作为参考日期
|
||||
const refDate = props.selectedDate
|
||||
const year = refDate.getFullYear()
|
||||
const month = refDate.getMonth()
|
||||
const daysInMonth = new Date(year, month + 1, 0).getDate()
|
||||
const currentDay = today.getDate()
|
||||
|
||||
const now = new Date()
|
||||
const isCurrentMonth = now.getFullYear() === year && now.getMonth() === month
|
||||
const isPastMonth =
|
||||
now.getFullYear() > year || (now.getFullYear() === year && now.getMonth() > month)
|
||||
// 如果是过去月份,显示完整数据;如果是当前月,显示到今天;如果是将来月,不显示实际数据
|
||||
const currentDay = isCurrentMonth ? now.getDate() : isPastMonth ? daysInMonth : 0
|
||||
const isExpense = props.activeTab === BudgetCategory.Expense
|
||||
|
||||
// 生成日期和理想燃尽线/积累线
|
||||
@@ -490,6 +548,7 @@ const updateBurndownChart = () => {
|
||||
|
||||
// 实际燃尽:根据当前日期显示
|
||||
if (trend.length > 0) {
|
||||
// 后端返回了趋势数据
|
||||
const dayValue = trend[i - 1]
|
||||
if (dayValue !== undefined && dayValue !== null) {
|
||||
const actualRemaining = Math.max(0, totalBudget - dayValue)
|
||||
@@ -498,6 +557,7 @@ const updateBurndownChart = () => {
|
||||
actualBurndown.push(null)
|
||||
}
|
||||
} else {
|
||||
// 后端没有趋势数据, fallback 到线性估算
|
||||
if (i <= currentDay && totalBudget > 0) {
|
||||
const actualRemaining = Math.max(0, totalBudget - (currentExpense * i / currentDay))
|
||||
actualBurndown.push(Math.round(actualRemaining))
|
||||
@@ -620,18 +680,22 @@ const updateBurndownChart = () => {
|
||||
]
|
||||
}
|
||||
|
||||
burndownChart.setOption(option)
|
||||
burndownChart.setOption(option, true)
|
||||
}
|
||||
|
||||
const updateYearBurndownChart = () => {
|
||||
if (!yearBurndownChart) { return }
|
||||
|
||||
// 获取当前年份的日期
|
||||
const today = new Date()
|
||||
const year = today.getFullYear()
|
||||
const currentMonth = today.getMonth()
|
||||
const currentDay = today.getDate()
|
||||
const daysInCurrentMonth = new Date(year, currentMonth + 1, 0).getDate()
|
||||
// 使用参考日期
|
||||
const refDate = props.selectedDate
|
||||
const year = refDate.getFullYear()
|
||||
const refMonth = refDate.getMonth()
|
||||
|
||||
const now = new Date()
|
||||
const currentYear = now.getFullYear()
|
||||
const currentMonth = now.getMonth()
|
||||
const currentDay = now.getDate()
|
||||
const daysInCurrentMonth = new Date(currentYear, currentMonth + 1, 0).getDate()
|
||||
const isExpense = props.activeTab === BudgetCategory.Expense
|
||||
|
||||
// 生成月份和理想燃尽线/积累线
|
||||
@@ -654,21 +718,19 @@ const updateYearBurndownChart = () => {
|
||||
daysInYear += new Date(year, j + 1, 0).getDate()
|
||||
}
|
||||
|
||||
if (i < currentMonth) {
|
||||
// 之前的月份都已完成
|
||||
if (year < currentYear || (year === currentYear && i < currentMonth)) {
|
||||
// 以前的年/月
|
||||
daysPassedInYear = daysInYear + new Date(year, i + 1, 0).getDate()
|
||||
} else if (i === currentMonth) {
|
||||
// 当前月份
|
||||
} else if (year === currentYear && i === currentMonth) {
|
||||
// 当前月
|
||||
daysPassedInYear = daysInYear + currentDay
|
||||
daysInYear += daysInCurrentMonth
|
||||
} else {
|
||||
// 未来的月份
|
||||
daysInYear += new Date(year, i + 1, 0).getDate()
|
||||
// 未来的年/月
|
||||
daysPassedInYear = 0
|
||||
}
|
||||
|
||||
// 全年总天数(365或366)
|
||||
const daysInYearTotal = new Date(year, 12, 0).getDate() === 29 ? 366 : 365
|
||||
const yearProgress = i === 11 ? 1 : daysPassedInYear / daysInYearTotal
|
||||
const daysInYearTotal = (year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)) ? 366 : 365
|
||||
const yearProgress = daysPassedInYear / daysInYearTotal
|
||||
|
||||
if (isExpense) {
|
||||
// 支出:燃尽图(向下走)
|
||||
@@ -676,7 +738,7 @@ const updateYearBurndownChart = () => {
|
||||
const idealRemaining = Math.max(0, totalBudget * (1 - (i + 1) / 12))
|
||||
idealBurndown.push(Math.round(idealRemaining))
|
||||
|
||||
// 实际燃尽:根据当前日期显示
|
||||
// 实际燃尽:根据日期显示
|
||||
if (trend.length > 0) {
|
||||
const monthValue = trend[i]
|
||||
if (monthValue !== undefined && monthValue !== null) {
|
||||
@@ -686,7 +748,9 @@ const updateYearBurndownChart = () => {
|
||||
actualBurndown.push(null)
|
||||
}
|
||||
} else {
|
||||
if ((i < currentMonth || (i === currentMonth && currentDay > 0)) && totalBudget > 0) {
|
||||
// Fallback: 如果是今年且月份未开始,或者去年,做线性统计
|
||||
const isFuture = year > currentYear || (year === currentYear && i > currentMonth)
|
||||
if (!isFuture && totalBudget > 0) {
|
||||
const actualRemaining = Math.max(0, totalBudget - (currentExpense * yearProgress))
|
||||
actualBurndown.push(Math.round(actualRemaining))
|
||||
} else {
|
||||
@@ -699,7 +763,7 @@ const updateYearBurndownChart = () => {
|
||||
const idealAccumulated = Math.min(totalBudget, totalBudget * ((i + 1) / 12))
|
||||
idealBurndown.push(Math.round(idealAccumulated))
|
||||
|
||||
// 实际积累:根据当前日期显示
|
||||
// 实际积累:根据参数显示
|
||||
if (trend.length > 0) {
|
||||
const monthValue = trend[i]
|
||||
if (monthValue !== undefined && monthValue !== null) {
|
||||
@@ -708,7 +772,8 @@ const updateYearBurndownChart = () => {
|
||||
actualBurndown.push(null)
|
||||
}
|
||||
} else {
|
||||
if ((i < currentMonth || (i === currentMonth && currentDay > 0)) && totalBudget > 0) {
|
||||
const isFuture = year > currentYear || (year === currentYear && i > currentMonth)
|
||||
if (!isFuture && totalBudget > 0) {
|
||||
const actualAccumulated = Math.min(totalBudget, currentExpense * yearProgress)
|
||||
actualBurndown.push(Math.round(actualAccumulated))
|
||||
} else {
|
||||
@@ -806,7 +871,7 @@ const updateYearBurndownChart = () => {
|
||||
]
|
||||
}
|
||||
|
||||
yearBurndownChart.setOption(option)
|
||||
yearBurndownChart.setOption(option, true)
|
||||
}
|
||||
|
||||
watch(() => props.overallStats, () => nextTick(updateCharts), { deep: true })
|
||||
@@ -816,6 +881,12 @@ watch(() => props.budgets, () => {
|
||||
})
|
||||
}, { deep: true })
|
||||
|
||||
watch(() => props.selectedDate, () => {
|
||||
nextTick(() => {
|
||||
updateCharts()
|
||||
})
|
||||
})
|
||||
|
||||
watch(() => props.activeTab, () => {
|
||||
nextTick(() => {
|
||||
updateCharts()
|
||||
@@ -838,28 +909,7 @@ const handleResize = () => {
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
const isExpense = props.activeTab === BudgetCategory.Expense
|
||||
monthGaugeChart = initGaugeChart(monthGaugeChart, monthGaugeRef.value, props.overallStats.month, isExpense)
|
||||
yearGaugeChart = initGaugeChart(yearGaugeChart, yearGaugeRef.value, props.overallStats.year, isExpense)
|
||||
// 只在有数据时初始化柱状图
|
||||
if (props.budgets.length > 0) {
|
||||
// 初始化偏差图
|
||||
if (varianceChartRef.value) {
|
||||
varianceChart = echarts.init(varianceChartRef.value)
|
||||
updateVarianceChart(varianceChart, props.budgets)
|
||||
}
|
||||
|
||||
// 初始化燃尽图/积累图
|
||||
if (burndownChartRef.value) {
|
||||
burndownChart = echarts.init(burndownChartRef.value)
|
||||
updateBurndownChart()
|
||||
}
|
||||
|
||||
if (yearBurndownChartRef.value) {
|
||||
yearBurndownChart = echarts.init(yearBurndownChartRef.value)
|
||||
updateYearBurndownChart()
|
||||
}
|
||||
}
|
||||
updateCharts()
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
})
|
||||
@@ -867,10 +917,10 @@ onMounted(() => {
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
monthGaugeChart?.dispose()
|
||||
monthGaugeChart = null
|
||||
yearGaugeChart?.dispose()
|
||||
varianceChart?.dispose()
|
||||
burndownChart?.dispose()
|
||||
yearBurndownChart?.dispose()
|
||||
yearGaugeChart = null
|
||||
disposeBudgetCharts()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
:overall-stats="overallStats"
|
||||
:budgets="expenseBudgets"
|
||||
:active-tab="activeTab"
|
||||
:selected-date="selectedDate"
|
||||
/>
|
||||
</div>
|
||||
</van-tab>
|
||||
@@ -72,6 +73,7 @@
|
||||
:overall-stats="overallStats"
|
||||
:budgets="incomeBudgets"
|
||||
:active-tab="activeTab"
|
||||
:selected-date="selectedDate"
|
||||
/>
|
||||
</div>
|
||||
</van-tab>
|
||||
@@ -501,6 +503,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { showToast, showConfirmDialog } from 'vant'
|
||||
import dayjs from 'dayjs'
|
||||
import {
|
||||
getBudgetList,
|
||||
deleteBudget,
|
||||
@@ -520,7 +523,7 @@ const activeTab = ref(BudgetCategory.Expense)
|
||||
const selectedDate = ref(new Date())
|
||||
const showDatePicker = ref(false)
|
||||
const minDate = new Date(2020, 0, 1)
|
||||
const maxDate = new Date(2030, 11, 31)
|
||||
const maxDate = new Date()
|
||||
const pickerDate = ref([
|
||||
selectedDate.value.getFullYear().toString(),
|
||||
(selectedDate.value.getMonth() + 1).toString().padStart(2, '0')
|
||||
@@ -597,33 +600,9 @@ const onConfirmDate = ({ selectedValues }) => {
|
||||
showDatePicker.value = false
|
||||
}
|
||||
|
||||
const getValueClass = (rate) => {
|
||||
const numRate = parseFloat(rate)
|
||||
if (numRate === 0) {
|
||||
return ''
|
||||
}
|
||||
if (activeTab.value === BudgetCategory.Expense) {
|
||||
if (numRate >= 100) {
|
||||
return 'expense'
|
||||
}
|
||||
if (numRate >= 80) {
|
||||
return 'warning'
|
||||
}
|
||||
return 'income'
|
||||
} else {
|
||||
if (numRate >= 100) {
|
||||
return 'income'
|
||||
}
|
||||
if (numRate >= 80) {
|
||||
return 'warning'
|
||||
}
|
||||
return 'expense'
|
||||
}
|
||||
}
|
||||
|
||||
const fetchBudgetList = async () => {
|
||||
try {
|
||||
const res = await getBudgetList(selectedDate.value.toISOString())
|
||||
const res = await getBudgetList(dayjs(selectedDate.value).format('YYYY-MM-DD'))
|
||||
if (res.success) {
|
||||
const data = res.data || []
|
||||
expenseBudgets.value = data.filter((b) => b.category === BudgetCategory.Expense)
|
||||
@@ -647,7 +626,10 @@ const onRefresh = async () => {
|
||||
|
||||
const fetchCategoryStats = async () => {
|
||||
try {
|
||||
const res = await getCategoryStats(activeTab.value, selectedDate.value.toISOString())
|
||||
const res = await getCategoryStats(
|
||||
activeTab.value,
|
||||
dayjs(selectedDate.value).format('YYYY-MM-DD')
|
||||
)
|
||||
if (res.success) {
|
||||
// 转换后端返回的数据格式为前端需要的格式
|
||||
const data = res.data
|
||||
@@ -679,7 +661,10 @@ const fetchUncoveredCategories = async () => {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await getUncoveredCategories(activeTab.value, selectedDate.value.toISOString())
|
||||
const res = await getUncoveredCategories(
|
||||
activeTab.value,
|
||||
dayjs(selectedDate.value).format('YYYY-MM-DD')
|
||||
)
|
||||
if (res.success) {
|
||||
uncoveredCategories.value = res.data || []
|
||||
}
|
||||
@@ -795,7 +780,7 @@ const getProgressColor = (budget) => {
|
||||
|
||||
const showArchiveSummary = async () => {
|
||||
try {
|
||||
const res = await getArchiveSummary(selectedDate.value.toISOString())
|
||||
const res = await getArchiveSummary(dayjs(selectedDate.value).format('YYYY-MM-DD'))
|
||||
if (res.success) {
|
||||
archiveSummary.value = res.data || ''
|
||||
showSummaryPopup.value = true
|
||||
|
||||
Reference in New Issue
Block a user