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

This commit is contained in:
SunCheng
2026-02-19 11:04:05 +08:00
parent 6ca00c1478
commit 3402ffaae2
9 changed files with 233 additions and 34 deletions

View File

@@ -445,7 +445,7 @@ const getIconByClassify = (classify) => {
if (categoryIconMap.value[classify]) {
return categoryIconMap.value[classify]
}
// 降级:使用本地映射(向后兼容)
const iconMap = {
餐饮: 'food-o',
@@ -648,7 +648,7 @@ const loadCategories = async () => {
const response = await getCategoryList()
if (response && response.success) {
categories.value = response.data || []
// 构建分类名称 -> 图标的映射
const iconMap = {}
categories.value.forEach(category => {
@@ -668,7 +668,7 @@ const loadCategories = async () => {
onMounted(() => {
// 加载分类列表(用于图标映射)
loadCategories()
if (props.dataSource === 'api') {
fetchTransactions()
}

View File

@@ -105,7 +105,28 @@ const isEmpty = 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
})
// 图表插件(包含用户传入的插件)

View File

@@ -72,7 +72,7 @@ export function useChartTheme() {
label += ': '
}
if (context.parsed.y !== null) {
label += '¥' + context.parsed.y.toFixed(2)
label += '¥' + context.parsed.y.toFixed(0)
}
return label
}

View File

@@ -1,14 +1,15 @@
/**
* 格式化金额
* @param {number} value 金额数值
* @param {number} decimals 小数位数
* @returns {string} 格式化后的金额字符串
*/
export const formatMoney = (value) => {
export const formatMoney = (value, decimals = 1) => {
if (!value && value !== 0) {
return '0'
return Number(0).toFixed(decimals)
}
return Number(value)
.toFixed(0)
.toFixed(decimals)
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}

View File

@@ -134,6 +134,8 @@ const chartData = computed(() => {
label: '支出',
data: expenseData,
borderColor: '#ff6b6b',
yAxisID: 'yExpense',
order: 2,
backgroundColor: (context) => {
const chart = context.chart
const { ctx, chartArea } = chart
@@ -150,13 +152,15 @@ const chartData = computed(() => {
label: '收入',
data: incomeData,
borderColor: '#4ade80',
yAxisID: 'yIncome',
order: 1,
backgroundColor: (context) => {
const chart = context.chart
const { ctx, chartArea } = chart
if (!chartArea) {return 'rgba(74, 222, 128, 0.1)'}
return createGradient(ctx, chartArea, '#4ade80')
},
fill: true,
fill: false,
tension: 0.4,
pointRadius: 0,
pointHoverRadius: 4,
@@ -168,12 +172,40 @@ const chartData = computed(() => {
// Chart.js 配置
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({
scales: {
x: { display: false },
y: { display: false }
x: {
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: {
legend: { display: false },
@@ -202,7 +234,7 @@ const chartOptions = computed(() => {
},
label: (context) => {
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)}`
}
}
}

View File

@@ -210,13 +210,14 @@ const pieLabelLinePlugin = {
}
// 格式化金额
const formatMoney = (value) => {
const formatMoney = (value, decimals = 1) => {
if (!value && value !== 0) {
return '0'
return Number(0).toFixed(decimals)
}
return Number(value)
.toFixed(0)
.toFixed(decimals)
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
.replace(/\.0$/, '')
}
// 计算属性
@@ -317,6 +318,11 @@ const chartOptions = computed(() => {
right: 2
}
},
// 显式禁用笛卡尔坐标系Doughnut 图表不需要)
scales: {
x: { display: false },
y: { display: false }
},
plugins: {
legend: {
display: false
@@ -335,12 +341,12 @@ const chartOptions = computed(() => {
const value = context.parsed || 0
const total = context.dataset.data.reduce((a, b) => a + b, 0)
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : 0
return `${label}: ¥${formatMoney(value)} (${percentage}%)`
return `${label}: ¥${formatMoney(value, 0)} (${percentage}%)`
}
}
},
pieCenterText: {
text: `¥${formatMoney(totalAmount.value)}`,
text: `¥${formatMoney(totalAmount.value, 0)}`,
subtext: '总支出',
textColor: isDarkMode ? '#ffffff' : '#323233',
subtextColor: isDarkMode ? '#969799' : '#969799',
@@ -403,7 +409,7 @@ const onChartRender = (chart) => {
.ring-chart {
position: relative;
width: 100%;
height: 170px;
height: 190px;
margin: 0px auto 0;
overflow: visible;
}

View File

@@ -10,7 +10,7 @@
class="income-text"
style="font-size: 13px; margin-left: 4px"
>
¥{{ formatMoney(totalIncome) }}
¥{{ formatMoney(totalIncome, 0) }}
</span>
</h3>
<van-tag
@@ -36,7 +36,7 @@
<span class="category-name text-ellipsis">{{ category.classify || '未分类' }}</span>
</div>
<div class="category-amount income-text">
¥{{ formatMoney(category.amount) }}
¥{{ formatMoney(category.amount, 0) }}
</div>
</div>
</div>
@@ -80,7 +80,7 @@
<span class="category-name text-ellipsis">{{ category.classify || '未分类' }}</span>
</div>
<div class="category-amount none-text">
¥{{ formatMoney(category.amount) }}
¥{{ formatMoney(category.amount, 0) }}
</div>
</div>
</div>

View File

@@ -7,7 +7,7 @@
支出
</div>
<div class="stat-amount">
¥{{ formatMoney(amount) }}
¥{{ formatMoney(amount, 0) }}
</div>
</div>
<div class="stat-item income">
@@ -15,7 +15,7 @@
收入
</div>
<div class="stat-amount">
¥{{ formatMoney(income) }}
¥{{ formatMoney(income, 0) }}
</div>
</div>
<div class="stat-item balance">
@@ -26,7 +26,7 @@
class="stat-amount"
:class="balanceClass"
>
¥{{ formatMoney(balance) }}
¥{{ formatMoney(balance, 0) }}
</div>
</div>
</div>
@@ -203,10 +203,12 @@ const prepareChartData = () => {
let expense = 0
let income = 0
if (item.expense !== undefined || item.income !== undefined) {
// 优先使用 expense 和 income 字段
if ('expense' in item && 'income' in item) {
expense = item.expense || 0
income = item.income || 0
} else {
} else if ('amount' in item) {
// 如果只有 amount 字段,根据正负值判断
const amount = item.amount || 0
if (amount < 0) {
expense = Math.abs(amount)
@@ -239,6 +241,8 @@ const chartData = computed(() => {
label: '支出',
data: expenseData,
borderColor: expenseColor.value,
yAxisID: 'yExpense',
order: 2,
backgroundColor: (context) => {
const chart = context.chart
const { ctx, chartArea } = chart
@@ -258,6 +262,8 @@ const chartData = computed(() => {
label: '收入',
data: incomeData,
borderColor: incomeColor.value,
yAxisID: 'yIncome',
order: 1,
backgroundColor: (context) => {
const chart = context.chart
const { ctx, chartArea } = chart
@@ -266,7 +272,7 @@ const chartData = computed(() => {
}
return createGradient(ctx, chartArea, incomeColor.value)
},
fill: true,
fill: false,
tension: 0.4,
pointRadius: 0,
pointHoverRadius: 6,
@@ -279,12 +285,40 @@ const chartData = computed(() => {
// Chart.js 配置
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({
scales: {
x: { display: false },
y: { display: false }
x: {
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: {
legend: { display: false },
@@ -326,10 +360,12 @@ const chartOptions = computed(() => {
let dailyExpense = 0
let dailyIncome = 0
if (item.expense !== undefined || item.income !== undefined) {
// 优先使用 expense 和 income 字段
if ('expense' in item && 'income' in item) {
dailyExpense = item.expense || 0
dailyIncome = item.income || 0
} else {
} else if ('amount' in item) {
// 如果只有 amount 字段,根据正负值判断
const amount = item.amount || 0
if (amount < 0) {
dailyExpense = Math.abs(amount)
@@ -343,7 +379,7 @@ const chartOptions = computed(() => {
return null
}
return `${context.dataset.label}: ¥${value.toFixed(2)}`
return `${context.dataset.label}: ¥${value.toFixed(1)}`
}
}
}