You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
5.7 KiB
210 lines
5.7 KiB
1 week ago
|
Page({
|
||
|
data: {
|
||
|
selectedMonth: '',
|
||
|
selectedMonthText: '',
|
||
|
summary: {
|
||
|
income: '0.00',
|
||
|
expense: '0.00',
|
||
|
balance: '0.00'
|
||
|
},
|
||
|
activeType: 'expense',
|
||
|
categoryStats: [],
|
||
|
monthRecords: []
|
||
|
},
|
||
|
|
||
|
onLoad() {
|
||
|
// 初始化当前月份
|
||
|
const today = new Date();
|
||
|
const year = today.getFullYear();
|
||
|
const month = String(today.getMonth() + 1).padStart(2, '0');
|
||
|
const selectedMonth = `${year}-${month}`;
|
||
|
|
||
|
this.setData({
|
||
|
selectedMonth,
|
||
|
selectedMonthText: `${year}年${month}月`
|
||
|
});
|
||
|
|
||
|
this.updateStatistics();
|
||
|
},
|
||
|
|
||
|
onMonthChange(e) {
|
||
|
const selectedMonth = e.detail.value;
|
||
|
const [year, month] = selectedMonth.split('-');
|
||
|
this.setData({
|
||
|
selectedMonth,
|
||
|
selectedMonthText: `${year}年${month}月`
|
||
|
});
|
||
|
|
||
|
this.updateStatistics();
|
||
|
},
|
||
|
|
||
|
setActiveType(e) {
|
||
|
const type = e.currentTarget.dataset.type;
|
||
|
this.setData({ activeType: type }, () => {
|
||
|
this.calculateCategoryStats();
|
||
|
this.drawPieChart();
|
||
|
});
|
||
|
},
|
||
|
|
||
|
updateStatistics() {
|
||
|
// 获取所有记录
|
||
|
const records = wx.getStorageSync('records') || [];
|
||
|
const [year, month] = this.data.selectedMonth.split('-');
|
||
|
|
||
|
// 筛选当月记录
|
||
|
const monthRecords = records.filter(record => {
|
||
|
const recordDate = new Date(record.date);
|
||
|
return recordDate.getFullYear() === parseInt(year) &&
|
||
|
(recordDate.getMonth() + 1) === parseInt(month);
|
||
|
});
|
||
|
|
||
|
// 按日期倒序排列
|
||
|
monthRecords.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||
|
|
||
|
// 计算收支汇总
|
||
|
let income = 0;
|
||
|
let expense = 0;
|
||
|
|
||
|
monthRecords.forEach(record => {
|
||
|
if (record.type === 'income') {
|
||
|
income += parseFloat(record.amount);
|
||
|
} else {
|
||
|
expense += parseFloat(record.amount);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
const balance = income - expense;
|
||
|
|
||
|
this.setData({
|
||
|
monthRecords,
|
||
|
summary: {
|
||
|
income: income.toFixed(2),
|
||
|
expense: expense.toFixed(2),
|
||
|
balance: balance.toFixed(2)
|
||
|
}
|
||
|
}, () => {
|
||
|
this.calculateCategoryStats();
|
||
|
this.drawPieChart();
|
||
|
});
|
||
|
},
|
||
|
|
||
|
calculateCategoryStats() {
|
||
|
const { monthRecords, activeType } = this.data;
|
||
|
const typeRecords = monthRecords.filter(record => record.type === activeType);
|
||
|
|
||
|
// 按分类汇总
|
||
|
const categoryMap = {};
|
||
|
|
||
|
typeRecords.forEach(record => {
|
||
|
if (!categoryMap[record.categoryId]) {
|
||
|
categoryMap[record.categoryId] = {
|
||
|
categoryId: record.categoryId,
|
||
|
categoryName: record.categoryName,
|
||
|
amount: 0,
|
||
|
count: 0
|
||
|
};
|
||
|
}
|
||
|
|
||
|
categoryMap[record.categoryId].amount += parseFloat(record.amount);
|
||
|
categoryMap[record.categoryId].count += 1;
|
||
|
});
|
||
|
|
||
|
// 转换为数组并排序
|
||
|
let categoryStats = Object.values(categoryMap);
|
||
|
categoryStats.sort((a, b) => b.amount - a.amount);
|
||
|
|
||
|
// 计算百分比
|
||
|
const total = activeType === 'income'
|
||
|
? parseFloat(this.data.summary.income)
|
||
|
: parseFloat(this.data.summary.expense);
|
||
|
|
||
|
if (total > 0) {
|
||
|
categoryStats.forEach(item => {
|
||
|
item.percentage = Math.round((item.amount / total) * 100);
|
||
|
item.amount = item.amount.toFixed(2);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// 分配颜色
|
||
|
const colors = ['#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39'];
|
||
|
categoryStats.forEach((item, index) => {
|
||
|
item.color = colors[index % colors.length];
|
||
|
});
|
||
|
|
||
|
this.setData({ categoryStats });
|
||
|
},
|
||
|
|
||
|
drawPieChart() {
|
||
|
const { categoryStats } = this.data;
|
||
|
|
||
|
if (categoryStats.length === 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const ctx = wx.createCanvasContext('pieChart', this);
|
||
|
const centerX = 120; // 饼图中心X坐标
|
||
|
const centerY = 120; // 饼图中心Y坐标
|
||
|
const radius = 90; // 饼图半径
|
||
|
|
||
|
let startAngle = 0;
|
||
|
|
||
|
categoryStats.forEach(item => {
|
||
|
const percentage = parseFloat(item.percentage);
|
||
|
const endAngle = startAngle + 2 * Math.PI * (percentage / 100);
|
||
|
|
||
|
// 绘制扇形
|
||
|
ctx.beginPath();
|
||
|
ctx.setFillStyle(item.color);
|
||
|
ctx.moveTo(centerX, centerY);
|
||
|
ctx.arc(centerX, centerY, radius, startAngle, endAngle, false);
|
||
|
ctx.closePath();
|
||
|
ctx.fill();
|
||
|
|
||
|
// 计算文本位置
|
||
|
const midAngle = startAngle + (endAngle - startAngle) / 2;
|
||
|
const textRadius = radius * 0.6; // 文本距离中心的距离
|
||
|
const textX = centerX + Math.cos(midAngle) * textRadius;
|
||
|
const textY = centerY + Math.sin(midAngle) * textRadius;
|
||
|
|
||
|
// 绘制百分比文本
|
||
|
ctx.setFontSize(14);
|
||
|
ctx.setFillStyle('#333');
|
||
|
ctx.setTextAlign('center');
|
||
|
ctx.setTextBaseline('middle');
|
||
|
ctx.fillText(`${percentage}%`, textX, textY);
|
||
|
|
||
|
startAngle = endAngle;
|
||
|
});
|
||
|
|
||
|
// 绘制中心空白区域
|
||
|
ctx.beginPath();
|
||
|
ctx.setFillStyle('#ffffff');
|
||
|
ctx.arc(centerX, centerY, radius * 0.4, 0, 2 * Math.PI, false);
|
||
|
ctx.fill();
|
||
|
|
||
|
// 绘制中心文本
|
||
|
const total = this.data.activeType === 'income'
|
||
|
? this.data.summary.income
|
||
|
: this.data.summary.expense;
|
||
|
|
||
|
ctx.setFontSize(16);
|
||
|
ctx.setFillStyle('#333');
|
||
|
ctx.setTextAlign('center');
|
||
|
ctx.setTextBaseline('middle');
|
||
|
ctx.fillText(`总计`, centerX, centerY - 10);
|
||
|
|
||
|
ctx.setFontSize(18);
|
||
|
ctx.setFillStyle(this.data.activeType === 'income' ? '#4CAF50' : '#f44336');
|
||
|
ctx.fillText(`¥${total}`, centerX, centerY + 15);
|
||
|
|
||
|
ctx.draw();
|
||
|
},
|
||
|
|
||
|
formatDate(dateStr) {
|
||
|
const date = new Date(dateStr);
|
||
|
const month = date.getMonth() + 1;
|
||
|
const day = date.getDate();
|
||
|
return `${month}月${day}日`;
|
||
|
}
|
||
|
})
|