This commit is contained in:
SunCheng
2026-02-15 10:10:28 +08:00
parent e51a3edd50
commit a88556c784
92 changed files with 6751 additions and 776 deletions

View File

@@ -153,7 +153,7 @@ const router = useRouter()
const messageStore = useMessageStore()
// 主题
const theme = computed(() => messageStore.isDarkMode ? 'dark' : 'light')
const theme = computed(() => (messageStore.isDarkMode ? 'dark' : 'light'))
// 状态管理
const loading = ref(false)
@@ -196,8 +196,16 @@ const noneCategories = ref([])
// 颜色配置
const categoryColors = [
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7',
'#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9'
'#FF6B6B',
'#4ECDC4',
'#45B7D1',
'#96CEB4',
'#FFEAA7',
'#DDA0DD',
'#98D8C8',
'#F7DC6F',
'#BB8FCE',
'#85C1E9'
]
// 计算属性
@@ -266,7 +274,6 @@ const loadStatistics = async () => {
// 加载分类统计
await loadCategoryStatistics(year, month)
} catch (error) {
console.error('加载统计数据失败:', error)
hasError.value = true
@@ -303,8 +310,8 @@ const loadMonthlyData = async (year, month) => {
if (dailyResult?.success && dailyResult.data) {
// 转换数据格式:添加完整的 date 字段
trendStats.value = dailyResult.data
.filter(item => item != null)
.map(item => ({
.filter((item) => item != null)
.map((item) => ({
date: `${year}-${month.toString().padStart(2, '0')}-${item.day.toString().padStart(2, '0')}`,
expense: item.expense || 0,
income: item.income || 0,
@@ -323,15 +330,18 @@ const loadYearlyData = async (year) => {
const trendResult = await getTrendStatistics({ startYear: year, startMonth: 1, monthCount: 12 })
if (trendResult?.success && trendResult.data) {
// 计算年度汇总
const yearTotal = trendResult.data.reduce((acc, item) => {
const expense = item.expense || 0
const income = item.income || 0
return {
totalExpense: acc.totalExpense + expense,
totalIncome: acc.totalIncome + income,
balance: acc.balance + income - expense
}
}, { totalExpense: 0, totalIncome: 0, balance: 0 })
const yearTotal = trendResult.data.reduce(
(acc, item) => {
const expense = item.expense || 0
const income = item.income || 0
return {
totalExpense: acc.totalExpense + expense,
totalIncome: acc.totalIncome + income,
balance: acc.balance + income - expense
}
},
{ totalExpense: 0, totalIncome: 0, balance: 0 }
)
monthlyStats.value = {
...yearTotal,
@@ -339,7 +349,7 @@ const loadYearlyData = async (year) => {
incomeCount: 0
}
trendStats.value = trendResult.data.map(item => ({
trendStats.value = trendResult.data.map((item) => ({
date: `${item.year}-${item.month.toString().padStart(2, '0')}-01`,
amount: (item.income || 0) - (item.expense || 0),
count: 1
@@ -371,7 +381,8 @@ const loadWeeklyData = async () => {
monthlyStats.value = {
totalExpense: weekSummaryResult.data.totalExpense || 0,
totalIncome: weekSummaryResult.data.totalIncome || 0,
balance: (weekSummaryResult.data.totalIncome || 0) - (weekSummaryResult.data.totalExpense || 0),
balance:
(weekSummaryResult.data.totalIncome || 0) - (weekSummaryResult.data.totalExpense || 0),
expenseCount: weekSummaryResult.data.expenseCount || 0,
incomeCount: weekSummaryResult.data.incomeCount || 0
}
@@ -450,7 +461,11 @@ const loadCategoryStatistics = async (year, month) => {
const currentColors = getChartColors()
// 处理支出分类结果
if (expenseResult.status === 'fulfilled' && expenseResult.value?.success && expenseResult.value.data) {
if (
expenseResult.status === 'fulfilled' &&
expenseResult.value?.success &&
expenseResult.value.data
) {
expenseCategories.value = expenseResult.value.data.map((item, index) => ({
classify: item.classify,
amount: item.amount || 0,
@@ -461,7 +476,11 @@ const loadCategoryStatistics = async (year, month) => {
}
// 处理收入分类结果
if (incomeResult.status === 'fulfilled' && incomeResult.value?.success && incomeResult.value.data) {
if (
incomeResult.status === 'fulfilled' &&
incomeResult.value?.success &&
incomeResult.value.data
) {
incomeCategories.value = incomeResult.value.data.map((item) => ({
classify: item.classify,
amount: item.amount || 0,
@@ -510,7 +529,11 @@ const loadCategoryStatistics = async (year, month) => {
const currentColors = getChartColors()
// 处理支出分类结果
if (expenseResult.status === 'fulfilled' && expenseResult.value?.success && expenseResult.value.data) {
if (
expenseResult.status === 'fulfilled' &&
expenseResult.value?.success &&
expenseResult.value.data
) {
expenseCategories.value = expenseResult.value.data.map((item, index) => ({
classify: item.classify,
amount: item.amount || 0,
@@ -521,7 +544,11 @@ const loadCategoryStatistics = async (year, month) => {
}
// 处理收入分类结果
if (incomeResult.status === 'fulfilled' && incomeResult.value?.success && incomeResult.value.data) {
if (
incomeResult.status === 'fulfilled' &&
incomeResult.value?.success &&
incomeResult.value.data
) {
incomeCategories.value = incomeResult.value.data.map((item) => ({
classify: item.classify,
amount: item.amount || 0,
@@ -618,8 +645,7 @@ const isLastPeriod = () => {
}
case 'month': {
// 比较年月
return current.getFullYear() === now.getFullYear() &&
current.getMonth() === now.getMonth()
return current.getFullYear() === now.getFullYear() && current.getMonth() === now.getMonth()
}
case 'year': {
// 比较年份
@@ -717,20 +743,14 @@ watch(currentPeriod, () => {
if (currentPeriod.value === 'year') {
selectedDate.value = [currentDate.value.getFullYear()]
} else {
selectedDate.value = [
currentDate.value.getFullYear(),
currentDate.value.getMonth() + 1
]
selectedDate.value = [currentDate.value.getFullYear(), currentDate.value.getMonth() + 1]
}
})
// 初始化
onMounted(() => {
// 设置默认选中日期
selectedDate.value = [
currentDate.value.getFullYear(),
currentDate.value.getMonth() + 1
]
selectedDate.value = [currentDate.value.getFullYear(), currentDate.value.getMonth() + 1]
loadStatistics()
})
</script>
@@ -792,4 +812,4 @@ onMounted(() => {
padding-bottom: calc(95px + env(safe-area-inset-bottom, 0px));
}
}
</style>
</style>

View File

@@ -101,7 +101,7 @@ const updateChart = () => {
if (props.period === 'week') {
// 周统计:直接使用传入的数据,按日期排序
chartData = [...props.data].sort((a, b) => new Date(a.date) - new Date(b.date))
xAxisLabels = chartData.map(item => {
xAxisLabels = chartData.map((item) => {
const date = new Date(item.date)
const weekDays = ['日', '一', '二', '三', '四', '五', '六']
return weekDays[date.getDay()]
@@ -121,14 +121,14 @@ const updateChart = () => {
// 创建完整的数据映射
const dataMap = new Map()
props.data.forEach(item => {
props.data.forEach((item) => {
if (item && item.date) {
dataMap.set(item.date, item)
}
})
// 生成完整的数据序列
chartData = allDays.map(date => {
chartData = allDays.map((date) => {
const dayData = dataMap.get(date)
return {
date,
@@ -141,9 +141,9 @@ const updateChart = () => {
} else if (props.period === 'year') {
// 年统计:直接使用数据,显示月份标签
chartData = [...props.data]
.filter(item => item && item.date)
.filter((item) => item && item.date)
.sort((a, b) => new Date(a.date) - new Date(b.date))
xAxisLabels = chartData.map(item => {
xAxisLabels = chartData.map((item) => {
const date = new Date(item.date)
return `${date.getMonth() + 1}`
})
@@ -153,27 +153,29 @@ const updateChart = () => {
if (chartData.length === 0) {
const option = {
backgroundColor: 'transparent',
graphic: [{
type: 'text',
left: 'center',
top: 'middle',
style: {
text: '暂无数据',
fontSize: 16,
fill: messageStore.isDarkMode ? '#9CA3AF' : '#6B7280'
graphic: [
{
type: 'text',
left: 'center',
top: 'middle',
style: {
text: '暂无数据',
fontSize: 16,
fill: messageStore.isDarkMode ? '#9CA3AF' : '#6B7280'
}
}
}]
]
}
chartInstance.setOption(option)
return
}
// 准备图表数据
const expenseData = chartData.map(item => {
const expenseData = chartData.map((item) => {
const amount = item.amount || 0
return amount < 0 ? Math.abs(amount) : 0
})
const incomeData = chartData.map(item => {
const incomeData = chartData.map((item) => {
const amount = item.amount || 0
return amount > 0 ? amount : 0
})
@@ -305,18 +307,25 @@ const updateChart = () => {
}
// 监听数据变化
watch(() => props.data, () => {
if (chartInstance) {
updateChart()
}
}, { deep: true })
watch(
() => props.data,
() => {
if (chartInstance) {
updateChart()
}
},
{ deep: true }
)
// 监听主题变化
watch(() => messageStore.isDarkMode, () => {
if (chartInstance) {
updateChart()
watch(
() => messageStore.isDarkMode,
() => {
if (chartInstance) {
updateChart()
}
}
})
)
onMounted(() => {
initChart()
@@ -358,4 +367,4 @@ onBeforeUnmount(() => {
width: 100%;
height: 180px;
}
</style>
</style>

View File

@@ -24,4 +24,4 @@ const emit = defineEmits(['category-click'])
const handleCategoryClick = (classify, type) => {
emit('category-click', classify, type)
}
</script>
</script>

View File

@@ -2,7 +2,7 @@
<!-- 支出分类统计 -->
<div
class="common-card"
style="padding-bottom: 10px;"
style="padding-bottom: 10px"
>
<div class="card-header">
<h3 class="card-title">
@@ -255,11 +255,15 @@ const renderPieChart = () => {
}
// 监听数据变化重新渲染图表
watch(() => [props.categories, props.totalExpense, props.colors], () => {
nextTick(() => {
renderPieChart()
})
}, { deep: true, immediate: true })
watch(
() => [props.categories, props.totalExpense, props.colors],
() => {
nextTick(() => {
renderPieChart()
})
},
{ deep: true, immediate: true }
)
// 组件销毁时清理图表实例
onBeforeUnmount(() => {
@@ -404,4 +408,4 @@ onBeforeUnmount(() => {
padding: 2px 8px;
border-radius: 10px;
}
</style>
</style>

View File

@@ -41,8 +41,8 @@ const props = defineProps({
})
const balanceClass = computed(() => ({
'positive': props.balance >= 0,
'negative': props.balance < 0
positive: props.balance >= 0,
negative: props.balance < 0
}))
</script>
@@ -90,4 +90,4 @@ const balanceClass = computed(() => ({
}
}
}
</style>
</style>

View File

@@ -24,4 +24,4 @@ const emit = defineEmits(['category-click'])
const handleCategoryClick = (classify, type) => {
emit('category-click', classify, type)
}
</script>
</script>

View File

@@ -269,4 +269,4 @@ const noneCategories = computed(() => {
.none-text {
color: var(--van-gray-6);
}
</style>
</style>

View File

@@ -80,8 +80,8 @@ let chartInstance = null
// 计算结余样式类
const balanceClass = computed(() => ({
'positive': props.balance >= 0,
'negative': props.balance < 0
positive: props.balance >= 0,
negative: props.balance < 0
}))
// 计算图表标题
@@ -152,7 +152,7 @@ const updateChart = () => {
if (props.period === 'week') {
// 周统计:直接使用传入的数据,按日期排序
chartData = [...props.trendData].sort((a, b) => new Date(a.date) - new Date(b.date))
xAxisLabels = chartData.map(item => {
xAxisLabels = chartData.map((item) => {
const date = new Date(item.date)
const weekDays = ['日', '一', '二', '三', '四', '五', '六']
return weekDays[date.getDay()]
@@ -172,14 +172,14 @@ const updateChart = () => {
// 创建完整的数据映射
const dataMap = new Map()
props.trendData.forEach(item => {
props.trendData.forEach((item) => {
if (item && item.date) {
dataMap.set(item.date, item)
}
})
// 生成完整的数据序列
chartData = allDays.map(date => {
chartData = allDays.map((date) => {
const dayData = dataMap.get(date)
return {
date,
@@ -193,9 +193,9 @@ const updateChart = () => {
} else if (props.period === 'year') {
// 年统计:直接使用数据,显示月份标签
chartData = [...props.trendData]
.filter(item => item && item.date)
.filter((item) => item && item.date)
.sort((a, b) => new Date(a.date) - new Date(b.date))
xAxisLabels = chartData.map(item => {
xAxisLabels = chartData.map((item) => {
const date = new Date(item.date)
return `${date.getMonth() + 1}`
})
@@ -205,16 +205,18 @@ const updateChart = () => {
if (chartData.length === 0) {
const option = {
backgroundColor: 'transparent',
graphic: [{
type: 'text',
left: 'center',
top: 'middle',
style: {
text: '暂无数据',
fontSize: 16,
fill: messageStore.isDarkMode ? '#9CA3AF' : '#6B7280'
graphic: [
{
type: 'text',
left: 'center',
top: 'middle',
style: {
text: '暂无数据',
fontSize: 16,
fill: messageStore.isDarkMode ? '#9CA3AF' : '#6B7280'
}
}
}]
]
}
chartInstance.setOption(option)
return
@@ -227,7 +229,7 @@ const updateChart = () => {
const expenseData = []
const incomeData = []
chartData.forEach(item => {
chartData.forEach((item) => {
// 支持两种数据格式1) expense/income字段 2) amount字段兼容旧数据
let expense = 0
let income = 0
@@ -401,18 +403,25 @@ const updateChart = () => {
}
// 监听数据变化
watch(() => props.trendData, () => {
if (chartInstance) {
updateChart()
}
}, { deep: true })
watch(
() => props.trendData,
() => {
if (chartInstance) {
updateChart()
}
},
{ deep: true }
)
// 监听主题变化
watch(() => messageStore.isDarkMode, () => {
if (chartInstance) {
updateChart()
watch(
() => messageStore.isDarkMode,
() => {
if (chartInstance) {
updateChart()
}
}
})
)
onMounted(() => {
initChart()