在这一节中,我们将实现首页医生日程列表条目从服务器端获取并显示的功能,主要涉及数据库设计和Node操作数据库的技术。
代码重构
我们在进行项目开发时,由于考虑不周等原因,经常在后期发现前期的实现存在问题,这就需要进行代码重构,以使代码结构更加合理,更加利于维护。我们倡导经常进行代码重构,这样可以提高代码质量和可维护性。
我们在进行网络访问时,直接给出了URL,如果我们一直采用这种方式,假设我们有100个网络请求,那么就有100个完整的URL,这时如果我们想改一下服务器的地址或从HTTP转为HTTPS,那么我们就需要修改100处,而且还不保证能改完,因此我们需要对这种方式进行重构。
我们首先定义一个存储全局变量的模块,在lib目录创建common目录,在其中创建全局类AppGlobal.dart,如下所示:
// 程序中的全局变量
// 需要在程序启动时加入hospitalId, deptId, doctorId参数
String serverBase = 'http://192.168.1.106:8443/';
String reqVerPlat = '?v=1&p=1'; // p:1-Android;2-IOS;3-web
String loginUser = ''; // hospitalId, deptId, doctorId
// 系统中所有可能用到的请求
String req_getServerDeault = serverBase + 'getServerDefault' +
reqVerPlat;
void initAppGlobal() {
initNetReqs();
}
void initNetReqs() {
loginUser = '&hospitalId=1&deptId=2&doctorId=3';
req_getServerDeault += loginUser;
}
在上面代码中,我们统一定义了服务器地址和协议,同时定了版本号和平台,这样我们服务器端就可以根据版本号和平台的平台,提供不同的服务。所有的网络请求都以req_为前缀。并定义了全局变量初始化方法,在系统启动时调用,对全局变量进行初始化,在这里我们需要为每个网络请求添加上注册用户对应的医院编号、科室编号、医生编号。
在程序开始时,需要调用全局变量初始化方法,我们在main.dart进行调用,如下所示:
import './common/AppGlobal.dart' as ag;
...
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
int _counter = 0;
TabController tabController;
@override
void initState() {
// 初始化全局变量
ag.initAppGlobal();
tabController = new TabController(vsync: this, length: 5);
}
我们在发起请求时,直接使用全局变量中定义的请求即可,在GetServerDefault.dart中的代码如下所示:
import 'package:szys/common/AppGlobal.dart' as ag;
Future<GetServerDefaultResp> fetchPost() async {
final response =
await http.get(ag.req_getServerDeault + '&msg=Hello&zw=中国');
if (response.statusCode == 200) {
......
}
日程列表服务器端接口
我们先在服务器端实现一个获取日程条目的接口,在这里我们仅返回固定的数据用于调试开发。首先在index.js中定义一个新接口:
var server = require("./server.js");
var router = require("./router.js");
// 系统级请求
...
// 应用请求
var getServerDefaultHdlr = require("./controller/get_server_default.js");
var getSchedulesHdlr = require('./controller/get_schedules.js');
var handlers = new Object();
// 系统级请求
...
// 应用请求
handlers['/'] = getServerDefaultHdlr.processRequest;
handlers['/getServerDefault'] = getServerDefaultHdlr.processRequest;
handlers['/getSchedules'] = getSchedulesHdlr.processRequest;
......
如上所示,我们定义了getSchedules接口,接下来我们实现这个接口,我们先用固定的数据,在controller目录下创建get_schedules.js文件:
var exec = require("child_process").exec;
function processRequest(request, response) {
console.log("get schedules\r\n");
var options = {};
options.timeout = 10000;
options.maxBuffer = 20000 * 1024;
response.writeHead(200, {"Content-Type": "application/json", "charset": "utf-8"});
var obj = new Object();
obj.total = 8;
obj.startIdx = 0;
obj.amount = 10;
var idx = 0;
obj.schedules = new Array();
obj.schedules[idx++] = {
'scheduleId': 1,
'scheduleTypeId': 1,
'scheduleTypeName': '出诊',
'planStartTime': '2017-07-31 10:00:00',
'onsiteAddr': '双榆树南里6#502',
'patientId': 101,
'patientName': '王一'
};
......
//
obj.schedules[idx++] = {
'scheduleId': 2,
'scheduleTypeId': 2,
'scheduleTypeName': '预约',
'appointTime': '2017-08-02 15:00:00',
'hospitalId': 201,
'hospitalName': '海淀医院',
'deptId': 301,
'deptName': '普通内科',
'appointStateId': 1,
'appointStateName': '待批准',
'patientId': 102,
'patientName': '李一'
};
response.write(JSON.stringify(obj));
response.end();
}
exports.processRequest = processRequest;
我们通过浏览器访问,会得到如下所示的结果,表明我们接口是开发成功的:
客户端调用及显示
我们首先定义网络请求类,在lib/nets目录下创建GetSchedules.dart:
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:szys/common/AppGlobal.dart' as ag;
Future<GetSchedulesResp> fetchPost() async {
final response =
await http.get(ag.req_getSchedules + '&msg=Hello&zw=中国');
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON
return GetSchedulesResp.fromJson(json.decode(response.body));
//return json.decode(response.body);
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
class GetSchedulesResp {
final int total;
final int startIdx;
final int endIdx;
final List<Map<String, dynamic>> schedules;
GetSchedulesResp({this.total, this.startIdx, this.endIdx, this.schedules});
factory GetSchedulesResp.fromJson(Map<String, dynamic> json) {
return GetSchedulesResp(
total: json['total'],
startIdx: json['startIdx'],
endIdx: json['endIdx'],
schedules: new List<Map<String, dynamic>>.from(json['schedules']),
);
}
}
我们在日程页面的列表功能是在components/ScheduleListView.dart中实现的,我们将原来静态的列表内容,替换为从网络中获取。这里我们需要改变一下做法,在前面的例子中,我们直接在界面中显示网络获取到的数据,所以业务逻辑写在FutureBuilder里面了,但是现在我们使用的是列表控件,我们需要数据更新时,列表内容跟着改变,这就需要不同的实现方式了。
我们首先来看怎样进行网络服务调用获取数据,修改ScheduleListView.dart中的getScheduleItems方法:
void getScheduleListItems(ScheduleListViewState stateObj) {
List<ScheduleListItem> items = new List<ScheduleListItem>();
GetSchedules.fetchPost().then((resp){
int total = resp.total;
int startIdx = resp.startIdx;
int endIdx = resp.endIdx;
List<Map<String, dynamic>> recs = resp.schedules;
ScheduleListItem item = null;
for (final rec in recs) {
if (1 == rec['scheduleTypeId']) {
// 出诊日程条目
item = new OnsiteScheduleListItem(rec['scheduleId'],
rec['planStartTime'], utf8.decode(rec['onsiteAddr'].codeUnits),
rec['patientId'], utf8.decode(rec['patientName'].codeUnits)
);
} else if (2 == rec['scheduleTypeId']) {
// 预约日程条目
item = new AppointScheduleListItem(rec['scheduleId'],
rec['hospitalId'], utf8.decode(rec['hospitalName'].codeUnits),
rec['deptId'], utf8.decode(rec['deptName'].codeUnits),
rec['appointId'], rec['appointTime'],
rec['appointStateId'], utf8.decode(rec['appointStateName'].codeUnits),
rec['patientId'], utf8.decode(rec['patientName'].codeUnits));
} else {
item = new OnsiteScheduleListItem(9999,
'2999-09-28 10:30:00', '???????',
102, '????');
}
items.add(item);
}
stateObj.onReceiveData(items);
});
}
在这里,我们看到方法的参数为ScheduleListViewState对象。因为GetSchedules里面定义的fetchPost方法返回的是Future对象,而Future对象在完成时会调用then里面的内容,我们将网络传过来的数据转化为列表控件需要的数据格式,这时我们不需要返回列表条目,而是调用ScheduleListViewState里面的onReceiveData方法,由该方法更新界面内容。
在ScheduleListViewState的build方法中,我们就不再去获取列表条目了,去掉该段代码。
我们重载State类的生命周期函数didUpdate,这个函数会在每次显示前调用,我们在这个方法中去获取列表数据:
@override
void didUpdateWidget(ScheduleListView oldWidget) {
// TODO: implement didUpdateWidget
debugPrint('didUpdateWidget is running...');
getScheduleListItems(this);
super.didUpdateWidget(oldWidget);
}
当获取到网络数据并形成列表所需数据之后,我们调用了onReceiveData方法,这个方法将更新界面内容,更新的机制与React Native等前端机制差不太多,都是通过setState函数通知界面更新,代码如下所示:
List<ScheduleListItem> items = new List<ScheduleListItem>();
void onReceiveData(List<ScheduleListItem> _items) {
debugPrint('onReceiveData is running...');
setState(() {
items = _items;
});
}
这时我们运行程序,会出现如下的界面:
虽然界面上并没有明显的变化,但是数据已经是从后台服务器上获取的了。
在这一节里,我们虽然从服务器端获取到的列表的数据,但是这些数据在服务器端是在程序里定义的,而不是数据库驱动的。在下一节里,我们将讲述从数据库中读出相关内容并返回给客户端,敬请期待!