MaYue's Notes

Quick notes


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于

  • 搜索

Axios:基于Promise的HTTP库

发表于 2017-04-14   |   分类于 HTTP库   |  

前言

在Vue1.0中我们知道和服务端通信发送请求获取数据依赖的是vue-resource,但自Vue更新到2.0之后,作者就宣告不再对vue-resource更新,而是改成了推荐的Axios这一HTTP库。作用上类似我们熟知的一些Ajax库,但是Axios是基于Promise的HTTP请求客户端,可同时在浏览器和Node.js中使用,这好像更符合目前前端的技术新趋势。现将Axios的官方文档及示例做一介绍。

实现的功能特点

  • 可以从浏览器端发起XMLHttpRequests请求
  • node端发起http请求
  • 支持Promise API
  • 监听请求和响应
  • 转化请求和返回响应数据
  • 取消请求
  • JSON数据自动转化
  • 客户端支持防御CSRF攻击

浏览器支持

Chrome Firefox Safari Opera Edge IE
Latest ✔ Latest ✔ Latest ✔ Latest ✔ Latest ✔ 8+ ✔

Browser Matrix

安装

使用npm
1
$ npm install axios
使用bower
1
$ bower install axios
使用CDN
1
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

示例

发起一个GET请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 发起一个user请求
axios.get('/user?ID=1234')
.then(function(respone) {
console.log(response);
})
.catch(function(error) {
console.log(error);
});

// 上面的请求也可选择下面的方式来写
axios.get('/user',{
params:{
ID:12345
}
})
.then(function(response){
console.log(response);
})
.catch(function(error){
console.log(error)
});
发起一个POST请求
1
2
3
4
5
6
7
8
9
10
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
发起一个多重并发请求
1
2
3
4
5
6
7
8
9
10
11
12
13
function getUserAccount() {
return axios.get('/user/12345');
}

function getUserPermissions() {
return axios.get('/user/12345/permissions');
}

axios.all([getUerAccount(),getUserPermissions()])
.then(axios.spread(function(acc,pers){
// 两个请求现在都完成
// TODO
}));

Axios API

axios能够在进行请求时进行一些设置。

axios(config)

1
2
3
4
5
6
7
8
9
// 发起POST请求
axios({
method: 'post',
url: '/user/12345',
data:{
firstName:'Fred',
lastName:'Flintstone'
}
});
1
2
3
4
5
6
7
8
9
// 请求远程服务的一张图片
axios({
method: 'get',
url: 'http://bit.ly/2mTM3nY',
responseType: 'stream'
})
.then(function(response) {
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
});

axios(url[, config])

1
2
// 发起一个GET请求
axios('/user/12345/);

请求方法别名

为了方便axios提供了所有请求方法的别名

1
axios.request(config)
1
axios.get(url[, config])
1
axios.delete(url[, config])
1
axios.head(url[, config])
1
axios.options(url[, config])
1
axios.post(url[, data[, config]])
1
axios.put(url[, data[, config]])
1
axios.patch(url[, data[, config]])
并发

处理并发请求有用的方法

1
axios.all(iterable)
1
axios.spread(callback)

创建一个实例

你可以使用自定义设置创建一个新的实例

1
axios.create([config])
1
2
3
4
5
var instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});

请求配置

以下列出了一些请求时的设置。只有url是必须的,如果没有指明的话,默认的请求方法是GET。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
{
// url 是服务器链接接口地址
url: '/user',

// method 是发起请求时的请求方法
method: 'get',

// baseURL 如果url不是绝对地址,那么将会加在其前面
// 当axios使用相对地址时这个设置非常方便
// 在其实例中的方法
baseURL: 'http://some-domain.com/api/',

// transformRequest 允许请求的数据在传到服务器之前进行转化
// 这个只适用于PUT,GET,PATCH方法
// 数组中的最后一个函数必须返回一个字符串,一个ArrayBuffer,或者Stream
transformRequest: [function(data) {
// 依自己的需求对请求数据进行处理
return data;
}],

// transformResponse 允许返回的数据传入then/catch之前进行处理
transformResponse: [function(data) {
// 依需要对数据进行处理
return data;
}],

// headers 是自定义的要被发送的头信息
headers: {'X-Requested-with': 'XMLHttpRequest'},

// params是请求连接中的请求参数,必须是一个纯对象,或者URLSearchParams对象
params: {
ID: 12345
},

// paramsSerializer 是一个可选的函数,是用来序列化参数
// 例如:(https://ww.npmjs.com/package/qs,http://api.jquery.com/jquery.param/)
paramsSerializer: function(params) {
return Qs.stringify(params,{arrayFormat: 'brackets'})
},

// data 是请求体需要设置的数据
// 只适用于应用的PUT,POST,PATCH请求方法
// 当没有设置transformRequest时,必须是以下其中之一的类型:
// -string,plain object,ArrayBuffer,ArrayBufferView,URLSearchParams
// -仅浏览器:FormData,File,Blob
// -仅Node:Stream
data: {
firstName: 'fred'
},
// timeout 定义请求的时间,单位是毫秒。
// 如果请求的时间超过这个设定时间,请求将会停止
timeout: 1000,

// withCredentials 表明是否跨域请求,
// 应该是用证书
withCredentials: false // 默认值

// adapter 适配器,允许自定义处理请求,这会使测试更简单
// 返回一个promise,并且提供验证返回
adapter: function(config) {

},

// auth 表明HTTP基础的认证应该被使用,并且提供证书
// 这个会设置一个authorization头(header),并且覆盖你在header设置的Authorization头信息
auth: {
username: 'janedoe',
password: 's00pers3cret'
},

// responsetype 表明服务器返回的数据类型,这些类型的设置应该是:
//'arraybuffer','blob','document','json','text',stream'
responsetype: 'json',

// xsrfHeaderName 是http头(header)的名字,并且该头携带xsrf的值
xrsfHeadername: 'X-XSRF-TOKEN',// 默认值

// onUploadProgress允许处理上传过程的事件
onUploadProgress: function(progressEvent){
//本地过程事件发生时想做的事
},

//`onDownloadProgress`允许处理下载过程的事件
onDownloadProgress: function(progressEvent) {
// 下载过程中想做的事
},

// maxContentLength 定义http返回内容的最大容量
maxContentLength: 2000,

// validateStatus 定义promise的resolve和reject
// http返回状态码,如果validateStatus返回true(或者设置成null/undefined),promise将会接受;其他的promise将会拒绝。
validateStatus: function(status) {
return status >= 200 && stauts < 300; // 默认
},

// httpAgent和httpsAgent当产生一个http或者https请求时分别定义一个自定义的代理,在nodejs中。
// 这个允许设置一些选选个,像是`keepAlive`--这个在默认中是没有开启的。
httpAgent: new http.Agent({keepAlive: treu}),
httpsAgent: new https.Agent({keepAlive: true}),

// proxy 定义服务器的主机名字和端口号
// auth表明HTTP基本认证应该跟proxy相连接,并且提供证书
// 这个将设置一个'Proxy-Authorization'头(header),覆盖原先自定义的
proxy: {
host: 127.0.0.1,
port: 9000,
auth: {
username: 'cdd',
password: '123456'
}
},

// cancelTaken定义一个取消,能够用来取消请求
//(查看下面的Cancellation的详细部分)
cancelToke: new CancelToken(function(cancel) {

})
}

响应概要

一个请求的响应包含以下信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
// data是服务器的提供的返回值
data: {},

// status是服务器返回的http状态码
status: 200,

// statusText是服务器返回的http状态信息
statusText: 'ok',

// headers是服务器返回中携带的headers
headers: {},

// config是对axios进行的设置,目的是为了请求
config: {}
}

如果你使用then,你将获取到以下响应返回结果

1
2
3
4
5
6
7
8
axios.get('/user/12345')
.then(function(response) {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
});

使用catch时,或者传入一个reject callback作为then的第二个参数,那么返回的错误信息将能够被使用。

默认设置

你可以设置一个默认的设置将在所有的请求中有效。

全局默认设置:
1
2
3
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
实例中自定义默认值
1
2
3
4
5
6
7
// 当创建一个实例时进行默认设置
var instance = axios.create({
baseURL: 'https://api.example.com'
});

// 在实例创建之后改变默认值
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
设置优先级

设置(config)将按照优先顺序整合起来。首先的是在 lib/defaults.js 中定义的默认设置,其次是 defaults 实例属性的设置,最后是请求中 config 参数的设置。越往后面的等级越高,会覆盖前面的设置

1
2
3
4
5
6
7
8
9
10
11
12
// 使用默认库的设置创建一个实例,
// 这个实例中使用的是默认库的timeout设置,默认值是0。
var instance = axios.create();

// 覆盖默认库中timeout的默认值
// 此时所有的请求的timeout时间是2.5秒
instance.defaults.timeout = 2500;

// 覆盖该次请求中timeout的值,这个值设置的时间更长一些
instance.get('/longRequest',{
timeout: 5000
});
拦截器

你可以在requests或者responses被then或者catch处理之前对他们进行拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 添加一个请求拦截器
axios.interceptors.request.use(function(config) {
// 在请求发送之前
// TODO
return config;
},function(error) {
// 当出现请求错误
// TODO
return Promise.reject(error);
});

// 添加一个返回拦截器
axios.interceptors.response.use(function(response) {
// 对返回的数据进行处理
// TODO
return response;
},function(error) {
// 对返回的错误进行处理
//TODO
return Promise.reject(error);
});

如果你需要在稍后移除拦截器

1
2
var myInterceptor = axios.interceptors.request.use(function(){/*...*/});
axios.interceptors.rquest.eject(myInterceptor);

你可以在一个axios实例中使用拦截器

1
2
var instance = axios.create();
instance.interceptors.request.use(function(){/*...*/});
错误处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
axios.get('user/12345')
.catch(function(error) {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else {
// 一些错误是在设置请求时触发的
console.log('Error', error.message);
}
console.log(error.config);
});
```

可以使用validateStatus设置选项自定义HTTP状态码的错误范围。

axios.get(‘user/12345’,{
validateStatus:function(status){
return status < 500; // Reject only if the status code is greater than or equal to 500
}
});

1
2
3
4
5
6

### 取消请求

可以使用cancel token取消一个请求。

可以使用CancelToke.source工厂函数创建一个cancel token:

var source = CancelToken.source();

axios.get(‘/user/12345’, {
cancelToken:source.toke
}).catch(function(thrown) {
if (axiso.isCancel(thrown)) {
console.log(‘Rquest canceled’, thrown.message);
} else {
// handle error
// TODO
}
});

// 取消请求(信息参数是可设置的)
source.cancel(‘Operation canceled by the user.’);

1
2

可以给CancelToken构造函数传递一个executor function来创建一个cancel token:

var CancelToken = axios.CancelToken;
var cancel;

axios.get(‘/user/12345’, {
cancelToken: new CancelToken(function executor(c) {
// 这个executor函数接受一个cancel function作为参数
cancel = c;
})
});

// 取消请求
cancel();

1
2
3
4

### Node.js

在nodejs中可以如下使用querystring:

var querystring = require(‘querystring’);
axios.post(‘http://something.com/', querystring.stringify({foo:’bar’}));
`

Promises

axios基于原生的ES6 Promise实现。如果环境不支持请使用polyfill。

vue-router2基础+实践

发表于 2017-03-02   |   分类于 Vue   |  

前言

Vue.js的一大特色就是构建单页面应用十分方便,vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中,则是路径之间的切换,也就是组件的切换。既然要方便构建单页面应用那么自然少不了路由,vue-router就是vue官方提供的一个路由框架。

路由的安装

npm安装

可以使用npm直接安装插件

1
npm install vue-router --save

执行命令完成vue-router的安装,并在package.json中添加了vue-router的依赖。

1
2
3
4
5
"dependencies": {
...
"vue-router": "^2.1.0"
...
}

如果是要安装在开发环境下,则使用以下命令行:

1
npm install vue-router --save-dev
1
2
3
4
5
"devDependencies": {
...
"vue-router": "^2.1.0",
...
}

直接下载/CDN

https://unpkg.com/vue-router/dist/vue-router.js

1
2
<script src="/path/to/vue.js"></script>
<script src="/path/to/vue-router.js"></script>

路由实现

一个最简单的单页面应用

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Vue from 'vue'
import App from './App'
import VueRouter from 'vue-router'
import Page01 from './components/page01'
import Page02 from './components/page02'

Vue.use(VueRouter) // 全局安装路由功能

// 定义路径
const routes = [
{ path: '/', component: Page01 },
{ path: '/02', component: Page02 },
]

// 创建路由对象
const router = new VueRouter({
routes
})

new Vue({
el: '#app',
template: '<App/>',
components: { App },
router
})

App.vue

1
2
3
4
5
6
7
8
<template>
<div id="app">
<router-link to="/">01</router-link>
<router-link to="/02">02</router-link>
<br/>
<router-view></router-view>
</div>
</template>

page01.vue

1
2
3
4
5
<template>
<div>
<h1>page01</h1>
</div>
</template>

page02.vue

1
2
3
4
5
<template>
<div>
<h1>page02</h1>
</div>
</template>
  • npm安装vue-router
  • Vue.use(VueRouter)全局安装路由功能
  • 定义路径数组routes并创建路由对象router
  • 将路由注入到Vue对象中
  • 在根组件中使用定义跳转路径
  • 在根组件中使用来渲染组件
  • 创建子组件

路由的跳转

router-link

router-link标签用于页面的跳转

1
<router-link to="/page01">page01</router-link>

点击router-link标签router-view就会渲染路径为/page01的页面。

router.push

通过JS代码控制路由的界面渲染

1
2
3
4
5
6
7
8
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: 123 }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

实例中的语法:

1
2
3
4
5
6
7
8
// 字符串
this.$router.push('home')
// 对象
this.$router.push({ path: 'home' })
// 命名的路由
this.$router.push({ name: 'user', params: { userId: 123 }})
// 带查询参数,变成 /register?plan=private
this.$router.push({ path: 'register', query: { plan: 'private' }})

router.replace

push方法会向 history 栈添加一个新的记录,而replace方法是替换当前的页面,不会向 history 栈添加一个新的记录。

1
<router-link to="/05" replace>05</router-link>
1
this.$router.replace({ path: '/05' })

router.go

控制history记录的前进和后退

1
2
3
4
5
6
// 在浏览器记录中前进一步,等同于 history.forward()
this.$router.go(1)
// 后退一步记录,等同于 history.back()
this.$router.go(-1)
// 前进3步记录router.go(3)
this.$router.go(3)

传参方式

由跳转的过程中会传递一个object,我们可以通过watch方法查看路由信息对象。

1
2
3
4
5
6
watch: {
'$route' (to, from) {
console.log(to);
console.log(from);
},
}

控制台看到的路由信息对象

1
2
3
4
5
6
{
...
params: { id: '123' },
query: { name: 'jack' },
...
}

params

路由配置文件中定义参数

1
2
3
4
5
6
{
// 订单详情
path: '/order/detail/:orderId',
name: 'orderDetail',
component: OrderDetailView
}

路径后面的/:orderId就是我们要传递的参数

1
this.$router.push({ path: '/order/detail/441'});

此时路由跳转的地址

1
http://localhost:3000/#/order/detail/441
组件中获取数据
1
<h2>{{ $route.params.orderId }}</h2>
1
console.log(this.$route.params.orderId)

query

query传递数据的方式就是URL常见的查询参数

1
<router-link :to="{ path: '/order/detail', query: { id: item.id, stateId: item.stateId }}">
1
this.$router.push({path: '/order/detail', query: {id: orderIdCache}});

获取数据和params是一样

1
<h2>{{ $route.query.orderId }}</h2>
1
console.log(this.$route.query.orderId)

微信小程序开发实践总结

发表于 2017-01-10   |   分类于 小程序   |  

前言

近日,小程序如约而至在1月9日凌晨在微信IOS和Android最新6.5.3版本中上线了期待已久的微信小程序,用户只需要更新最新版微信并搜索自己感兴趣的就能够体验已发布的小程序了。本人有幸和另一位团队的小伙伴也一起参与了公司车管家小程序的开发,我们前端这边的开发整体进度都还算顺利没有遇到特别坑的地方,当然这里离不开小伙伴雪娇童鞋她们在这之前做了很多产品和技术上的调研以及做了Demo的实践还有分享等等工作,都确保了我们开发上没有遇到大的阻碍。现将我从小程序的学习到开发到测试的过程中遇到的问题和踩的一些坑做一个小总结。

关于整体的开发模式

小程序不像我们前端er日常开发web和H5那样在任意一个文本编辑器里编写代码在浏览器中就能实时看到页面还有本地和对外访问的链接地址,小程序是没有这些的(后面有牛人开发并开源了WEPT,集成了微信小程序组件UI可以在浏览器中查看调试)。微信为开发小程序应用提供了web开发者工具供开发者使用(实际开发中这工具开发体验真是low到可以了),建议大家实际开发时依旧使用自己的IDE,用WEPT或者web开发者工具查看页面。从小程序的开发语言中我们可以看到一些React、Angular以及Vue的影子,也许小程序就是集大成了这三大目前最主流的前端框架(?问号脸),因此只要开发者之前有这三种框架的开发经验再认真阅读阅读小程序的API文档,那么小程序的学习成本和上手开发成本都不是很高。它的模式很闭环很严格限制,只要按照最新的官方文档去规范开发,开发完成利用开发者工具平台打包压缩上传至微信服务器,然后微信进行审核,审核通过后会发布上线,用户在客户端搜索小程序名称获取并离线缓存至微信本地文件系统,供用户离线浏览访问。

关于开发者工具

左侧边栏切换选项依次是:

编辑

编辑选项中可以实时查看页面UI并且支持切换不同主流移动设备和网络环境以供适配和测试。右侧是代码编辑区,包括项目目录结构和文件编辑区。这里建议在新建目录或是文件时选择在开发者工具的这里新建比较快捷方便。编辑代码在自己的IDE中。

调试

看着是不是眼熟?这不就是我们熟悉的Chrome的调试面板吗,是的没错,但需要注意我们看到的“Dom元素”是在wxml文件下的,这里看到的也不是标准的Dom,而是类似React微信封装的一系列自定义组件,比如view、text、button、map、image等。右边是Styles和Dataset,Styles查看组件样式内容的,Dataset是查看绑定在UI组件上的自定义data-*属性。这里注意调试页面时要将Wxml这个Tab选项排在面板第一位才能查看组件元素。

Sources面板可以查看项目资源文件,可以打断点调试页面对应js逻辑代码及Watch我们需要观察的变量等,这和Chrome里的差不多就不多赘述了。

AppData选项面板是web开发者工具特有的一项,提供的这个辅助功能非常有用,这里会显示每个页面Page({})实例中data数据对象的所有属性和值并且所有值都是可编辑的,这样就省去了不用我们开发调试时每次都要更改代码才能看到视图页面的变化,只要在这里编辑data属性的值就可以了,方便快捷高效。这里会显示你访问浏览过的页面对应的Page({})实例下的data数据对象,当然也可以只展示当前调试页面的data对象,将启动页面设置为当前调试页面即可。这里的data对象还支持Code和Tree两种切换查看模式,切换至Code模式下可以看到当前data对象下具体的所有数据。当声明的变量过多时这里还支持查询,可以快速锁定需要调试的变量属性。

项目

在项目面板中有以下几点小细节可以关注下:

  • 点击预览会生成一个二维码,开发者可以用手机设备扫二维码进行真机调试,如果在app.json中配置了debug: true,则如果在真机中打开调试就会出现vconsole控制台,可以进行一些log的输出及设备信息的查看。
  • 如果只想单独调试某一个page,则可以点击预览右侧的小箭头进行自定义预览的页面设置。
  • 下面有几个工具支持的功能,如果项目统一用ES6则可以勾选开启ES6转ES5;可以灵活的选择勾选监听文件变化自动刷新开发者工具,本人实际开发时是写页面的时候不勾选(因为每次习惯性的按Ctrl+S或Commond+S开发工具都会reload一遍,作为有轻微强迫症的我还是有点心疼自己mac的…我一般是手动刷新),当页面写完调试js逻辑功能时可以勾选。
  • 最后一项开发环境不校验请求域名以及TLS版本,这是一个挺重要的功能,在开发时我们用的更多都是测试环境的接口,可能这些接口服务器域名不是https的或者不在事先管理后台配置的合法域名内,此时勾选这一项就可以正常使用测试环境的接口以及一些第三方平台提供的Mock数据接口,因为开发者工具帮你做了请求代理。
  • 最下面的删除项目按钮可以将当前小程序项目从开发者工具中删除。

关于开发语言及小技巧

  • wxml:基本上和写html是一样的,但一定要用小程序API提供的组件标记,不可随意使用HTML的一些标签元素!使用这些组件标记就会有默认的组件样式;可以像Angular和Vue一样在这些组件标记上绑定事件、条件判断指令、循环指令等。
  • wxss:小程序的页面样式表文件,官方推荐使用Flex弹性盒方式进行页面布局,以及提供了响应式单位rpx(设计图最好给成750的,开发时设计图是多少px对应样式属性就是多少rpx!),小程序提供了全局样式app.wxss和页面局部样式page的wxss文件,我们可以将项目的所有公共样式放在app.wxss中,这样可以复用到任何一个页面中。
  • 特殊标记block:这不是一个UI组件,是一个包装元素,可以利用它很好的进行对页面结构进行判断循环展示等。
  • 以bind和catch开头再加上事件名称就组成了小程序的绑定事件,还可以在标记元素上绑定自定义属性供逻辑层js获取。
1
2
3
<block wx:if="{{item.status === 1}}">
<button type="default" hover-class="button-hover" hover-stay-time="100" class="btn-pay" data-ordernum="{{item.ordernum}}" bindtap="bindOrderPayTap">立即支付</button>
</block>
1
2
3
4
5
6
7
bindOrderPayTap: function(e) {
// 微信支付
Payment.requestPayment({
ordernum: e.target.dataset.ordernum ? e.target.dataset.ordernum : '',
openId: App.globalData.openId ? App.globalData.openId : ''
});
}
  • 页面和路由跳转传参
1
<navigator url="/pages/wz-records/wz-records?plateNum=京A12345"></navigator>
1
2
3
wx.navigateTo({
url: '../wz-records/wz-records?plateNum=' + params.plateNum
});

目标页面接收参数:

1
2
3
4
5
6
7
8
9
10
Page({
data: {

},
onLoad(option) {
wx.setNavigationBarTitle({
title: option.plateNum
});
}
})
  • 在使用wx:for循环渲染时要添加wx:key否则会报warning警告。
1
2
3
<view class="dj-group mb30" wx:for="{{orderList}}" wx:for-item="item" wx:key="*this">
<!-- ... -->
</view>
  • 小程序虽然提供了ES6转化ES5,但是新的API比如说Promise它并不支持,那么恶心的异步回调地狱问题就不可避免了,我的做法是除过第一次的request外之后需要进行异步request的都单另封装在一个方法中,这样在请求的回调中调用方法即可不用层层request请求嵌套下去。
  • 注意在request请求中,method不同对应的header的content-type也是不同的(这点很重要!)
1
2
3
4
5
6
7
8
9
10
11
12
13
wx.request({
url: '',
method: 'GET',
header: {
'content-type': 'application/json'
},
success: (res) => {

},
fail: (res) => {

}
});

当method为GET方式时:'content-type': 'application/json'

当method为POST方式(或者表单提交)时:'content-type': 'application/x-www-form-urlencoded'

  • 注意event.target.dataset与event.currentTarget.dataset的区别:target.dataset是取源组件上设置的数据集合,currentTarget.dataset是取当前组件上设置的数据集合。

  • 微信官方给出的page的生命周期的图片:

关于开发建议

  • 在项目中我们可以将公共的工具方法、公共的配置项(如接口URL等)、公用的业务逻辑(如调起微信支付等)抽离当前业务成公共方法,这样满足模块化开发的思想也提高了代码的高度复用性。
  • 如果团队统一使用ES6语法开发切记一定要在开发者工具、IOS手机设备、Android手机设备上运行都是不报错没有问题的!!我在一处使用了for (let item of arr) {}语法循环数据时就踩坑了,一直以为没问题直到最后提测时才发现在安卓上这ES6语法报错不识别,最后改成了.forEach(function(item, index) {})。
  • 写页面布局结构时最好使用Flex弹性盒布局。
  • 使用navigateTo路由跳转时,小程序会缓存上一个页面到内存栈中(最多5个!)所以一些加载页面时做的一些逻辑判断一定写在onLoad或者onReady onShow中。
  • 所有图片资源都先在本地压缩(小图标图做sprit合并)然后再统一上传到CDN服务器上,页面图片地址直接走https服务器(因为小程序最大限制1M所以最好别把图片放本地了)。
  • 微信登陆通过code获取openId作为小程序用户的唯一标识,小程序提供了微信用户登录的接口API以及获取用户信息的API,不过并不会反回用户的openId,此时需要我们自己的服务端提供这样的接口来获取用户的openId。具体实现方法是先调用登陆请求方法wx.login({})后返回code,再通过code去请求我们自己服务器的接口用来返回openId。具体代码实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 用户登录
wx.login({
success: (res) => {
// 获取openId
if (res.code) {
wx.request({
url: '',
method: 'POST',
data: {
code: res.code
},
header: {
'content-type': 'application/x-www-form-urlencoded'
},
success: (res) => {
if (res.data && res.data.errcode === 0) {
if (res.data.openid) {
that.globalData.openId = res.data.openid;
}
} else {
console.log('获取openId失败:' + res.data.errcode + '=====>' + res.data.errmsg)
}
}
});
}
},
});

成功获取到openId后我们可以将其绑定在全局变量globalData上,这样就可以在任意page中访问到globeData下的openId。

  • 微信支付:小程序提供了调用微信支付的API方法
1
2
3
4
5
6
7
8
9
10
11
12
13
wx.requestPayment({
'timeStamp': '',
'nonceStr': '',
'package': '',
'signType': 'MD5',
'paySign': '',
'success':function(res){

},
'fail':function(res){

}
})

一开始看文档以为需要传的这5个参数值是前端要做的,后面仔细看了关于支付方面的文档和技术原理示意图,发现这几个参数不需要前端处理(也不安全…),这依旧需要我们自己的服务端提供接口给返回,最后沟通了公司负责支付相关的部门他们给予了支持,所有所需参数都由负责支付部门的提供返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
let requestPayment = (obj) => {
return wx.request({
url: '', // 开发者自己业务的服务端接口
method: 'GET',
data: {
ordernum: obj.ordernum,
openid: obj.openId
},
header: {
'content-type': 'application/json'
},
success: (res) => {
if (res.data && res.data.returncode === 1) {
const result = res.data.result ? res.data.result : {};

// 微信支付
wx.requestPayment({
'timeStamp': result.timeStamp, // 当前时间戳
'nonceStr': result.nonceStr, // 随机字符串
'package': result.package, // 统一下单接口
'signType': result.signType, // 签名算法
'paySign': result.paySign, // 签名
'success': (res) => {
if (res.errMsg === 'requestPayment:ok') {
// 成功
} else {
// TODO
}
},
'fail': (res) => {
// 用户取消支付
}
});
} else {
// TODO
}
},
fail: (res) => {
// TODO
},
complete: (res) => {
// TODO
}
});
}

思考与总结

优势?

  • 低门槛下载,作为微信的一环,可以通过微信直接进入,即可使用。

  • 跨平台,微信客户端底层封装,支持小程序跨平台。

  • 开发成本低,通过之前的开发对比,小程序的开发比web app 的开发成本还低,并且前端的资源存放、发布运维都集成在微信中。

  • 页面仿原生,体验更流畅。

  • 小程序可以使用微信的支付功能。

局限?

  • 开发基于微信框架,部分功能受限,不支持现有的其他第三方插件。

  • 小程序页面只能同时打开5个,如果交互流程较长难以支持。

  • 小程序包大小限制为1M(目前),所有只适合轻量级。

最后安利一段最近看到的不错的语录:

“所有的成长过程都是从提出一个问题开始,并找到了答案,最后融入自身的价值观,完成下一次更好的选择,周而复始。”

使用Service workers实现离线内容

发表于 2016-11-10   |   分类于 PWA   |  

原文地址:Offline content with service workers

Service workers不只是对网页进行离线支持它还可以做很多事情,但是包括我自己在内对于大多数人而言,这将是他们的第一次使用Service workers经验。我最近给我的博客实现了一个简单的离线页面,令我惊喜的是使用它竟然是如此的简单。(充满着十足的自信)我想我可以做更多的事情。我决定开始保存我的博客文章从而实现离线阅读,并且很快开始投入实践。之后我很快意识到这个“坑”很深。

这并不是在责怪service workers,而这是一个迹象,说明它们是多么的强大和灵活。还好发现的及时,随着概念变得越来越熟悉,以及复杂性逐渐被抽象化,离线内容将变得更加常见。事实上,我喝了Kool-Aid(译者注:一种饮料),并且我可以知道为什么很多人认为,在几年内离线内容将像如今在web开发中的响应式设计一样变的无处不在。

话虽如此,但是在开始之前这里有一些事情我希望我是知道的。

浏览器支持缓存

Service workers表面上看是一个渐进增强的简单备选方案,在注册一个service worker之前检测它的支持性是很简单的。你可以像这样去做:

1
2
3
4
if ('serviceWorker' in navigator) {
// Yay, service workers work!
navigator.serviceWorker.register('/sw.js');
}

看上去似乎足够简单,但是这里有一个问题。如果你看过了MDN page for the service worker cache API,你将会了解到不同版本的Chrome浏览器支持不同的缓存方法。这也就意味着,尽管你认真努力的做了功能的支持性检测,当使用addAll方法的时候介于版本为40和45之间的Chrome将会出现一个错误。当这些版本的浏览器被更加广泛的使用的时候这就是一个问题了。我在写这篇文章的时候已经在Can I Use检测过了,看上去它可能会影响到约1.15%的用户。

在开始service workers之前我已经读了几篇相关的博客和教程。一些人主张仅仅是使用put而不是addAll,其他人则推荐使用cache pollyfill,尽管其他一些人还仍然没有提到它。明显的这些都是在不同的时间段写的,并且为了找出正确的方式我花费了很多的时间去研究它。

阅读全文 »

构建跨平台Electron应用4个必知的小技巧

发表于 2016-10-31   |   分类于 Electron   |  

原文地址:4 must-know tips for building cross platform Electron apps

在其他众多的应用中,Electron技术驱动着Avocode(译者注:一款设计师和开发者的合作工具),Electron可以让你构建一个运行非常快速流畅的跨平台桌面应用。

If you don’t pay attention you will quickly end up in the uncanny valley of apps, though. Apps that don’t feel quite in place among your other apps.(如果有些问题不注意的话,你的App很快就会掉到“坑”里,无法从其它众多App中脱颖而出。)

这是2016年5月在阿姆斯特丹的Electron Meetup上我做的发言的整理稿内容,由于要考虑到API的变化所以这里做了一些更新。值得注意的是,我这里会做一些更加详细的叙述,并且假设你们对Electron有了一些了解。

首先,我是谁?

我是Kilian Valkhof,一个前端开发者、交互设计师、app开发者,当然这取决于你问的对象是谁了。我有超过10年的互联网从业经验,并且在各式各样的环境下开发出很多的桌面应用,比如GTK和Qt,当然了它们都是使用的Electron。

我最近开发的应用是Fromscratch,它是一款跨平台免费的带有自动保存功能的记笔记应用。

在开发Fromscratch期间,我花了大量时间去确保这款应用在所有三大平台上都能保持良好的运行且可以感觉到体验很棒,并找出了可以用Electron来实现它的方法。这些都是我挖坑填坑过程中积累起来的。

让一款使用Electron开发的应用体验起来很好并且很稳定并不难,而你仅仅需要去确定你关注到的是它的细节。

阅读全文 »

优惠券活动模版功能开发之填坑小结

发表于 2016-09-27   |   分类于 JavaScript   |  

踩坑背景简述

最近负责养车M端优惠券活动模版功能的开发,由于这次开发任务本来不是安排给我的,所以这个功能产品在做需求评审宣讲时我没有参加,固很多逻辑和交互细节都不是很清楚,当然也包括M端一些不能解的坑在宣讲时也没有向产品讲清楚,到我这就只能接受得按照原型去实现…迎难而上,开整!

一.fixed定位和输入框弹出键盘问题

遇到的坑

看了原型设计就发现了一个不好解的坑:需求是要把带文本框的登录窗浮层固定在浏览器可视区最底部。

大概就是这个样子(红色区域):

大家想必一看也知道怎么回事了,要实现底部固定定位显示就得用position: fixed;,但是有输入框就会有软键盘弹出,fixed加上弹出键盘产生的各种兼容性问题各种奇奇怪怪的现象各种闪屏卡屏问题,像这种常识性的前端坑应该在需求评审时就打回去或者M端存在的问题和风险应该向产品和项目讲清楚的,好像并没有…

实践折中解决办法

经过和产品耐心的一遍又一遍的讲解这个坑的问题(包括走邮件这种很正式的流程…)后,产品才答应将登录窗底部浮层固定显示改为点击按钮模态弹窗形式显示。有输入框弹出键盘那fixed肯定不能用啊,那只有弹窗position: absolute;了。

1
2
3
4
5
6
7
.modal-dialog {
position: absolute;
top: 38%;
left: 50%;
-webkit-transform: translate3d(-50%, -50%, 0);
transform: translate3d(-50%, -50%, 0);
}

那么这样的话为了不让浮层跟随页面滑动,body和html就得动态添加class。

阅读全文 »

给所有的Web应用程序添加离线支持

发表于 2016-09-02   |   分类于 Web App   |  

原文地址:Add offline support to any Web app

最近,我参加了Angular Attack 2016 Hackathon编程马拉松活动,并且开发出了一款叫做“Let Me See”的应用,它可以帮助那些失明的人们看到世界(PS:瞬间感觉很伟大的样子…)。这款应用通过使用Ionic2(当然也有Angular2)开发而成。最重要的是我想要让这款应用是更加进步更加优秀的,所以为了得到一个更好的及时加载体验,我给它添加了支持离线缓存的功能。那么下面就让我告诉你们我是如何简单又快速地添加这项功能的。

Oh! By the way, “Let Me See” has won the Innovation prize!(哦!对了,“Let Me See”已经荣获了创新奖!)

在我们开始之前,我要假设你们已经有了一个现有的Web app应用了(PS:哈哈,够强势)。接下来我将用“Let Me See”作为例子一起来进行以下的步骤操作,并且这些步骤将适用于任何现代的单页面应用。

阅读全文 »

使用命令行API更轻易得调试Web应用

发表于 2016-07-26   |   分类于 Dev Tool   |  

原文链接:http://developer.telerik.com/content-types/tutorials/easier-web-application-debugging-command-line-api/

图片 文章参考截图

很多年以前,调试一段JavaScript代码几乎全部依赖于console.log()来调用正在研究中的一个或多个函数。有时你已经意识到,问题不是在这些功能而是在一个由他们调用的函数中,从而更加使得console.log()的调用将被添加到代码中。这通常也会伴随着相当多的脏话(吐槽?抱怨?)。

阅读全文 »

jQuery基础

发表于 2015-04-28   |   分类于 jQuery   |  

1.有两个版本的 jQuery 可供下载:
Production version - 用于实际的网站中,已被精简和压缩。
Development version - 用于测试和开发(未压缩,是可读的代码)

2.$(this).hide() - 隐藏当前元素

3.隐藏/显示:
$(selector).hide(speed,callback);

$(selector).show(speed,callback);
可选的 speed 参数规定隐藏/显示的速度,可以取以下值:”slow”、”fast” 或毫秒。
可选的 callback 参数是隐藏或显示完成后所执行的函数名称。

4.切换显示和隐藏:
$(selector).toggle(speed,callback);

5.淡入/淡出
$(selector).fadeIn(speed,callback);
$(selector).fadeOut(speed,callback);
$(selector).fadeToggle(speed,callback);
$(selector).fadeTo(speed,opacity,callback);

6.上下滑动:
$(selector).slideDown(speed,callback);
$(selector).slideUp(speed,callback);
$(selector).slideToggle(speed,callback);

7.动画
$(selector).animate({params},speed,callback);
必需的 params 参数定义形成动画的 CSS 属性。
可选的 speed 参数规定效果的时长。它可以取以下值:”slow”、”fast” 或毫秒。
可选的 callback 参数是动画完成后所执行的函数名称。
默认地,所有 HTML 元素都有一个静态位置,且无法移动。如需对位置进行操作,要记得首先把元素的 CSS position 属性设置为 relative、fixed 或 absolute!

8.停止动画
$(selector).stop(stopAll,goToEnd); //(false,false) —> (true,true)

Callback 、Chaining …

9.获得内容 - text()、html() 以及 val()
三个简单实用的用于 DOM 操作的 jQuery 方法:
text() - 设置或返回所选元素的文本内容
html() - 设置或返回所选元素的内容(包括 HTML 标记)
val() - 设置或返回表单字段的值

attr() 方法用于获取属性值。

10.添加新的 HTML 内容
我们将学习用于添加新内容的四个 jQuery 方法:
append() - 在被选元素的结尾插入内容
prepend() - 在被选元素的开头插入内容
after() - 在被选元素之后插入内容
before() - 在被选元素之前插入内容

11.删除
jQuery remove() 方法删除被选元素及其子元素。
jQuery empty() 方法删除被选元素的子元素。

12.jQuery 操作 CSS
jQuery 拥有若干进行 CSS 操作的方法。我们将学习下面这些:
addClass() - 向被选元素添加一个或多个类
removeClass() - 从被选元素删除一个或多个类
toggleClass() - 对被选元素进行添加/删除类的切换操作
css() - 设置或返回样式属性

13.jQuery 尺寸 方法
jQuery 提供多个处理尺寸的重要方法:
width() //.width(200) 设置值
height()
innerWidth()
innerHeight()
outerWidth() //true:包括内边距、边框和外边距
outerHeight() //true:包括内边距、边框和外边距

14.向上遍历 DOM 树
这些 jQuery 方法很有用,它们用于向上遍历 DOM 树:
parent() 直接
parents() 所有
parentsUntil() 之间

15.向下遍历 DOM 树
下面是两个用于向下遍历 DOM 树的 jQuery 方法:
children() 直接
find() 所有后代 find(‘*’)

16.在 DOM 树中水平遍历
有许多有用的方法让我们在 DOM 树进行水平遍历:
siblings() 方法返回被选元素的所有同胞元素。
next() 方法返回被选元素的下一个同胞元素。
nextAll() 方法返回被选元素的所有跟随的同胞元素。
nextUntil() 方法返回介于两个给定参数之间的所有跟随的同胞元素
prev()
prevAll()
prevUntil()

17.过滤
first() 方法返回被选元素的首个元素。
last() 方法返回被选元素的最后一个元素。
eq() 方法返回被选元素中带有指定索引号的元素。// 0 1 2 …
filter() 方法允许您规定一个标准。不匹配这个标准的元素会被从集合中删除,匹配的元素会被返回。
not() 方法返回不匹配标准的所有元素。提示:not() 方法与 filter() 相反。

  1. load() 方法完成后所要允许的回调函数。回调函数可以设置不同的参数:
    responseTxt - 包含调用成功时的结果内容
    statusTXT - 包含调用的状态
    xhr - 包含 XMLHttpRequest 对象

19.
$.get(URL,callback);
$.get() 的第一个参数是我们希望请求的 URL(”demo_test.asp”)。第二个参数是回调函数。第一个回调参数存有被请求页面的内容,第二个回调参数存有请求的状态。

$.post(URL,data,callback);
$.post() 的第一个参数是我们希望请求的 URL (“demo_test_post.asp”)。然后我们连同请求(name 和 city)一起发送数据。”demo_test_post.asp” 中的 ASP 脚本读取这些参数,对它们进行处理,然后返回结果。第三个参数是回调函数。第一个回调参数存有被请求页面的内容,而第二个参数存有请求的状态。

20.jQuery noConflict() 方法
如果你的 jQuery 代码块使用 $ 简写,并且您不愿意改变这个快捷方式,那么您可以把 $ 符号作为变量传递给 ready 方法。这样就可以在函数内使用 $ 符号了 - 而在函数外,依旧不得不使用 “jQuery”:
$.noConflict();
jQuery(document).ready(function($){
$(“button”).click(function(){
$(“p”).text(“jQuery 仍在运行!”);
});
});

21.
GET POST
后退按钮/刷新 无害 数据会被重新提交(浏览器应该告知用户数据会被重新提交)。
书签 可收藏为书签 不可收藏为书签
缓存 能被缓存 不能缓存
编码类型 application/x-www-form-urlencoded application/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。
历史 参数保留在浏览器历史中。 参数不会保存在浏览器历史中。
对数据长度的限制 是的。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。 无限制。
对数据类型的限制 只允许 ASCII 字符。 没有限制。也允许二进制数据。
安全性 与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。在发送密码或其他敏感信息时绝不要使用 GET ! POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。
可见性 数据在 URL 中对所有人都是可见的。 数据不会显示在 URL 中。

方法 描述
HEAD 与 GET 相同,但只返回 HTTP 报头,不返回文档主体。
PUT 上传指定的 URI 表示。
DELETE 删除指定资源。
OPTIONS 返回服务器支持的 HTTP 方法。
CONNECT 把请求连接转换到透明的 TCP/IP 通道。

append()前面是要选择的对象,后面是要在对象内插入的元素内容
appendTo()前面是要插入的元素内容,而后面是要选择的对象


选择器 描述
.after( content ) 在匹配元素集合中的每个元素后面插入参数所指定的内容,作为其兄弟节点
.before( content ) 据参数设定,在匹配元素的前面插入内容
after向元素的后边添加html代码,如果元素后面有元素了,那将后面的元素后移,然后将html代码插入

before向元素的前边添加html代码,如果元素前面有元素了,那将前面的元素前移,然后将html代码插

选择器 描述
prepend 向每个匹配的元素内部前置内容
prependTo 把所有匹配的元素前置到另一个指定的元素集合中
append()向每个匹配的元素内部追加内容
prepend()向每个匹配的元素内部前置内容
appendTo()把所有匹配的元素追加到另一个指定元素的集合中

prependTo()把所有匹配的元素前置到另一个指定的元素集合中

选择器 描述
insertBefore 在目标元素前面插入集合中每个匹配的元素
insertAfter 在目标元素后面插入集合中每个匹配的元素
.before()和.insertBefore()实现同样的功能。主要的区别是语法——内容和目标的位置。 对于before()选择表达式在函数前面,内容作为参数,而.insertBefore()刚好相反,内容在方法前面,它将被放在参数里元素的前面
.after()和.insertAfter() 实现同样的功能。主要的不同是语法——特别是(插入)内容和目标的位置。 对于after()选择表达式在函数的前面,参数是将要插入的内容。对于 .insertAfter(), 刚好相反,内容在方法前面,它将被放在参数里元素的后面

before、after与insertBefore。insertAfter的除了目标与位置的不同外,后面的不支持多参数处理

方法名 参数 事件及数据是否也被移除 元素自身是否被移除
remove 支持选择器表达 是 是(无参数时),有参数时要根据参数所涉及的范围

detach 参数同remove 否 情况同remove

$( “html” ).parent()方法返回一个包含document的集合,而$( “html” ).parents()返回一个空集合。

parents()和.closest():
起始位置不同:.closest开始于当前元素 .parents开始于父元素
遍历的目标不同:.closest要找到指定的目标,.parents遍历到文档根元素,closest向上查找,知道找到一个匹配就停止查找,parents一直查找到根元素,并将匹配的元素加入集合

结果不同:.closest返回的是包含零个或一个元素的jquery对象,parents返回的是包含零个或一个或多个元素的jquery对象

each是一个for循环的包装迭代器
each通过回调的方式处理,并且会有2个固定的实参,索引与元素
each回调方法中的this指向当前迭代的dom元素


几种方式可以隐藏一个元素:
CSS display的值是none。
type=”hidden”的表单元素。
宽度和高度都显式设置为0。
一个祖先元素是隐藏的,该元素是不会在页面上显示
CSS visibility的值是hidden

CSS opacity的指是0

Oracle 行转列SQL后台代码

发表于 2014-09-11   |   分类于 Java   |  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
String mapSql = "";

for(String key : map.keySet()) {
Map<String, String> tempMap = map.get(key);

for(String tempKey : tempMap.keySet()) {
colName = tempMap.get(tempKey);
colCode = tempKey;
if(!"".equals(mapSql)){
mapSql += " UNION ALL ";
}
StringBuffer sqs = new StringBuffer();
sqs.append("SELECT '"+colCode+"', T.* FROM (WITH pivot_data AS (SELECT '"+ colName +"',"+ colCode +", TAB_YEAR ");
sqs.append("FROM US_APP."+key+") SELECT * FROM pivot_data PIVOT (SUM("+ colCode +")");
sqs.append("FOR TAB_YEAR IN ("+ years +" ) )) T ");
mapSql += sqs.toString();
}
}

List<Object[]> _querylist = genericDao.getDataWithNativeSql(mapSql);

StringBuffer bf = new StringBuffer();

if (_querylist.size() > 0) {
for(int i = 0; i < _querylist.size(); i++) {
Object[] obj = (Object[]) _querylist.get(i);
int size = _listTabYear.size();
bf.append("{codeName:'"+obj[0]+"',");
bf.append("gdpName:'"+obj[1]+"',");

for(int t = 0; t < size; t++){
Object[] tempObj = _listTabYear.get(t);
bf.append(tempObj[0] + ":'"+obj[t+2]+"'");
if(t == size - 1){
bf.append("}");
}
bf.append(",");
}
}

return "{success : true, gridData : [" + bf.substring(0, bf.length() -1) + "], fieldsCode : ["+years+"]}";
}
123
Marico

Marico

Front-End Developer

21 日志
13 分类
18 标签
GitHub Stack Overflow 微博 Twitter
© 2016 - 2021 Marico
由 Hexo 强力驱动
主题 - NexT.Pisces