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
  • 使用工具:微信开发者工具

三、软件运行截图

Image 1 Image 2 Image 3
Image 4 Image 5

四、关键代码

关键代码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);  
}  

五、软件亮点

  1. 金额格式化与单位转换

    • inputMoney 函数中,使用了 makeMoneyTrue 工具函数来格式化用户输入的金额,确保金额的正确性和一致性。同时,在存储时将金额转换为“分”的单位(parseFloat(money) * 100),避免了浮点数计算误差,确保数据的精度。
  2. 防止重复提交

    • add 函数中,通过设置 isAddisDisabled 状态,防止用户在请求未完成时多次点击“入账”按钮,从而避免了重复提交和数据冲突的问题。这种做法提高了用户体验和数据安全性。
  3. 用户体验优化

    • reload 方法通过模拟异步操作(使用 setTimeout),在数据重载时显示加载动画,提升用户体验。这种方法避免了在数据重载过程中页面空白或卡顿的情况,使用户界面更加流畅和友好。
    • changeTime 方法在每次时间选择器改变时都会实时触发,确保用户在选择不同日期后能够立即看到对应的支出和收入数据,而不需要在 onLoad 生命周期中加载数据,提高了用户体验。
    • 通过 _saveToLocal 函数,不仅将用户的记账记录存储到本地,还通过 _loadStorage 函数实时更新账户余额。这种设计确保了数据的完整性和一致性,使得用户能够实时查看账户余额的变化,增强了应用的实用性和可靠性。

六、团队经历

1. 选定工具与类型

在项目初期,我们计划使用 Python 和 Django 框架来构建一个 Web 页面版的个人记账本。然而,在进一步讨论和调研后,我们发现微信小程序在用户基础、易用性以及与微信生态系统的整合能力上具有明显优势。经过深入讨论和权衡,我们最终决定使用微信开发者工具来开发小程序。这一决定引发了一些争论,因为团队中有些成员对微信小程序的功能限制有所担忧。经过多轮讨论和技术评估,我们决定转向微信开发者工具,重点关注用户体验和功能实现。这一过程让我们学到了在项目初期进行充分讨论和调研的重要性,以及如何权衡各种选择的利弊。

2. 选定功能模块并分配任务

在确定了使用微信开发者工具之后,我们需要选定要实现的功能模块,包括detailsaddindexsetting。这一阶段的讨论非常费时,因为每个组员都对自己感兴趣的模块有不同的提议和设计思路。经过多轮讨论,我们决定根据每位成员的技术能力和兴趣进行分配,让大家在自己擅长的领域发挥最佳水平。这一过程不仅提高了团队成员的参与感,也增强了我们对项目整体结构的理解,确保了后续开发的进行。

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
posted @ 2024-11-02 21:16  Meay  阅读(109)  评论(0)    收藏  举报