【Ionic+AngularJS 开发】之『个人日常管理』App(二)
准备工作
资源
预装工具
安装bower
npm install -g bower
bower install ngCordova
(*由于网络获取资源的原因,后面几次建项目后都无法下载到,自己便复制了原来的ngCordova目录(到YourProject\wwww\lib目录下),发现也是可以使用的)
下载好后,在项目的index.hmtl进行引用:
<script src="lib/ngCordova/dist/ng-cordova.js">
日历工具
安装插件
本项目需要(安装)的插件有:
插件名 | 说明 | 扩展阅读 |
---|---|---|
cordova-plugin-x-toast | 消息提示,使用方法如:$cordovaToast.showShortBottom('屏幕下方提示'); (*仅限平台运行,浏览器调试无效,所以在PC调试时应注意其引起的错误而导致后面代码没执行) |
cordova ionic消息提示 |
cordova-sqlite-storage | sqlite数据库 |
cordova调用本地SQLite数据库的方法 more... |
cordova-plugin-x-socialsharing | 内容分享 |
插件的安装基本命令是:
cordova plugin add XXXX
安装好后可在YourProject\wwww\lib目录下看到新增的插件目录,这样就可以在项目中引用了(不用使用<script src="xxx">)。
在生成platform后,或需再用
cordova prepare
该命令用以复制文件到平台(并更改一些xml文件的内容)
概念理解
service服务
AngularJS服务是一种单例对象,其主要功能是为实现应用的功能提供数据和对象,通过直接调用服务,可以将复杂的应用功能进行简化或分块化。 按功能的不同,分为内置服务和自定义服务。
AngularJS提供的常用内置服务有:$scope、$http、$window、$location等
自定义服务主要包含以下两种:
1)使用内置的$provide服务
2)调用模块中的服务注册(如factory、service、constant、value等方法)
本项目主要采用service来创建服务(service方法与factory不同的是,它可以接收一个构造函数)
设计与开发
app.js
angular.module('pdm'
, ['ionic'
, 'ngCordova'
])
.config(function ($stateProvider, $urlRouterProvider, $ionicConfigProvider) { //在android下,tab位置为top,如果想修改其位置在底部,加上下面一句代码:
$ionicConfigProvider.tabs.position('bottom'); //... })
.run(function ($ionicPlatform) {
//...
}) // 自定义服务:$alertPopup
.service('$alertPopup',
['$ionicPopup'
, function ($ionicPopup) {
return function (content, title) {
if (title == undefined || title == null)title = '提示';
var alertPopup = $ionicPopup.alert({
title: title,
template: content
}); alertPopup.then(function (res) {
log('alertPopup.then: ' + res);
});
}
}]) // 自定义服务:$db
.service('$db', ['$cordovaSQLite', '$alertPopup', '$cordovaToast'
, function ($cordovaSQLite, $alertPopup, $cordovaToast) {
// 初始化数据表
var db = null;
try {
var _dbName = 'sk';
if (!(window.cordova && window.SQLitePlugin)) {
// 创建数据库对象
db = window.openDatabase(_dbName, '1.0', _dbName, 100 * 1024 * 1024); // web-sql 执行sql方式
// 首次创建记账表
db.transaction(
function (transaction) {
transaction.executeSql("CREATE TABLE IF NOT EXISTS Finacial_KeepAccount " +
"( id integer primary key" +
", account text " +
", SuitType text " +
", ItemText text " +
", MoneyFlowDirect text " +
", Cash REAL " +
", AccountType text " +
", RecordDate text " +
", Remark text" +
")");
}
); // 自定义执行sql方式
// 首次创建日常表
$cordovaSQLite.execute(db, 'CREATE TABLE IF NOT EXISTS Life_DailyActivity(id integer primary key' +
', account text' +
', Date text' +
', Business text' +
', Study text' +
', Health text' +
', Sport text' +
', Others text' +
', Remark text' +
')'); }
else {
$alertPopup('fail create ' + _dbName + '.db');
}
} catch (e) {
$alertPopup('fail init: ' + e.toString(), '$db Err');
} // 内部函数
function db_exec(sql, param, succ_callback, err_callback){
if (param == undefined || param == null) param = [];
$cordovaSQLite.execute(db, sql, param)
.then(function (rst) {
if (succ_callback == undefined)log('exec: ' + sql);
else succ_callback(rst);
}, function (err) {
if (err_callback == undefined)$alertPopup('exec error: ' + err.message);
else err_callback(err);
});
} // 外部可调用接口
return {
// 执行sql
_exec: function (sql, param, succ_callback, err_callback) {
db_exec(sql, param, succ_callback, err_callback);
},
// 获取数据
get: function (tbl, cndt, callback) {
var sql = 'SELECT * FROM ' + tbl + ' WHERE 1=1 ';
if (cndt != undefined && cndt != '')sql += (' AND ' + cndt);
db_exec(sql, [],
function (rst) {
var data = [];
for (var i = 0; i < rst.rows.length; i++) data.push(rst.rows.item(i));
callback(data);
});
},
// 添加
add: function (tbl, fields, valueArr, silenceExec) {
var _param = '';
for (var i = 0; i < fields.split(',').length; i++)_param += ',?';
_param = _param.substr(1);
var sql = 'INSERT INTO ' + tbl + '(' + fields + ') values(' + _param + ')';
db_exec(sql, valueArr,
function (rst) {
if (silenceExec == undefined || silenceExec != true)
if(!g_debug)
$cordovaToast.showShortCenter('add to ' + tbl + ' success');
});
},
// 更新
update: function (tbl, fields, valueArr, cndt, silenceExec) {
var fv = '';
var flds = fields.split(',');
for (var i = 0; i < flds.length; i++) fv += (', ' + flds[i] + '=? ');
fv = fv.substr(1);
var sql = 'UPDATE ' + tbl + ' SET ' + fv + ' WHERE ' + cndt;
db_exec(sql, valueArr,
function (rst) {
if (silenceExec == undefined || silenceExec != true)
if(!g_debug)
$cordovaToast.showShortCenter('update ' + tbl + ' success');
});
},
// 删除
delete: function (tbl, cndt, silenceExec) {
var sql = 'DELETE FROM ' + tbl + ' WHERE ' + cndt;
db_exec(sql, [],
function (rst) {
if (silenceExec == undefined || silenceExec != true)
if(!g_debug)
$cordovaToast.showShortCenter('delete from ' + tbl + ' success');
});
} }
}]) ;
自定义服务:$alertPopup
为方便项目内调用,对$ionicPopup进行封装,也方便日后扩展。
自定义服务:$db
此$db服务基本就是一个DAL层了,封装了基本的CRUD功能,并根据项目需要做了一些“默认处理”(在程序初始化时,自动创建记账和日常表等)。
(*这个sqlite文件物理路径很难找,有什么方法可以快速定位,还望知道的园友赐教:))
记账视图
HTML部分
<ion-view view-title="DailyKeeper">
<ion-nav-title><b>记账</b></ion-nav-title>
<div class="bar bar-subheader bar-dark">
<h2 class="title">
<a class="button button-icon icon ion-plus-circled" ng-click="showDetail()"></a>
</h2>
</div> <ion-content class="has-tabs has-subheader">
<ion-list>
<div ng-repeat="da in dailyAccount">
<div class="item item-divider" style="display: {{da.ext_displayDivider}}">
{{da.RecordDate}}
</div>
<ion-item class="item-remove-animate item-icon-right"
type="item-text-wrap" ng-click="showDetail({{da}})" style="color: {{da.ext_TextColor}}">
【{{da.SuitType}}】{{da.ItemText}}
<br/>{{da.Cash}}
<i class="icon ion-chevron-right icon-accessory"></i> <ion-option-button class="button-assertive" ng-click="remove(da)">
Delete
</ion-option-button>
</ion-item>
</div>
</ion-list>
</ion-content> <!--弹出内容-->
<script id="detail.html" type="text/ng-template">
<ion-modal-view>
<ion-header-bar>
<h1 class="title">{{currDA.title}}</h1>
<button class="button" ng-click="closeDetail()">关闭</button>
</ion-header-bar>
<ion-content>
<div class="item-input-inset">
<i class="icon ion-android-calendar"></i>
<input type="date" ng-model="currDA.RecordDate">
</div>
<div class="item item-input-inset">
<select
ng-model="currDA.SuitType"
ng-options="value.SuitType as value.SuitType group by value.MainClass for value in Finacial_SuitClass">
<option value=""> -账目类型- </option>
</select>
<label class="item-input-wrapper">
<input type="text" ng-model="currDA.ItemText" placeholder="消费项">
</label>
</div>
<div class="item item-input-inset">
<label class="item-input-wrapper">
<input type="number" ng-model="currDA.Cash" placeholder="金额">
</label>
<label class="toggle">
<input type="checkbox" ng-model="currDA.Income">
<div class="track">
<div class="handle"></div>
</div>
</label>(入账)
</div>
<div class="item item-input-inset">
<textarea style="width: 100%" ng-model="currDA.Remark" placeholder="备注"></textarea>
</div>
<div class="item-input-inset">
<button class="button button-block button-positive" ng-click="save()">
Save
</button>
</div>
</ion-content>
</ion-modal-view>
</script>
</ion-view>
JavaScript部分
angular.module('pdm')
.controller('Ctrl_DailyKeeper',
['$scope', '$ionicModal', '$db', '$cordovaToast', '$ionicPopup', '$alertPopup'
, function ($scope, $ionicModal, $db, $cordovaToast, $ionicPopup, $alertPopup) { // BLL
$scope.getKA = function (callback, cndt) {
var sql = "SELECT * FROM Finacial_KeepAccount WHERE 1=1 ";
if (cndt != undefined && cndt != '')sql += (' AND ' + cndt);
sql += ' ORDER BY RecordDate desc';
$db._exec(sql, [], function (rst) {
if (rst.rows.length == 0) {
if(!g_debug)
$cordovaToast.showShortCenter('load ka success but no data');
//return;
}
var data = [];
for (var i = 0; i < rst.rows.length; i++) data.push(rst.rows.item(i));
callback(data);
});
};
$scope.addKA = function (SuitType, ItemText, MoneyFlowDirect, Cash, AccountType, RecordDate, Remark) {
$db.add('Finacial_KeepAccount'
, 'SuitType,ItemText,MoneyFlowDirect,Cash,AccountType,RecordDate,Remark, account'
, [SuitType, ItemText, MoneyFlowDirect, Cash, AccountType, RecordDate, Remark, g_user]);
};
$scope.updateKA = function (id, SuitType, ItemText, MoneyFlowDirect, Cash, AccountType, RecordDate, Remark) {
$db.update('Finacial_KeepAccount'
, 'SuitType,ItemText,MoneyFlowDirect,Cash,AccountType,RecordDate,Remark'
, [SuitType, ItemText, MoneyFlowDirect, Cash, AccountType, RecordDate, Remark]
, 'id=' + id.toString());
};
$scope.deleteKA = function (id) {
$db.delete('Finacial_KeepAccount', 'id=' + id.toString());
}; $scope.Finacial_SuitClass = [
{MainClass: '基本生活', SuitType: '餐饮饮食'}
, {MainClass: '基本生活', SuitType: '柴米油盐'}
, {MainClass: '美容化妆', SuitType: '服饰装扮'}
, {MainClass: '收入', SuitType: '福利津贴'}
, {MainClass: '收入', SuitType: '工资'}
, {MainClass: '美容化妆', SuitType: '化妆品美容'}
, {MainClass: '交通通讯', SuitType: '话费网费'}
, {MainClass: '交通通讯', SuitType: '交通费'}
, {MainClass: '人情往来', SuitType: '借出'}
, {MainClass: '投资', SuitType: '理财投资'}
, {MainClass: '文化娱乐', SuitType: '旅游娱乐'}
, {MainClass: '收入', SuitType: '其他收入'}
, {MainClass: '其他支出', SuitType: '其他支出'}
, {MainClass: '人情往来', SuitType: '人际往来'}
, {MainClass: '基本生活', SuitType: '日常用品'}
, {MainClass: '文化娱乐', SuitType: '书报音像'}
, {MainClass: '文化娱乐', SuitType: '数码产品'}
, {MainClass: '基本生活', SuitType: '水果零食'}
, {MainClass: '基本生活', SuitType: '物业水电'}
, {MainClass: '人情往来', SuitType: '孝敬长辈'}
, {MainClass: '基本生活', SuitType: '医药保健'}
, {MainClass: '文化娱乐', SuitType: '运动健身'}
];
$scope.currDA = {
title: '新增'
, id: 0
, RecordDate: new Date()
, SuitType: ''
, ItemText: ''
, Cash: 0
, Income: false
, Remark: ''
} $scope.arrageData = function () {
var _data = $scope.dailyAccount; if (_data.length > 0) {
_data[0].ext_displayDivider = ''; if (_data.length > 1) {
var lastDA = _data[0];
for (var i = 1; i < _data.length; i++) {
_data[i].ext_displayDivider = 'none';
if (new Date(_data[i].RecordDate) < new Date(lastDA.RecordDate)) {
_data[i].ext_displayDivider = '';
lastDA = _data[i];
}
}
}
}
}; $scope.remove = function (da) {
$ionicPopup.confirm({
title: 'Confrim',
template: 'Do you really want to delete?',
scope: $scope,
buttons: [
{
text: '<b>Yes</b>',
type: 'button-positive',
onTap: function (e) {
//$scope.dailyAccount.splice($scope.dailyAccount.indexOf(da), 1);
$scope.deleteKA(da.id);
$scope.loadDate();
}
},
{
type: 'button-canceldark',
text: '<b>Cancel</b>',
onTap: function (e) {
console.log('cancel delete');
}
}
]
});
}; $scope.showDetail = function (da) {
if (da == undefined) {
// 新增
$scope.currDA.title = '新增'; $scope.currDA.id = 0;
$scope.currDA.RecordDate = new Date();
$scope.currDA.SuitType = '';
$scope.currDA.ItemText = '';
$scope.currDA.Cash = 0;
$scope.currDA.Income = false;
$scope.currDA.Remark = '';
} else {
// 读取
$scope.currDA.title = '编辑'; $scope.getKA(function (data) {
if (data.length > 0) {
var item = data[0]; $scope.currDA.id = item.id;
$scope.currDA.RecordDate = new Date(item.RecordDate);
$scope.currDA.SuitType = item.SuitType;
$scope.currDA.ItemText = item.ItemText;
$scope.currDA.Cash = item.Cash;
$scope.currDA.Income = (item.MoneyFlowDirect == '入账');
$scope.currDA.Remark = item.Remark;
}
}
, ' id = ' + da.id);
} $scope.openModal();
} $scope.save = function () {
//log(angular.toJson($scope.currDA)); if ($scope.currDA.SuitType == ''
|| $scope.currDA.SuitType.indexOf('账目类型') >= 0) {
$alertPopup('账目类型没有选定哦');
return;
} var _moneyFlowDirection = '出账';
if ($scope.currDA.Income) _moneyFlowDirection = '入账'; if ($scope.currDA.id == 0) {
// 新增
$scope.addKA(
$scope.currDA.SuitType
, $scope.currDA.ItemText
, _moneyFlowDirection
, $scope.currDA.Cash
, '我的钱包'
, dateFormat($scope.currDA.RecordDate, 'ymd')
, $scope.currDA.Remark
);
$scope.closeDetail();
$scope.loadDate();
}
else {
// 更新
$ionicPopup.confirm({
title: 'Confrim',
template: 'Do you really want to update?',
scope: $scope,
buttons: [
{
text: '<b>Yes</b>',
type: 'button-positive',
onTap: function (e) {
$scope.updateKA(
$scope.currDA.id
, $scope.currDA.SuitType
, $scope.currDA.ItemText
, _moneyFlowDirection
, $scope.currDA.Cash
, '我的钱包'
, dateFormat($scope.currDA.RecordDate, 'ymd')
, $scope.currDA.Remark
);
$scope.closeDetail();
$scope.loadDate();
}
},
{
type: 'button-canceldark',
text: '<b>Cancel</b>',
onTap: function (e) {
console.log('cancel update');
}
}
]
});
}
} // 弹窗
$ionicModal.fromTemplateUrl('detail.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function (modal) {
$scope.modal = modal;
});
$scope.openModal = function () {
$scope.modal.show();
};
$scope.closeDetail = function () {
$scope.modal.hide();
}
$scope.$on('$destroy', function () {
$scope.modal.remove();
}); $scope.loadDate = function () {
$scope.getKA(function (data) {
for (var i = 0; i < data.length; i++) {
var _d = data[i];
_d['ext_displayDivider'] = 'none';
_d['ext_TextColor'] = 'black';
if (_d.MoneyFlowDirect == '入账')_d['ext_TextColor'] = 'blue';
}
$scope.dailyAccount = data;
$scope.arrageData();
});
} // start
$scope.loadDate(); }])
;
说明:
- arrageData()函数根据(按日期倒序)排序好的数据,设置当日最后一条数据(因为是倒序,所以采用最后一条)的ext_displayDivider属性为none,如此实现在“日期-当日各项收支项”的显示效果——按日分割后来发觉也可以用Ionic的Card,当然也许也有第三方控件可以直接用了。
- $ionicModal调用的弹窗功能,弹出的是一个完整的页面,本项目为了简便,就直接写在了同页面里“< script id="detail.html" type="text/ng-template">”
日常视图
HTML部分
<ion-view view-title="DailyActivity">
<ion-nav-title><b>日常</b></ion-nav-title>
<ion-content class="has-tabs">
<br/>
<div id='calendar'></div>
</ion-content> <!--弹出内容-->
<script id="detail.html" type="text/ng-template">
<ion-modal-view>
<ion-header-bar>
<h1 class="title">活动 / 计划</h1>
<button class="button" ng-click="closeDetail()">关闭</button>
</ion-header-bar>
<ion-content>
<div class="item-input-inset">
<label class="item-input-wrapper">
<i class="icon ion-android-calendar"></i>
<span>{{act.tDate}}</span>
</label>
<span>{{act.id}}</span>
<!--<a class="button button-small" ng-click="save()">Save</a>-->
</div>
<div class="list card">
<div class="item item-divider">
事务
</div>
<div class="item item-body">
<label class="item-input">
<textarea style="background-color: whitesmoke" ng-model="act.Business"></textarea>
</label>
</div>
<div class="item item-divider">
学习
</div>
<div class="item item-body">
<label class="item-input">
<textarea style="background-color: whitesmoke" ng-model="act.Study"></textarea>
</label>
</div>
<div class="item item-divider">
健康
</div>
<div class="item item-body">
<label class="item-input">
<textarea style="background-color: whitesmoke" ng-model="act.Health"></textarea>
</label>
</div>
<div class="item item-divider">
运动
</div>
<div class="item item-body">
<label class="item-input">
<textarea style="background-color: whitesmoke" ng-model="act.Sport"></textarea>
</label>
</div>
<div class="item item-divider">
其他
</div>
<div class="item item-body">
<label class="item-input">
<textarea style="background-color: whitesmoke" ng-model="act.Others"></textarea>
</label>
</div>
</div>
</ion-content>
</ion-modal-view>
</script>
</ion-view>
JavaScript部分
angular.module('pdm')
.controller('Ctrl_DailyActivity',
['$scope', '$ionicModal', '$db', '$cordovaToast', '$alertPopup'
, function ($scope, $ionicModal, $db, $cordovaToast, $alertPopup) { // BLL
$scope.getDA = function (callback, cndt) {
var sql = "SELECT * FROM Life_DailyActivity WHERE 1=1 ";
if (cndt != undefined && cndt != '')sql += (' AND ' + cndt);
sql += ' ORDER BY Date';
$db._exec(sql, [], function (rst) {
var data = [];
for (var i = 0; i < rst.rows.length; i++) data.push(rst.rows.item(i));
callback(data);
});
};
$scope.addDA = function (Date, Business, Study, Health, Sport, Others, Remark, callback) {
var tbl = 'Life_DailyActivity';
var fields = 'account,Date,Business,Study,Health,Sport,Others,Remark';
var valueArr = [g_user, Date, Business, Study, Health, Sport, Others, Remark];
var _param = '';
for (var i = 0; i < fields.split(',').length; i++)_param += ',?';
_param = _param.substr(1);
var sql = 'INSERT INTO ' + tbl + '(' + fields + ') values(' + _param + ')';
$db._exec(sql, valueArr,
function (rst) {
if (callback != undefined && callback != null) callback(rst);
else $cordovaToast.showShortCenter('add to ' + tbl + ' success');
});
};
$scope.updateDA = function (Date, Business, Study, Health, Sport, Others, Remark) {
$db.update('Life_DailyActivity'
, 'Business,Study,Health,Sport,Others,Remark'
, [Business, Study, Health, Sport, Others, Remark]
, "Date='" + Date + "'"
, true);
} $scope.editing = false;
$scope.act = {
id: 0,
tDate: dateFormat(new Date(), 'ymd'),
Business: '',
Study: '',
Health: '',
Sport: '',
Others: '',
Remark: ''
};
var _lastDate = $scope.act.tDate; $scope.loadData = function () {
$scope.getDA(function (data) {
$scope.act.Business = '';
$scope.act.Study = '';
$scope.act.Health = '';
$scope.act.Sport = '';
$scope.act.Others = '';
$scope.act.Remark = ''; if (data.length > 0) {
var item = data[0];
$scope.act.id = item.id;
$scope.act.Business = item.Business;
$scope.act.Study = item.Study;
$scope.act.Health = item.Health;
$scope.act.Sport = item.Sport;
$scope.act.Others = item.Others;
$scope.act.Remark = item.Remark; if ($scope.act.id > 0) {
$db._exec("delete from Life_DailyActivity where Date='" + $scope.act.tDate + "' and id!=" + $scope.act.id);
}
} else {
$scope.addDA($scope.act.tDate
, $scope.act.Business
, $scope.act.Study
, $scope.act.Health
, $scope.act.Sport
, $scope.act.Others
, $scope.act.Remark
, function (rst) {
$scope.act.id = rst.insertId;
}
);
}
},
"Date='" + $scope.act.tDate + "'");
} $scope.save = function () {
$scope.updateDA($scope.act.tDate
, $scope.act.Business
, $scope.act.Study
, $scope.act.Health
, $scope.act.Sport
, $scope.act.Others
, $scope.act.Remark
);
} // 监听数据变化
$scope.$watch('act.Business', function (newValue, oldValue, scope) {
if ($scope.editing && newValue != oldValue)$scope.save();
});
$scope.$watch('act.Study', function (newValue, oldValue, scope) {
if ($scope.editing && newValue != oldValue)$scope.save();
});
$scope.$watch('act.Health', function (newValue, oldValue, scope) {
if ($scope.editing && newValue != oldValue)$scope.save();
});
$scope.$watch('act.Sport', function (newValue, oldValue, scope) {
if ($scope.editing && newValue != oldValue)$scope.save();
});
$scope.$watch('act.Others', function (newValue, oldValue, scope) {
if ($scope.editing && newValue != oldValue)$scope.save();
}); $scope.initData = function () {
var events_data = []; // 日常
$scope.getDA(function (data) {
var op = [];
op['Business'] = '#387EF5';
op['Study'] = '#FFC900';
op['Health'] = '#EF473A';
op['Sport'] = '#33CD5F';
op['Others'] = '#B2B2B2'; for (var i = 0; i < data.length; i++) {
var dd = data[i];
for (var k in op) {
if (dd[k.toString()] != undefined && dd[k.toString()] != '') {
var item = [];
item['color'] = op[k];
item['title'] = dd[k.toString()].replace('\n','|').substring(0, 10);
item['start'] = new Date(dd['Date']);
events_data.push(item);
}
}
} $('#calendar').fullCalendar('destroy');
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month'//,agendaWeek,agendaDay'
},
firstDay: 1,
events: events_data,
// 点击空白
dayClick: function (date, allDay, jsEvent, view) {
var selDate = $.fullCalendar.formatDate(date, 'yyyy-MM-dd');//格式化日期
$scope.act.tDate = selDate; $scope.loadData();
$scope.openModal();
},
//单击事件项时触发
eventClick: function (calEvent, jsEvent, view) {
$scope.act.tDate = dateFormat(calEvent.start,'ymd'); $scope.loadData();
$scope.openModal();
}
});
});
} // 弹窗
$ionicModal.fromTemplateUrl('detail.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function (modal) {
$scope.modal = modal;
});
$scope.openModal = function () {
$scope.modal.show(); $scope.editing = true;
};
$scope.closeDetail = function () {
$scope.initData();
$scope.editing = false; $scope.modal.hide();
}
$scope.$on('$destroy', function () {
$scope.modal.remove();
}); // start
$scope.initData(); }])
;
说明:
- 日常数据的录入,采用了“即变即更新”的模式,这里使用$watch函数来监听数据变化。同时为了数据更新功能的便利性,在用户点击某一日弹框时,自动判断当日数据是否存在,不存在则插入空数据。
打包发布
生成Android平台安装包
使用命令:
cordova platform add android
cordova build android
(*注意,如果以上步骤出错,常见原因有:
- 安装的Android SDK和打包的SDK版本不对,下载相应SDK
- 环境变量没有配置好
- 安装最新node.js
)
*附录
【源码文件】
【APK文件】
作者:Ken
出处:http://www.cnblogs.com/glife/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【Ionic+AngularJS 开发】之『个人日常管理』App(二)的更多相关文章
- 【Ionic+AngularJS 开发】之『个人日常管理』App(一)
写在前面的话 过去一年自己接触了不少手机前端开发,得益于现在手机性能的提升和4G普及,感觉使用混合技术开发手机App已经可以满足越来越多的应用场景了.新年伊始,对自己2016年所学知识做一个阶段性 ...
- 实践分享:开始用Cordova+Ionic+AngularJS开发App
http://www.cocoachina.com/webapp/20150707/12395.html 本文是一篇关于我本人在使用Cordova+Ionic以及AngularJS开发移动App的过程 ...
- Ionic+AngularJS 开发的页面在微信公众号下显示不出来原因查究
ionic 页面 微信浏览器遇到的坑 公司的微信公众号一部分页面是用AngularJS+Ioinc开发,发现在本地浏览器测试的时候都没问题,传到服务器在微信公众号下跑就出问题来,经查是: index- ...
- ionic+angularjs开发hybrid App(环境配置+创建测试项目)
本文使用的系统是win10 因为后期需要使用nodejs 所以先把node装好 https://nodejs.org/download/ 下载JDK并配置Java运行环境 http://www.ora ...
- Linux 笔记 - 第十三章 Linux 系统日常管理之(二)Linux 防火墙和任务计划
博客地址:http://www.moonxy.com 一.前言 Linux 下的的防火墙功能是非常丰富的,作为 Linux 系统工程师有必要了解一下.防火墙一般分为硬件防火墙和软件防火墙.但是,不管是 ...
- 【python测试开发栈】—python内存管理机制(二)—垃圾回收
在上一篇文章中(python 内存管理机制-引用计数)中,我们介绍了python内存管理机制中的引用计数,python正是通过它来有效的管理内存.今天来介绍python的垃圾回收,其主要策略是引用计数 ...
- Cordova Ionic AngularJS
实践分享:开始用Cordova+Ionic+AngularJS开发App http://www.cocoachina.com/webapp/20150707/12395.html
- 搭建 AngularJS+Ionic+Cordova 开发环境并运行一个demo
目前的手机APP有三类:原生APP,WebAPP,HybridApp:HybridApp结合了前两类APP各自的优点,越来越流行. Cordova就是一个中间件,让我们把WebAPP打包成Hybrid ...
- WebApp开发框架Ionic+AngularJS+Cordova
目前的手机APP有三类:原生APP.WebAPP.HybridApp:HybridApp结合了前两类APP各自的优点,越来越流行. Ionic Ionic是一个新的.可以使用HTML5构建混合移动应用 ...
随机推荐
- asp.net 输出Excel
private void lbtExportToExcel_Click(object sender, EventArgs e) { string strdate = DateTime.Now.Mont ...
- LPC1768串口使用
Lpc1768内置了四个串口通讯模块,都是异步通讯模块,其中,串口0/2/3是普通串口通讯,串口1与 UART0/2/3 基本相同,只是增加了一个 Modem 接口和 RS-486/EIA-486 模 ...
- iOS Socket第三方开源类库 ----AsyncSocket
假如你也是一个java程序员,而你又不是很懂Socket. 下面我的这篇文章也许能帮助你一些. http://xiva.iteye.com/blog/993336 首先我们写好上面文章中的server ...
- BZOJ2733 [HNOI2012]永无乡 【线段树合并】
本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...
- u-boot-2016.07 README文档结构
Author:AP0904225版权声明:本文为博主原创文章,转载请标明出处. 阅读u-boot的README文档,可以获取很多有用的信息,例如从哪里可以获得帮助,帮助:u-boot版本命名规则,目录 ...
- JSON详解(转载)
JSON详解 阅读目录 JSON的两种结构 认识JSON字符串 在JS中如何使用JSON 在.NET中如何使用JSON 总结 JSON的全称是”JavaScript Object Notation”, ...
- 315.Count of Smaller Numbers After Self My Submissions Question
You are given an integer array nums and you have to return a new counts array. Thecounts array has t ...
- arcgis 瓦片图加载规则(转载)
arcgis 瓦片图加载规则 最近需要做地图离线的功能,要能下载指定区域的瓦片图,我们都知道如何加载谷歌和天地图的加载规则,但是网上貌似没有找到如何加载arcgis自己发布的瓦片图规则,好不容易找到一 ...
- HibernateSessionFactory类中Session对象的创建步骤
HibernateSessionFactory类中Session对象的创建步骤: 1.初始化Hibernate配置管理类Configuration 2.通过Configuration类实例创建Sess ...
- Unity 压缩texture
当我们往服务器保存图片时 并不会仅仅保留原图 一般会另外保存一张缩略图 当加载文件夹时只加载缩略图 当在点击缩略图打开图片时 再加载原缩略图 以节省时间和内存 下面以将屏幕截图保存到服务器为例 将屏幕 ...