准备工作

资源

预装工具

安装bower

 npm install -g bower

安装ngCordova

bower install ngCordova

(*由于网络获取资源的原因,后面几次建项目后都无法下载到,自己便复制了原来的ngCordova目录(到YourProject\wwww\lib目录下),发现也是可以使用的)

下载好后,在项目的index.hmtl进行引用:

<script src="lib/ngCordova/dist/ng-cordova.js">

日历工具

FullCalendar

安装插件

本项目需要(安装)的插件有:

插件名 说明 扩展阅读
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>&nbsp;
<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>&nbsp;
<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>&nbsp;
<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>&nbsp;
<span>{{act.tDate}}</span>
</label>
&nbsp;
<span>{{act.id}}</span>
&nbsp;
<!--<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(二)的更多相关文章

  1. 【Ionic+AngularJS 开发】之『个人日常管理』App(一)

      写在前面的话 过去一年自己接触了不少手机前端开发,得益于现在手机性能的提升和4G普及,感觉使用混合技术开发手机App已经可以满足越来越多的应用场景了.新年伊始,对自己2016年所学知识做一个阶段性 ...

  2. 实践分享:开始用Cordova+Ionic+AngularJS开发App

    http://www.cocoachina.com/webapp/20150707/12395.html 本文是一篇关于我本人在使用Cordova+Ionic以及AngularJS开发移动App的过程 ...

  3. Ionic+AngularJS 开发的页面在微信公众号下显示不出来原因查究

    ionic 页面 微信浏览器遇到的坑 公司的微信公众号一部分页面是用AngularJS+Ioinc开发,发现在本地浏览器测试的时候都没问题,传到服务器在微信公众号下跑就出问题来,经查是: index- ...

  4. ionic+angularjs开发hybrid App(环境配置+创建测试项目)

    本文使用的系统是win10 因为后期需要使用nodejs 所以先把node装好 https://nodejs.org/download/ 下载JDK并配置Java运行环境 http://www.ora ...

  5. Linux 笔记 - 第十三章 Linux 系统日常管理之(二)Linux 防火墙和任务计划

    博客地址:http://www.moonxy.com 一.前言 Linux 下的的防火墙功能是非常丰富的,作为 Linux 系统工程师有必要了解一下.防火墙一般分为硬件防火墙和软件防火墙.但是,不管是 ...

  6. 【python测试开发栈】—python内存管理机制(二)—垃圾回收

    在上一篇文章中(python 内存管理机制-引用计数)中,我们介绍了python内存管理机制中的引用计数,python正是通过它来有效的管理内存.今天来介绍python的垃圾回收,其主要策略是引用计数 ...

  7. Cordova Ionic AngularJS

    实践分享:开始用Cordova+Ionic+AngularJS开发App http://www.cocoachina.com/webapp/20150707/12395.html

  8. 搭建 AngularJS+Ionic+Cordova 开发环境并运行一个demo

    目前的手机APP有三类:原生APP,WebAPP,HybridApp:HybridApp结合了前两类APP各自的优点,越来越流行. Cordova就是一个中间件,让我们把WebAPP打包成Hybrid ...

  9. WebApp开发框架Ionic+AngularJS+Cordova

    目前的手机APP有三类:原生APP.WebAPP.HybridApp:HybridApp结合了前两类APP各自的优点,越来越流行. Ionic Ionic是一个新的.可以使用HTML5构建混合移动应用 ...

随机推荐

  1. asp.net 输出Excel

    private void lbtExportToExcel_Click(object sender, EventArgs e) { string strdate = DateTime.Now.Mont ...

  2. LPC1768串口使用

    Lpc1768内置了四个串口通讯模块,都是异步通讯模块,其中,串口0/2/3是普通串口通讯,串口1与 UART0/2/3 基本相同,只是增加了一个 Modem 接口和 RS-486/EIA-486 模 ...

  3. iOS Socket第三方开源类库 ----AsyncSocket

    假如你也是一个java程序员,而你又不是很懂Socket. 下面我的这篇文章也许能帮助你一些. http://xiva.iteye.com/blog/993336 首先我们写好上面文章中的server ...

  4. BZOJ2733 [HNOI2012]永无乡 【线段树合并】

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...

  5. u-boot-2016.07 README文档结构

    Author:AP0904225版权声明:本文为博主原创文章,转载请标明出处. 阅读u-boot的README文档,可以获取很多有用的信息,例如从哪里可以获得帮助,帮助:u-boot版本命名规则,目录 ...

  6. JSON详解(转载)

    JSON详解 阅读目录 JSON的两种结构 认识JSON字符串 在JS中如何使用JSON 在.NET中如何使用JSON 总结 JSON的全称是”JavaScript Object Notation”, ...

  7. 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 ...

  8. arcgis 瓦片图加载规则(转载)

    arcgis 瓦片图加载规则 最近需要做地图离线的功能,要能下载指定区域的瓦片图,我们都知道如何加载谷歌和天地图的加载规则,但是网上貌似没有找到如何加载arcgis自己发布的瓦片图规则,好不容易找到一 ...

  9. HibernateSessionFactory类中Session对象的创建步骤

    HibernateSessionFactory类中Session对象的创建步骤: 1.初始化Hibernate配置管理类Configuration 2.通过Configuration类实例创建Sess ...

  10. Unity 压缩texture

    当我们往服务器保存图片时 并不会仅仅保留原图 一般会另外保存一张缩略图 当加载文件夹时只加载缩略图 当在点击缩略图打开图片时 再加载原缩略图 以节省时间和内存 下面以将屏幕截图保存到服务器为例 将屏幕 ...