fix
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 31s
Docker Build & Deploy / Deploy to Production (push) Successful in 7s
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 31s
Docker Build & Deploy / Deploy to Production (push) Successful in 7s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
This commit is contained in:
@@ -27,12 +27,27 @@
|
||||
<div
|
||||
class="remaining-label"
|
||||
>
|
||||
{{ activeTab === BudgetCategory.Expense ? '余额' : '差额' }}
|
||||
{{
|
||||
activeTab === BudgetCategory.Expense
|
||||
? (
|
||||
overallStats.month.current > overallStats.month.limit
|
||||
? '超支'
|
||||
: '余额'
|
||||
)
|
||||
: overallStats.month.current > overallStats.month.limit
|
||||
? '超额'
|
||||
: '差额'
|
||||
}}
|
||||
</div>
|
||||
<div
|
||||
class="remaining-value"
|
||||
:style="{ color:
|
||||
overallStats.month.current > overallStats.month.limit
|
||||
? activeTab === BudgetCategory.Expense ? 'var(--van-danger-color)' : 'var(--van-success-color)'
|
||||
: ''
|
||||
}"
|
||||
>
|
||||
¥{{ formatMoney(Math.max(0, overallStats.month.limit - overallStats.month.current)) }}
|
||||
¥{{ formatMoney(Math.abs(overallStats.month.limit - overallStats.month.current)) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,12 +91,13 @@
|
||||
<div
|
||||
class="remaining-label"
|
||||
>
|
||||
{{ activeTab === BudgetCategory.Expense ? '余额' : '差额' }}
|
||||
{{ activeTab === BudgetCategory.Expense ? (overallStats.year.current > overallStats.year.limit ? '超支' : '余额') : '差额' }}
|
||||
</div>
|
||||
<div
|
||||
class="remaining-value"
|
||||
:style="{ color: activeTab === BudgetCategory.Expense && overallStats.year.current > overallStats.year.limit ? 'var(--van-danger-color)' : '' }"
|
||||
>
|
||||
¥{{ formatMoney(Math.max(0, overallStats.year.limit - overallStats.year.current)) }}
|
||||
¥{{ formatMoney(Math.abs(overallStats.year.limit - overallStats.year.current)) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -247,20 +263,30 @@ const updateSingleGauge = (chart, data, isExpense) => {
|
||||
// 展示逻辑:支出显示剩余,收入显示已积累
|
||||
let displayRate
|
||||
if (isExpense) {
|
||||
// 支出:显示剩余容量 (100% - 已消耗%),随支出增大逐渐消耗
|
||||
// 支出:显示剩余容量 (100% - 已消耗%),随支出增大逐渐消耗;超支时显示超出部分
|
||||
displayRate = Math.max(0, 100 - rate)
|
||||
// 如果超支(rate > 100),显示超支部分(例如110% -> 显示10%超支)
|
||||
if (rate > 100) {
|
||||
displayRate = rate - 100
|
||||
}
|
||||
} else {
|
||||
// 收入:显示已积累 (%),随收入增多逐渐增多
|
||||
displayRate = Math.min(100, rate)
|
||||
// 收入:显示已积累 (%),随收入增多逐渐增多,可以超过100%
|
||||
displayRate = rate
|
||||
}
|
||||
|
||||
// 颜色逻辑:支出从绿色消耗到红色,收入从红色积累到绿色
|
||||
let color
|
||||
if (isExpense) {
|
||||
// 支出:满格绿色,随消耗逐渐变红 (根据剩余容量)
|
||||
if (displayRate <= 30) { color = getCssVar('--chart-danger') } // 红色
|
||||
else if (displayRate <= 65) { color = getCssVar('--chart-warning') } // 橙色
|
||||
else { color = getCssVar('--chart-success') } // 绿色
|
||||
if (rate > 100) {
|
||||
color = getCssVar('--chart-danger') // 超支显示红色
|
||||
} else if (displayRate <= 30) {
|
||||
color = getCssVar('--chart-danger') // 红色(剩余很少)
|
||||
} else if (displayRate <= 65) {
|
||||
color = getCssVar('--chart-warning') // 橙色
|
||||
} else {
|
||||
color = getCssVar('--chart-success') // 绿色(剩余充足)
|
||||
}
|
||||
} else {
|
||||
// 收入:空红色,随积累逐渐变绿 (根据已积累)
|
||||
if (displayRate <= 30) { color = getCssVar('--chart-danger') } // 红色
|
||||
@@ -275,7 +301,7 @@ const updateSingleGauge = (chart, data, isExpense) => {
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
min: 0,
|
||||
max: 100,
|
||||
max: isExpense && rate > 100 ? 50 : 100, // 超支时显示0-50%范围(实际代表0-150%)
|
||||
splitNumber: 5,
|
||||
radius: '120%', // 放大一点以适应小卡片
|
||||
center: ['50%', '70%'],
|
||||
@@ -570,15 +596,15 @@ const updateBurndownChart = () => {
|
||||
if (isExpense) {
|
||||
// 支出:燃尽图(向下走)
|
||||
// 理想燃尽:每天均匀消耗
|
||||
const idealRemaining = Math.max(0, totalBudget * (1 - i / daysInMonth))
|
||||
const idealRemaining = totalBudget * (1 - i / daysInMonth)
|
||||
idealBurndown.push(Math.round(idealRemaining))
|
||||
|
||||
// 实际燃尽:根据当前日期显示
|
||||
// 实际燃尽:根据当前日期显示,允许负值以表示超支
|
||||
if (trend.length > 0) {
|
||||
// 后端返回了趋势数据
|
||||
const dayValue = trend[i - 1]
|
||||
if (dayValue !== undefined && dayValue !== null) {
|
||||
const actualRemaining = Math.max(0, totalBudget - dayValue)
|
||||
const actualRemaining = totalBudget - dayValue
|
||||
actualBurndown.push(Math.round(actualRemaining))
|
||||
} else {
|
||||
actualBurndown.push(null)
|
||||
@@ -586,7 +612,8 @@ const updateBurndownChart = () => {
|
||||
} else {
|
||||
// 后端没有趋势数据, fallback 到线性估算
|
||||
if (i <= currentDay && totalBudget > 0) {
|
||||
const actualRemaining = Math.max(0, totalBudget - (currentExpense * i / currentDay))
|
||||
// 允许显示负值以表示超支
|
||||
const actualRemaining = totalBudget - (currentExpense * i / currentDay)
|
||||
actualBurndown.push(Math.round(actualRemaining))
|
||||
} else {
|
||||
actualBurndown.push(null)
|
||||
@@ -762,14 +789,14 @@ const updateYearBurndownChart = () => {
|
||||
if (isExpense) {
|
||||
// 支出:燃尽图(向下走)
|
||||
// 理想燃尽:每月均匀消耗
|
||||
const idealRemaining = Math.max(0, totalBudget * (1 - (i + 1) / 12))
|
||||
const idealRemaining = totalBudget * (1 - (i + 1) / 12)
|
||||
idealBurndown.push(Math.round(idealRemaining))
|
||||
|
||||
// 实际燃尽:根据日期显示
|
||||
// 实际燃尽:根据日期显示,允许负值以表示超支
|
||||
if (trend.length > 0) {
|
||||
const monthValue = trend[i]
|
||||
if (monthValue !== undefined && monthValue !== null) {
|
||||
const actualRemaining = Math.max(0, totalBudget - monthValue)
|
||||
const actualRemaining = totalBudget - monthValue
|
||||
actualBurndown.push(Math.round(actualRemaining))
|
||||
} else {
|
||||
actualBurndown.push(null)
|
||||
@@ -778,7 +805,7 @@ const updateYearBurndownChart = () => {
|
||||
// Fallback: 如果是今年且月份未开始,或者去年,做线性统计
|
||||
const isFuture = year > currentYear || (year === currentYear && i > currentMonth)
|
||||
if (!isFuture && totalBudget > 0) {
|
||||
const actualRemaining = Math.max(0, totalBudget - (currentExpense * yearProgress))
|
||||
const actualRemaining = totalBudget - (currentExpense * yearProgress)
|
||||
actualBurndown.push(Math.round(actualRemaining))
|
||||
} else {
|
||||
actualBurndown.push(null)
|
||||
|
||||
@@ -66,10 +66,10 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 余额变化图表 -->
|
||||
<!-- 余额变化图表(融合收支趋势) -->
|
||||
<div
|
||||
class="balance-chart"
|
||||
style="height: 130px; padding: 0"
|
||||
style="height: 190px; padding: 0"
|
||||
>
|
||||
<div
|
||||
ref="balanceChartRef"
|
||||
@@ -77,31 +77,6 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 趋势统计 -->
|
||||
<div
|
||||
class="common-card"
|
||||
style="padding-bottom: 5px; margin-top: 12px;"
|
||||
>
|
||||
<div
|
||||
class="card-header"
|
||||
style="padding-bottom: 0;"
|
||||
>
|
||||
<h3 class="card-title">
|
||||
收支趋势
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="trend-chart"
|
||||
style="height: 240px; padding: 10px 0"
|
||||
>
|
||||
<div
|
||||
ref="chartRef"
|
||||
style="width: 100%; height: 100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分类统计 -->
|
||||
<div
|
||||
class="common-card"
|
||||
@@ -450,10 +425,8 @@ const noneCategoriesView = computed(() => {
|
||||
const dailyData = ref([])
|
||||
// 余额数据(独立)
|
||||
const balanceData = ref([])
|
||||
const chartRef = ref(null)
|
||||
const pieChartRef = ref(null)
|
||||
const balanceChartRef = ref(null)
|
||||
let chartInstance = null
|
||||
let pieChartInstance = null
|
||||
let balanceChartInstance = null
|
||||
|
||||
@@ -576,7 +549,6 @@ const fetchStatistics = async (showLoading = true) => {
|
||||
firstLoading.value = false
|
||||
// DOM 更新后渲染图表
|
||||
nextTick(() => {
|
||||
renderChart(dailyData.value)
|
||||
renderPieChart()
|
||||
renderBalanceChart()
|
||||
})
|
||||
@@ -671,7 +643,7 @@ const fetchDailyData = async () => {
|
||||
// 如果不是首次加载(即DOM已存在),直接渲染
|
||||
if (!firstLoading.value) {
|
||||
nextTick(() => {
|
||||
renderChart(response.data)
|
||||
renderBalanceChart()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -691,6 +663,12 @@ const fetchBalanceData = async () => {
|
||||
|
||||
if (response.success && response.data) {
|
||||
balanceData.value = response.data
|
||||
// 如果不是首次加载,重新渲染余额图表
|
||||
if (!firstLoading.value) {
|
||||
nextTick(() => {
|
||||
renderBalanceChart()
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取余额统计数据失败:', error)
|
||||
@@ -698,193 +676,6 @@ const fetchBalanceData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const renderChart = (data) => {
|
||||
if (!chartRef.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// 尝试获取DOM上的现有实例
|
||||
const existingInstance = echarts.getInstanceByDom(chartRef.value)
|
||||
|
||||
// 如果当前保存的实例与DOM不一致,或者DOM上已经有实例但我们没保存引用
|
||||
if (chartInstance && chartInstance !== existingInstance) {
|
||||
// 这种情况很少见,但为了保险,销毁旧的引用
|
||||
if (!chartInstance.isDisposed()) {
|
||||
chartInstance.dispose()
|
||||
}
|
||||
chartInstance = null
|
||||
}
|
||||
|
||||
// 如果DOM变了(transition导致的),旧的chartInstance绑定的DOM已经不在了
|
||||
// 这时 chartInstance.getDom() !== chartRef.value
|
||||
if (chartInstance && chartInstance.getDom() !== chartRef.value) {
|
||||
chartInstance.dispose()
|
||||
chartInstance = null
|
||||
}
|
||||
|
||||
// 如果DOM上已经有实例(可能由其他途径创建),复用它
|
||||
if (!chartInstance && existingInstance) {
|
||||
chartInstance = existingInstance
|
||||
}
|
||||
|
||||
if (!chartInstance) {
|
||||
chartInstance = echarts.init(chartRef.value)
|
||||
}
|
||||
|
||||
// 补全当月所有日期
|
||||
const now = new Date()
|
||||
let daysInMonth
|
||||
|
||||
if (currentYear.value === now.getFullYear() && currentMonth.value === now.getMonth() + 1) {
|
||||
// 如果是当前月,只显示到今天
|
||||
daysInMonth = now.getDate()
|
||||
} else {
|
||||
// 如果是过去月,显示整月
|
||||
daysInMonth = new Date(currentYear.value, currentMonth.value, 0).getDate()
|
||||
}
|
||||
|
||||
const fullData = []
|
||||
|
||||
// 创建日期映射
|
||||
const dataMap = new Map()
|
||||
data.forEach((item) => {
|
||||
const day = new Date(item.date).getDate()
|
||||
dataMap.set(day, item)
|
||||
})
|
||||
|
||||
for (let i = 1; i <= daysInMonth; i++) {
|
||||
const item = dataMap.get(i)
|
||||
if (item) {
|
||||
fullData.push(item)
|
||||
} else {
|
||||
fullData.push({
|
||||
date: `${currentYear.value}-${String(currentMonth.value).padStart(2, '0')}-${String(i).padStart(2, '0')}`,
|
||||
count: 0,
|
||||
expense: 0,
|
||||
income: 0,
|
||||
balance: 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const dates = fullData.map((item) => {
|
||||
const date = new Date(item.date)
|
||||
return `${date.getDate()}日`
|
||||
})
|
||||
|
||||
// Calculate cumulative values
|
||||
let accumulatedExpense = 0
|
||||
let accumulatedIncome = 0
|
||||
let accumulatedBalance = 0
|
||||
|
||||
const expenses = fullData.map((item) => {
|
||||
accumulatedExpense += item.expense
|
||||
return accumulatedExpense
|
||||
})
|
||||
|
||||
const incomes = fullData.map((item) => {
|
||||
accumulatedIncome += item.income
|
||||
return accumulatedIncome
|
||||
})
|
||||
|
||||
const balances = fullData.map((item) => {
|
||||
accumulatedBalance += item.balance
|
||||
return accumulatedBalance
|
||||
})
|
||||
|
||||
const legendData = [
|
||||
{ name: '支出', value: '¥' + formatMoney(expenses[expenses.length - 1]) },
|
||||
{ name: '收入', value: '¥' + formatMoney(incomes[incomes.length - 1]) },
|
||||
{ name: '存款', value: '¥' + formatMoney(balances[balances.length - 1]) }
|
||||
]
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: function (params) {
|
||||
let result = params[0].name + '<br/>'
|
||||
params.forEach((param) => {
|
||||
result += param.marker + param.seriesName + ': ' + formatMoney(param.value) + '<br/>'
|
||||
})
|
||||
return result
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: legendData.map((item) => item.name),
|
||||
bottom: 0,
|
||||
textStyle: {
|
||||
color: getCssVar('--chart-text-muted') // 适配深色模式
|
||||
},
|
||||
formatter: function (name) {
|
||||
const item = legendData.find((d) => d.name === name)
|
||||
return item ? `${name} ${item.value}` : name
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '15%',
|
||||
top: '5%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: dates,
|
||||
axisLabel: {
|
||||
color: getCssVar('--chart-text-muted') // 适配深色模式
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
splitNumber: 5,
|
||||
axisLabel: {
|
||||
color: getCssVar('--chart-text-muted'), // 适配深色模式
|
||||
formatter: (value) => {
|
||||
return value / 1000 + 'k'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: getCssVar('--van-border-color') // 深色分割线
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '支出',
|
||||
type: 'line',
|
||||
data: expenses,
|
||||
itemStyle: { color: getCssVar('--chart-color-1') },
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
lineStyle: { width: 2 }
|
||||
},
|
||||
{
|
||||
name: '收入',
|
||||
type: 'line',
|
||||
data: incomes,
|
||||
itemStyle: { color: getCssVar('--chart-color-2') },
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
lineStyle: { width: 2 }
|
||||
},
|
||||
{
|
||||
name: '存款',
|
||||
type: 'line',
|
||||
data: balances,
|
||||
itemStyle: { color: getCssVar('--chart-color-13') },
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
lineStyle: { width: 2 }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
chartInstance.setOption(option)
|
||||
}
|
||||
|
||||
const renderPieChart = () => {
|
||||
if (!pieChartRef.value) {
|
||||
return
|
||||
@@ -1010,12 +801,12 @@ const renderPieChart = () => {
|
||||
pieChartInstance.setOption(option)
|
||||
}
|
||||
|
||||
// 渲染余额变化图表
|
||||
// 渲染余额变化图表(融合支出、收入、余额三条线)
|
||||
const renderBalanceChart = () => {
|
||||
if (!balanceChartRef.value) {
|
||||
return
|
||||
}
|
||||
if (balanceData.value.length === 0) {
|
||||
if (balanceData.value.length === 0 && dailyData.value.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1042,28 +833,168 @@ const renderBalanceChart = () => {
|
||||
balanceChartInstance = echarts.init(balanceChartRef.value)
|
||||
}
|
||||
|
||||
const dates = balanceData.value.map((item) => {
|
||||
const date = new Date(item.date)
|
||||
return `${date.getMonth() + 1}/${date.getDate()}`
|
||||
})
|
||||
// 判断是年度统计还是月度统计
|
||||
const isYearlyView = currentMonth.value === 0
|
||||
let dates, expenses, incomes, balances
|
||||
|
||||
const balances = balanceData.value.map((item) => item.cumulativeBalance)
|
||||
if (isYearlyView) {
|
||||
// 按年统计:按月聚合数据
|
||||
const monthlyMap = new Map()
|
||||
const balanceMonthlyMap = new Map()
|
||||
|
||||
// 聚合 dailyData 按月
|
||||
dailyData.value.forEach((item) => {
|
||||
const date = new Date(item.date)
|
||||
const month = date.getMonth() + 1 // 1-12
|
||||
if (!monthlyMap.has(month)) {
|
||||
monthlyMap.set(month, { expense: 0, income: 0 })
|
||||
}
|
||||
const data = monthlyMap.get(month)
|
||||
data.expense += item.expense
|
||||
data.income += item.income
|
||||
})
|
||||
|
||||
// 聚合 balanceData 按月(取每月最后一天的余额)
|
||||
balanceData.value.forEach((item) => {
|
||||
const date = new Date(item.date)
|
||||
const month = date.getMonth() + 1
|
||||
const day = date.getDate()
|
||||
|
||||
if (!balanceMonthlyMap.has(month) || day > balanceMonthlyMap.get(month).day) {
|
||||
balanceMonthlyMap.set(month, { balance: item.cumulativeBalance, day })
|
||||
}
|
||||
})
|
||||
|
||||
// 构建12个月的完整数据
|
||||
const now = new Date()
|
||||
const currentMonthNum = now.getFullYear() === currentYear.value ? now.getMonth() + 1 : 12
|
||||
|
||||
dates = []
|
||||
const monthlyExpenses = []
|
||||
const monthlyIncomes = []
|
||||
const monthlyBalances = []
|
||||
|
||||
let accumulatedExpense = 0
|
||||
let accumulatedIncome = 0
|
||||
|
||||
for (let m = 1; m <= currentMonthNum; m++) {
|
||||
dates.push(`${m}月`)
|
||||
|
||||
const data = monthlyMap.get(m) || { expense: 0, income: 0 }
|
||||
accumulatedExpense += data.expense
|
||||
accumulatedIncome += data.income
|
||||
|
||||
monthlyExpenses.push(accumulatedExpense)
|
||||
monthlyIncomes.push(accumulatedIncome)
|
||||
|
||||
const balanceData = balanceMonthlyMap.get(m)
|
||||
monthlyBalances.push(balanceData ? balanceData.balance : 0)
|
||||
}
|
||||
|
||||
expenses = monthlyExpenses
|
||||
incomes = monthlyIncomes
|
||||
balances = monthlyBalances
|
||||
|
||||
} else {
|
||||
// 按月统计:按日显示
|
||||
const now = new Date()
|
||||
let daysInMonth
|
||||
|
||||
if (currentYear.value === now.getFullYear() && currentMonth.value === now.getMonth() + 1) {
|
||||
daysInMonth = now.getDate()
|
||||
} else {
|
||||
daysInMonth = new Date(currentYear.value, currentMonth.value, 0).getDate()
|
||||
}
|
||||
|
||||
const fullData = []
|
||||
const dataMap = new Map()
|
||||
dailyData.value.forEach((item) => {
|
||||
const day = new Date(item.date).getDate()
|
||||
dataMap.set(day, item)
|
||||
})
|
||||
|
||||
// 创建余额映射
|
||||
const balanceMap = new Map()
|
||||
if (balanceData.value && balanceData.value.length > 0) {
|
||||
balanceData.value.forEach((item) => {
|
||||
const day = new Date(item.date).getDate()
|
||||
balanceMap.set(day, item.cumulativeBalance)
|
||||
})
|
||||
}
|
||||
|
||||
for (let i = 1; i <= daysInMonth; i++) {
|
||||
const item = dataMap.get(i)
|
||||
if (item) {
|
||||
fullData.push(item)
|
||||
} else {
|
||||
fullData.push({
|
||||
date: `${currentYear.value}-${String(currentMonth.value).padStart(2, '0')}-${String(i).padStart(2, '0')}`,
|
||||
count: 0,
|
||||
expense: 0,
|
||||
income: 0,
|
||||
balance: 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
dates = fullData.map((item) => {
|
||||
const date = new Date(item.date)
|
||||
return `${date.getDate()}日`
|
||||
})
|
||||
|
||||
// 计算累计支出和收入
|
||||
let accumulatedExpense = 0
|
||||
let accumulatedIncome = 0
|
||||
|
||||
expenses = fullData.map((item) => {
|
||||
accumulatedExpense += item.expense
|
||||
return accumulatedExpense
|
||||
})
|
||||
|
||||
incomes = fullData.map((item) => {
|
||||
accumulatedIncome += item.income
|
||||
return accumulatedIncome
|
||||
})
|
||||
|
||||
// 使用余额接口数据
|
||||
balances = fullData.map((item, index) => {
|
||||
const day = index + 1
|
||||
return balanceMap.get(day) || 0
|
||||
})
|
||||
}
|
||||
|
||||
const legendData = [
|
||||
{ name: '支出', value: '¥' + formatMoney(expenses[expenses.length - 1]) },
|
||||
{ name: '收入', value: '¥' + formatMoney(incomes[incomes.length - 1]) },
|
||||
{ name: '余额', value: '¥' + formatMoney(balances[balances.length - 1]) }
|
||||
]
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: function (params) {
|
||||
if (params.length === 0) {
|
||||
return ''
|
||||
}
|
||||
const param = params[0]
|
||||
return `${param.name}<br/>余额: ¥${formatMoney(param.value)}`
|
||||
let result = params[0].name + '<br/>'
|
||||
params.forEach((param) => {
|
||||
result += param.marker + param.seriesName + ': ¥' + formatMoney(param.value) + '<br/>'
|
||||
})
|
||||
return result
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: legendData.map((item) => item.name),
|
||||
bottom: 0,
|
||||
textStyle: {
|
||||
color: getCssVar('--chart-text-muted')
|
||||
},
|
||||
formatter: function (name) {
|
||||
const item = legendData.find((d) => d.name === name)
|
||||
return item ? `${name} ${item.value}` : name
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '5%',
|
||||
bottom: '15%',
|
||||
top: '5%',
|
||||
containLabel: true
|
||||
},
|
||||
@@ -1094,35 +1025,37 @@ const renderBalanceChart = () => {
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '支出',
|
||||
type: 'line',
|
||||
data: expenses,
|
||||
itemStyle: { color: '#ff6b6b' },
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
lineStyle: { width: 2 }
|
||||
},
|
||||
{
|
||||
name: '收入',
|
||||
type: 'line',
|
||||
data: incomes,
|
||||
itemStyle: { color: '#51cf66' },
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
lineStyle: { width: 2 }
|
||||
},
|
||||
{
|
||||
name: '余额',
|
||||
type: 'line',
|
||||
data: balances,
|
||||
itemStyle: { color: getCssVar('--chart-color-13') },
|
||||
itemStyle: { color: '#4c9cf1' },
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
lineStyle: { width: 2 },
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: getCssVar('--chart-color-13')
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: getCssVar('--chart-color-13')
|
||||
}
|
||||
])
|
||||
}
|
||||
lineStyle: { width: 2 }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
balanceChartInstance.setOption(option)
|
||||
// 设置图表透明度
|
||||
if (balanceChartRef.value) {
|
||||
balanceChartRef.value.style.opacity = '0.85'
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到智能分析页面
|
||||
@@ -1308,24 +1241,11 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
const handleResize = () => {
|
||||
chartInstance && chartInstance.resize()
|
||||
pieChartInstance && pieChartInstance.resize()
|
||||
balanceChartInstance && balanceChartInstance.resize()
|
||||
}
|
||||
|
||||
// 监听DOM引用变化,确保在月份切换DOM重建后重新渲染图表
|
||||
watch(chartRef, (newVal) => {
|
||||
// 无论有没有数据,只要DOM变了,就尝试渲染
|
||||
// 如果没有数据,renderChart 内部也应该处理(或者我们可以传空数据)
|
||||
if (newVal) {
|
||||
setTimeout(() => {
|
||||
// 传入当前 dailyData,即使是空的,renderChart 应该能处理
|
||||
renderChart(dailyData.value || [])
|
||||
chartInstance && chartInstance.resize()
|
||||
}, 50)
|
||||
}
|
||||
})
|
||||
|
||||
watch(pieChartRef, (newVal) => {
|
||||
if (newVal) {
|
||||
setTimeout(() => {
|
||||
@@ -1362,7 +1282,6 @@ onBeforeUnmount(() => {
|
||||
window.removeEventListener &&
|
||||
window.removeEventListener('transaction-deleted', onGlobalTransactionDeleted)
|
||||
window.removeEventListener('resize', handleResize)
|
||||
chartInstance && chartInstance.dispose()
|
||||
pieChartInstance && pieChartInstance.dispose()
|
||||
balanceChartInstance && balanceChartInstance.dispose()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user