如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
选择器
学习目的 熟练使用 css 选择器
css1 中的选择器
E #myid id选择器
E .warning 类选择器
E F 包含选择器
E:link 定义超链接未被访问
E:visited 定义超链接已经被访问
E:actice 匹配被激活的元素
E:hover 鼠标经过的元素
E:focus 获取焦点
E::first-line 元素第一行
E::first-letter 元素第一个字符
css2
* 通配选择文档中所有元素
E[foo] 包含foo属性的元素
E[foo="bar"] 包含属性foo值为bar的元素
CSS3 中的选择器可替代 了解即可
E[foo~="bar"] 含有属性foo值包括bar的元素例如 <a foo="bar bar1 bar2">link</a>
E[foo|="en"] 属性值是一个“-”分割的 比如 en-us
E:first-child 父元素的第一个子元素
E:lang(fr) 匹配属性,元素显示内容为语言为 fr
E::before 在元素前面插入内容
E::after 在元素后面插入内容
E>F 子包含
E+E 相邻兄弟选择器 后面的兄弟
css3
E[foo^="bar"] 属性foo的值开头是bar
E[foo$="bar"] 属性foo的值得结尾是bar
E[foo*="bar"] 属性foo的值包含bar <a foo="abc_bar_as">link</a>
结构类选择器
E:root 属性文档所在的根元素
E:nth-child(n) E元素第n个位置的子元素 n可以是 (1,2,3) 关键字(odd,even) 公式(2n,2n+3) 起始值为1
E:nth-last-child(n) 与上面的使用方法一样 倒数的第N个位置的子元素
E:nth-of-type(n) 匹配父元素中与E相同的元素中的第n个元素
E:nth-last-of-type(n) 匹配父元素中与E相同的倒数第n个元素
E:last-child 选择位于其父元素的最后一个位置,且匹配E的子元素
E:first-of-type 选择在其父元素中匹配E的第一个同类型的子元素
E:last-of-type 选择在其父元素中匹配E的最后一个同类型的子元素
E:only-child 选择其父元素只包含一个子元素,且该子元素匹配E
E:only-of-type 选择其父元素只包含一个同类型的子元素,且该子元素匹配E
E:empty 选择匹配E的元素,且该元素不包含子节点,文本也是节点
E~F 通用兄弟选择器
E:not(s) 否定伪类选择器
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。
如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
前言
对比网上的例子
<a href="#" id="username" data-type="text" data-pk="1">awesome</a> <script> $(function(){ $('#username').editable({
url: '/post',
title: 'Enter username' });
}); </script>
$('#db_dependences').bootstrapTable({
method:'POST',
dataType:'json',
contentType: "application/x-www-form-urlencoded",
cache: false,
striped: true, //是否显示行间隔色 sidePagination: "client", //分页方式:client客户端分页,server服务端分页(*) showColumns:true,
pagination:true,
minimumCountColumns:2,
pageNumber:1, //初始化加载第一页,默认第一页 pageSize: 10, //每页的记录行数(*) pageList: [10, 15, 20, 25], //可供选择的每页的行数(*) uniqueId: "id", //每一行的唯一标识,一般为主键列 showExport: true,
exportDataType: 'all',
exportTypes:[ 'csv', 'txt', 'sql', 'doc', 'excel', 'xlsx', 'pdf'], //导出文件类型 onEditableSave: function (field, row, oldValue, $el) { $.ajax({
success: function (data, status) { if (status == "success") {
alert("编辑成功");
}
},
error: function () { alert("Error");
},
complete: function () { }
});
},
data: [{
id: 1,
name: '张三',
sex: '男',
time: '2017-08-09' }, {
id: 2,
name: '王五',
sex: '女',
time: '2017-08-09' }, {
id: 3,
name: '李四',
sex: '男',
time: '2017-08-09' }, {
id: 4,
name: '杨朝来',
sex: '男',
time: '2017-08-09' }, {
id: 5,
name: '蒋平',
sex: '男',
time: '2017-08-09' }, {
id: 6,
name: '唐灿华',
sex: '男',
time: '2017-08-09' }],
columns: [{
field: 'id',
title: '序号' }, {
field: 'name',
title: '姓名',
editable: {
type: 'text',
validate: function (value) { if ($.trim(value) == '') { return '姓名不能为空!';
}
}
}
}, {
field: 'sex',
title: '性别',
editable: {
type: 'select',
pk: 1,
source: [
{value: 1, text: '男'},
{value: 2, text: '女'},
]
}
}, {
field: 'time',
title: '时间',
editable: {
type: 'date',
format: 'yyyy-mm-dd',
viewformat: 'yyyy-mm-dd',
datepicker: {
weekStart: 1 }
}
}]
});
结果图如下:
由于开源,很快就找到原因,由于formatter我们没有写这个function导致调用的默认的formatter,默认的没有把表格的值传入html中,bootstrap-table-editable.js源码如下,初始定义_dont_edit_formatter为false,我们没有实现noeditFormatter的function就会执行第二个if语句,其中的标签中没有对内容赋值,导致最后显示结果为它默认的Empty:
column.formatter = function(value, row, index) { var result = column._formatter ? column._formatter(value, row, index) : value;
$.each(column, processDataOptions);
$.each(editableOptions, function(key, value) {
editableDataMarkup.push(' ' + key + '="' + value + '"');
}); var _dont_edit_formatter = false; if (column.editable.hasOwnProperty('noeditFormatter')) {
_dont_edit_formatter = column.editable.noeditFormatter(value, row, index);
} if (_dont_edit_formatter === false) { return ['<a href="javascript:void(0)"', ' data-name="' + column.field + '"', ' data-pk="' + row[that.options.idField] + '"', ' data-value="' + result + '"',
editableDataMarkup.join(''), '>' + '</a>' ].join('');
} else { return _dont_edit_formatter;
}
};
由于要实现多样式,则把上面的代码改变,并在使用的时候实现noeditFormatter:function(value){…}就是了。将上面的代码改为如下(此为我自己改的,你可以根据自己的需要做修改):
column.formatter = function(value, row, index) { var result = column._formatter ? column._formatter(value, row, index) : value;
$.each(column, processDataOptions);
$.each(editableOptions, function(key, value) {
editableDataMarkup.push(' ' + key + '="' + value + '"');
}); var _dont_edit_formatter = false; if (column.editable.hasOwnProperty('noeditFormatter')) { var process = column.editable.noeditFormatter(value, row, index); if(!process.hasOwnProperty('class')){
process.class = '';
} if(!process.hasOwnProperty('style')){
process.style = '';
}
_dont_edit_formatter = ['<a href="javascript:void(0)"', ' data-name="'+process.filed+'"', ' data-pk="1"', ' data-value="' + process.value + '"', ' class="'+process.class+'" style="'+process.style+'"', '>' + process.value + '</a>' ].join('');
} if (_dont_edit_formatter === false) { return ['<a href="javascript:void(0)"', ' data-name="' + column.field + '"', ' data-pk="' + row[that.options.idField] + '"', ' data-value="' + result + '"',
editableDataMarkup.join(''), '>' + value + '</a>' ].join('');
} else { return _dont_edit_formatter;
}
};
结果如下:
然后是bootstrap table的使用js文件,在其中实现noeditFormatter函数。返回的result必须包含filed和value,class和style可以不需要,class可以额外用其它插件之类,比如badge,style是增加样式(背景,颜色,字体等)。
$('#db_dependences').bootstrapTable({
method:'POST',
dataType:'json',
contentType: "application/x-www-form-urlencoded",
cache: false,
striped: true, //是否显示行间隔色 sidePagination: "client", //分页方式:client客户端分页,server服务端分页(*) showColumns:true,
pagination:true,
minimumCountColumns:2,
pageNumber:1, //初始化加载第一页,默认第一页 pageSize: 10, //每页的记录行数(*) pageList: [10, 15, 20, 25], //可供选择的每页的行数(*) uniqueId: "id", //每一行的唯一标识,一般为主键列 showExport: true,
exportDataType: 'all',
exportTypes:[ 'csv', 'txt', 'sql', 'doc', 'excel', 'xlsx', 'pdf'], //导出文件类型 onEditableSave: function (field, row, oldValue, $el) { $.ajax({
success: function (data, status) { if (status == "success") {
alert("编辑成功");
}
},
error: function () { alert("Error");
},
complete: function () { }
});
}, // onEditableHidden: function(field, row, $el, reason) { // 当编辑状态被隐藏时触发 // if(reason === 'save') { // var $td = $el.closest('tr').children(); // // $td.eq(-1).html((row.price*row.number).toFixed(2)); // // $el.closest('tr').next().find('.editable').editable('show'); //编辑状态向下一行移动 // } else if(reason === 'nochange') { // $el.closest('tr').next().find('.editable').editable('show'); // } // }, data: [{
id: 1,
name: '张三',
sex: '男',
time: '2017-08-09' }, {
id: 2,
name: '王五',
sex: '女',
time: '2017-08-09' }, {
id: 3,
name: '李四',
sex: '男',
time: '2017-08-09' }, {
id: 4,
name: '杨朝来',
sex: '男',
time: '2017-08-09' }, {
id: 5,
name: '蒋平',
sex: '男',
time: '2017-08-09' }, {
id: 6,
name: '唐灿华',
sex: '男',
time: '2017-08-09' }, {
id: 7,
name: '马达',
sex: '男',
time: '2017-08-09' }, {
id: 8,
name: '赵小雪',
sex: '女',
time: '2017-08-09' }, {
id: 9,
name: '薛文泉',
sex: '男',
time: '2017-08-09' }, {
id: 10,
name: '丁建',
sex: '男',
time: '2017-08-09' }, {
id: 11,
name: '王丽',
sex: '女',
time: '2017-08-09' }],
columns: [{
field: 'id',
title: '序号' }, {
field: 'name',
title: '姓名',
editable: {
type: 'text',
validate: function (value) { if ($.trim(value) == '') { return '姓名不能为空!';
}
}
}
}, {
field: 'sex',
title: '性别',
editable: {
type: 'select',
pk: 1,
source: [
{value: 1, text: '男'},
{value: 2, text: '女'},
],
noeditFormatter: function (value,row,index) { var result={filed:"sex",value:value,class:"badge",style:"background:#333;padding:5px 10px;"}; return result;
}
}
}, {
field: 'time',
title: '时间',
editable: {
type: 'date',
format: 'yyyy-mm-dd',
viewformat: 'yyyy-mm-dd',
datepicker: {
weekStart: 1 },
noeditFormatter: function (value,row,index) { var result={filed:"time",value:value,class:"badge",style:"background:#333;padding:5px 10px;"}; return result;
}
}
}]
});
关于bootstrap table的导出及使用可以看我另外一篇博客。
下载和引用
下载x-editable,并如下引用。
<link href="js/bootstrap_above/x-editable-develop/dist/bootstrap-editable/css/bootstrap-editable.css" rel="stylesheet"> <script src="js/bootstrap_above/x-editable-develop/dist/bootstrap-editable/js/bootstrap-editable.js"></script> <script src="js/bootstrap_above/bootstrap-table-develop/dist/extensions/editable/bootstrap-table-editable.js"></script>
然后讲上诉的一些文件修改添加,就完成了。
另外项目的结果展示
其中的样式都是自行在x-editable的基础上添加的。如配置出问题,以下是源码链接。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。
如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>表单标签及属性介绍</title>
</head>
<body>
<!--form:表单标签,在html页面创建一个表单(浏览器上不显示),若要提交数据到服务器则负责收集数据的标签要放到form内 ;-->
<!--action:(确定表单提交的路径);-->
<!--method提交方式:get(默认值)有内容 ,post没有-->
<form action="#" method="get">
<!--input:输入域标签,获取用户输入信息;-->
<!--type值不同收集方式不同:hidden(隐藏字段,数据会发送到服务器但浏览器不显示),text(文本框),password(密码框),radio(单选框),checkbox(复选框),
file(文件上传组件),submit(提交按钮),button(普通按钮),reset(重置按钮);-->
<!--name:元素名(表单数据需提交到服务器必提供name属性值),服务器通过属性值获取提交数据;-->
<!--readonly:只读-->
<!--value:设置input默认值。submit和reset为按键上显示数据-->
<!--size:大小-->
<!--maxlength:允许输入的最大长度-->
隐藏字段:<input type="hidden" name="id" value=""/><br/>
用户名:<input type="text" name="username" readonly="readonly" value="zhangsan" size="40px" maxlength="20"/><br/>
密码:<input type="password" name="password"/><br/>
确认密码:<input type="password" name="repassword"/><br/>
性别:<input type="radio" name="sex" value="man"/>男
<input type="radio" name="sex" value="woman"/>女<br/>
爱好:<input type="checkbox" name="hobby" value="钓鱼"/>钓鱼
<input type="checkbox" name="hobby" value="打电动"/>打电动
<!--checked:单选或复选框默认勾选-->
<input type="checkbox" name="hobby" value="画画" checked="checked"/>画画<br/>
头像:<input type="file" /><br/>
<!--select:下拉列表标签-->
籍贯:<select name="province">
<!--option:子标签,下拉列表中的一个选项-->
<option>---请选择---</option>
<!--value:发送得服务器的选项值-->
<option value="北京">北京</option>
<option value="上海">上海</option>
<!--selected:勾选当前列表项-->
<option value="广州" selected="selected">广州</option>
</select><br/>
自我介绍:
<!--textarea:文本域-->
<textarea>
</textarea><br/>
提交按钮:<input type="submit" value="注册"/><br/>
普通按钮:<input type="button" value="普通按钮"><br/>
重置按钮:<input type="reset"/>
</form>
</body>
</html>
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。
如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
MVP架构在Android这一块已经盛行依旧,对于一些学习能力比较强的人来说,已经能够运用自如甚至改造优化了,对于吾等菜鸟,却是如此的陌生,今日这篇博客,算是小弟在学习和应用上的一点总结罢了,如有不足,还请各位大神不吝指教。
MVP架构是什么就不多说了,博主主要很大家分享的是,如何设计MVP架构。
先来分析一下MVP如何使用:M-V-P三层之间,P作为中间层,负责M,V之间的数据交互的中介,将数据从M层获取,处理之后提交到V层,换句话说,V需要持有P的实例,P层需要持有V的实例。原理很简单,使用泛型对数据进行封装处理:
2.定义一个P层的接口:
3.封装P基类:绑定解绑V实例
4.M层封装:
之后,将数据提交到activity或者fragment就行了。
收工,MVP基础框架搭建完成了。没错,就是基础框架,但是能不能用呢,让我们拭目以待吧。
然后是Presneter:
最后来完成Activity的逻辑:
1.定义一个V层的空接口,主要是方便封装:
/**
* V层接口
*/ public interface IView { }
/**
* 抽象为接口
*
*/ public interface IPresenter<V extends IView> { /**
* 绑定视图
*
* @param view
*/ void attachView(V view); /**
* 解除绑定(每个V记得使用完之后解绑,主要是用于防止内存泄漏问题)
*/ void dettachView();
}
/**
* 抽象类 统一管理View层绑定和解除绑定
*
* @param <V>
*/ public class BasePresenter<V extends IView, M extends IModel> implements IPresenter<V> { private WeakReference<V> weakView; protected M model;
public V getView() { return proxyView;
} /**
* 用于检查View是否为空对象
*
* @return */ public boolean isAttachView() { return this.weakView != null && this.weakView.get() != null;
} @Override public void attachView(V view) { this.weakView = new WeakReference<V>(view);
} @Override public void dettachView() { if (this.weakView != null) { this.weakView.clear(); this.weakView = null;
}
}
}
/**
* M层
*/ public interface IModel { } /**
* 登录model
* Created by admin on 2018/2/5.
*/ public interface ILoginModel extends IModel { void login();
} /**
* 登录
* Created by admin on 2018/2/5.
*/ public class LoginModel implements ILoginModel { @Override public void login() { // TODO: 2018/2/5 发起登录请求 }
}
最基本的铺垫已经做好了,接下来就该封装View了:
/**
* Created by admin on 2018/2/5.
*/ public abstract class MvpActivity<V extends IView, P extends BasePresenter<V>> extends AppCompatActivity implements IView { private P presenter;
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState);
...
presenter=getPresenter();
presenter.attachView(this);
} protected P getPresenter() { return presenter;
} protected void setPresenter(P presenter) { this.presenter = presenter;
} protected V getView() { return (V) this;
}
...
@Override protected void onDestroy() {
presenter.dettachView();
... super.onDestroy();
}
}
先来写一个View:
public interface ILoginView extends IView { void onLoginSuccess(); void onFailed();
}
/**
* Created by admin on 2018/2/5.
*/ public class LoginPresenter extends BasePresenter<ILogin, LoginModel> { public LoginPresenter() {
model = new LoginModel();
}
public void login(){
model.login(new LoginCallBack() { @Override public void onSuccess() { if(null!=(ILogin)getView()){
weakView.onLoginSuccess();
}
} @Override public void onFailure() { if(null!=(ILogin)getView()){
weakView.onFailure();
}
}
});
}
}
public class LoginActivity extends MvpActivity<ILoginView, LoginPresenter> implements ILoginView { ...
@Override public LoginPresenter getPresenter() { return new LoginPresenter();
} public void login(View view) {
String name = etUserName.getText().toString();
String pwd = etUserPwd.getText().toString();
getPresenter().login(name, pwd);
}
@Override public void onLoginSuccess() {
}
@Override public void onFailed(){
...
}
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
1、简介
retrofit是一个封装okhttp请求的网络请求库,可以通过Rxjava适配返回信息。
我们通过Retrofit.Builder建造者模式创建一个Retrofit实例对象
public static final class Builder {
/**
*Android线程切换的类
*/
private final Platform platform;
private @Nullable okhttp3.Call.Factory callFactory;
private HttpUrl baseUrl;
private final List<Converter.Factory> converterFactories = new ArrayList<>();
private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
private @Nullable Executor callbackExecutor;
private boolean validateEagerly;
Builder(Platform platform) {
this.platform = platform;
}
public Builder() {
this(Platform.get());
}
Builder(Retrofit retrofit) {
platform = Platform.get();
callFactory = retrofit.callFactory;
baseUrl = retrofit.baseUrl;
converterFactories.addAll(retrofit.converterFactories);
// Remove the default BuiltInConverters instance added by build().
converterFactories.remove(0);
callAdapterFactories.addAll(retrofit.callAdapterFactories);
// Remove the default, platform-aware call adapter added by build().
callAdapterFactories.remove(callAdapterFactories.size() - 1);
callbackExecutor = retrofit.callbackExecutor;
validateEagerly = retrofit.validateEagerly;
}
public Builder client(OkHttpClient client) {
return callFactory(checkNotNull(client, "client == null"));
}
public Builder callFactory(okhttp3.Call.Factory factory) {
this.callFactory = checkNotNull(factory, "factory == null");
return this;
}
public Builder baseUrl(String baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
HttpUrl httpUrl = HttpUrl.parse(baseUrl);
if (httpUrl == null) {
throw new IllegalArgumentException("Illegal URL: " + baseUrl);
}
return baseUrl(httpUrl);
}
public Builder baseUrl(HttpUrl baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
List<String> pathSegments = baseUrl.pathSegments();
if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
}
this.baseUrl = baseUrl;
return this;
}
public Builder addConverterFactory(Converter.Factory factory) {
converterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
callAdapterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
public Builder callbackExecutor(Executor executor) {
this.callbackExecutor = checkNotNull(executor, "executor == null");
return this;
}
public List<CallAdapter.Factory> callAdapterFactories() {
return this.callAdapterFactories;
}
public List<Converter.Factory> converterFactories() {
return this.converterFactories;
}
public Builder validateEagerly(boolean validateEagerly) {
this.validateEagerly = validateEagerly;
return this;
}
public Retrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();
}
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
// Make a defensive copy of the converters.
List<Converter.Factory> converterFactories =
new ArrayList<>(1 + this.converterFactories.size());
// Add the built-in converter factory first. This prevents overriding its behavior but also
// ensures correct behavior when using converters that consume all types.
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}
}
通过Retrofit.Builder中build方法创建一个Retrofit实例对象,在创建Retrofit时会判断用户创建OkhttpClient对象,没有创建Retrofit会创建一个默认okhttpClient对象,然后设置Platform中的主线程线程池,设置线程池处理器交给主线程Looper对象。然后创建一个Retrofit对象。我们通过Retrofit.create创建一个接口代理类
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall);
}
});
}
在调用Creater方法时,通过代理类创建Service实例对象,当我们通过接口实例对象调用方法时,通过invoke方法时,通过Method创建一个ServiceMethod对象,然后把ServiceMethod存储起来
public ServiceMethod build() {
callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
if (responseType == Response.class || responseType == okhttp3.Response.class) {
throw methodError("'"
+ Utils.getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?");
}
responseConverter = createResponseConverter();
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
if (httpMethod == null) {
throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}
if (!hasBody) {
if (isMultipart) {
throw methodError(
"Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
}
if (isFormEncoded) {
throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
+ "request body (e.g., @POST).");
}
}
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
Type parameterType = parameterTypes[p];
if (Utils.hasUnresolvableType(parameterType)) {
throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
parameterType);
}
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
if (parameterAnnotations == null) {
throw parameterError(p, "No Retrofit annotation found.");
}
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
if (relativeUrl == null && !gotUrl) {
throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
}
if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
throw methodError("Non-body HTTP method cannot contain @Body.");
}
if (isFormEncoded && !gotField) {
throw methodError("Form-encoded method must contain at least one @Field.");
}
if (isMultipart && !gotPart) {
throw methodError("Multipart method must contain at least one @Part.");
}
return new ServiceMethod<>(this);
}
private CallAdapter<T, R> createCallAdapter() {
/**
*获取方法返回值类型
*/
Type returnType = method.getGenericReturnType();
if (Utils.hasUnresolvableType(returnType)) {
throw methodError(
"Method return type must not include a type variable or wildcard: %s", returnType);
}
if (returnType == void.class) {
throw methodError("Service methods cannot return void.");
}
//获取注解信息
Annotation[] annotations = method.getAnnotations();
try {
//noinspection unchecked
return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(e, "Unable to create call adapter for %s", returnType);
}
}
在创建ServiceMethod时,获取我们okhttp请求是否有返回值,没有返回值抛出异常,然后获取注解信息,然后获取retrofit中CallAdapter.Factory,然后调用get方法,我们在通过rxjavaFactoryAdapter.create创建的就是实现CallAdapter.Factory对象,然后调用CallAdapter.Factory中respenseType方法,然后通过我们传递converter对数据进行序列化,可以通过gson和fastjson进行实例化对象,然后通过parseMethodAnnomation解析请求类型
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
if (this.httpMethod != null) {
throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
this.httpMethod, httpMethod);
}
this.httpMethod = httpMethod;
this.hasBody = hasBody;
if (value.isEmpty()) {
return;
}
// Get the relative URL path and existing query string, if present.
int question = value.indexOf('?');
if (question != -1 && question < value.length() - 1) {
// Ensure the query string does not have any named parameters.
String queryParams = value.substring(question + 1);
Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
if (queryParamMatcher.find()) {
throw methodError("URL query string \"%s\" must not have replace block. "
+ "For dynamic query parameters use @Query.", queryParams);
}
}
this.relativeUrl = value;
this.relativeUrlParamNames = parsePathParameters(value);
}
通过注解类型获取到请求类型时,通过调用相关方法解析获取到请求url,然后通过注解获取方法中是否有注解字段,有注解信息存储到Set集合中。然后创建一个OkhttpCall对象,通过调用serviceMethod.adapt方法做网络请求,serviceMethod.adapt调用是callAdapter中的adapt方法,如果用户没有设置callAdapter模式使用的是ExecutorCallAdapterFactory中的adapt方法
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call.class) {
return null;
} else {
final Type responseType = Utils.getCallResponseType(returnType);
return new CallAdapter<Object, Call<?>>() {
public Type responseType() {
return responseType;
}
public Call<Object> adapt(Call<Object> call) {
return new ExecutorCallAdapterFactory.ExecutorCallbackCall(ExecutorCallAdapterFactory.this.callbackExecutor, call);
}
};
}
}
在ExectorCallAdapterFactory中调用组装的Call方法中enqueue方法调用异步网络请求,成功后通过Platform中MainThreadExecutor切换到主线程。在调用callback中的enqueue,onResponse和onFairlure方法时实际是调用到OkhttpCall方法的onResponse方法,在OkHttpCall.enqueue中重新组建OkHttp.Call url和参数信息,然后封装请求,请求成功后通过parseResponse解析返回信息状态,然后把返回信息状态成ResponseBody对象,调用ServiceMethod.toResponse解析,在toResponse中实际是我们设置ConverterFactory对象解析数据,完成后调用callBack中onSuccess方法。
@Override public void enqueue(final Callback<T> callback) {
checkNotNull(callback, "callback == null");
okhttp3.Call call;
Throwable failure;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
call = rawCall;
failure = creationFailure;
if (call == null && failure == null) {
try {
call = rawCall = createRawCall();
} catch (Throwable t) {
throwIfFatal(t);
failure = creationFailure = t;
}
}
}
if (failure != null) {
callback.onFailure(this, failure);
return;
}
if (canceled) {
call.cancel();
}
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
response = parseResponse(rawResponse);
} catch (Throwable e) {
callFailure(e);
return;
}
try {
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
t.printStackTrace();
}
}
@Override public void onFailure(okhttp3.Call call, IOException e) {
callFailure(e);
}
private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
t.printStackTrace();
}
}
});
}
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
图片三个网站的图片搜索结果进行爬取和下载。
首先通过爬虫过程中遇到的问题,总结如下:
1、一次页面加载的图片数量各个网站是不定的,每翻一页就会刷新一次,对于数据量大的爬虫几乎都需要用到翻页功能,有如下两种方式:
1)通过网站上的网址进行刷新,例如必应图片:
url = 'http://cn.bing.com/images/async?q={0}&first={1}&count=35&relp=35&lostate=r
&mmasync=1&dgState=x*175_y*848_h*199_c*1_i*106_r*0'
2)通过selenium来实现模拟鼠标操作来进行翻页,这一点会在Google图片爬取的时候进行讲解。
2、每个网站应用的图片加载技术都不一样,对于静态加载的网站爬取图片非常容易,因为每张图片的url都直接显示在网页源码中,找到每张图片对应的url即可使用urlretrieve()进行下载。然而对于动态加载的网站就比较复杂,需要具体问题具体分析,例如google图片每次就会加载35张图片(只能得到35张图片的url),当滚动一次后网页并不刷新但是会再次加载一批图片,与前面加载完成的都一起显示在网页源码中。对于动态加载的网站我推荐使用selenium库来爬取。
对于爬取图片的流程基本如下(对于可以通过网址实现翻页或者无需翻页的网站):
1. 找到你需要爬取图片的网站。(以必应为例)
2. 使用google元素检查(其他的没用过不做介绍)来查看网页源码。
3. 使用左上角的元素检查来找到对应图片的代码。
4. 通过观察找到翻页的规律(有些网站的动态加载是完全看不出来的,这种方法不推荐)
从图中可以看到标签div,class=’dgControl hover’中的data-nexturl的内容随着我们滚动页面翻页first会一直改变,q=二进制码即我们关键字的二进制表示形式。加上前缀之后由此我们才得到了我们要用的url。
5. 我们将网页的源码放进BeautifulSoup中,代码如下:
url = 'http://cn.bing.com/images/async?q={0}&first={1}&count=35&relp=35&lostate=r&mmasync=1&dgState=x*175_y*848_h*199_c*1_i*106_r*0' agent = {'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.165063 Safari/537.36 AppEngine-Google."}
page1 = urllib.request.Request(url.format(InputData, i*35+1), headers=agent)
page = urllib.request.urlopen(page1)
soup = BeautifulSoup(page.read(), 'html.parser')
我们得到的soup是一个class ‘bs4.BeautifulSoup’对象,可以直接对其进行操作,具体内容自行查找。
首先选取我们需要的url所在的class,如下图:
波浪线是我们需要的url。
我们由下面的代码得到我们需要的url:
if not os.path.exists("./" + word):#创建文件夹 os.mkdir('./' + word) for StepOne in soup.select('.mimg'):
link=StepOne.attrs['src']#将得到的<class 'bs4.element.Tag'>转化为字典形式并取src对应的value。 count = len(os.listdir('./' + word)) + 1 SaveImage(link,word,count)#调用函数保存得到的图片。
最后调用urlretrieve()函数下载我们得到的图片url,代码如下:
try:
time.sleep(0.2)
urllib.request.urlretrieve(link,'./'+InputData+'/'+str(count)+'.jpg') except urllib.error.HTTPError as urllib_err:
print(urllib_err) except Exception as err:
time.sleep(1)
print(err)
print("产生未知错误,放弃保存") else:
print("图+1,已有" + str(count) + "张图")
这里需要强调是像前面的打开网址和现在的下载图片都需要使用try except进行错误测试,否则出错时程序很容易崩溃,大大浪费了数据采集的时间。
以上就是对单个页面进行数据采集的流程,紧接着改变url中{1}进行翻页操作继续采集下一页。
数据采集结果如下:
有问题请留言。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
JS页面代码段:
-
const app = getApp()
-
let goodsList = [
-
{ actEndTime: '2018-07-21 21:00:34' },
-
{ actEndTime: '2028-07-17 21:00:37' },
-
{ actEndTime: '2018-09-21 05:00:59' },
-
{ actEndTime: '2018-08-19 07:00:48' },
-
{ actEndTime: '2018-08-28 03:00:11' }
-
]
-
Page({
-
-
data: {
-
countDownList: [],
-
actEndTimeList: []
-
},
-
-
onLoad: function () {
-
let endTimeList = [];
-
// 将活动的结束时间参数提成一个单独的数组,方便操作
-
goodsList.forEach(o => { endTimeList.push(o.actEndTime) })
-
this.setData({ actEndTimeList: endTimeList });
-
// 执行倒计时函数
-
this.countDown();
-
},
-
-
//当时间小于两位数时十位数补零。
-
timeFormat: function (param) {//小于10的格式化函数
-
return param < 10 ? '0' + param : param;
-
},
-
-
//倒计时函数
-
countDown: function () {
-
// 获取当前时间,同时得到活动结束时间数组
-
let newTime = new Date().getTime();//当前时间
-
let endTimeList = this.data.actEndTimeList;//结束时间的数组集合
-
let countDownArr = [];//初始化倒计时数组
-
-
// 对结束时间进行处理渲染到页面
-
endTimeList.forEach(o => {
-
let endTime = new Date(o).getTime();
-
let obj = null;
-
// 如果活动未结束,对时间进行处理
-
if (endTime - newTime > 0) {
-
let time = (endTime - newTime) / 1000;
-
// 获取天、时、分、秒
-
let day = parseInt(time / (60 * 60 * 24));
-
let hou = parseInt(time % (60 * 60 * 24) / 3600);
-
let min = parseInt(time % (60 * 60 * 24) % 3600 / 60);
-
let sec = parseInt(time % (60 * 60 * 24) % 3600 % 60);
-
obj = {
-
day: this.timeFormat(day),
-
hou: this.timeFormat(hou),
-
min: this.timeFormat(min),
-
sec: this.timeFormat(sec)
-
}
-
} else {//活动已结束,全部设置为'00'
-
obj = {
-
day: '00',
-
hou: '00',
-
min: '00',
-
sec: '00'
-
}
-
}
-
countDownArr.push(obj);
-
})
-
//每隔一秒执行一次倒计时函数, 渲染
-
this.setData({ countDownList: countDownArr })
-
setTimeout(this.countDown, 1000);
-
}
-
})
wxml页面代码段
-
<view class='tui-countdown-content' wx:for="{{countDownList}}" wx:key="countDownList">
-
距结束
-
<text class='tui-conutdown-box'>{{item.day}}</text>天
-
<text class='tui-conutdown-box'>{{item.hou}}</text>时
-
<text class='tui-conutdown-box'>{{item.min}}</text>分
-
<text class='tui-conutdown-box tui-countdown-bg'>{{item.sec}}</text>秒
-
</view>
-
wxss页面代码段
-
page{
-
background: #f5f5f5;
-
}
-
.tui-countdown-content{
-
height: 50px;
-
line-height: 50px;
-
text-align: center;
-
background-color: #fff;
-
margin-top: 15px;
-
padding: 0 15px;
-
font-size: 18px;
-
}
-
.tui-conutdown-box{
-
display: inline-block;
-
height: 26px;
-
width: 26px;
-
line-height: 26px;
-
text-align: center;
-
background:#ccc;
-
color: #000;
-
margin: 0 5px;
-
}
-
.tui-countdown-bg{
-
background: red;
-
color: #fff;
-
}
-
-
.container{
-
width: 100%;
-
display: flex;
-
justify-content: center;
-
}
-
.backView{
-
width:690rpx;
-
background: #fff;
-
display: flex;
-
flex-direction: column;
-
margin-bottom: 30rpx;
-
}
-
.createDate
-
{
-
background: #f5f5f5;
-
padding:15rpx 15rpx 10rpx 15rpx;
-
line-height: 50rpx;
-
font-size: 28rpx;
-
color: gainsboro;
-
text-align: center;
-
}
-
.backViewitem1{
-
-
display: flex;
-
flex-direction: row;
-
height: 55rpx;
-
align-items: center;
-
padding:8rpx 40rpx;
-
border-bottom: 2rpx solid #f5f5f5;
-
}
-
.ico
-
{
-
width:35rpx;
-
height:35rpx;
-
}
-
.name
-
{
-
color: #c13176;
-
margin-left: 20rpx;
-
font-size: 28rpx;
-
}
-
-
.details
-
{
-
font-size:24rpx;
-
letter-spacing: 2rpx;
-
-
}
-
.backViewitem2{
-
-
display: flex;
-
flex-direction: row;
-
line-height: 35rpx;
-
min-height: 70rpx;
-
padding: 15rpx 40rpx 10rpx 40rpx;
-
border-bottom: 2rpx solid #f5f5f5;
-
}
-
.details1
-
{
-
color:#888;
-
font-size:23rpx;
-
letter-spacing: 2rpx;
-
-
}
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。
如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
一、什么是跨域访问
举个栗子:在A网站中,我们希望使用Ajax来获得B网站中的特定内容。如果A网站与B网站不在同一个域中,那么就出现了跨域访问问题。你可以理解为两个域名之间不能跨过域名来发送请求或者请求数据,否则就是不安全的。跨域访问违反了同源策略,同源策略的详细信息可以点击如下链接:Same-origin_policy;
总而言之,同源策略规定,浏览器的ajax只能访问跟它的HTML页面同源(相同域名或IP)的资源。
JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。
由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的<script>
元素是一个例外。利用<script>
元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。更具体的原理需要更多篇幅的讲解,小伙伴可以自行去百度。
JQuery Ajax对JSONP进行了很好的封装,我们使用起来很方便。前端示例:
$.ajax({
type:"GET",
url:"http://www.deardull.com:9090/getMySeat", //访问的链接 dataType:"jsonp", //数据格式设置为jsonp jsonp:"callback", //Jquery生成验证参数的名称 success:function(data){ //成功的回调函数 alert(data);
},
error: function (e) { alert("error");
}
});
需要注意的地方是:
后端要配合使用jsonp,那么首先得了解Jquery Ajax jsonp的一个特点:
Jquery在发送一个Ajax jsonp请求时,会在访问链接的后面自动加上一个验证参数,这个参数是Jquery随机生成的,例如链接
http://www.deardull.com:9090/getMySeat?callback=jQuery31106628680598769732_1512186387045&_=1512186387046
中,参数callback=jQuery31106628680598769732_1512186387045&_=1512186387046
就是jquery自动添加的。
添加这个参数的目的是唯一标识这次请求。当服务器端接收到该请求时,需要将该参数的值与实际要返回的json值进行构造(如何构造下面讲解),并且返回,而前端会验证这个参数,如果是它之前发出的参数,那么就会接收并解析数据,如果不是这个参数,那么就拒绝接受。
需要特别注意的是这个验证参数的名字(我在这个坑上浪费了2小时),这个名字来源于前端的jsonp参数的值。如果把前端jsonp参数的值改为“aaa”,那么相应的参数就应该是
aaa=jQuery31106628680598769732_1512186387045&_=1512186387046
知道了Jquery Ajax Jsonp的原理,也知道了需要接受的参数,我们就可以来编写服务器端程序了。
为了配合json,服务器端需要做的事情可以概括为两步:
根据与前端Ajax约定的jsonp参数名来接收验证参数,示例如下(使用SpringMVC,其他语言及框架原理类似)
@ResponseBody @RequestMapping("/getJsonp") public String getMySeatSuccess(@RequestParam("callback") String callback){
将接收的的验证参数callback与实际要返回的json数据按“callback(json)”的方式构造:
@ResponseBody
@RequestMapping("/getMySeat") public String getMySeatSuccess(@RequestParam("callback") String callback){
Gson gson=new Gson(); //google的一个json工具库 Map<String,String> map=new HashMap<>(); map.put("seat","1_2_06_12"); return callback+"("+gson.toJson(map)+")"; //构造返回值 }
最终,前后端的相应代码应该是这样的:
前端
$.ajax({
type:"GET",
url:"http://www.deardull.com:9090/getMySeat", //访问的链接 dataType:"jsonp", //数据格式设置为jsonp jsonp:"callback", //Jquery生成验证参数的名称 success:function(data){ //成功的回调函数 alert(data);
},
error: function (e) { alert("error");
}
});
后端
@ResponseBody
@RequestMapping("/getMySeat") public String getMySeatSuccess(@RequestParam("callback") String callback){
Gson gson=new Gson(); Map<String,String> map=new HashMap<>(); map.put("seat","1_2_06_12");
logger.info(callback); return callback+"("+gson.toJson(map)+")";
}
需要注意的是:
完整的示例就是上面两段代码,这里就不提供Github连接了。上面的示例亲测有效,如果有遇到问题的,欢迎留言提问。
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
有时候当小程序向后台拿数据是一篇html标签的文章时,把它放进小程序会发现很多标签就不兼容,如果要一个个改又很麻烦,有没有方法可以很快地兼容html标签呢?
有个工具可以做到:wxParse
下载了它的压缩包后解压,复制wxParse文件夹放到小程序pages页面里:
在wxml里引入,这里的路径仅供参考:
<import src="../../../../wxParse/wxParse.wxml" /> <view> //在需要放置html文本的地方使用wxParse模板 <template is="wxParse" data="{{wxParseData:content.nodes}}" /> </view>
在js里引入:
let wxparse = require("../../../../wxParse/wxParse.js");
Page({ /**
* 页面的初始数据
*/ data: {
content: '' },
onLoad: function(options) { var that = this;
..... /**
* WxParse.wxParse(bindName , type, data, target,imagePadding)
* 1.bindName绑定的数据名(必填)
* 2.type可以为html或者md(必填)
* 3.data为传入的具体数据(必填)
* 4.target为Page对象,一般为this(必填)
* 5.imagePadding为当图片自适应是左右的单一padding(默认为0,可选)
*/ wxparse.wxParse('content', 'html', result.data.content, that);
}
在wxss引入:
@import "../../../../wxParse/wxParse.wxss";
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里
亲测必须将代码放在记事本中,改成html格式,在用IE运行(必须)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>显示指定驱动器的大小及可用空间</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>
<body>
<form name="form1" method="post" action="">
盘符:
<input type="text" name="text1">
<input type="button" name="Button1" value="磁盘空间" onclick="DriveSize(document.form1.text1)">
</form>
<script language="javascript">
<!--
function DriveSize(Drivename)
{
var fso=new ActiveXObject("Scripting.FileSystemObject");
var s=fso.GetDrive(Drivename.value);
if (s.IsReady)
{
var str,str1,AllSize=0.0;
str="当前驱动器的名称为:"+s.DriveLetter+"\n";
AllSize=s.TotalSize/1024/1024/1024;
str=str+"当前驱动器的大小为:"+parseInt(AllSize*10)/10+"\n";
AllSize=s.FreeSpace/1024/1024/1024;
str=str+"当前驱动器的可用空间为:"+parseInt(AllSize*10)/10;
alert(str);
}
else
alert("该驱动器无效。")
}
//-->
</script>
</body>
</html>
运行:
结果:单位为g
蓝蓝设计( www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 、平面设计服务。
蓝蓝设计的小编 http://www.lanlanwork.com