TODO List
已上线,可扫描二维码测试:
设计思想
MVC
MVC是经典的软件架构模式,将应用程序划分为三种组件,模型,视图,控制器设计定义它们之间的相互作用。
微信小程序的MINA框架其核心思想正是MVC。通过对MVC的了解,我们可以从更抽象的层面设计小程序的功能逻辑,避免因为繁杂的技术细节,而失去了对程序功能的全面理解
前后端分离
前后端分离不光体现在软件架构的分离上,还体现在开发人员与工作流程的分离上。
在小组合作的过程中,通过事先的合作,设计小程序所需要的相关api与对应json数据格式。前端通过小程序提供的wx.request(),后端通过Go Gin进行相应的网络请求的应答,最后使用mysql数据库进行数据的管理。
在这个过程中,负责前端的成员与负责后端的成员同时独立进行,定期沟通进展,确保小程序开发的进展顺利。
前后端分离的设计方法,不但保证了项目在设计层面的简单,也极大的提高了开发效率,避免了因为沟通等问题耽误了开发进展。
界面设计
在原则上,小程序的整体设计风格以简约为主,力图使程序简单易懂,便于上手。
数据交互与实时反馈
为了用户的无感知使用,避免因网络原因造成的等待,小程序对网络的请求遵循了最小化原则,不进行不必要的网络请求,对重要的业务逻辑,先在本地进行数据更新,之后再与云端同步。
关键技术点
前端
<view class="header">
<image class="plus" src="../../assets/plus.png"/>
<input class="new-todo" value="{{ input }}" placeholder="请添加代办" auto-focus bindinput="inputChangeHandle" bindconfirm="addTodoHandle"/>
</view>
inputChangeHandle: function (e) {
this.setData({ input: e.detail.value })
},
addTodoHandle: function (e) {
if (!this.data.input || !this.data.input.trim()) return
var todos = this.data.todos
todos.push({ name: this.data.input, completed: false })
var logs = this.data.logs
logs.push({ timestamp: new Date(), action: '添加', name: this.data.input })
this.setData({
input: '',
todos: todos,
leftCount: this.data.leftCount + 1,
logs: logs
})
this.save()
},
//打勾toggleTodoHandle
//清除打勾代办clearCompletedHandle
//直接清除removeTodoHandle
<block wx:if="{{ todos.length }}">
<view class="todos">
<!-- 完成的项目划线 -->
<view class="item{{ item.completed ? ' completed' : '' }}" wx:for="{{ todos }}" wx:key="{{ index }}" bindtap="toggleTodoHandle" data-index="{{ index }}">
<!-- 完成的打勾,没完成的圆圈 -->
<icon class="checkbox" type="{{ item.completed ? 'success' : 'circle' }}"/>
<text class="name">{{ item.name }}</text>
<icon class="remove" type="clear" size="16" catchtap="removeTodoHandle" data-index="{{ index }}"/>
</view>
</view>
<view class="footer">
<text class="btn" bindtap="toggleAllHandle">完成全部代办</text>
<text wx:if="{{ leftCount }}"> 还剩 {{ leftCount }} {{ leftCount === 1 ? '个代办' : '个代办' }} </text>
<text class="btn" wx:if="{{ todos.length > leftCount }}" bindtap="clearCompletedHandle">确认清除</text>
</view>
</block>
//数据结构
data: {
input: '',
todos: [], //todo
leftCount: 0,
allCompleted: false,
logs: [], //记录
id:"0"
},
//添加记录信息
toggleTodoHandle: function (e) {
var index = e.currentTarget.dataset.index
var todos = this.data.todos
todos[index].completed = !todos[index].completed
var logs = this.data.logs
logs.push({
timestamp: new Date(),
action: todos[index].completed ? '完成' : '重启',
name: todos[index].name
})
this.setData({
todos: todos,
leftCount: this.data.leftCount + (todos[index].completed ? -1 : 1),
logs: logs
})
this.save()
},
removeTodoHandle: function (e) {
var index = e.currentTarget.dataset.index
var todos = this.data.todos
var remove = todos.splice(index, 1)[0]
var logs = this.data.logs
logs.push({ timestamp: new Date(), action: '直接清除', name: remove.name })
this.setData({
todos: todos,
leftCount: this.data.leftCount - (remove.completed ? 0 : 1),
logs: logs
})
this.save()
},
toggleAllHandle: function (e) {
this.data.allCompleted = !this.data.allCompleted
var todos = this.data.todos
for (var i = todos.length - 1; i >= 0; i--) {
todos[i].completed = this.data.allCompleted
}
var logs = this.data.logs
logs.push({
timestamp: new Date(),
action: this.data.allCompleted ? '完成' : '重启',
name: '完成全部代办'
})
this.setData({
todos: todos,
leftCount: this.data.allCompleted ? 0 : todos.length,
logs: logs
})
this.save()
},
clearCompletedHandle: function (e) {
var todos = this.data.todos
var remains = []
for (var i = 0; i < todos.length; i++) {
todos[i].completed || remains.push(todos[i])
}
var logs = this.data.logs
logs.push({
timestamp: new Date(),
action: '清除',
name: '清除代办'
})
this.setData({ todos: remains, logs: logs })
this.save()
}
<block wx:else>
<view class="empty">
<text class="title">太棒了,现在没有代办!</text>
<text class="content">要么你真的做完了,要么你还没有开始</text>
<view style=" color:darkgrey; padding: 120rpx; ">
<text style="font-size: 30rpx;">
</text>
</view>
</view>
</block>
//wxml
<view class="container">
<view class="logs" wx:if="{{ logs.length }}">
<view class="item" wx:for="{{ logs }}" wx:key="{{ index }}">
<text class="name">代办名称: {{ item.name }}</text>
<text class="action">状态:{{ item.action }}</text>
<text class="timestamp">时间:[{{ item.timestamp }}]</text>
</view>
</view>
</view>
//logs.js
Page({
data:{
logs: []
},
onShow: function () {
var logs = wx.getStorageSync('todo_logs')
if (logs) {
this.setData({ logs: logs.reverse() })
}
},
})
//index.js
Page({
data: {
input: '',
todos: [],
leftCount: 0,
allCompleted: false,
logs: [],
id:"0"
},
save: function () {
wx.setStorageSync('todo_list', this.data.todos)
wx.setStorageSync('todo_logs', this.data.logs)
},
getUserID: function(){
wx.getUserInfo({
success: function(res){
id = res.userInfo.nickName
}
})
},
loadFromDB: function() {
wx.request({
url : "175.24.118.206:8080/api/v1/GetUserInfo",
method: "POST",
data: {
answer : JSON.stringify({ID: "1"}),
},
header: {
"Content-Type": "application/json"
},
success: function (res) {
console.log(res.data);
this.setData({
todos:res.data.things
})
},
})
},
load: function () {
this.loadFromDB()
if (this.data.todos) {
var leftCount = this.data.todos.filter(function (item) {
return !item.completed
}).length
this.setData({leftCount: leftCount })
}
var logs = wx.getStorageSync('todo_logs')
if (logs) {
this.setData({ logs: logs })
}
},
onLoad: function () {
this.load()
},
inputChangeHandle: function (e) {
this.setData({ input: e.detail.value })
},
addTodoHandle: function (e) {
if (!this.data.input || !this.data.input.trim()) return
var todos = this.data.todos
todos.push({ name: this.data.input, completed: false })
var logs = this.data.logs
logs.push({ timestamp: new Date(), action: '添加', name: this.data.input })
this.setData({
input: '',
todos: todos,
leftCount: this.data.leftCount + 1,
logs: logs
})
this.save()
},
toggleTodoHandle: function (e) {
var index = e.currentTarget.dataset.index
var todos = this.data.todos
todos[index].completed = !todos[index].completed
var logs = this.data.logs
logs.push({
timestamp: new Date(),
action: todos[index].completed ? '完成' : '重启',
name: todos[index].name
})
this.setData({
todos: todos,
leftCount: this.data.leftCount + (todos[index].completed ? -1 : 1),
logs: logs
})
this.save()
},
removeTodoHandle: function (e) {
var index = e.currentTarget.dataset.index
var todos = this.data.todos
var remove = todos.splice(index, 1)[0]
var logs = this.data.logs
logs.push({ timestamp: new Date(), action: '直接清除', name: remove.name })
this.setData({
todos: todos,
leftCount: this.data.leftCount - (remove.completed ? 0 : 1),
logs: logs
})
this.save()
},
toggleAllHandle: function (e) {
this.data.allCompleted = !this.data.allCompleted
var todos = this.data.todos
for (var i = todos.length - 1; i >= 0; i--) {
todos[i].completed = this.data.allCompleted
}
var logs = this.data.logs
logs.push({
timestamp: new Date(),
action: this.data.allCompleted ? '完成' : '重启',
name: '完成全部代办'
})
this.setData({
todos: todos,
leftCount: this.data.allCompleted ? 0 : todos.length,
logs: logs
})
this.save()
},
clearCompletedHandle: function (e) {
var todos = this.data.todos
var remains = []
for (var i = 0; i < todos.length; i++) {
todos[i].completed || remains.push(todos[i])
}
var logs = this.data.logs
logs.push({
timestamp: new Date(),
action: '清除',
name: '清除代办'
})
this.setData({ todos: remains, logs: logs })
this.save()
}
})
后端
package main
import (
//"go/printer"
"log"
"net/http"
"database/sql"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
)
func main() {
r := gin.New()
db, err := sql.Open("mysql", "root:123456@tcp(175.24.118.206:3306)/homework")
if err != nil //GetUserInfo api,用来获取服务器上的用户信息和数据
r.POST("/api/v1/GetUserInfo", func(c *gin.Context) {
param := struct {
UserID string `json:"ID" binding:"required"`
}{}
if err := c.ShouldBind(¶m); err != nil {
c.JSON(http.StatusOK, gin.H{
"status": 400,
"msg": "param error",
})
return
}
log.Println(param)
sql := "select NumByID,things from todo_list_dbs where ID = " + "'" + param.UserID + "'"
log.Println(sql)
rs, err := db.Query(sql)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"status": 400,
"msg": "query error 1",
})
return
}
var result []interface{}
temp := struct {
Num int
Things string
}{}
for rs.Next() {
if err = rs.Scan(&temp.Num, &temp.Things); err != nil {
c.JSON(http.StatusOK, gin.H{
"status": 400,
"msg": "query error 2",
})
return
}
log.Println(temp)
result = append(result, temp)
log.Println(result...)
}
log.Println(result...)
c.JSON(http.StatusOK, gin.H{"result": result})
})
//SetUserInfo api,用来新增代办事项
r.POST("/api/v1/SetUserInfo", func(c *gin.Context) {
param := struct {
ID string `json:"ID" binding:"required"`
Things string `json:"Things" binding:"required"`
}{}
log.Println(param)
if err := c.ShouldBind(¶m); err != nil {
c.JSON(http.StatusOK, gin.H{
"status": 400,
"msg": "param error",
})
return
}
stmt, err := db.Prepare("insert into todo_list_dbs set ID =?,things=?")
if err != nil {
log.Println(err)
}
_, err = stmt.Exec(param.ID, param.Things)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"status": 400,
"msg": "query error 1",
})
return
}
})
log.Fatal(r.Run(":8080"))
}
总结与展望
小程序虽然已经开发完毕,但在功能上还稍显单薄,像待办提醒,附件上传,日程安排等功能,虽然一开始是放在了设计中的,但在最后因为种种原因只能遗憾的取消,此外,像页面的交互逻辑上还有待优化。在开发过程中,我们也进一步认识到了前期的项目文档,中期的及时沟通,后期的反思整理的重要性。