fix
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 3m13s
Docker Build & Deploy / Deploy to Production (push) Successful in 8s
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 3m13s
Docker Build & Deploy / Deploy to Production (push) Successful in 8s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
This commit is contained in:
103
.doc/chart-grid-lines-issue.md
Normal file
103
.doc/chart-grid-lines-issue.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
---
|
||||||
|
title: Doughnut/Pie 图表显示网格线问题修复
|
||||||
|
author: AI Assistant
|
||||||
|
date: 2026-02-19
|
||||||
|
status: final
|
||||||
|
category: 技术修复
|
||||||
|
---
|
||||||
|
|
||||||
|
# Doughnut/Pie 图表显示网格线问题修复
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
|
||||||
|
在使用 Chart.js 的 Doughnut(环形图)或 Pie(饼图)时,图表中不应该显示笛卡尔坐标系的网格线,但在某些情况下会错误地显示出来。
|
||||||
|
|
||||||
|
## 问题根源
|
||||||
|
|
||||||
|
`useChartTheme.ts` 中的 `baseChartOptions` 包含了 `scales.x` 和 `scales.y` 配置(第 82-108 行),这些配置适用于折线图、柱状图等**笛卡尔坐标系图表**,但不适用于 Doughnut/Pie 这类**极坐标图表**。
|
||||||
|
|
||||||
|
当使用 `getChartOptions()` 合并配置时,这些默认的 `scales` 配置会被带入到圆形图表中,导致显示网格线。
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
### 方案 1:在具体组件中显式禁用(已应用)
|
||||||
|
|
||||||
|
在使用 Doughnut/Pie 图表的组件中,调用 `getChartOptions()` 时显式传入 `scales` 配置:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const chartOptions = computed(() => {
|
||||||
|
return getChartOptions({
|
||||||
|
cutout: '65%',
|
||||||
|
// 显式禁用笛卡尔坐标系(Doughnut 图表不需要)
|
||||||
|
scales: {
|
||||||
|
x: { display: false },
|
||||||
|
y: { display: false }
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
// ...其他插件配置
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方案 2:BaseChart 组件自动处理(已优化)
|
||||||
|
|
||||||
|
优化 `BaseChart.vue` 组件(第 106-128 行),使其能够自动检测圆形图表并强制禁用坐标轴:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const mergedOptions = computed(() => {
|
||||||
|
const isCircularChart = props.type === 'pie' || props.type === 'doughnut'
|
||||||
|
|
||||||
|
const merged = getChartOptions(props.options)
|
||||||
|
|
||||||
|
if (isCircularChart) {
|
||||||
|
if (!props.options?.scales) {
|
||||||
|
// 用户完全没传 scales,直接删除
|
||||||
|
delete merged.scales
|
||||||
|
} else {
|
||||||
|
// 用户传了 scales,确保 display 设置为 false
|
||||||
|
if (merged.scales) {
|
||||||
|
if (merged.scales.x) merged.scales.x.display = false
|
||||||
|
if (merged.scales.y) merged.scales.y.display = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 已修复的文件
|
||||||
|
|
||||||
|
1. **Web/src/views/statisticsV2/modules/ExpenseCategoryCard.vue**
|
||||||
|
- 在 `chartOptions` 中添加了显式的 `scales` 禁用配置(第 321-324 行)
|
||||||
|
|
||||||
|
2. **Web/src/components/Charts/BaseChart.vue**
|
||||||
|
- 优化了圆形图表的 `scales` 处理逻辑(第 106-128 行)
|
||||||
|
|
||||||
|
## 已验证的文件(无需修改)
|
||||||
|
|
||||||
|
1. **Web/src/components/Budget/BudgetChartAnalysis.vue**
|
||||||
|
- `monthGaugeOptions` 和 `yearGaugeOptions` 已经包含正确的 `scales` 配置
|
||||||
|
|
||||||
|
## 预防措施
|
||||||
|
|
||||||
|
1. **新增 Doughnut/Pie 图表时**:始终显式设置 `scales: { x: { display: false }, y: { display: false } }`
|
||||||
|
2. **使用 BaseChart 组件**:依赖其自动处理逻辑(已优化)
|
||||||
|
3. **代码审查**:检查所有圆形图表配置,确保不包含笛卡尔坐标系配置
|
||||||
|
|
||||||
|
## Chart.js 图表类型说明
|
||||||
|
|
||||||
|
| 图表类型 | 坐标系 | 是否需要 scales |
|
||||||
|
|---------|--------|----------------|
|
||||||
|
| Line | 笛卡尔 | ✓ 需要 x/y |
|
||||||
|
| Bar | 笛卡尔 | ✓ 需要 x/y |
|
||||||
|
| Pie | 极坐标 | ✗ 不需要 |
|
||||||
|
| Doughnut| 极坐标 | ✗ 不需要 |
|
||||||
|
| Radar | 极坐标 | ✗ 不需要 |
|
||||||
|
|
||||||
|
## 相关资源
|
||||||
|
|
||||||
|
- Chart.js 官方文档:https://www.chartjs.org/docs/latest/
|
||||||
|
- 项目主题配置:`Web/src/composables/useChartTheme.ts`
|
||||||
|
- 图表基础组件:`Web/src/components/Charts/BaseChart.vue`
|
||||||
@@ -445,7 +445,7 @@ const getIconByClassify = (classify) => {
|
|||||||
if (categoryIconMap.value[classify]) {
|
if (categoryIconMap.value[classify]) {
|
||||||
return categoryIconMap.value[classify]
|
return categoryIconMap.value[classify]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 降级:使用本地映射(向后兼容)
|
// 降级:使用本地映射(向后兼容)
|
||||||
const iconMap = {
|
const iconMap = {
|
||||||
餐饮: 'food-o',
|
餐饮: 'food-o',
|
||||||
@@ -648,7 +648,7 @@ const loadCategories = async () => {
|
|||||||
const response = await getCategoryList()
|
const response = await getCategoryList()
|
||||||
if (response && response.success) {
|
if (response && response.success) {
|
||||||
categories.value = response.data || []
|
categories.value = response.data || []
|
||||||
|
|
||||||
// 构建分类名称 -> 图标的映射
|
// 构建分类名称 -> 图标的映射
|
||||||
const iconMap = {}
|
const iconMap = {}
|
||||||
categories.value.forEach(category => {
|
categories.value.forEach(category => {
|
||||||
@@ -668,7 +668,7 @@ const loadCategories = async () => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 加载分类列表(用于图标映射)
|
// 加载分类列表(用于图标映射)
|
||||||
loadCategories()
|
loadCategories()
|
||||||
|
|
||||||
if (props.dataSource === 'api') {
|
if (props.dataSource === 'api') {
|
||||||
fetchTransactions()
|
fetchTransactions()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,28 @@ const isEmpty = computed(() => {
|
|||||||
|
|
||||||
// 合并配置项
|
// 合并配置项
|
||||||
const mergedOptions = computed(() => {
|
const mergedOptions = computed(() => {
|
||||||
return getChartOptions(props.options)
|
const isCircularChart = props.type === 'pie' || props.type === 'doughnut'
|
||||||
|
|
||||||
|
// 先调用主题合并
|
||||||
|
const merged = getChartOptions(props.options)
|
||||||
|
|
||||||
|
// pie/doughnut 不需要 x/y 坐标轴;强制隐藏 scales 避免网格线
|
||||||
|
if (isCircularChart) {
|
||||||
|
// 如果用户没有显式传 scales,或者传入的 scales 没有明确 display 设置
|
||||||
|
// 则强制禁用坐标轴(圆形图表不应该显示笛卡尔坐标系)
|
||||||
|
if (!props.options?.scales) {
|
||||||
|
// 用户完全没传 scales,直接删除
|
||||||
|
delete merged.scales
|
||||||
|
} else {
|
||||||
|
// 用户传了 scales,确保 display 设置为 false
|
||||||
|
if (merged.scales) {
|
||||||
|
if (merged.scales.x) {merged.scales.x.display = false}
|
||||||
|
if (merged.scales.y) {merged.scales.y.display = false}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged
|
||||||
})
|
})
|
||||||
|
|
||||||
// 图表插件(包含用户传入的插件)
|
// 图表插件(包含用户传入的插件)
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export function useChartTheme() {
|
|||||||
label += ': '
|
label += ': '
|
||||||
}
|
}
|
||||||
if (context.parsed.y !== null) {
|
if (context.parsed.y !== null) {
|
||||||
label += '¥' + context.parsed.y.toFixed(2)
|
label += '¥' + context.parsed.y.toFixed(0)
|
||||||
}
|
}
|
||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* 格式化金额
|
* 格式化金额
|
||||||
* @param {number} value 金额数值
|
* @param {number} value 金额数值
|
||||||
|
* @param {number} decimals 小数位数
|
||||||
* @returns {string} 格式化后的金额字符串
|
* @returns {string} 格式化后的金额字符串
|
||||||
*/
|
*/
|
||||||
export const formatMoney = (value) => {
|
export const formatMoney = (value, decimals = 1) => {
|
||||||
if (!value && value !== 0) {
|
if (!value && value !== 0) {
|
||||||
return '0'
|
return Number(0).toFixed(decimals)
|
||||||
}
|
}
|
||||||
return Number(value)
|
return Number(value)
|
||||||
.toFixed(0)
|
.toFixed(decimals)
|
||||||
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -134,6 +134,8 @@ const chartData = computed(() => {
|
|||||||
label: '支出',
|
label: '支出',
|
||||||
data: expenseData,
|
data: expenseData,
|
||||||
borderColor: '#ff6b6b',
|
borderColor: '#ff6b6b',
|
||||||
|
yAxisID: 'yExpense',
|
||||||
|
order: 2,
|
||||||
backgroundColor: (context) => {
|
backgroundColor: (context) => {
|
||||||
const chart = context.chart
|
const chart = context.chart
|
||||||
const { ctx, chartArea } = chart
|
const { ctx, chartArea } = chart
|
||||||
@@ -150,13 +152,15 @@ const chartData = computed(() => {
|
|||||||
label: '收入',
|
label: '收入',
|
||||||
data: incomeData,
|
data: incomeData,
|
||||||
borderColor: '#4ade80',
|
borderColor: '#4ade80',
|
||||||
|
yAxisID: 'yIncome',
|
||||||
|
order: 1,
|
||||||
backgroundColor: (context) => {
|
backgroundColor: (context) => {
|
||||||
const chart = context.chart
|
const chart = context.chart
|
||||||
const { ctx, chartArea } = chart
|
const { ctx, chartArea } = chart
|
||||||
if (!chartArea) {return 'rgba(74, 222, 128, 0.1)'}
|
if (!chartArea) {return 'rgba(74, 222, 128, 0.1)'}
|
||||||
return createGradient(ctx, chartArea, '#4ade80')
|
return createGradient(ctx, chartArea, '#4ade80')
|
||||||
},
|
},
|
||||||
fill: true,
|
fill: false,
|
||||||
tension: 0.4,
|
tension: 0.4,
|
||||||
pointRadius: 0,
|
pointRadius: 0,
|
||||||
pointHoverRadius: 4,
|
pointHoverRadius: 4,
|
||||||
@@ -168,12 +172,40 @@ const chartData = computed(() => {
|
|||||||
|
|
||||||
// Chart.js 配置
|
// Chart.js 配置
|
||||||
const chartOptions = computed(() => {
|
const chartOptions = computed(() => {
|
||||||
const { chartData: rawData } = prepareChartData()
|
const { chartData: rawData, expenseData, incomeData } = prepareChartData()
|
||||||
|
const maxExpense = Math.max(...expenseData, 0)
|
||||||
|
const maxIncome = Math.max(...incomeData, 0)
|
||||||
|
|
||||||
return getChartOptions({
|
return getChartOptions({
|
||||||
scales: {
|
scales: {
|
||||||
x: { display: false },
|
x: {
|
||||||
y: { display: false }
|
display: false,
|
||||||
|
grid: { display: false, drawBorder: false },
|
||||||
|
ticks: { display: false },
|
||||||
|
border: { display: false }
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
display: false,
|
||||||
|
grid: { display: false, drawBorder: false },
|
||||||
|
ticks: { display: false },
|
||||||
|
border: { display: false }
|
||||||
|
},
|
||||||
|
yIncome: {
|
||||||
|
display: false,
|
||||||
|
beginAtZero: true,
|
||||||
|
suggestedMax: maxIncome ? maxIncome * 1.1 : undefined,
|
||||||
|
grid: { display: false, drawBorder: false },
|
||||||
|
ticks: { display: false },
|
||||||
|
border: { display: false }
|
||||||
|
},
|
||||||
|
yExpense: {
|
||||||
|
display: false,
|
||||||
|
beginAtZero: true,
|
||||||
|
suggestedMax: maxExpense ? maxExpense * 1.1 : undefined,
|
||||||
|
grid: { display: false, drawBorder: false },
|
||||||
|
ticks: { display: false },
|
||||||
|
border: { display: false }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { display: false },
|
legend: { display: false },
|
||||||
@@ -202,7 +234,7 @@ const chartOptions = computed(() => {
|
|||||||
},
|
},
|
||||||
label: (context) => {
|
label: (context) => {
|
||||||
if (context.parsed.y === 0) {return null}
|
if (context.parsed.y === 0) {return null}
|
||||||
return `${context.dataset.label}: ¥${context.parsed.y.toFixed(2)}`
|
return `${context.dataset.label}: ¥${context.parsed.y.toFixed(1)}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,13 +210,14 @@ const pieLabelLinePlugin = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 格式化金额
|
// 格式化金额
|
||||||
const formatMoney = (value) => {
|
const formatMoney = (value, decimals = 1) => {
|
||||||
if (!value && value !== 0) {
|
if (!value && value !== 0) {
|
||||||
return '0'
|
return Number(0).toFixed(decimals)
|
||||||
}
|
}
|
||||||
return Number(value)
|
return Number(value)
|
||||||
.toFixed(0)
|
.toFixed(decimals)
|
||||||
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||||
|
.replace(/\.0$/, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
@@ -317,6 +318,11 @@ const chartOptions = computed(() => {
|
|||||||
right: 2
|
right: 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 显式禁用笛卡尔坐标系(Doughnut 图表不需要)
|
||||||
|
scales: {
|
||||||
|
x: { display: false },
|
||||||
|
y: { display: false }
|
||||||
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
display: false
|
display: false
|
||||||
@@ -335,12 +341,12 @@ const chartOptions = computed(() => {
|
|||||||
const value = context.parsed || 0
|
const value = context.parsed || 0
|
||||||
const total = context.dataset.data.reduce((a, b) => a + b, 0)
|
const total = context.dataset.data.reduce((a, b) => a + b, 0)
|
||||||
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : 0
|
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : 0
|
||||||
return `${label}: ¥${formatMoney(value)} (${percentage}%)`
|
return `${label}: ¥${formatMoney(value, 0)} (${percentage}%)`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pieCenterText: {
|
pieCenterText: {
|
||||||
text: `¥${formatMoney(totalAmount.value)}`,
|
text: `¥${formatMoney(totalAmount.value, 0)}`,
|
||||||
subtext: '总支出',
|
subtext: '总支出',
|
||||||
textColor: isDarkMode ? '#ffffff' : '#323233',
|
textColor: isDarkMode ? '#ffffff' : '#323233',
|
||||||
subtextColor: isDarkMode ? '#969799' : '#969799',
|
subtextColor: isDarkMode ? '#969799' : '#969799',
|
||||||
@@ -403,7 +409,7 @@ const onChartRender = (chart) => {
|
|||||||
.ring-chart {
|
.ring-chart {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 170px;
|
height: 190px;
|
||||||
margin: 0px auto 0;
|
margin: 0px auto 0;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
class="income-text"
|
class="income-text"
|
||||||
style="font-size: 13px; margin-left: 4px"
|
style="font-size: 13px; margin-left: 4px"
|
||||||
>
|
>
|
||||||
¥{{ formatMoney(totalIncome) }}
|
¥{{ formatMoney(totalIncome, 0) }}
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
<van-tag
|
<van-tag
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<span class="category-name text-ellipsis">{{ category.classify || '未分类' }}</span>
|
<span class="category-name text-ellipsis">{{ category.classify || '未分类' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="category-amount income-text">
|
<div class="category-amount income-text">
|
||||||
¥{{ formatMoney(category.amount) }}
|
¥{{ formatMoney(category.amount, 0) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
<span class="category-name text-ellipsis">{{ category.classify || '未分类' }}</span>
|
<span class="category-name text-ellipsis">{{ category.classify || '未分类' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="category-amount none-text">
|
<div class="category-amount none-text">
|
||||||
¥{{ formatMoney(category.amount) }}
|
¥{{ formatMoney(category.amount, 0) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
支出
|
支出
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-amount">
|
<div class="stat-amount">
|
||||||
¥{{ formatMoney(amount) }}
|
¥{{ formatMoney(amount, 0) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item income">
|
<div class="stat-item income">
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
收入
|
收入
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-amount">
|
<div class="stat-amount">
|
||||||
¥{{ formatMoney(income) }}
|
¥{{ formatMoney(income, 0) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item balance">
|
<div class="stat-item balance">
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
class="stat-amount"
|
class="stat-amount"
|
||||||
:class="balanceClass"
|
:class="balanceClass"
|
||||||
>
|
>
|
||||||
¥{{ formatMoney(balance) }}
|
¥{{ formatMoney(balance, 0) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -203,10 +203,12 @@ const prepareChartData = () => {
|
|||||||
let expense = 0
|
let expense = 0
|
||||||
let income = 0
|
let income = 0
|
||||||
|
|
||||||
if (item.expense !== undefined || item.income !== undefined) {
|
// 优先使用 expense 和 income 字段
|
||||||
|
if ('expense' in item && 'income' in item) {
|
||||||
expense = item.expense || 0
|
expense = item.expense || 0
|
||||||
income = item.income || 0
|
income = item.income || 0
|
||||||
} else {
|
} else if ('amount' in item) {
|
||||||
|
// 如果只有 amount 字段,根据正负值判断
|
||||||
const amount = item.amount || 0
|
const amount = item.amount || 0
|
||||||
if (amount < 0) {
|
if (amount < 0) {
|
||||||
expense = Math.abs(amount)
|
expense = Math.abs(amount)
|
||||||
@@ -239,6 +241,8 @@ const chartData = computed(() => {
|
|||||||
label: '支出',
|
label: '支出',
|
||||||
data: expenseData,
|
data: expenseData,
|
||||||
borderColor: expenseColor.value,
|
borderColor: expenseColor.value,
|
||||||
|
yAxisID: 'yExpense',
|
||||||
|
order: 2,
|
||||||
backgroundColor: (context) => {
|
backgroundColor: (context) => {
|
||||||
const chart = context.chart
|
const chart = context.chart
|
||||||
const { ctx, chartArea } = chart
|
const { ctx, chartArea } = chart
|
||||||
@@ -258,6 +262,8 @@ const chartData = computed(() => {
|
|||||||
label: '收入',
|
label: '收入',
|
||||||
data: incomeData,
|
data: incomeData,
|
||||||
borderColor: incomeColor.value,
|
borderColor: incomeColor.value,
|
||||||
|
yAxisID: 'yIncome',
|
||||||
|
order: 1,
|
||||||
backgroundColor: (context) => {
|
backgroundColor: (context) => {
|
||||||
const chart = context.chart
|
const chart = context.chart
|
||||||
const { ctx, chartArea } = chart
|
const { ctx, chartArea } = chart
|
||||||
@@ -266,7 +272,7 @@ const chartData = computed(() => {
|
|||||||
}
|
}
|
||||||
return createGradient(ctx, chartArea, incomeColor.value)
|
return createGradient(ctx, chartArea, incomeColor.value)
|
||||||
},
|
},
|
||||||
fill: true,
|
fill: false,
|
||||||
tension: 0.4,
|
tension: 0.4,
|
||||||
pointRadius: 0,
|
pointRadius: 0,
|
||||||
pointHoverRadius: 6,
|
pointHoverRadius: 6,
|
||||||
@@ -279,12 +285,40 @@ const chartData = computed(() => {
|
|||||||
|
|
||||||
// Chart.js 配置
|
// Chart.js 配置
|
||||||
const chartOptions = computed(() => {
|
const chartOptions = computed(() => {
|
||||||
const { chartData: rawData } = prepareChartData()
|
const { chartData: rawData, expenseData, incomeData } = prepareChartData()
|
||||||
|
const maxExpense = Math.max(...expenseData, 0)
|
||||||
|
const maxIncome = Math.max(...incomeData, 0)
|
||||||
|
|
||||||
return getChartOptions({
|
return getChartOptions({
|
||||||
scales: {
|
scales: {
|
||||||
x: { display: false },
|
x: {
|
||||||
y: { display: false }
|
display: false,
|
||||||
|
grid: { display: false, drawBorder: false },
|
||||||
|
ticks: { display: false },
|
||||||
|
border: { display: false }
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
display: false,
|
||||||
|
grid: { display: false, drawBorder: false },
|
||||||
|
ticks: { display: false },
|
||||||
|
border: { display: false }
|
||||||
|
},
|
||||||
|
yIncome: {
|
||||||
|
display: false,
|
||||||
|
beginAtZero: true,
|
||||||
|
suggestedMax: maxIncome ? maxIncome * 1.1 : undefined,
|
||||||
|
grid: { display: false, drawBorder: false },
|
||||||
|
ticks: { display: false },
|
||||||
|
border: { display: false }
|
||||||
|
},
|
||||||
|
yExpense: {
|
||||||
|
display: false,
|
||||||
|
beginAtZero: true,
|
||||||
|
suggestedMax: maxExpense ? maxExpense * 1.1 : undefined,
|
||||||
|
grid: { display: false, drawBorder: false },
|
||||||
|
ticks: { display: false },
|
||||||
|
border: { display: false }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { display: false },
|
legend: { display: false },
|
||||||
@@ -326,10 +360,12 @@ const chartOptions = computed(() => {
|
|||||||
let dailyExpense = 0
|
let dailyExpense = 0
|
||||||
let dailyIncome = 0
|
let dailyIncome = 0
|
||||||
|
|
||||||
if (item.expense !== undefined || item.income !== undefined) {
|
// 优先使用 expense 和 income 字段
|
||||||
|
if ('expense' in item && 'income' in item) {
|
||||||
dailyExpense = item.expense || 0
|
dailyExpense = item.expense || 0
|
||||||
dailyIncome = item.income || 0
|
dailyIncome = item.income || 0
|
||||||
} else {
|
} else if ('amount' in item) {
|
||||||
|
// 如果只有 amount 字段,根据正负值判断
|
||||||
const amount = item.amount || 0
|
const amount = item.amount || 0
|
||||||
if (amount < 0) {
|
if (amount < 0) {
|
||||||
dailyExpense = Math.abs(amount)
|
dailyExpense = Math.abs(amount)
|
||||||
@@ -343,7 +379,7 @@ const chartOptions = computed(() => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${context.dataset.label}: ¥${value.toFixed(2)}`
|
return `${context.dataset.label}: ¥${value.toFixed(1)}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user