
技术交流群,旨在互相解决问题。反灌水,反乱聊。
QQ群号:194178496
欢迎开发中遇到的问题和乐意为别人提供帮助的请加群!
点击链接加入群【HBuilder+MUI技术交流群】:https://jq.qq.com/?_wv=1027&k=4BF2dhv
QQ群号:194178496
欢迎开发中遇到的问题和乐意为别人提供帮助的请加群!
点击链接加入群【HBuilder+MUI技术交流群】:https://jq.qq.com/?_wv=1027&k=4BF2dhv

【示例】解决软键盘弹出时底部元素上浮的问题
问题描述
在html5plus环境下,当html中存在固定在底部的元素时,此时弹出软键盘的话,底部的元素也会被弹上来。
出现这种情况时,看起来页面布局好像乱掉了,这样给用户的体验不太理想。
问题分析
问题原因
之所以会出现这种情况,是因为软键盘弹出会导致Webview的高度发生变化,进而导致html中元素位置发生变化。
其实也可以认为元素定位没有变,只是元素所在容器的宽高变化,看起来像是元素的位置变了。意思就是,元素定位相关的css并没有错误或者说失效。
验证方法
想验证这个变化,可以通过监听window的resize事件在回调中打印日志即可。
// 监听window的resize
window.addEventListener('resize', function() {
var height = document.documentElement.clientHeight;
var width = document.documentElement.clientWidth;
console.log('可见区域高度:' + height);
console.log('可见区域宽度:' + width);
});
当然,通过chrome调试Webview的功能,在chrome中可以很清楚地看到Webview的高度变化。
简单来说
软键盘被唤起的时候,原本属于Webview的空间被软键盘占用了(是占用,不是覆盖或者遮盖)。屏幕一共就那么高,软键盘把Webview的空间占用了一部分,那么Webview自然就被挤压,高度也就变了。
解决办法
固定元素
元素之所以会上浮,是因为通常我们都是设置元素的bottom值使其固定在底部。
.bottom {
height: 40px;
position: fixed;
bottom: 0;
}
但是Webview被挤压后,底部的位置上去了。元素是基于bottom值进行定位,那么它看起来就是在软键盘的上面。
逆向固定
既然bottom值会使元素上浮,但是我们又希望元素固定在底部。此时,可以使用逆向的方式,也就是设置top值让元素固定在顶部。
var bottomEl = document.querySelector('.bottom');
// 可见区域高度减去元素的高度
bottomEl.style.top = document.documentElement.clientHeight - 40 + 'px';
由于是基于top值定位,Webview被向上挤压,顶部的位置是不变的,那么元素的位置自然就不会发生变化。
效果图及源码
改进后的效果如下

示例代码见附件,下载后真机运行即可。
其它办法
如果各位有其它更好的解决办法,欢迎指教,并在社区分享你宝贵的经验。
问题描述
在html5plus环境下,当html中存在固定在底部的元素时,此时弹出软键盘的话,底部的元素也会被弹上来。
出现这种情况时,看起来页面布局好像乱掉了,这样给用户的体验不太理想。
问题分析
问题原因
之所以会出现这种情况,是因为软键盘弹出会导致Webview的高度发生变化,进而导致html中元素位置发生变化。
其实也可以认为元素定位没有变,只是元素所在容器的宽高变化,看起来像是元素的位置变了。意思就是,元素定位相关的css并没有错误或者说失效。
验证方法
想验证这个变化,可以通过监听window的resize事件在回调中打印日志即可。
// 监听window的resize
window.addEventListener('resize', function() {
var height = document.documentElement.clientHeight;
var width = document.documentElement.clientWidth;
console.log('可见区域高度:' + height);
console.log('可见区域宽度:' + width);
});
当然,通过chrome调试Webview的功能,在chrome中可以很清楚地看到Webview的高度变化。
简单来说
软键盘被唤起的时候,原本属于Webview的空间被软键盘占用了(是占用,不是覆盖或者遮盖)。屏幕一共就那么高,软键盘把Webview的空间占用了一部分,那么Webview自然就被挤压,高度也就变了。
解决办法
固定元素
元素之所以会上浮,是因为通常我们都是设置元素的bottom值使其固定在底部。
.bottom {
height: 40px;
position: fixed;
bottom: 0;
}
但是Webview被挤压后,底部的位置上去了。元素是基于bottom值进行定位,那么它看起来就是在软键盘的上面。
逆向固定
既然bottom值会使元素上浮,但是我们又希望元素固定在底部。此时,可以使用逆向的方式,也就是设置top值让元素固定在顶部。
var bottomEl = document.querySelector('.bottom');
// 可见区域高度减去元素的高度
bottomEl.style.top = document.documentElement.clientHeight - 40 + 'px';
由于是基于top值定位,Webview被向上挤压,顶部的位置是不变的,那么元素的位置自然就不会发生变化。
效果图及源码
改进后的效果如下
示例代码见附件,下载后真机运行即可。
其它办法
如果各位有其它更好的解决办法,欢迎指教,并在社区分享你宝贵的经验。
收起阅读 »
关于input点击整个页面上移解决方法
先给你的body加如下代码:
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
-webkit-touch-callout: none;
-webkit-user-select: none;
}
然后在你的plus里面添加一个样式设置该Activity总是调整屏幕的大小以便留出软键盘的空间:
plus.webview.currentWebview().setStyle({
softinputMode: "adjustResize"
});
先给你的body加如下代码:
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
-webkit-touch-callout: none;
-webkit-user-select: none;
}
然后在你的plus里面添加一个样式设置该Activity总是调整屏幕的大小以便留出软键盘的空间:
plus.webview.currentWebview().setStyle({
softinputMode: "adjustResize"
});

如何更新通过nativeObj.View创建的view控件教程
需求说明
此教程将针对使用new plus.nativeObj.View() 创建的view对象如何更新作出详细说明;
创建示例
var currWV = plus.webview.currentWebview(),
leftPos = Math.ceil((window.innerWidth - 60) / 2);
var drawNativeIcon = new plus.nativeObj.View('tabIcon', {
bottom: '10px',
left: leftPos + 5 + 'px',
width: '50px',
height: '50px',
position:'dock' //此种停靠方式表明该控件应浮在窗口最上层,以免被其他窗口遮住
}, [{
tag: 'rect',
id: 'iconBg',
position: {
top: '0px',
left: '0px',
width: '50px',
height: '100%'
},
rectStyles: {
color: '#d74b28',
size: '50px',
radius:'50%'
}
},{
tag: 'font',
id: 'icon',
text: '\ue600', //此为字体图标Unicode码'\e600'转换为'\ue600'
position: {
top: '0px',
left: '0px',
width: '50px',
height: '100%'
},
textStyles: {
fontSrc: '_www/fonts/iconfont.ttf',
align:'center',
color: '#fff',
size: '30px'
}
}]);
currWV.append(drawNativeIcon);
实现更新方法
使用nativeObj提供的方法更新
通过 nativeObj 提供的 draw/drawBitmap/drawRect/drawText 方法对指定id重新绘制
说明:在当前View控件之上绘制指定的内容,可一次指定绘制多个元素,绘制元素可以是图片/矩形区域/文本, 即将多次调用drawBitmap/drawRect/drawText方法合并调用一次draw方法来实现, 推荐使用draw方法来替换多次调用drawBitmap/drawRect/drawText。
// 当前view控件需要更新的tags
drawNativeIcon.draw(tags);
// 更新指定id
drawNativeIcon.drawRect(styles, position, id) //绘制元素为矩形区域
drawNativeIcon.drawBitmap(src, sprite, position, id) //绘制元素为图片
drawNativeIcon.drawText(text, position, styles, id) //绘制元素为文本,包括字体图标
// 比如更新drawNativeIcon 控件的纯色背景颜色
drawNativeIcon.drawRect({
color: '#0ff',
radius: '50%'
}, {}, 'iconBg');
// 比如更新drawNativeIcon 控件的字体图标颜色
drawNativeIcon.drawText('\ue600', {}, {
color: '#ccc',
fontSrc: '_www/fonts/iconfont.ttf',
size: '30px'
}, 'icon');
// 使用draw方法
drawNativeIcon.draw([
{
id: 'iconBg',
tag:'rect',
rectStyles: {
color: '#ff0',
size: '50px',
radius: '50%'
}
}, {
id: 'icon',
tag:'font',
text: '\ue600', //此为字体图标Unicode码'\e600'转换为'\ue600'
textStyles: {
fontSrc: '_www/fonts/iconfont.ttf',
align: 'center',
color: '#000',
size: '30px'
}
}
])
说明:
1.position 的默认值为 {top:'0px',left:'0px',width:'100%',height:'100%'},如果与默认值相同,可以传空对象。
2.需要更新的tag 如果与默认值不相同,需要重新传值。
使用webview提供的方法更新
此方法使用 webview 提供的updateSubNViews()更新控件,是集成了nativeObj方法给webview提供一个直接操作原生view控件的方法。通过要更新view控件中的id属性值匹配子View控件更新绘制内容,如果没有查找到对应id的子View控件则忽略。 此操作仅更新子View控件上绘制的内容,不会添加或删除原生子View控件对象。
var currWV = plus.webview.currentWebview();
//比如更新控件颜色
currWV.updateSubNViews([{
id: 'tabIcon',
tags: [{
id: 'iconBg',
rectStyles: {
color: '#ff0',
size: '50px',
radius: '50%'
}
}, {
id: 'icon',
text: '\ue600', //此为字体图标Unicode码'\e600'转换为'\ue600'
textStyles: {
fontSrc: '_www/fonts/iconfont.ttf',
align: 'center',
color: '#000',
size: '30px'
}
}]
}])
说明:
1.position 的默认值为 {top:'0px',left:'0px',width:'100%',height:'100%'},如果与默认值相同,可以不传。
2.需要更新的tag 如果与默认值不相同,需要重新传值。
总结:
1.以上提供两种方式更新此类view控件,实现方法基本类似,开发者可根据使用场景选择恰当的方式。
2.此类方法创建的view控件与通过subNViews节点配置方法实现的效果一样,区别在于subNViews配置需要在创建webview时就引入。但是这样配置的信息就可以在页面中通过以下方式获取到view控件的相应信息。对于处理多个view控件的自动更新方便很多,比如首页底部选项卡
var nviews = currWV.getStyle().subNViews(); //返回配置的view控件的所有信息
3.关于如何更新subNViews节点配置的view控件。请下载参考教程 中上传的附件demo。
需求说明
此教程将针对使用new plus.nativeObj.View() 创建的view对象如何更新作出详细说明;
创建示例
var currWV = plus.webview.currentWebview(),
leftPos = Math.ceil((window.innerWidth - 60) / 2);
var drawNativeIcon = new plus.nativeObj.View('tabIcon', {
bottom: '10px',
left: leftPos + 5 + 'px',
width: '50px',
height: '50px',
position:'dock' //此种停靠方式表明该控件应浮在窗口最上层,以免被其他窗口遮住
}, [{
tag: 'rect',
id: 'iconBg',
position: {
top: '0px',
left: '0px',
width: '50px',
height: '100%'
},
rectStyles: {
color: '#d74b28',
size: '50px',
radius:'50%'
}
},{
tag: 'font',
id: 'icon',
text: '\ue600', //此为字体图标Unicode码'\e600'转换为'\ue600'
position: {
top: '0px',
left: '0px',
width: '50px',
height: '100%'
},
textStyles: {
fontSrc: '_www/fonts/iconfont.ttf',
align:'center',
color: '#fff',
size: '30px'
}
}]);
currWV.append(drawNativeIcon);
实现更新方法
使用nativeObj提供的方法更新
通过 nativeObj 提供的 draw/drawBitmap/drawRect/drawText 方法对指定id重新绘制
说明:在当前View控件之上绘制指定的内容,可一次指定绘制多个元素,绘制元素可以是图片/矩形区域/文本, 即将多次调用drawBitmap/drawRect/drawText方法合并调用一次draw方法来实现, 推荐使用draw方法来替换多次调用drawBitmap/drawRect/drawText。
// 当前view控件需要更新的tags
drawNativeIcon.draw(tags);
// 更新指定id
drawNativeIcon.drawRect(styles, position, id) //绘制元素为矩形区域
drawNativeIcon.drawBitmap(src, sprite, position, id) //绘制元素为图片
drawNativeIcon.drawText(text, position, styles, id) //绘制元素为文本,包括字体图标
// 比如更新drawNativeIcon 控件的纯色背景颜色
drawNativeIcon.drawRect({
color: '#0ff',
radius: '50%'
}, {}, 'iconBg');
// 比如更新drawNativeIcon 控件的字体图标颜色
drawNativeIcon.drawText('\ue600', {}, {
color: '#ccc',
fontSrc: '_www/fonts/iconfont.ttf',
size: '30px'
}, 'icon');
// 使用draw方法
drawNativeIcon.draw([
{
id: 'iconBg',
tag:'rect',
rectStyles: {
color: '#ff0',
size: '50px',
radius: '50%'
}
}, {
id: 'icon',
tag:'font',
text: '\ue600', //此为字体图标Unicode码'\e600'转换为'\ue600'
textStyles: {
fontSrc: '_www/fonts/iconfont.ttf',
align: 'center',
color: '#000',
size: '30px'
}
}
])
说明:
1.position 的默认值为 {top:'0px',left:'0px',width:'100%',height:'100%'},如果与默认值相同,可以传空对象。
2.需要更新的tag 如果与默认值不相同,需要重新传值。
使用webview提供的方法更新
此方法使用 webview 提供的updateSubNViews()更新控件,是集成了nativeObj方法给webview提供一个直接操作原生view控件的方法。通过要更新view控件中的id属性值匹配子View控件更新绘制内容,如果没有查找到对应id的子View控件则忽略。 此操作仅更新子View控件上绘制的内容,不会添加或删除原生子View控件对象。
var currWV = plus.webview.currentWebview();
//比如更新控件颜色
currWV.updateSubNViews([{
id: 'tabIcon',
tags: [{
id: 'iconBg',
rectStyles: {
color: '#ff0',
size: '50px',
radius: '50%'
}
}, {
id: 'icon',
text: '\ue600', //此为字体图标Unicode码'\e600'转换为'\ue600'
textStyles: {
fontSrc: '_www/fonts/iconfont.ttf',
align: 'center',
color: '#000',
size: '30px'
}
}]
}])
说明:
1.position 的默认值为 {top:'0px',left:'0px',width:'100%',height:'100%'},如果与默认值相同,可以不传。
2.需要更新的tag 如果与默认值不相同,需要重新传值。
总结:
1.以上提供两种方式更新此类view控件,实现方法基本类似,开发者可根据使用场景选择恰当的方式。
2.此类方法创建的view控件与通过subNViews节点配置方法实现的效果一样,区别在于subNViews配置需要在创建webview时就引入。但是这样配置的信息就可以在页面中通过以下方式获取到view控件的相应信息。对于处理多个view控件的自动更新方便很多,比如首页底部选项卡
var nviews = currWV.getStyle().subNViews(); //返回配置的view控件的所有信息
3.关于如何更新subNViews节点配置的view控件。请下载参考教程 中上传的附件demo。
收起阅读 »
发现很多问题都是重复的,官方其实可以把回复问题的时间,集中到写文档上面,一劳多得
发现很多问题都是重复的,官方其实可以把回复问题的时间,集中到写文档上面,一劳多得
发现很多问题都是重复的,官方其实可以把回复问题的时间,集中到写文档上面,一劳多得

字体图标绘制原生view控件
概述
这篇教程将向大家介绍新发布的 HTML 5+ SDK 新增的webview窗口如何使用 字体图标 绘制原生view控件的功能(绘制view控件的内容不仅限于字体图标,详情请参考plus.nativeObj.view)
早在之前 sdk 就已经支持添加原生标题栏 navigationbar,现在调整了navigationbar为 titleNView ,与 subNViews 统一理念,并补充 buttons 配置,方便配置右侧按钮.
这里的 titleNView 与 subNViews 都属于原生view 控件,titleNView 指的是原生标题栏view控件,subNViews 指的是该页面所有的原生view 控件,包括titleNView.
// 获取该webview 所有原生控件
plus.webview.currentWebview().getSubNViews() // 返回数组
实现方法
下面就以subNViews为例来说明一下字体图标的绘制原生view控件的方法。
1.找到相应字体图标编码,以下以mui字体图标作为示例介绍。
作为开发者,同学们都有自己的方法去拿到图标编码,这里简单介绍几种方法:
a:前往mui官网文档 —> ui组件 icon(图标) —> 找到图标单击复制类名 —> 到mui.css 查找该类名,获取相应before伪元素中content 属性中的编码字符串,比如:'\e502'.
b:打开浏览器调试器,自行查找元素的相应伪元素before,获取style样式中content属性值。
这里需要注意的是,拿到编码字符串,需要把它变成原生层能解析的ASCII编码, 也就是在e前面加u字符,比如:’\e502‘ —> '\ue502'.
2.根据以下方法配置图标路径,大小,位置,颜色等信息。
var styles = {
subNViews: [
{
id: 'view',
styles: { // 该view控件的位置 大小等信息
bottom:'0',
left:'0',
width:'100%',
height:'50px',
backgroundColor:'#fff'
},
tags: [
{ // 图标的大小 位置 颜色等信息
tag:'font',
id:'viewTag',
text:'\ue500', // 由'\e500'转换为原生层识别的ASCII编码
position: { // 相对view 控件的位置 大小等信息
top:'0',
left:'0',
width:'100%',
height:'100%'
},
textStyles: {
fontSrc:'_www/fonts/mui.ttf', // _www代表根目录
align:'center',
size:'24px'
}
}
]
}
]
}
plus.webview.create(url,id,styles,aniShow,duration);
说明:
- subNViews因为要在webview创建时绘制,所以不支持setStyle注入。
- 支持根据updateSubNViews()方法更新已有view控件。参考这里
扩展阅读
titleNView 中 buttons 配置方法类似以上方法,区别是在标题栏控件添加字体图标按钮,并且支持直接设置click响应事件,详情请参考 titleNView 配置教程
概述
这篇教程将向大家介绍新发布的 HTML 5+ SDK 新增的webview窗口如何使用 字体图标 绘制原生view控件的功能(绘制view控件的内容不仅限于字体图标,详情请参考plus.nativeObj.view)
早在之前 sdk 就已经支持添加原生标题栏 navigationbar,现在调整了navigationbar为 titleNView ,与 subNViews 统一理念,并补充 buttons 配置,方便配置右侧按钮.
这里的 titleNView 与 subNViews 都属于原生view 控件,titleNView 指的是原生标题栏view控件,subNViews 指的是该页面所有的原生view 控件,包括titleNView.
// 获取该webview 所有原生控件
plus.webview.currentWebview().getSubNViews() // 返回数组
实现方法
下面就以subNViews为例来说明一下字体图标的绘制原生view控件的方法。
1.找到相应字体图标编码,以下以mui字体图标作为示例介绍。
作为开发者,同学们都有自己的方法去拿到图标编码,这里简单介绍几种方法:
a:前往mui官网文档 —> ui组件 icon(图标) —> 找到图标单击复制类名 —> 到mui.css 查找该类名,获取相应before伪元素中content 属性中的编码字符串,比如:'\e502'.
b:打开浏览器调试器,自行查找元素的相应伪元素before,获取style样式中content属性值。
这里需要注意的是,拿到编码字符串,需要把它变成原生层能解析的ASCII编码, 也就是在e前面加u字符,比如:’\e502‘ —> '\ue502'.
2.根据以下方法配置图标路径,大小,位置,颜色等信息。
var styles = {
subNViews: [
{
id: 'view',
styles: { // 该view控件的位置 大小等信息
bottom:'0',
left:'0',
width:'100%',
height:'50px',
backgroundColor:'#fff'
},
tags: [
{ // 图标的大小 位置 颜色等信息
tag:'font',
id:'viewTag',
text:'\ue500', // 由'\e500'转换为原生层识别的ASCII编码
position: { // 相对view 控件的位置 大小等信息
top:'0',
left:'0',
width:'100%',
height:'100%'
},
textStyles: {
fontSrc:'_www/fonts/mui.ttf', // _www代表根目录
align:'center',
size:'24px'
}
}
]
}
]
}
plus.webview.create(url,id,styles,aniShow,duration);
说明:
- subNViews因为要在webview创建时绘制,所以不支持setStyle注入。
- 支持根据updateSubNViews()方法更新已有view控件。参考这里
扩展阅读
titleNView 中 buttons 配置方法类似以上方法,区别是在标题栏控件添加字体图标按钮,并且支持直接设置click响应事件,详情请参考 titleNView 配置教程
收起阅读 »
完美的mui图片裁切上传(含服务器端、相册选择、拍照,可直接运行)
附件有含有源码下载
。
开篇背景:
在网上找了许多资料,汇总之后写了这么一个示例出来,现将完整代码整理处理,包括前端完整的Hbuilder工程代码和后端的处理上传Java实现。图片裁切使用到了jquery.cropper插件,所以这个图片裁切的功能模块是需要使用到jquery的,也尝试自己写了一点实现了基本的图片放大缩小、图片移动后感觉脑袋太晕了,还是找一款插件更加合适,经详细了解了cropper插件后觉得太强大了,兼容pc、安卓、ios等,一些特色功能满足来了我个人对于图片裁切所需要的所有,插件下载地址为:https://github.com/fengyuanchen/cropper ,网上也能找到需要关于此插件的在线运行,也可以将附件源码下载下来,在源码的docs/index.html文件打开可以直接运行代码示例。特色功能有:图片拖动大小缩放、背景图片移动、裁切区域移动、选区大小调整、选区移动、照片左右上下变换、旋转等,这些功能也是我本次示例中使用到了的。
后端处理:
无论用什么语言,它的目的是将用户裁切完成后的数据进行存储,由于前端裁切使用了canvas裁切图片,所以提交到后端的是裁切后的区域图片字符串编码,而非图片文件,后端需要将此字符串采用base64解码转换成一张图片文件存储。。。也就是说这里的后端处理文件上传并非传统的处理图片上传,而是解码base64字符串,如果还不清楚base64字符串编码与图片的转换,可以使用word验证一下。这里临时写了一个简单的Java的servlet程序来处理文件上传,参考代码如下:
package com;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.util.Base64;
import com.frame.base.utils.properties.PropertiesUtils;
import com.frame.base.utils.properties.enums.EnumProperties;
@WebServlet("/testaa.servlet")
public class Test extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String imageBase64 = req.getParameter("imageBase64");
//将base64的图片编码存储至服务器,并将路径修改至数据库
String type = "data:image/png;base64,";//前台是以png的格式存储的
imageBase64 = imageBase64.substring(type.length());
String realPath = "sysuser" + File.separator + UUID.randomUUID().toString() + ".png";
JSONObject json = new JSONObject();
try {
this.uploadFileByBase64(realPath , imageBase64);
json.put("result", "success");
json.put("message", realPath);
} catch (Exception e) {
json.put("result", "error");
json.put("message", "上传图片失败!");
e.printStackTrace();
}
//将参数输出至浏览器
PrintWriter out = resp.getWriter();
out.write(json.toJSONString());
out.flush();
out.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req, resp);
}
public File uploadFileByBase64(String destRealPath , String imageBase64) throws Exception{
OutputStream out = null;
try {
byte bytes[] = Base64.decodeFast(imageBase64);
//这里是我的获取图片文件服务器的路径,可以是随便一个地址,如d:\\temp\\之类的
String imageServer = PropertiesUtils.getInstance().getProperty(EnumProperties.APPLICATION, "system.fileupload.information");
String destFilePath = imageServer + File.separator + destRealPath;
File destFile = new File(destFilePath);
if(! destFile.getParentFile().exists()){
destFile.getParentFile().mkdirs();
}
for(int i=0 , lens = bytes.length ; i < lens ; i++){
if(bytes[i] < 0){
bytes[i] += 256;
}
}
out = new FileOutputStream(destFile);
out.write(bytes);
return destFile;
} catch (Exception e) {
throw new Exception(e);
} finally {
if(out != null){
try {
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
上传至服务器的图片下载
服务器端是我临时写的代码,如果示例提示上传失败,有可能是我的服务器关闭了无法正常访问,运行示例中提示的上传成功可将相关地址复制到浏览器访问,我的服务器会输出这个裁切后上传到服务器的图片,图片结尾地址为.image,即xxxxx.png.image
其他说明
前台代码中用到了一些windows.xxx没有别的意思,就是为了不想定义全部变量而已。
附件有含有源码下载
。
开篇背景:
在网上找了许多资料,汇总之后写了这么一个示例出来,现将完整代码整理处理,包括前端完整的Hbuilder工程代码和后端的处理上传Java实现。图片裁切使用到了jquery.cropper插件,所以这个图片裁切的功能模块是需要使用到jquery的,也尝试自己写了一点实现了基本的图片放大缩小、图片移动后感觉脑袋太晕了,还是找一款插件更加合适,经详细了解了cropper插件后觉得太强大了,兼容pc、安卓、ios等,一些特色功能满足来了我个人对于图片裁切所需要的所有,插件下载地址为:https://github.com/fengyuanchen/cropper ,网上也能找到需要关于此插件的在线运行,也可以将附件源码下载下来,在源码的docs/index.html文件打开可以直接运行代码示例。特色功能有:图片拖动大小缩放、背景图片移动、裁切区域移动、选区大小调整、选区移动、照片左右上下变换、旋转等,这些功能也是我本次示例中使用到了的。
后端处理:
无论用什么语言,它的目的是将用户裁切完成后的数据进行存储,由于前端裁切使用了canvas裁切图片,所以提交到后端的是裁切后的区域图片字符串编码,而非图片文件,后端需要将此字符串采用base64解码转换成一张图片文件存储。。。也就是说这里的后端处理文件上传并非传统的处理图片上传,而是解码base64字符串,如果还不清楚base64字符串编码与图片的转换,可以使用word验证一下。这里临时写了一个简单的Java的servlet程序来处理文件上传,参考代码如下:
package com;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.util.Base64;
import com.frame.base.utils.properties.PropertiesUtils;
import com.frame.base.utils.properties.enums.EnumProperties;
@WebServlet("/testaa.servlet")
public class Test extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String imageBase64 = req.getParameter("imageBase64");
//将base64的图片编码存储至服务器,并将路径修改至数据库
String type = "data:image/png;base64,";//前台是以png的格式存储的
imageBase64 = imageBase64.substring(type.length());
String realPath = "sysuser" + File.separator + UUID.randomUUID().toString() + ".png";
JSONObject json = new JSONObject();
try {
this.uploadFileByBase64(realPath , imageBase64);
json.put("result", "success");
json.put("message", realPath);
} catch (Exception e) {
json.put("result", "error");
json.put("message", "上传图片失败!");
e.printStackTrace();
}
//将参数输出至浏览器
PrintWriter out = resp.getWriter();
out.write(json.toJSONString());
out.flush();
out.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req, resp);
}
public File uploadFileByBase64(String destRealPath , String imageBase64) throws Exception{
OutputStream out = null;
try {
byte bytes[] = Base64.decodeFast(imageBase64);
//这里是我的获取图片文件服务器的路径,可以是随便一个地址,如d:\\temp\\之类的
String imageServer = PropertiesUtils.getInstance().getProperty(EnumProperties.APPLICATION, "system.fileupload.information");
String destFilePath = imageServer + File.separator + destRealPath;
File destFile = new File(destFilePath);
if(! destFile.getParentFile().exists()){
destFile.getParentFile().mkdirs();
}
for(int i=0 , lens = bytes.length ; i < lens ; i++){
if(bytes[i] < 0){
bytes[i] += 256;
}
}
out = new FileOutputStream(destFile);
out.write(bytes);
return destFile;
} catch (Exception e) {
throw new Exception(e);
} finally {
if(out != null){
try {
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
上传至服务器的图片下载
服务器端是我临时写的代码,如果示例提示上传失败,有可能是我的服务器关闭了无法正常访问,运行示例中提示的上传成功可将相关地址复制到浏览器访问,我的服务器会输出这个裁切后上传到服务器的图片,图片结尾地址为.image,即xxxxx.png.image
收起阅读 »其他说明
前台代码中用到了一些windows.xxx没有别的意思,就是为了不想定义全部变量而已。

MUI+VUE2.0:开发一个actionsheet组件
- 开发工具:HBuilder
- 开发目标:使用vue2.0+mui.min.css开发一个与mui官方功能一致的actionsheet组件
- 实现功能
- actionsheet组件可接收一个items参数来显示具体子项
- actionsheet组件可接收一个onItemClick回调来处理子项点击事件
备注:简单起见,不使用vue的单文件组件开发模式
开发步骤
- 使用HBuilder,创建一个移动App(mui项目)
- 引入mui.min.css及vue.js
-
搭建一个页面框架,代码如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title></title> <link rel="stylesheet" href="css/mui.min.css" /> </head> <body> <div id="app"></div> <script type="text/javascript" src="js/vue.js"></script> </body>
</html>
4.开发actionsheet组件,代码如下
组件动画样式
/**
- 重写mui.min.css中的样式
*/
.mui-popover.mui-popover-action {
display: block;
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
-webkit-transition: none;
transition: none;
}
/**
- 定义actionsheet的backdrop进入动画效果
*/
.mui-actionsheet-enter {
opacity: 1;
}
/**
- 定义actionsheet的backdrop离开动画效果
*/
.mui-actionsheet-leave-to {
opacity: 0;
}
/**
- 定义actionsheet动画持续时间
*/
.mui-actionsheet-enter-active,
.mui-actionsheet-leave-active,
.mui-actionsheet-enter-active .mui-popover.mui-popover-action,
.mui-actionsheet-leave-active .mui-popover.mui-popover-action {
-webkit-transition: -webkit-transform .3s, opacity .3s;
transition: transform .3s, opacity .3s;
}
/**
- 定义actionsheet进入时,离开时动画效果
*/
.mui-actionsheet-enter .mui-popover.mui-popover-action,
.mui-actionsheet-leave-to .mui-popover.mui-popover-action {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}
组件模板代码
<script id="actionSheetTpl" type="text/html">
<transition name="mui-actionsheet">
<div class="mui-backdrop" v-show="isOpen" v-on:click="hide">
<div class="mui-popover mui-popover-action mui-popover-bottom">
<ul class="mui-table-view">
<li v-for="(item,index) in items" v-on:click="handleItemClick(item,index+1)" class="mui-table-view-cell">
<a>{{item}}</a>
</li>
</ul>
<ul class="mui-table-view">
<li v-on:click="handleItemClick('取消',0)" class="mui-table-view-cell">
<a><b>取消</b></a>
</li>
</ul>
</div>
</div>
</transition>
</script>
组件实现代码
/**
-
注册action-sheet组件
*/
Vue.component('action-sheet', {
props: ['items', 'shown'],
data: function() {
return {
isOpen: !!this.shown
}
},
watch: {
shown: function() {
this.isOpen = !!this.shown;
}
},
methods: {
show: function() {
this.isOpen = true; //显示actionsheet
},
hide: function() {
this.isOpen = false; //关闭actionsheet
},
toggle: function() {
this.isOpen = !this.isOpen; //切换显示状态
},
handleItemClick: function(item, index) {
this.$emit('onItemClick', item, index); //触发onItemClick事件
}
},
template: document.getElementById("actionSheetTpl").innerText,
});5.渲染示例(仿官网actionsheet示例) 组件使用示例
<script id="demoTpl" type="text/html">
<div>
<header class="mui-bar mui-bar-nav">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
<h1 class="mui-title">H5模式actionsheet</h1>
</header>
<nav class="mui-bar mui-bar-tab">
<a v-on:click="toggleActionSheet('delete')" class="mui-tab-item">
<span class="mui-icon mui-icon-trash"></span>
</a>
<a class="mui-tab-item" href="#">
</a>
<a class="mui-tab-item" href="#">
</a>
<a v-on:click="toggleActionSheet('forward')" class="mui-tab-item">
<span class="mui-icon mui-icon-undo"></span>
</a>
</nav>
<div class="mui-content">
<div class="mui-content-padded">
<p>actionsheet一般从底部弹出,显示一系列可选择的操作按钮,供用户选择; actionSheet可从任意位置触发, 点击本页面左下角
<span class="mui-icon mui-icon-trash"></span>会弹出删除信息确认框; 点击本页面右下角
<span class="mui-icon mui-icon-undo"></span>会弹出转发确认框; 你也可点击如下按钮,打开照片选择框:
</p>
<p>
<a v-on:click="toggleActionSheet('picture')" class="mui-btn mui-btn-primary mui-btn-block mui-btn-outlined" style="padding: 5px 20px;">打开actionsheet</a>
</p>
<p>本页面为H5模式的actionsheet演示示例,该模式具有如下优点:</p>
<ul class="des">
<li>可通过css自由定制展现样式</li>
</ul><p>另一方面,H5模式的actionsheet也具备如下缺点:</p> <ul class="des"> <li>不支持覆盖顶部状态栏</li> <li>不支持跨webview的遮罩</li> <li>在有map等原生组件时,容易被遮挡</li> </ul> <p id="info"></p> <action-sheet ref="picture" :items="pictureItems" v-on:onItemClick="handleItemClick"></action-sheet> <action-sheet ref="forward" :items="forwardItems" v-on:onItemClick="handleItemClick"></action-sheet> <action-sheet ref="delete" :items="deleteItems" v-on:onItemClick="handleItemClick"></action-sheet> </div>
</div>
</div>
</script>渲染示例代码
//渲染示例页面
var app = new Vue({
el: '#app',
data: function() {
return {
pictureItems: ['拍照或录像', '选取现有的'],
forwardItems: ['回复', '转发', '打印'],
deleteItems: ['删除']
}
},
methods: {
toggleActionSheet: function(actionsheet) {
this.$refs[actionsheet]['toggle']();
},
handleItemClick: function(item, index) {
console.log(item, index);
}
},
template: document.getElementById("demoTpl").innerText
});
完整代码,见附件
- 开发工具:HBuilder
- 开发目标:使用vue2.0+mui.min.css开发一个与mui官方功能一致的actionsheet组件
- 实现功能
- actionsheet组件可接收一个items参数来显示具体子项
- actionsheet组件可接收一个onItemClick回调来处理子项点击事件
备注:简单起见,不使用vue的单文件组件开发模式
开发步骤
- 使用HBuilder,创建一个移动App(mui项目)
- 引入mui.min.css及vue.js
-
搭建一个页面框架,代码如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title></title> <link rel="stylesheet" href="css/mui.min.css" /> </head> <body> <div id="app"></div> <script type="text/javascript" src="js/vue.js"></script> </body>
</html>
4.开发actionsheet组件,代码如下
组件动画样式
/**
- 重写mui.min.css中的样式
*/
.mui-popover.mui-popover-action {
display: block;
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
-webkit-transition: none;
transition: none;
}
/**
- 定义actionsheet的backdrop进入动画效果
*/
.mui-actionsheet-enter {
opacity: 1;
}
/**
- 定义actionsheet的backdrop离开动画效果
*/
.mui-actionsheet-leave-to {
opacity: 0;
}
/**
- 定义actionsheet动画持续时间
*/
.mui-actionsheet-enter-active,
.mui-actionsheet-leave-active,
.mui-actionsheet-enter-active .mui-popover.mui-popover-action,
.mui-actionsheet-leave-active .mui-popover.mui-popover-action {
-webkit-transition: -webkit-transform .3s, opacity .3s;
transition: transform .3s, opacity .3s;
}
/**
- 定义actionsheet进入时,离开时动画效果
*/
.mui-actionsheet-enter .mui-popover.mui-popover-action,
.mui-actionsheet-leave-to .mui-popover.mui-popover-action {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}
组件模板代码
<script id="actionSheetTpl" type="text/html">
<transition name="mui-actionsheet">
<div class="mui-backdrop" v-show="isOpen" v-on:click="hide">
<div class="mui-popover mui-popover-action mui-popover-bottom">
<ul class="mui-table-view">
<li v-for="(item,index) in items" v-on:click="handleItemClick(item,index+1)" class="mui-table-view-cell">
<a>{{item}}</a>
</li>
</ul>
<ul class="mui-table-view">
<li v-on:click="handleItemClick('取消',0)" class="mui-table-view-cell">
<a><b>取消</b></a>
</li>
</ul>
</div>
</div>
</transition>
</script>
组件实现代码
/**
-
注册action-sheet组件
*/
Vue.component('action-sheet', {
props: ['items', 'shown'],
data: function() {
return {
isOpen: !!this.shown
}
},
watch: {
shown: function() {
this.isOpen = !!this.shown;
}
},
methods: {
show: function() {
this.isOpen = true; //显示actionsheet
},
hide: function() {
this.isOpen = false; //关闭actionsheet
},
toggle: function() {
this.isOpen = !this.isOpen; //切换显示状态
},
handleItemClick: function(item, index) {
this.$emit('onItemClick', item, index); //触发onItemClick事件
}
},
template: document.getElementById("actionSheetTpl").innerText,
});5.渲染示例(仿官网actionsheet示例) 组件使用示例
<script id="demoTpl" type="text/html">
<div>
<header class="mui-bar mui-bar-nav">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
<h1 class="mui-title">H5模式actionsheet</h1>
</header>
<nav class="mui-bar mui-bar-tab">
<a v-on:click="toggleActionSheet('delete')" class="mui-tab-item">
<span class="mui-icon mui-icon-trash"></span>
</a>
<a class="mui-tab-item" href="#">
</a>
<a class="mui-tab-item" href="#">
</a>
<a v-on:click="toggleActionSheet('forward')" class="mui-tab-item">
<span class="mui-icon mui-icon-undo"></span>
</a>
</nav>
<div class="mui-content">
<div class="mui-content-padded">
<p>actionsheet一般从底部弹出,显示一系列可选择的操作按钮,供用户选择; actionSheet可从任意位置触发, 点击本页面左下角
<span class="mui-icon mui-icon-trash"></span>会弹出删除信息确认框; 点击本页面右下角
<span class="mui-icon mui-icon-undo"></span>会弹出转发确认框; 你也可点击如下按钮,打开照片选择框:
</p>
<p>
<a v-on:click="toggleActionSheet('picture')" class="mui-btn mui-btn-primary mui-btn-block mui-btn-outlined" style="padding: 5px 20px;">打开actionsheet</a>
</p>
<p>本页面为H5模式的actionsheet演示示例,该模式具有如下优点:</p>
<ul class="des">
<li>可通过css自由定制展现样式</li>
</ul><p>另一方面,H5模式的actionsheet也具备如下缺点:</p> <ul class="des"> <li>不支持覆盖顶部状态栏</li> <li>不支持跨webview的遮罩</li> <li>在有map等原生组件时,容易被遮挡</li> </ul> <p id="info"></p> <action-sheet ref="picture" :items="pictureItems" v-on:onItemClick="handleItemClick"></action-sheet> <action-sheet ref="forward" :items="forwardItems" v-on:onItemClick="handleItemClick"></action-sheet> <action-sheet ref="delete" :items="deleteItems" v-on:onItemClick="handleItemClick"></action-sheet> </div>
</div>
</div>
</script>渲染示例代码
//渲染示例页面
var app = new Vue({
el: '#app',
data: function() {
return {
pictureItems: ['拍照或录像', '选取现有的'],
forwardItems: ['回复', '转发', '打印'],
deleteItems: ['删除']
}
},
methods: {
toggleActionSheet: function(actionsheet) {
this.$refs[actionsheet]['toggle']();
},
handleItemClick: function(item, index) {
console.log(item, index);
}
},
template: document.getElementById("demoTpl").innerText
});
完整代码,见附件
收起阅读 »
详解COOKIE和SESSION关系和区别
在技术面试中,经常被问到“说说Cookie和Session的区别”,大家都知道,Session是存储在服务器端的,Cookie是存储在客户端的,然而如果让你更详细地说明,你能说出几点?今天个推君就和大家谈谈“Cookie和Session”的那些事儿。
Cookie是什么?
从它的词语本身含义来看:
Cookie:
n. 饼干;小甜点
N-COUNT A cookie is a piece of computer software which enables a website you have visited to recognize you if you visit it again. 再次访问某一网站时,能令网站识别访问人的计算机软件。
Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息。如何识别特定的客户呢?cookie就可以做到。每次HTTP请求时,客户端都会发送相应的Cookie信息到服务端。它的过期时间可以任意设置,如果你不主动清除它,在很长一段时间里面都可以保留着,即便这之间你把电脑关机了。
既然它是存储在客户端的,换句话说通过某些手法我就可以篡改本地存储的信息来欺骗服务端的某些策略,那该怎么办呢?我们先按下不表,来看看另外一位朋友 —— Session。
Session是什么?
同样,我们先来看看释义:
Session:
普通释义:n. 会议;(法庭的)开庭;(议会等的)开会;学期;讲习会
计算机释义:会话
Session是在无状态的HTTP协议下,服务端记录用户状态时用于标识具体用户的机制。它是在服务端保存的用来跟踪用户的状态的数据结构,可以保存在文件、数据库或者集群中。在浏览器关闭后这次的Session就消失了,下次打开就不再拥有这个Session。其实并不是Session消失了,而是Session ID变了,服务器端可能还是存着你上次的Session ID及其Session 信息,只是他们是无主状态,也许一段时间后会被删除。
实际上Cookie与Session都是会话的一种方式。它们的典型使用场景比如“购物车”,当你点击下单按钮时,服务端并不清楚具体用户的具体操作,为了标识并跟踪该用户,了解购物车中有几样物品,服务端通过为该用户创建Cookie/Session来获取这些信息。
如果你的站点是多节点部署,使用Nginx做负载均衡,那么有可能会出现Session丢失的情况(比如,忽然就处于未登录状态)。这时可以使用IP负载均衡(IP绑定 ip_hash,每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决Session的问题),或者将Session信息存储在集群中。在大型的网站中,一般会有专门的Session服务器集群,用来保存用户会话,这时可以使用缓存服务比如Memcached或者Redis之类的来存放Session。
目前大多数的应用都是用 Cookie 实现Session跟踪的。第一次创建Session时,服务端会通过在HTTP协议中反馈到客户端,需要在 Cookie 中记录一个Session ID,以便今后每次请求时都可分辨你是谁。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?建议使用URL重写技术进行会话跟踪,即每次HTTP交互,URL后面都被附加上诸如 sid=xxxxx 的参数,以便服务端依此识别用户。
换个姿势~
客户端和服务端之间的通信交流,可以这样简单理解:
比如当你在个推技术分享沙龙上觉得某位讲师讲得很好,在会后问了他几个问题,他对你这些问题进行了回答,这就是一个会话。但这个讲师太受欢迎,于是工作人员收集问题,并给每个提问者一个号码牌,讲师按照号码牌依次给出相应解答并告诉相应的人。这就是Session。一段时间后,当你再次遇见这位讲师,他发现你身上有上次回复你的答案,知晓你是那个好学的程序猿。于是你欣喜若狂,哇塞,讲师居然认出我了,这就是Cookie,你的小甜点。客户端好比听课的技术爱好者,服务端就是这位讲师。
Cookie还可以在一些方便用户的场景下使用。比如你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以被写到Cookie里面,当访问网站时,网站页面的脚本可以读取这个信息,自动填写用户名,方便用户使用,给用户一点甜头。
总结语:
1、Cookie 在客户端(浏览器),Session 在服务器端。
2、Cookie的安全性一般,他人可通过分析存放在本地的Cookie并进行Cookie欺骗。在安全性第一的前提下,选择Session更优。重要交互信息比如权限等就要放在Session中,一般的信息记录放Cookie就好了。
3、单个Cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个Cookie。
4、Session 可以放在 文件、数据库或内存中,比如在使用Node时将Session保存在redis中。由于一定时间内它是保存在服务器上的,当访问增多时,会较大地占用服务器的性能。考虑到减轻服务器性能方面,应当适时使用Cookie。
5、Session 的运行依赖Session ID,而 Session ID 是存在 Cookie 中的,也就是说,如果浏览器禁用了 Cookie,Session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 Session ID)。
6、用户验证这种场合一般会用 Session。因此,维持一个会话的核心就是客户端的唯一标识,即Session ID。
题外话,那么话说Session Cookie能被篡改么?
理论上可以,只要改变了连接时的Session ID 就可以了~
在技术面试中,经常被问到“说说Cookie和Session的区别”,大家都知道,Session是存储在服务器端的,Cookie是存储在客户端的,然而如果让你更详细地说明,你能说出几点?今天个推君就和大家谈谈“Cookie和Session”的那些事儿。
Cookie是什么?
从它的词语本身含义来看:
Cookie:
n. 饼干;小甜点
N-COUNT A cookie is a piece of computer software which enables a website you have visited to recognize you if you visit it again. 再次访问某一网站时,能令网站识别访问人的计算机软件。
Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息。如何识别特定的客户呢?cookie就可以做到。每次HTTP请求时,客户端都会发送相应的Cookie信息到服务端。它的过期时间可以任意设置,如果你不主动清除它,在很长一段时间里面都可以保留着,即便这之间你把电脑关机了。
既然它是存储在客户端的,换句话说通过某些手法我就可以篡改本地存储的信息来欺骗服务端的某些策略,那该怎么办呢?我们先按下不表,来看看另外一位朋友 —— Session。
Session是什么?
同样,我们先来看看释义:
Session:
普通释义:n. 会议;(法庭的)开庭;(议会等的)开会;学期;讲习会
计算机释义:会话
Session是在无状态的HTTP协议下,服务端记录用户状态时用于标识具体用户的机制。它是在服务端保存的用来跟踪用户的状态的数据结构,可以保存在文件、数据库或者集群中。在浏览器关闭后这次的Session就消失了,下次打开就不再拥有这个Session。其实并不是Session消失了,而是Session ID变了,服务器端可能还是存着你上次的Session ID及其Session 信息,只是他们是无主状态,也许一段时间后会被删除。
实际上Cookie与Session都是会话的一种方式。它们的典型使用场景比如“购物车”,当你点击下单按钮时,服务端并不清楚具体用户的具体操作,为了标识并跟踪该用户,了解购物车中有几样物品,服务端通过为该用户创建Cookie/Session来获取这些信息。
如果你的站点是多节点部署,使用Nginx做负载均衡,那么有可能会出现Session丢失的情况(比如,忽然就处于未登录状态)。这时可以使用IP负载均衡(IP绑定 ip_hash,每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决Session的问题),或者将Session信息存储在集群中。在大型的网站中,一般会有专门的Session服务器集群,用来保存用户会话,这时可以使用缓存服务比如Memcached或者Redis之类的来存放Session。
目前大多数的应用都是用 Cookie 实现Session跟踪的。第一次创建Session时,服务端会通过在HTTP协议中反馈到客户端,需要在 Cookie 中记录一个Session ID,以便今后每次请求时都可分辨你是谁。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?建议使用URL重写技术进行会话跟踪,即每次HTTP交互,URL后面都被附加上诸如 sid=xxxxx 的参数,以便服务端依此识别用户。
换个姿势~
客户端和服务端之间的通信交流,可以这样简单理解:
比如当你在个推技术分享沙龙上觉得某位讲师讲得很好,在会后问了他几个问题,他对你这些问题进行了回答,这就是一个会话。但这个讲师太受欢迎,于是工作人员收集问题,并给每个提问者一个号码牌,讲师按照号码牌依次给出相应解答并告诉相应的人。这就是Session。一段时间后,当你再次遇见这位讲师,他发现你身上有上次回复你的答案,知晓你是那个好学的程序猿。于是你欣喜若狂,哇塞,讲师居然认出我了,这就是Cookie,你的小甜点。客户端好比听课的技术爱好者,服务端就是这位讲师。
Cookie还可以在一些方便用户的场景下使用。比如你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以被写到Cookie里面,当访问网站时,网站页面的脚本可以读取这个信息,自动填写用户名,方便用户使用,给用户一点甜头。
总结语:
1、Cookie 在客户端(浏览器),Session 在服务器端。
2、Cookie的安全性一般,他人可通过分析存放在本地的Cookie并进行Cookie欺骗。在安全性第一的前提下,选择Session更优。重要交互信息比如权限等就要放在Session中,一般的信息记录放Cookie就好了。
3、单个Cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个Cookie。
4、Session 可以放在 文件、数据库或内存中,比如在使用Node时将Session保存在redis中。由于一定时间内它是保存在服务器上的,当访问增多时,会较大地占用服务器的性能。考虑到减轻服务器性能方面,应当适时使用Cookie。
5、Session 的运行依赖Session ID,而 Session ID 是存在 Cookie 中的,也就是说,如果浏览器禁用了 Cookie,Session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 Session ID)。
6、用户验证这种场合一般会用 Session。因此,维持一个会话的核心就是客户端的唯一标识,即Session ID。
题外话,那么话说Session Cookie能被篡改么?
理论上可以,只要改变了连接时的Session ID 就可以了~
收起阅读 »
MUI+VUE2.0:开发一个accordion组件
- 开发工具:HBuilder
- 开发目标:使用vue2.0+mui.min.css开发一个与mui官方功能一致的accordion组件
- 实现功能
- accordion组件可接收一个index参数来控制显示哪个子项
- accordion组件内同时仅能展开一个子项
- accordion组件可同时使用其他非collapse的mui-table-view-cell
- accordion-item组件可单独使用,可接收一个title(子项标题)和collapse(子项是否展开)
备注:简单起见,不使用vue的单文件组件开发模式
开发步骤
- 使用HBuilder,创建一个移动App(mui项目)
- 引入mui.min.css及vue.js
-
搭建一个页面框架,代码如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title></title> <link rel="stylesheet" href="css/mui.min.css" /> </head> <body> <header class="mui-bar mui-bar-nav"> <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a> <h1 class="mui-title">折叠面板</h1> </header> <div class="mui-content"> <div class="mui-card"> </div> </div> <script type="text/javascript" src="js/vue.js"></script> </body>
</html>
4.开发accordion-item组件,代码如下
组件模板代码
<script id="itemTpl" type="text/html">
<li v-bind:class="['mui-table-view-cell','mui-collapse',{'mui-active':isOpen}]">
<a v-bind:class="['mui-navigate-right',{'mui-active':isOpen}]" v-on:click="isOpen=!isOpen">{{title}}</a>
<div class="mui-collapse-content">
<slot></slot>
</div>
</li>
</script>
组件实现代码
/**
- 注册accordion-item组件
*/
Vue.component('accordion-item', {
props: ['index', 'title', 'collapse'],//title,collapse对外开放,(index是与accordion同时使用时内部索引值)
data: function() {
return {
isOpen: !this.collapse//将props的collapse转化为data中的isOpen
}
},
watch: {//vue2.0移除了props的双向绑定,故使用watch来实现accordion-item的状态同步
collapse: function() {
this.isOpen = !this.collapse; //父组件修改了collapse
},
isOpen: function() {
this.$emit('onCollapseChange', this.index, this.isOpen); //子组件修改了collapse通知父组件
}
},
template: document.getElementById("itemTpl").innerText,
});5.开发accordion组件,代码如下
/**
-
注册accordion组件
*/
Vue.component('accordion', {
props: ['index'], //可指定一个索引值来控制哪个子项处于显示状态
data: function() {
return {
showIndex: parseInt(this.index) || 0
}
},
methods: {
onCollapseChange: function(itemIndex, isOpen) {
isOpen && (this.showIndex = itemIndex); //接收到子组件发生变化,修改当前showIndex
}
},
render: function(createElement) { //自定义渲染,主要为了实现accordion-item的双向绑定
var vnodes = this.$slots.default;
if (vnodes && vnodes.length) {
var itemIndex = 0;
for (var i = 0; i < vnodes.length; i++) {
var vnode = vnodes[i];
if (!vnode) {
continue;
}
var componentOptions = vnode.componentOptions;
if (!componentOptions) {
continue;
}
if (componentOptions.tag !== 'accordion-item') { //识别是accordion-item组件
continue;
}
componentOptions.propsData.index = itemIndex; //当前子组件索引值
componentOptions.propsData.collapse = itemIndex !== this.showIndex; //当前子组件收缩状态
if (!componentOptions.listeners) {
componentOptions.listeners = {};
}
componentOptions.listeners.onCollapseChange = this.onCollapseChange; //监听子组件收缩状态变化,实现双向绑定
itemIndex++;
}
}
return createElement('ul', {
'class': {
'mui-table-view': true,
}
}, this.$slots.default);
}
});6.使用accordion渲染示例(仿官网accordion示例) 组件使用示例
<script id="tableTpl" type="text/html">
<accordion>
<accordion-item title="表单">
<form class="mui-input-group">
<div class="mui-input-row">
<label>Input</label>
<input type="text" placeholder="普通输入框">
</div>
<div class="mui-input-row">
<label>Input</label>
<input type="text" class="mui-input-clear" placeholder="带清除按钮的输入框" data-input-clear="3"><span class="mui-icon mui-icon-clear mui-hidden"></span>
</div><div class="mui-input-row mui-plus-hidden"> <label>Input</label> <input type="text" class="mui-input-speech mui-input-clear" placeholder="语音输入" data-input-clear="4" data-input-speech="4"><span class="mui-icon mui-icon-clear mui-hidden"></span><span class="mui-icon mui-icon-speech"></span> </div> <div class="mui-button-row"> <button class="mui-btn mui-btn-primary" type="button" onclick="return false;">确认</button> <button class="mui-btn mui-btn-primary" type="button" onclick="return false;">取消</button> </div> </form>
</accordion-item>
<accordion-item title="文字排版">
<h1>h1. Heading</h1>
<h2>h2. Heading</h2>
<h3>h3. Heading</h3>
<h4>h4. Heading</h4>
<h5>h5. Heading</h5>
<h6>h6. Heading</h6>
<p>
p. 目前最接近原生App效果的框架。
</p>
</accordion-item>
</accordion>
</script>渲染示例代码
//渲染页面
var app = new Vue({
el: '.mui-card',
template: document.getElementById("tableTpl").innerText
});
完整代码,见附件
- 开发工具:HBuilder
- 开发目标:使用vue2.0+mui.min.css开发一个与mui官方功能一致的accordion组件
- 实现功能
- accordion组件可接收一个index参数来控制显示哪个子项
- accordion组件内同时仅能展开一个子项
- accordion组件可同时使用其他非collapse的mui-table-view-cell
- accordion-item组件可单独使用,可接收一个title(子项标题)和collapse(子项是否展开)
备注:简单起见,不使用vue的单文件组件开发模式
开发步骤
- 使用HBuilder,创建一个移动App(mui项目)
- 引入mui.min.css及vue.js
-
搭建一个页面框架,代码如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title></title> <link rel="stylesheet" href="css/mui.min.css" /> </head> <body> <header class="mui-bar mui-bar-nav"> <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a> <h1 class="mui-title">折叠面板</h1> </header> <div class="mui-content"> <div class="mui-card"> </div> </div> <script type="text/javascript" src="js/vue.js"></script> </body>
</html>
4.开发accordion-item组件,代码如下
组件模板代码
<script id="itemTpl" type="text/html">
<li v-bind:class="['mui-table-view-cell','mui-collapse',{'mui-active':isOpen}]">
<a v-bind:class="['mui-navigate-right',{'mui-active':isOpen}]" v-on:click="isOpen=!isOpen">{{title}}</a>
<div class="mui-collapse-content">
<slot></slot>
</div>
</li>
</script>
组件实现代码
/**
- 注册accordion-item组件
*/
Vue.component('accordion-item', {
props: ['index', 'title', 'collapse'],//title,collapse对外开放,(index是与accordion同时使用时内部索引值)
data: function() {
return {
isOpen: !this.collapse//将props的collapse转化为data中的isOpen
}
},
watch: {//vue2.0移除了props的双向绑定,故使用watch来实现accordion-item的状态同步
collapse: function() {
this.isOpen = !this.collapse; //父组件修改了collapse
},
isOpen: function() {
this.$emit('onCollapseChange', this.index, this.isOpen); //子组件修改了collapse通知父组件
}
},
template: document.getElementById("itemTpl").innerText,
});5.开发accordion组件,代码如下
/**
-
注册accordion组件
*/
Vue.component('accordion', {
props: ['index'], //可指定一个索引值来控制哪个子项处于显示状态
data: function() {
return {
showIndex: parseInt(this.index) || 0
}
},
methods: {
onCollapseChange: function(itemIndex, isOpen) {
isOpen && (this.showIndex = itemIndex); //接收到子组件发生变化,修改当前showIndex
}
},
render: function(createElement) { //自定义渲染,主要为了实现accordion-item的双向绑定
var vnodes = this.$slots.default;
if (vnodes && vnodes.length) {
var itemIndex = 0;
for (var i = 0; i < vnodes.length; i++) {
var vnode = vnodes[i];
if (!vnode) {
continue;
}
var componentOptions = vnode.componentOptions;
if (!componentOptions) {
continue;
}
if (componentOptions.tag !== 'accordion-item') { //识别是accordion-item组件
continue;
}
componentOptions.propsData.index = itemIndex; //当前子组件索引值
componentOptions.propsData.collapse = itemIndex !== this.showIndex; //当前子组件收缩状态
if (!componentOptions.listeners) {
componentOptions.listeners = {};
}
componentOptions.listeners.onCollapseChange = this.onCollapseChange; //监听子组件收缩状态变化,实现双向绑定
itemIndex++;
}
}
return createElement('ul', {
'class': {
'mui-table-view': true,
}
}, this.$slots.default);
}
});6.使用accordion渲染示例(仿官网accordion示例) 组件使用示例
<script id="tableTpl" type="text/html">
<accordion>
<accordion-item title="表单">
<form class="mui-input-group">
<div class="mui-input-row">
<label>Input</label>
<input type="text" placeholder="普通输入框">
</div>
<div class="mui-input-row">
<label>Input</label>
<input type="text" class="mui-input-clear" placeholder="带清除按钮的输入框" data-input-clear="3"><span class="mui-icon mui-icon-clear mui-hidden"></span>
</div><div class="mui-input-row mui-plus-hidden"> <label>Input</label> <input type="text" class="mui-input-speech mui-input-clear" placeholder="语音输入" data-input-clear="4" data-input-speech="4"><span class="mui-icon mui-icon-clear mui-hidden"></span><span class="mui-icon mui-icon-speech"></span> </div> <div class="mui-button-row"> <button class="mui-btn mui-btn-primary" type="button" onclick="return false;">确认</button> <button class="mui-btn mui-btn-primary" type="button" onclick="return false;">取消</button> </div> </form>
</accordion-item>
<accordion-item title="文字排版">
<h1>h1. Heading</h1>
<h2>h2. Heading</h2>
<h3>h3. Heading</h3>
<h4>h4. Heading</h4>
<h5>h5. Heading</h5>
<h6>h6. Heading</h6>
<p>
p. 目前最接近原生App效果的框架。
</p>
</accordion-item>
</accordion>
</script>渲染示例代码
//渲染页面
var app = new Vue({
el: '.mui-card',
template: document.getElementById("tableTpl").innerText
});
完整代码,见附件
收起阅读 »
Dcloud中mui 微信支付和支付宝支付接口完美实现付款代码(PHPdemo)
演示下载你可以参考这里:http://www.erdangjiade.com/php/2750.html
演示下载你可以参考这里:http://www.erdangjiade.com/php/2475.html
完整代码及教程:
最近项目里用到Dcloud、官网上给的Demo是各种坑啊,于是自己整理了mui 微信支付和支付宝支付接口完美实现付款代码(PHP支付宝demo)希望用到的同学沙走些弯路。
1.先上图片,由于mui自己集成了支付宝,所以不需要配置sdk和获取appid,微信配置有些小细节,不注意就会出错,在这里微信支付只能调用一次,详情看下去在特别注意里
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>支付</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<link rel="stylesheet" href="css/mui.min.css" />
<script type="text/javascript" src="js/mui.min.js"></script>
<style type="text/css">
.top {
margin-top: 40px;
}
.weixin {
width: 200px;
height: 50px;
margin-left: 50px;
background: url(../images/icon-weixin.png);
}
.zhifubao {
width: 200px;
height: 50px;
margin-left: 50px;
background: url(../images/alipay.jpg);
}
#jine{
-webkit-user-select:text;
text-align:right;
padding:0 1em;
border: 0px;
border-bottom:1px solid #ECB100;
border-radius: 0;
font-size:16px;
width:30%;
outline:none;
text-align:center;
}
</style>
</head>
<body>
<hrader class="mui-bar mui-bar-nav">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
<h1 class="mui-title">第三方支付</h1>
</hrader>
<div class="mui-content">
捐赠金额:<input id="jine" type="number" value="1" /> 元
<div class="top" id="testLogin" >
<input type="button" class="weixin" id="weixin1" value="微信支付" />
<input type="button" class="zhifubao" id="zhifubao" value="支付宝支付" />
</div>
</div>
<script>
var wxChannel = null; // 微信支付
var aliChannel = null; // 支付宝支付
var channel = null; //支付通道
mui.init({
swipeBack:true //启用右滑关闭功能
});
mui.plusReady(function() {
// 获取支付通道
plus.payment.getChannels(function(channels){
for (var i in channels) {
if (channels[i].id == "wxpay") {
wxChannel=channels[i];
}else{
aliChannel=channels[i];
}
}
},function(e){
alert("获取支付通道失败:"+e.message);
});
})
document.getElementById('weixin1').addEventListener('tap',function() {
console.log("微信");
pay('wxpay');
})
document.getElementById('zhifubao').addEventListener('tap',function() {
console.log("zhifubao");
pay('alipay');
})
var ALIPAYSERVER='http://demo.dcloud.net.cn/helloh5/payment/alipay.php?total=';
var WXPAYSERVER='http://demo.dcloud.net.cn/helloh5/payment/wxpay.php?total=';
// 2. 发起支付请求
function pay(id){
// 从服务器请求支付订单
var PAYSERVER='';
if(id=='alipay'){
PAYSERVER=ALIPAYSERVER;
channel = aliChannel;
}else if(id=='wxpay'){
PAYSERVER=WXPAYSERVER;
channel = wxChannel;
}else{
plus.nativeUI.alert("不支持此支付通道!",null,"捐赠");
return;
}
var xhr=new XMLHttpRequest();
var amount = document.getElementById('jine').value;
xhr.onreadystatechange=function(){
switch(xhr.readyState){
case 4:
if(xhr.status==200){
plus.payment.request(channel,xhr.responseText,function(result){
plus.nativeUI.alert("支付成功!",function(){
back();
});
},function(error){
plus.nativeUI.alert("支付失败:" + error.code);
});
}else{
alert("获取订单信息失败!");
}
break;
default:
break;
}
}
xhr.open('GET',PAYSERVER+amount);
xhr.send();
}
</script>
<script type="text/javascript" src="js/immersed.js" ></script>
</body>
</html>
3.重点看这里关于配置和质疑问题
如下图
点击manifest.json文件的“代码视图”,在permissions节点下添加Payment节点:
如下图
在plus -> distribute -> plugins 节点下添加payment节点:
如下图
4.特别注意
1.由于mui集成了支付宝插件,所以支付宝支付不需要配置就可以,
2,。注意微信weixin节点下配置微信支付相关信息
appid值为在微信开放平台申请应用的AppID值。(微信开放平台不是微信公众号平台申请的appid)
因为我在微信公众号申请的也不知到什么原因只成功调取一次,其余失败。
5.由于项目需要我会等后台完善后,在总结一份
演示下载你可以参考这里:http://www.erdangjiade.com/php/2750.html
演示下载你可以参考这里:http://www.erdangjiade.com/php/2475.html
完整代码及教程:
最近项目里用到Dcloud、官网上给的Demo是各种坑啊,于是自己整理了mui 微信支付和支付宝支付接口完美实现付款代码(PHP支付宝demo)希望用到的同学沙走些弯路。
1.先上图片,由于mui自己集成了支付宝,所以不需要配置sdk和获取appid,微信配置有些小细节,不注意就会出错,在这里微信支付只能调用一次,详情看下去在特别注意里
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>支付</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<link rel="stylesheet" href="css/mui.min.css" />
<script type="text/javascript" src="js/mui.min.js"></script>
<style type="text/css">
.top {
margin-top: 40px;
}
.weixin {
width: 200px;
height: 50px;
margin-left: 50px;
background: url(../images/icon-weixin.png);
}
.zhifubao {
width: 200px;
height: 50px;
margin-left: 50px;
background: url(../images/alipay.jpg);
}
#jine{
-webkit-user-select:text;
text-align:right;
padding:0 1em;
border: 0px;
border-bottom:1px solid #ECB100;
border-radius: 0;
font-size:16px;
width:30%;
outline:none;
text-align:center;
}
</style>
</head>
<body>
<hrader class="mui-bar mui-bar-nav">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
<h1 class="mui-title">第三方支付</h1>
</hrader>
<div class="mui-content">
捐赠金额:<input id="jine" type="number" value="1" /> 元
<div class="top" id="testLogin" >
<input type="button" class="weixin" id="weixin1" value="微信支付" />
<input type="button" class="zhifubao" id="zhifubao" value="支付宝支付" />
</div>
</div>
<script>
var wxChannel = null; // 微信支付
var aliChannel = null; // 支付宝支付
var channel = null; //支付通道
mui.init({
swipeBack:true //启用右滑关闭功能
});
mui.plusReady(function() {
// 获取支付通道
plus.payment.getChannels(function(channels){
for (var i in channels) {
if (channels[i].id == "wxpay") {
wxChannel=channels[i];
}else{
aliChannel=channels[i];
}
}
},function(e){
alert("获取支付通道失败:"+e.message);
});
})
document.getElementById('weixin1').addEventListener('tap',function() {
console.log("微信");
pay('wxpay');
})
document.getElementById('zhifubao').addEventListener('tap',function() {
console.log("zhifubao");
pay('alipay');
})
var ALIPAYSERVER='http://demo.dcloud.net.cn/helloh5/payment/alipay.php?total=';
var WXPAYSERVER='http://demo.dcloud.net.cn/helloh5/payment/wxpay.php?total=';
// 2. 发起支付请求
function pay(id){
// 从服务器请求支付订单
var PAYSERVER='';
if(id=='alipay'){
PAYSERVER=ALIPAYSERVER;
channel = aliChannel;
}else if(id=='wxpay'){
PAYSERVER=WXPAYSERVER;
channel = wxChannel;
}else{
plus.nativeUI.alert("不支持此支付通道!",null,"捐赠");
return;
}
var xhr=new XMLHttpRequest();
var amount = document.getElementById('jine').value;
xhr.onreadystatechange=function(){
switch(xhr.readyState){
case 4:
if(xhr.status==200){
plus.payment.request(channel,xhr.responseText,function(result){
plus.nativeUI.alert("支付成功!",function(){
back();
});
},function(error){
plus.nativeUI.alert("支付失败:" + error.code);
});
}else{
alert("获取订单信息失败!");
}
break;
default:
break;
}
}
xhr.open('GET',PAYSERVER+amount);
xhr.send();
}
</script>
<script type="text/javascript" src="js/immersed.js" ></script>
</body>
</html>
3.重点看这里关于配置和质疑问题
如下图
点击manifest.json文件的“代码视图”,在permissions节点下添加Payment节点:
如下图
在plus -> distribute -> plugins 节点下添加payment节点:
如下图
4.特别注意
1.由于mui集成了支付宝插件,所以支付宝支付不需要配置就可以,
2,。注意微信weixin节点下配置微信支付相关信息
appid值为在微信开放平台申请应用的AppID值。(微信开放平台不是微信公众号平台申请的appid)
因为我在微信公众号申请的也不知到什么原因只成功调取一次,其余失败。
5.由于项目需要我会等后台完善后,在总结一份
收起阅读 »
android系统设置角标建议增加主流系统支持,方法参考开源工程ShortcutBadger
plus.runtime.setBadgeNumber( number );
设置程序快捷方式上显示的提示数字
平台支持:
Android - 2.2 (支持): 目前仅支持小米(MIUI v5),其它设备调用后无任何效果。
iOS - 4.3 (支持): 应用需开启“Push Notifications”服务才生效
最近再用贵公司的dcloud,确实很强大。 就是设置角标只支持小米系统有点郁闷。建议增加主流andoid系统的支持。
该android开源项目有各个 android系统角标设置的方法
https://github.com/leolin310148/ShortcutBadger
.
plus.runtime.setBadgeNumber( number );
设置程序快捷方式上显示的提示数字
平台支持:
Android - 2.2 (支持): 目前仅支持小米(MIUI v5),其它设备调用后无任何效果。
iOS - 4.3 (支持): 应用需开启“Push Notifications”服务才生效
最近再用贵公司的dcloud,确实很强大。 就是设置角标只支持小米系统有点郁闷。建议增加主流andoid系统的支持。
该android开源项目有各个 android系统角标设置的方法
https://github.com/leolin310148/ShortcutBadger
.