Map地图上覆盖物实现事件透传
做地图页面的时候,很多情况下我们会在页面上加各种悬浮在地图上的dom元素,又不希望能影响地图的点击事件.
只要给dom元素css样式: pointer-events:none
当dom内部某些子元素按钮需要有点击事件时,只要给子元素css样式重新设置:pointer-events: auto
即可
小技巧在做地图时非常有用,自己记录一下~
做地图页面的时候,很多情况下我们会在页面上加各种悬浮在地图上的dom元素,又不希望能影响地图的点击事件.
只要给dom元素css样式: pointer-events:none
当dom内部某些子元素按钮需要有点击事件时,只要给子元素css样式重新设置:pointer-events: auto
即可
小技巧在做地图时非常有用,自己记录一下~
平时我们用JSON.parse
或者JSON.stringify
的时候,经常只会传入一个参数,即:要序列化的对象或者反序列化的对象字符串.实际上这两个方法都可以传入第2个function参数去处理对应的value值.
function函数支持两个参数key,value分别对应处理后的key,value,可以通过函数去动态改变对应的value值.
//JSON.stringify 示范代码.
let data = {
test: 'test',
msg: '成功',
data: {
value: 1,
text: 'hello',
time: new Date()
},
time: new Date(),
fun: function (a, b) {
console.log(a + b);
}
}
let jsonStr = JSON.stringify(data, (key, value) => {
switch (key) {
case 'test':
return undefined; //返回undefined,最终不会序列化
case 'time':
return value.replace('T', ' ');
default:
if (typeof value === 'function') {
//简单处理函数的序列化方式,实际上真正的处理要复杂的多
return value.toString();
}
return value;
}
}, 4); //stringify还支持第3个参数格式化字符串(4代表4个空格,也可以输入字符串去代替.)
使用JSON.stringify的时候也要注意对象是否有toJSON
这个方法.如果存在的话最终序列化会调用toJSON这个方法(一般情况不太可能重写js的序列化方法)
同样的,JSON.parse也支持第2个参数去处理最终的value值.
let jsonObj = JSON.parse(jsonStr, (key, value) => {
if (key === 'time') {
return new Date(value);
} else if (typeof value !== 'object' && /^function/.test(value)) {
//简单的对函数进行处理.
return eval('(' + value + ')');
}
return value;
});
这让我们有些情况下处理序列化及反序列化得到很大的灵活性,不过似乎没有找到修改key的办法(除了改写toJSON),如果有的话请千万告诉我.^_^
之前前端的技术栈偏向vue,针对整个生态圈环境,认为确实非常有必要看一下react,
确实react的生态圈是vue没法相比的,并且react native也对我非常有意义.
在阅读react技术文档过程中,redux相对理解的时间花费了长了一点.在这里记录一下,加深印象.
Redux提供一个createStore
函数来生成一个Store
createStore
接受第一个参数为function(我们叫做reducer),为了便于理解,以后再说后面的参数
reducer函数接受两个参数,
第1个是state,即当前store中存储的数据对象,可以有一个默认值.
第2个是action,是一个object对象,必须要有type属性,其他属性随意.
这个function的功能是根据对应type值,做出不同的处理,生成一个新的state被Store存储.
接下来我们在需要做出动作改变数据的地方,使用store.dispath(action)
这里的action
就是之前createStore
中的reducer的第2个参数.(当然按照规定也要有一个type属性)
当我们调用store.dispath
的时候,Redux会自动去调之前我们传入的function,通过不同的type去做不同的处理,最后Store会根据reducer返回的对象生成一个新的store
最后,还有一个最重要的store.subscribe
函数,他用来注册需要监听的函数.当store中的state数据发生了变化时,他会自动调用所有注册过的函数,在注册的函数内部可以通过store.getState()
获取数据快照,来对组件进行渲染.
当程序庞大到一定程度时,单个reducer函数会变的非常庞大,Redux提供了combineReducers
的方法.通过传入一个对象,对象中包裹多个reducer(多个reducer一般是分多个js去编写),来生成一个整体的reducer函数.使得程序维护变的简单.
React-Redux则变的简单很多.
他主要提供2个功能,
在线游戏,实时通讯都需要用到websocket技术.
其中搭建websocket最方便的就是nodejs,node的高并发,js的单线程,异步处理的优势无限放大.
socket.io是基于nodeJS的websocket的socket开发框架.相对原生websocket的优势在于他的浏览器兼容性以及封装输出统一性.
源码下载:socket.io搭建源码
在这里吐槽一下杭州的电信,github访问速度超慢,动不动直接封死.连我连接的SSR服务器也经常封住...
(无限流量套餐的可以用4G无线连,一般4G都是不封的)
首先下载socket.io, 客户端代码也可以放同一个服务器端口下,可以下载express搭建一个node静态网页服务器
npm init -y
npm install --save socket.io
npm install --save express
然后新建一个index.js,写入内容
const express = require('express');
const path = require('path');
let app = express();
const http = require('http').Server(app);
let io = require('socket.io')(http);
//搭建静态客户端页面.
app.use(express.static(path.join(__dirname, 'statics')));
//=================socket==========================
io.on('connection', socket => {
console.log('a user connected:' + socket.id);
});
http.listen(3000, () => {
console.log('服务已启动,http://localhost:3000');
});
然后在控制台中运行node index.js
当看到控制台中跳出"服务已启动,http://localhost:3000"字样后,就说明服务器已经成功启动了.
首先建一个文件夹,专门存放静态页面(个人习惯叫做statics,如果文件夹不叫这个名字的话注意要修改服务器的对应路径:app.use(express.static(path.join(__dirname, 'statics')));
)
新建一个index.html的首页.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>socket.io test</title>
</head>
<body>
<div id='message'>
</div>
</body>
<!-- 也可以把js下载下来存放在自己的服务器上 -->
<!-- <script src="https://cdn.bootcss.com/socket.io/2.0.4/socket.io.js"></script> -->
<script src="https://cdn.bootcss.com/socket.io/2.0.4/socket.io.slim.js"></script>
<script src="index.js"></script>
</html>
然后再新建一个index.js
var socket = io(); //只要这一句就可以连接了.
//socket连接成功事件
socket.on('connect',function(){
appendText("连接成功 " + socket.id);
});
//追加文字到html页面上
function appendText(strText){
var p = document.createElement("p");
p.innerText = strText;
document.getElementById("message").appendChild(p);
}
然后用浏览器访问http://localhost:3000/就可以看到链接成功的输出了.
用 webpack搭建socket.io客户端框架的话尽量不要和服务器端的放一起,不方便开发以及调试,当然打完包后还是可以放在服务器端目录下面的
附上我的测试代码的package.json
...
"scripts": {
"build": "webpack --config ./webpack.config.js --env production",
"start": "webpack-dev-server --config ./webpack.config.dev.js --env development --open"
},
"devDependencies": {
"clean-webpack-plugin": "^0.1.18",
"html-webpack-plugin": "^2.30.1",
"uglifyjs-webpack-plugin": "^1.2.2",
"webpack": "^3.11.0",
"webpack-dev-server": "^2.11.1"
},
"dependencies": {
"socket.io-client": "^2.0.4"
}
...
首先在根目录新建入口模板页面 index.impl.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>socket.io client</title>
</head>
<body>
<h1>socket client</h1>
<div id="message"></div>
</body>
<!-- 通过cdn引用socket.io -->
<script src="https://cdn.bootcss.com/socket.io/2.0.4/socket.io.slim.js"></script>
<script type="text/javascript" src="<%=htmlWebpackPlugin.files.chunks.index.entry%>"></script>
</html>
新建一个lib文件夹,专门用来存放需要打包的文件
let config = {
socketUrl: 'http://localhost:3000' , //socket.io 服务器路径.
};
export { config as default };
import config from './config';
//import io from 'socket.io-client'; //也可以把socket.io和自己的代码打包在一起.
const socket = io(config.socketUrl);
socket.on('connect',()=>{
console.log('connect ' + socket.id);
appendText("连接成功 " + socket.id);
});
//追加文字到html页面上
function appendText(strText){
var p = document.createElement("p");
p.innerText = strText;
document.getElementById("message").appendChild(p);
}
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
devtool: 'eval-source-map',
devServer: {
host: process.env.HOST,
port: 8080,
inline: true,
//historyApiFallback: true,
// overlay: {
// errors: true,
// warnings: true
// }
//hotOnly:true
},
entry: {
index: './lib/index.js'
},
output: {
path: path.join(__dirname, './dist'),
filename: '[name]-[hash].js'
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.impl.html',
inject: false
}),
]
};
const HtmlWebpackPlugin = require('html-webpack-plugin'); //html模板插件
const CleanWebpackPlugin = require("clean-webpack-plugin"); //清除历史打包文件
const Uglify = require('uglifyjs-webpack-plugin'); //压缩js代码
const path = require('path');
module.exports = {
entry: {
index: './lib/index.js'
},
output: {
path: path.join(__dirname, './dist'),
filename: '[name]-[hash].js'
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.impl.html',
inject: false
}),
new CleanWebpackPlugin('./dist/*.*', {
root: __dirname,
verbose: true,
dry: false
}),
new Uglify(), //压缩js代码
]
};
npm install
npm start //启动调试
npm run build //打包文件
当浏览器自动打开 http://localhost:8080/ 则说明框架搭建成功
(注意服务端程序不要停止,否则连接不上)
上一篇已经能够成功监听微信的消息了,现在只剩最后一个功能发送消息了.
发消息我只做了两个功能:发送文字和发送图片,其他的不太用的到,就没有做.
发送文字很简单,如图只有一个接口webwxsendmsg BaseRequest和之前的监听消息是一样的,另外一个Msg Type:1就是文本消息(Type就是监听消息的时候返回的Type类型)ClientMsgId和LocalID在源码中都是当前时间戳+4位随机数字,FromUserName当然是填自己的UserName了(监听里初始化的时候的自己的UserName),ToUserName当然是填发送方了. Content就是具体要发送的内容了.
当返回的消息BaseResponse.Ret == 0
的时候,就表示你发送成功了.
发送图片通过抓包可以发现是两个接口
1.上传文件 webwxuploadmedia?f=json
其他的根据抓到的包来填值都没问题
主要有一个参数是uploadmediarequest要观察一下源码来填内容.
参数比较多不一一说明,其中FileMd5其实没关系,不用管随便传个值都可以,我是对图片名称+图片大小进行md5加密后传送过去的.
上传图片需要用multipart/form-data
形式发送,这里附上我自己封装的上传方法供大家参考
//上传图片消息
private HttpResult WebWXUpLoadPicture(Contact model, string filePath)
{
string boundary = "----WebKitFormBoundary" + DateTime.Now.Ticks.ToString("x");
var fileInfo = new FileInfo(filePath);
List<FormItemModel> data = new List<FormItemModel>();
//id
data.Add(new FormItemModel("id", "WU_FILE_" + _fileId++));
//name
data.Add(new FormItemModel("name", fileInfo.Name));
//type
data.Add(new FormItemModel("type", System.Web.MimeMapping.GetMimeMapping(filePath)));
//lastModifiedDate
data.Add(new FormItemModel("lastModifiedDate", fileInfo.LastWriteTime.ToString("yyyy-MM-dd tt hh:mm:ss")));
//size
data.Add(new FormItemModel("size", fileInfo.Length.ToString()));
//mediatype
data.Add(new FormItemModel("mediatype", "pic"));
//uploadmediarequest
string md5 = Utils.EncryptToMD5(fileInfo.Name + fileInfo.Length);
PicModel pic = new PicModel()
{
UploadType = 2,
BaseRequest = new BaseRequest(this._wxModel),
TotalLen = fileInfo.Length,
FromUserName = this.User.UserName,
ToUserName = model.UserName,
FileMd5 = md5
};
data.Add(new FormItemModel("uploadmediarequest", JsonHelper.SerializeObject(pic)));
//webwx_data_ticket
string ticket = this.GetCookie("webwx_data_ticket");
data.Add(new FormItemModel("webwx_data_ticket", ticket));
//pass_ticket
data.Add(new FormItemModel("pass_ticket", this._wxModel.pass_ticket));
//file
data.Add(new FormItemModel("filename", fileInfo.Name, filePath));
var bytes = CreateFileByte(data, boundary);
HttpItem item = new HttpItem()
{
URL = "https://file.wx.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json",
Method = "POST",
PostDataType = PostDataType.Byte,
ContentType = "multipart/form-data; boundary=" + boundary,
PostEncoding = Encoding.UTF8,
PostdataByte = bytes
};
HttpHelper http = new HttpHelper();
var rst = http.GetHtml(item);
return rst;
}
//实体类模型
public class FormItemModel
{
public FormItemModel(string key, string value)
{
this.Key = key;
this.Value = value;
}
public FormItemModel(string key, string name, string path)
{
this.Key = key;
this.Value = name;
this.FilePath = path;
}
/// <summary>
/// 表单键,request["key"]
/// </summary>
public string Key { set; get; }
/// <summary>
/// 表单值,上传文件时为文件名
/// </summary>
public string Value { set; get; }
/// <summary>
/// 是否是文件
/// </summary>
public bool IsFile
{
get
{
if (string.IsNullOrEmpty(FilePath) && FileContent == null) return false;
return true;
}
}
public byte[] GetFileBytes(Encoding encoding)
{
if (encoding == null) encoding = Encoding.UTF8;
if (IsFile)
{
try
{
if (!string.IsNullOrEmpty(FilePath))
{
using (FileStream fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
byte[] bytes = new byte[fileStream.Length];
fileStream.Read(bytes, 0, bytes.Length);
return bytes;
}
}
else
{
byte[] bytes = new byte[FileContent.Length];
FileContent.Read(bytes, 0, bytes.Length);
FileContent.Seek(0, SeekOrigin.Begin);
return bytes;
}
}
catch (Exception)
{
return null;
}
}
else
{
return null;
}
}
//如果是文件,下面两个只要填一个即可.
/// <summary>
/// 上传的文件路径
/// </summary>
public string FilePath { set; get; }
/// <summary>
/// 文件流
/// </summary>
public Stream FileContent { get; set; }
}
//核心postdata 字节创建方法
private byte[] CreateFileByte(List<FormItemModel> formItems, string boundary, Encoding encoding = null)
{
if (encoding == null) encoding = Encoding.UTF8;
Stream postStream = new MemoryStream();
//文件模板
string fileFormdataTemplate =
"\r\n--" + boundary +
"\r\nContent-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"" +
"\r\nContent-Type: application/octet-stream" +
"\r\n\r\n";
//文本数据模板
string dataFormdataTemplate =
"\r\n--" + boundary +
"\r\nContent-Disposition: form-data; name=\"{0}\"" +
"\r\n\r\n{1}";
foreach (var item in formItems)
{
string formdata = null;
if (item.IsFile)
{ //文件
formdata = string.Format(
fileFormdataTemplate,
item.Key, //表单键
item.Value);
}
else
{ //文本
formdata = string.Format(
dataFormdataTemplate,
item.Key,
item.Value);
}
byte[] formdataBytes = null;
if (postStream.Length == 0)
formdataBytes = encoding.GetBytes(formdata.Substring(2));
else
formdataBytes = encoding.GetBytes(formdata);
//写入流
postStream.Write(formdataBytes, 0, formdataBytes.Length);
if (item.IsFile)
{
var bytes = item.GetFileBytes(encoding);
if (bytes == null)
{
this.PrintErrlog("读取文件错误:" + JsonHelper.SerializeObject(item));
throw new Exception("读取文件错误");
}
postStream.Write(bytes, 0, bytes.Length);
}
}
var footer = encoding.GetBytes("\r\n--" + boundary + "--\r\n");
postStream.Write(footer, 0, footer.Length);
byte[] buffer = StreamToBytes(postStream);
return buffer;
}
如果上传图片成功,服务器会返回一个MediaId,相当于图片已经成功上传到微信服务器了,这个就是图片的key
2.发送图片消息 webwxsendmsgimg
发送图片接口就比较简单了,和文字消息差不多,要改的就是Msg中的Type换成3,再加上刚才得到的MediaId,其他的都基本差不多,当返回的消息BaseResponse.Ret == 0 的时候,你就成功了
接下来那些发送小视频,声音等等的由于本人实际需求用不到,就没有继续写了.本来还想写个自动抢红包的,发现网页版根本不支持抢红包,就无从写起了...