2024秋软件工程现场编程作业
| 作业所属课程 | 软件工程2024 |
|---|---|
| 作业要求 | 2024秋软件工程现场编程作业 |
| 作业目标 | 现场开发出一个可运行的个人记账本应用程序 |
| 团队名称 | 当然不是草台班子 |
| 团队成员学号 | 姓名 |
|---|---|
| 102201427 | 侯丽珂 |
| 102201426 | 郑嘉祺 |
| 102201241 | 戴康怡 |
| 102201218 | 肖晗涵 |
| 112200328 | 谢李东 |
| 292300304 | 陈鹭 |
| 102201242 | 魏儀阳 |
| 082100170 | 朱胤帆 |
| 112200629 | 赵弈茗 |
| 102202132 | 郑冰智 |
一、组员职责分工
- 侯丽珂:负责团队协作、details功能前端实现
- 郑嘉祺:负责app.json编写、setting前端实现
- 肖晗涵:负责add功能前端实现
- 陈鹭:负责index功能前端实现
- 戴康怡:负责项目结构整理与项目信息汇总
- 谢李东:负责后端setting功能实现
- 郑冰智:负责后端index功能实现
- 朱胤帆:负责后端details功能实现
- 赵弈茗:负责后端add功能实现
- 魏儀阳:负责前后端对接及后端辅助编写
二、程序运行环境
- 操作系统: Windows 11
- 编程语言: Java
- 使用工具:微信开发者工具
三、软件运行截图
四、关键代码
关键代码1
Page({
data: {
type: 0, // 0 支出,1 收入,2 不计入收支
date: '',
money: '',
typeObj: expendType[0], // 当前选择的收支类型对象
expendType, // 支出类型
incomeType, // 收入类型
otherType, // 不计入收支类型
isAddRemark: false, // 是否添加备注
remark: '', // 备注内容
isAdd: false, // 添加请求是否结束,用于入账按钮加载效果
isDisabled: false, // 是否禁用入账按钮,防止多次触发
},
// 切换收支类型
switchType(e) {
const { type } = e.currentTarget.dataset;
const { expendType, incomeType, otherType } = this.data;
let obj = {};
// 根据类型选择对应的收支对象
if (type === 0) obj = expendType[0];
else if (type === 1) obj = incomeType[0];
else if (type === 2) obj = otherType[0];
// 更新页面数据中的 type 和 typeObj
this.setData({
type,
typeObj: obj,
});
},
// 输入金额,并格式化金额
inputMoney(e) {
this.setData({
money: makeMoneyTrue(e.detail.value), // 调用工具函数格式化金额
});
},
// 入账按钮点击事件
add() {
const { money } = this.data;
// 金额验证
if (money < 0.01) {
wx.showToast({
title: '金额不得小于 0.01',
icon: 'none',
});
return;
}
const { type, date, typeObj, account, remark } = this.data;
// 设置加载状态
this.setData({
isAdd: true,
isDisabled: true,
});
// 存储到本地
this._saveToLocal(type, date, typeObj, account, remark, money)
.then(() => {
// 清空输入,并跳转到首页
this.setData({
money: '',
remark: '',
});
wx.reLaunch({
url: '/pages/index/index',
});
})
.catch(() => {
wx.showToast({
title: '存储失败',
icon: 'none',
});
})
.finally(() => {
// 恢复按钮状态
this.setData({
isAdd: false,
isDisabled: false,
});
});
},
// 本地存储处理函数
_saveToLocal(type, date, typeObj, account, remark, money) {
return new Promise((resolve, reject) => {
// 获取当前存储的记账记录
const records = wx.getStorageSync('records') || [];
// 创建新的记账记录
const newRecord = {
type,
date,
name: typeObj.type,
account,
remark,
money: parseFloat(money) * 100, // 将金额转换为分为单位
};
// 将新记录添加到数组中
records.push(newRecord);
// 存储更新后的记录到本地存储
wx.setStorageSync('records', records);
// 更新账户余额
this._loadStorage(account, type, parseFloat(money) * 100);
resolve();
});
},
// 更新账户余额
_loadStorage(account, type, money) {
const accountObj = getAccount(); // 获取当前账户信息
// 根据收支类型更新账户余额
if (type === 0) {
accountObj[`${account}`] -= money;
if (accountObj[`${account}`] < 0) accountObj[`${account}`] = 0; // 确保账户余额不会小于0
}
if (type === 1) accountObj[`${account}`] += money;
// 存储更新后的账户信息
setAccount({
...accountObj
});
}
})
关键代码2
Page({
data: {
date: '', // 当前选择的日期
expend: '', // 当前选定日期的支出
income: '', // 当前选定日期的收入
classifyType: 0, // 分类构成类型
dailyComparisonType: 0 // 每日对比类型
},
/**
* changeTime 方法在 time-picker 首次加载和时间改变时触发
* 这个方法中直接加载数据,不需要在 onLoad 生命周期中加载
*/
changeTime (e) {
this.setData({
date: e.detail, // 更新当前选择的日期
expend: '', // 清空支出用于显示加载动画
income: '' // 清空收入用于显示加载动画
})
this._loadExpendIncome() // 调用加载支出和收入的方法
},
changeClassifyType (e) {
this.setData({
classifyType: e.detail // 更新分类构成类型
})
},
changeDailyComparisonType (e) {
this.setData({
dailyComparisonType: e.detail // 更新每日对比类型
})
},
/**
* _loadExpendIncome 方法用于加载指定日期的支出和收入
*/
_loadExpendIncome () {
const { beginTime, endTime } = getTimestamp(new Date(this.data.date)) // 获取时间戳
request('getDetailsByTypeGroup', { // 发起网络请求获取数据
beginTime,
endTime
}).then(res => {
const { list } = res.result // 解构获取的数据
this.setData({
expend: padMoney(list[0] ? list[0].money / 100 : 0), // 格式化支出金额
income: padMoney(list[1] ? list[1].money / 100 : 0) // 格式化收入金额
})
})
}
})
关键代码3
Page({
data: {
totalMoney: '', // 账户总余额
monthCanUseMoney: '', // 每月可支配金额
monthLeftoverMoney: '', // 月剩余可支配金额
expend: '', // 月总支出
income: '', // 月总收入
isReloading: false // 数据重载状态
},
onLoad() {
// 从本地缓存中取出账户余额以及每月可支配金额
const totalMoney = wx.getStorageSync('totalMoney') || '0.00';
const monthCanUseMoney = wx.getStorageSync('monthCanUseMoney') || '0.00';
this.setData({
totalMoney: padMoney(parseFloat(totalMoney)), // 格式化并显示账户总余额
monthCanUseMoney: padMoney(parseFloat(monthCanUseMoney)) // 格式化并显示每月可支配金额
});
// 加载模拟的每月总收入与总支出以及月剩余可支配金额
this._setMockData();
},
// 重新加载数据(由于数据是本地的,实际上只是重新读取数据)
reload() {
this.setData({
isReloading: true // 设置重载状态为 true,显示加载动画
});
setTimeout(() => {
this._setMockData(); // 重新加载模拟数据
this.setData({
isReloading: false // 设置重载状态为 false,隐藏加载动画
});
}, 0); // 使用 setTimeout 模拟异步操作,以便更新UI
},
/**
* 用于设置模拟的每月总收入与总支出以及月剩余可支配金额
*/
_setMockData() {
const mockIncome = '2000.00'; // 假设的月收入
const mockExpend = '1500.00'; // 假设的月支出
// 计算月剩余可支配金额
const monthLeftoverMoney = padMoney(parseFloat(this.data.monthCanUseMoney) - parseFloat(mockExpend));
// 设置数据到页面状态
this.setData({
expend: padMoney(parseFloat(mockExpend)), // 格式化并显示月总支出
income: padMoney(parseFloat(mockIncome)), // 格式化并显示月总收入
monthLeftoverMoney, // 显示月剩余可支配金额
isNetworkError: false // 重置网络错误状态
});
}
});
// 金额格式化函数,将数字格式化为两位小数
function padMoney(money) {
return money.toFixed(2);
}
五、软件亮点
-
金额格式化与单位转换:
- 在
inputMoney函数中,使用了makeMoneyTrue工具函数来格式化用户输入的金额,确保金额的正确性和一致性。同时,在存储时将金额转换为“分”的单位(parseFloat(money) * 100),避免了浮点数计算误差,确保数据的精度。
- 在
-
防止重复提交:
- 在
add函数中,通过设置isAdd和isDisabled状态,防止用户在请求未完成时多次点击“入账”按钮,从而避免了重复提交和数据冲突的问题。这种做法提高了用户体验和数据安全性。
- 在
-
用户体验优化:
reload方法通过模拟异步操作(使用setTimeout),在数据重载时显示加载动画,提升用户体验。这种方法避免了在数据重载过程中页面空白或卡顿的情况,使用户界面更加流畅和友好。changeTime方法在每次时间选择器改变时都会实时触发,确保用户在选择不同日期后能够立即看到对应的支出和收入数据,而不需要在onLoad生命周期中加载数据,提高了用户体验。- 通过
_saveToLocal函数,不仅将用户的记账记录存储到本地,还通过_loadStorage函数实时更新账户余额。这种设计确保了数据的完整性和一致性,使得用户能够实时查看账户余额的变化,增强了应用的实用性和可靠性。
六、团队经历
1. 选定工具与类型
在项目初期,我们计划使用 Python 和 Django 框架来构建一个 Web 页面版的个人记账本。然而,在进一步讨论和调研后,我们发现微信小程序在用户基础、易用性以及与微信生态系统的整合能力上具有明显优势。经过深入讨论和权衡,我们最终决定使用微信开发者工具来开发小程序。这一决定引发了一些争论,因为团队中有些成员对微信小程序的功能限制有所担忧。经过多轮讨论和技术评估,我们决定转向微信开发者工具,重点关注用户体验和功能实现。这一过程让我们学到了在项目初期进行充分讨论和调研的重要性,以及如何权衡各种选择的利弊。
2. 选定功能模块并分配任务
在确定了使用微信开发者工具之后,我们需要选定要实现的功能模块,包括details、add、index和setting。这一阶段的讨论非常费时,因为每个组员都对自己感兴趣的模块有不同的提议和设计思路。经过多轮讨论,我们决定根据每位成员的技术能力和兴趣进行分配,让大家在自己擅长的领域发挥最佳水平。这一过程不仅提高了团队成员的参与感,也增强了我们对项目整体结构的理解,确保了后续开发的进行。
3. 集成前后端功能的调试,解决缓存问题
在完成模块开发之后,我们面临着前后端功能集成时各种技术问题的挑战,尤其是缓存的问题。团队花费了相当多的时间来调试数据存储和读取的逻辑,确保在不同页面和模块之间能够正确共享状态。我们通过编写单元测试来验证数据的正确性,并对本地缓存进行逐步调试。在这一过程中,团队成员就如何管理状态与数据流开展了很多深入的讨论,分享各自对数据管理的看法。这一经历加深了团队成员间的技术交流,明确了今后在类似项目中避免缓存问题的最佳实践。
七、PSP
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 30 | 20 |
| · Estimate | · 估计这个任务需要多少时间 | 30 | 20 |
| Development | 开发 | 190 | 170 |
| · Analysis | · 需求分析 (包括学习新技术) | 20 | 15 |
| · Design Spec | · 生成设计文档 | 20 | 15 |
| · Design Review | · 设计复审 (和同事审核设计文档) | 15 | 10 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
| · Design | · 具体设计 | 20 | 25 |
| · Coding | · 具体编码 | 60 | 70 |
| · Code Review | · 代码复审 | 15 | 15 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 30 | 10 |
| Reporting | 报告 | 40 | 40 |
| · Test Report | · 测试报告 | 10 | 10 |
| · Size Measurement | · 计算工作量 | 10 | 10 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 60 |
| 合计 | 300 | 270 |
八、编程体验
- 侯丽珂: 此次现场编程暴露了很多之前线上多端编程没有发现的问题,诸如不同编程任务之间的合作,有重叠的编程任务中,在短时间内多次提交代码所造成的版本冲突等问题。由于缺乏短时间快速开发的经历,冷静程度和需要有人来引导、分工也显得至关重要,希望在日后的开发过程中,能多注意相关方面经验的积累。
- 郑嘉祺: 在这次团队编程挑战中,我们面临着时间紧迫与团队协作的双重考验。尽管初衷是携手打造一款功能齐全的记事本应用,但实际操作中,团队沟通的不畅与任务分配的模糊成为了我们最大的绊脚石。每个人对项目的整体愿景缺乏清晰的认识,加之初次合作带来的陌生感,导致我们像无头苍蝇般四处碰壁,难以形成有效的合力。最终,成果不尽如人意,不仅反映出我们在项目管理和团队协作上的短板,也深刻提醒了我们:在未来的合作中,明确目标、细化分工、强化沟通是通往成功的必经之路。
- 肖晗涵: 第一次一起现场编程没有经验有点手忙脚乱,下次一定有备而来做到更好!
- 朱胤帆:大致了解了微信小程序的开发流程,相比与传统的前后端开发,微信小程序开发可以将其耦合起来,适合开发小型的移动端开发,开发起来更有效率。
- 赵弈茗:团队编程对我来说是一次全新的体验和挑战,与之前的个人编程不一样。我们需要综合考虑队员们的技能和特长,确定好技术框架,并分配好任务和分工。每个团队成员写好各自的代码后各模块的集成也是一大挑战。这次的现场团队编程是一次宝贵的体验,不仅提升了我的技术能力,还提升了我与团队成员之间的沟通和协作能力。
- 陈鹭:在这次的团队编程活动中,我们小组成员发挥各自所长,共同完成了这项大任务。对于这项任务我有许多不懂的地方,但是大家都为我解答了疑惑。我明白了团队合作的重要性和有效性,也在这次的任务中感受到了小组成员的温暖。
- 戴康怡:这次由于个人原因请假了没有去到教室现场编程,而是采取线上的方式参与了编程,因此交流的时候就没有很方便。又是一场团队限时编程,就特别紧张!但还是在团队限时编程中体验到了前所未有的获得感。期待下次线下同大家一起编程!
- 谢李东:通过这次团队编程体验,我深刻感受到了团队编程在开发微信小程序方面的优势,比如前后端分离编程可以极大的提高编程的速度;有不懂的程序代码也可以组内成员讨论,快速解决。但是这次编程中时间分配稍不合理,在编程的最开始浪费了部分时间,但影响不大,最终还是顺利完成了紧急项目的编程。在本次团队编程中我受益匪浅。
- 郑冰智:在本次个人记账本的开发过程中,我负责实现账户信息的管理,包括用户的微信支付、支付宝、银行账户和现金余额的记录。在整个开发过程中,通过简洁的API设计,能够轻松地进行数据的存取操作。同时,在处理用户输入时,使用格式化方法保证了数据的准确性,提升了用户的操作体验。后续可以增加数据可视化功能,利用简单的图表展示用户的收支情况,帮助用户更直观地了解自己的财务状况,提升整体的交互体验
- 魏儀阳:通过这次编程体验,我不仅学到了很多知识,还收获了很多乐趣。编程让我更加深入地理解计算机原理。此外,通过这次团队编程的体验,我体会到了合作与沟通的重要性。一个优秀的项目的实现不仅需要技术过硬的成员,更需要良好的沟通和协作能力。
九、GitHub
1 仓库地址:
2 提交记录:

十、评分
| 成员 | 学号 | 组长打分(满分100分) |
|---|---|---|
| 成员2 | 郑嘉祺 | 95 |
| 成员3 | 肖晗涵 | 97 |
| 成员4 | 戴康怡 | 93 |
| 成员5 | 陈鹭 | 94 |
| 成员6 | 郑冰智 | 95 |
| 成员7 | 赵弈茗 | 96 |
| 成员8 | 朱胤帆 | 97 |
| 成员9 | 谢李东 | 95 |
| 成员10 | 魏儀阳 | 93 |

浙公网安备 33010602011771号