Kindeditor编辑器空格键被莫名删除解决方法

kindeditor.js 第725行(可以查找 var re = 试下),代码换成下面的内容。

var re = /([ \f\n\r\t\v]*)<(\/)?([\w\-:]+)((?:\s+|(?:\s+[\w\-:]+)|(?:\s+[\w\-:]+=[^\s"'<>]+)|(?:\s+[\w\-:"]+="[^"]*")|(?:\s+[\w\-:"]+='[^']*'))*)(\/)?>([ \f\n\r\t\v]*)/g;

就是把第一个和最后一个\s换成[ \f\n\r\t\v] ,上面的代码是已经替换的。

2017/12/20 posted in  网站开发

用TypeScript来写推箱子游戏

先介绍下TypeScript

TypeScript 是JavaScript类型的超集,可以编译成纯JavaScript,由微软开发。允许JavaScript开发者在开发JavaScript应用程序时使用高效的开发工具和常用操作比如静态检查和代码重构。

讲讲游戏

推箱子游戏,在一个狭小的仓库中,要求把木箱放到指定的位置,稍不小心就会出现箱子无法移动或者通道被堵住的情况,所以需要巧妙的利用有限的空间和通道,合理安排移动的次序和位置,才能顺利的完成任务。

使用的技术栈

Vue —用来渲染游戏
TypeScript —实现游戏逻辑

地图
游戏中的地图,我使用的是number[][]一个二维数组来表示。一维数组的下标表示坐标系的Y轴,二维数组的下标表示坐标系的X轴,二维数组的值是改坐标点表示的元素角色,根据不同的角色去渲染。

/**
* 地图坐标点角色
*/
enum Role {
    /**
     * 空白
     */
    Blank,
    /**
     * 墙
     */
    Wall,
    /**
     * 小人
     */
    People,
    /**
     * 站在点上小人
     */
    PeopleDot,
    /**
     * 箱子
     */
    Chest,
    /**
     * 箱子点
     */
    Dot,
    /**
     * 到点的箱子
     */
    ChestDot
}

代码中,我用一个Map类封装保存当前地图的状态和一些常用方法,比如当前关卡的层级、所有关卡的初始地图数据。

界面用vue去渲染

<template v-for="(yItem,y) in Map">
<ul>
    <li v-for="(xItem,x) in yItem" @click="SetRole(y,x)">
        <div :class="getRoleClass(xItem)" style="width:100%;height:100%;" :title="xItem" :key="y + '-' + x"></div>
    </li>
</ul>
<div style="clear:both;"></div>
</template>

小人
小人需要上、下、左、右的四处移动,并且移动之前还需要判断,如果是墙就不能移动;如果是箱子,就需要推动箱子。这也是推箱子游戏中核心的功能逻辑。所以在代码中,用一个People类来封装,保存小人当前的坐标和移动操作。

class People {
    private currentCoord: Coord;
    constructor(coord: Coord) {
        this.currentCoord = coord;
    }

    private getNewCoord(oldCoord: Coord, potion: MovePotion): Coord {
        let newCoord: Coord = {
            Y: oldCoord.Y,
            X: oldCoord.X
        }
        switch (potion) {
            case MovePotion.Up:
                newCoord.Y--;
                break;
            case MovePotion.Right:
                newCoord.X++;
                break;
            case MovePotion.Down:
                newCoord.Y++;
                break;
            case MovePotion.Left:
                newCoord.X--;
                break;
        }

        return newCoord;
    }

    setCoord(coord: Coord) {
        this.currentCoord.Y = coord.Y;
        this.currentCoord.X = coord.X;
    }

    /**
     * 移动小人
     * @param potion 移动方向
     */
    move(potion: MovePotion) {
        let peopleNewCoord: Coord = this.getNewCoord(this.currentCoord, potion);
        let currentCoordRole: Role = game.getCoordRole(this.currentCoord);
        let newCoordRole: Role = game.getCoordRole(peopleNewCoord);
        if (!(newCoordRole == Role.Blank || newCoordRole == Role.Chest || newCoordRole == Role.Dot || newCoordRole == Role.ChestDot))
            return;

        let updateCurrentCoord = () => {
            this.currentCoord.Y = peopleNewCoord.Y;
            this.currentCoord.X = peopleNewCoord.X;
            game.addPace();
        }

        if (newCoordRole == Role.Blank) {
            game.setCoordRole(peopleNewCoord, Role.People);
            game.setCoordRole(this.currentCoord, currentCoordRole == Role.PeopleDot ? Role.Dot : Role.Blank);
            updateCurrentCoord();
        } else if (newCoordRole == Role.Dot) {
            game.setCoordRole(peopleNewCoord, Role.PeopleDot);
            game.setCoordRole(this.currentCoord, currentCoordRole == Role.PeopleDot ? Role.Dot : Role.Blank);
            updateCurrentCoord();
        } else if (newCoordRole == Role.Chest || newCoordRole == Role.ChestDot) {
            let newChestCoord: Coord = this.getNewCoord(peopleNewCoord, potion);
            let newChestCoordRole: Role = game.getCoordRole(newChestCoord);
            if (newChestCoordRole == Role.Wall || newChestCoordRole == Role.ChestDot || newChestCoordRole == Role.Chest)
                return;

            game.setCoordRole(newChestCoord, newChestCoordRole == Role.Dot ? Role.ChestDot : Role.Chest);
            game.setCoordRole(peopleNewCoord, newCoordRole == Role.ChestDot ? Role.PeopleDot : Role.People);
            game.setCoordRole(this.currentCoord, currentCoordRole == Role.PeopleDot ? Role.Dot : Role.Blank);
            updateCurrentCoord();
        }

        game.verifySuccess();
    }
}

游戏交互
最后用一个Game类来封装游戏的渲染、交互等,用Vue给方向键绑定点击事件。

写在最后
总的来说,用TypeScript来开发JavaScript项目,是一个不错的选择,既有了类型检查,又不会失去JavaScript语言的灵活性。在加上VS Code IDE的智能感知,开发起来方便快速。

GitHub
演示地址

2017/11/25 posted in  网站开发

阿里云短信平台发送短信开发

官方文档:
短信发送API
短信业务规则

一、开发准备

下载SDK
申请秘钥
申请短信签名
添加短信模板

二、.NET版发送短信

需要引入SDK中的aliyun-net-sdk-core.dll和aliyun-net-sdk-dysmsapi.dll

using Aliyun.Acs.Core;
using Aliyun.Acs.Core.Exceptions;
using Aliyun.Acs.Core.Profile;
using Aliyun.Acs.Dysmsapi.Model.V20170525;

namespace Whir.SMS.AliYun
{
    public class SendSMS
    {
        private readonly string _sign;
        private readonly string _accessKeyId;
        private readonly string _accessKeySecret;
        /// <summary>
        /// </summary>
        /// <param name="sign">签名</param>
        /// <param name="accessKeyId">Access Key Id</param>
        /// <param name="accessKeySecret">Access Key Secret</param>
        public SendSMS(string sign, string accessKeyId, string accessKeySecret)
        {
            _sign = sign;
            _accessKeyId = accessKeyId;
            _accessKeySecret = accessKeySecret;
        }

        /// <summary>
        /// </summary>
        /// <param name="templateCode">短息模板Code</param>
        /// <param name="phone">手机号</param>
        /// <param name="content">内容键值对,{ 占位符名称:内容 }</param>
        /// <param name="outId">外部流水扩展字段,回执消息中带回</param>
        /// <returns></returns>
        public SendResult Send(string templateCode, string phone, Dictionary<string, string> content, string outId = "")
        {
            String product = "Dysmsapi";//短信API产品名称
            String domain = "dysmsapi.aliyuncs.com";//短信API产品域名
            String accessKeyId = _accessKeyId;//你的accessKeyId
            String accessKeySecret = _accessKeySecret;//你的accessKeySecret

            IClientProfile profile = DefaultProfile.GetProfile("cn-hangzhou", accessKeyId, accessKeySecret);

            DefaultProfile.AddEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
            IAcsClient acsClient = new DefaultAcsClient(profile);
            SendSmsRequest request = new SendSmsRequest();
            try
            {
                //必填:待发送手机号。支持以逗号分隔的形式进行批量调用,批量上限为20个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式
                request.PhoneNumbers = phone;
                //必填:短信签名-可在短信控制台中找到
                request.SignName = _sign;
                //必填:短信模板-可在短信控制台中找到
                request.TemplateCode = templateCode;
                //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
                request.TemplateParam = content != null && content.Keys.Count > 0 ? content.ToJson() : "";
                //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
                request.OutId = outId;
                //请求失败这里会抛ClientException异常
                SendSmsResponse sendSmsResponse = acsClient.GetAcsResponse(request);

                if (sendSmsResponse.Code.Equals("OK"))
                {
                    return new SendResult
                    {
                        IsSuccess = true,
                        ResponseData = sendSmsResponse.BizId
                    };
                }

                return new SendResult
                {
                    IsSuccess = false,
                    Message = sendSmsResponse.Message
                };
            }
            catch (ServerException e)
            {
                return new SendResult
                {
                    IsSuccess = false,
                    Message = e.Message
                };
            }
            catch (ClientException e)
            {
                return new SendResult
                {
                    IsSuccess = false,
                    Message = e.Message
                };
            }
        }
    }
}
2017/11/25 posted in  网站开发

Kindeditor 跨域上传图片

  1. 首先接受上传图片的域名需要加上 Access-Control-Allow-Origin 响应头,可以在IIS中添加请求头
  2. 在上传站点新建一个页面,上传成功之后跳转到这个页面,将上传结果通过Url参数传递到此页面。注意:需要区分批量上传,批量上传不需要跳转
  3. 批量上传时用到的是 swfupload,需要把 swfupload.swf 文件放到跨域的服务器
2017/11/25 posted in  网站开发

利用Vue、Socket.io 实现在线五子棋对战

网上有很多利用前端技术来做五子棋的Dome,所以本人为了学习socket.io 就自己也撸了一个。

游戏状态数据

棋盘由一个个方块组成,棋子落在横竖线的交界处

<table cellpadding="0" cellspacing="0">
    <tr v-for="(itemY,y) in Board">
        <td v-for="(itemX,x) in itemY" :class="getBoardCellClass(y,x)" style="position: relative;">
            <div class="cell"></div>
            <div :class="getPieceClass(y,x)" @click="clickPiece(y,x)"></div>
        </td>
    </tr>
</table>

这里我是用table和一个二维数组来画的棋盘,Board[y][x] === 0表示该位置为空,Board[y][x] === 1表示放置白子,Board[y][x] === -1表示放置黑子

下棋动作

由于给每个横竖交叉点都设置初始化数据,所以加上监听 clickPiece(y,x) 方法即可

判断输赢

这是五子棋最核心的地方,相对也是最麻烦的地方,每次落子都要从横向、竖向、两个斜向四个方向的其他棋子来判断结果。this.CurrentPieceRole 为当前落子角色

function verifySuccess(y, x) {
    function getContinuationPieceCount(callback) {
        var count = 1;
        for (var i = 1; i < 5; i++ , count++) {
            if (!callback(i)) {
                break;
            }
        }
        for (var z = -1; z > -5; z-- , count++) {
            if (!callback(z)) {
                break;
            }
        }
        return count === 5;
    }

    function legalY(i) {
        return i > -1 && i < boardY;
    }

    function legalX(i) {
        return i > -1 && i < boardX;
    }

    var yAxie = (() => {
        return getContinuationPieceCount((i) => {
            return legalY(y + i) && this.Board[y + i][x] === this.CurrentPieceRole;
        });
    });

    var xAxie = (() => {
        return getContinuationPieceCount((i) => {
            return legalX(x + i) && this.Board[y][x + i] === this.CurrentPieceRole;
        });
    });

    var yxAxie = (() => {
        return getContinuationPieceCount((i) => {
            return legalY(y + i) && legalX(x + i) && this.Board[y + i][x + i] === this.CurrentPieceRole;
        });
    });

    var xyAxie = (() => {
        return getContinuationPieceCount((i) => {
            return legalY(y - i) && legalX(x + i) && this.Board[y - i][x + i] === this.CurrentPieceRole;
        });
    });

    return xAxie() || yAxie() || yxAxie() || xyAxie();
}

利用 socket.io 进行联机

每个联机的用户都可以作为主机,每个联机的用户都有一个socket.id,可以通过选择在线的用户进行联机。

服务端

let socketClients = {} 当前在线用户对象,socket.id和socket对象映射关系 id:socket
let fiveDesk = [] 当前进行的棋局数组 { White : id1 , Black : id2 }

这里主要有两个事件
* 联机
* 落子

联机

监听联机事件,一方请求联机,通过服务器发送请求信息给对手,并新增棋局数据

socket.on('applyConnect', function (socketId) {
    var thisClient = socketClients[socket.id];
    if (!socketClients.hasOwnProperty(socketId)) {
        thisClient.socket.emit('errorResult', { IsSuccess: false, Message: '对方不在线' });
        return;
    }

    if (existsDesk(socketId)) {
        thisClient.socket.emit('errorResult', { IsSuccess: false, Message: '对方已加入其它棋局' });
        return;
    }

    var client = socketClients[socketId];
    if(!thisClient || !client)
        return;

    fiveDesk.push({
        White: thisClient.id,
        Black: client.id,
        Result: false
    });
    client.socket.emit('applyConnect', {
        IsSuccess: true,
        Message: thisClient.name,
        DeskId: fiveDesk.length - 1
    });
});

同意请求之后,双方开始棋局

socket.on('agreeConnect', function (deskId) {
    if (fiveDesk.length > deskId) {
        var desk = fiveDesk[deskId];
        desk.Result = true;

        socketClients[desk.White].socket.emit('deskBegin', { role: 1, deskId: deskId });
        socketClients[desk.Black].socket.emit('deskBegin', { role: -1, deskId: deskId });
    }
});

落子

监听落子事件,接收坐标和落子信息,并通知给对手

socket.on('clickPiece', function (data) {
    var deskId = data.deskId;
    if (fiveDesk.length > deskId) {
        var desk = fiveDesk[deskId];
            if(!desk.Result)
                return;

        var socket;
        if (data.role === 1) {
            socket = socketClients[desk.Black].socket;
        } else {
            socket = socketClients[desk.White].socket;
        }

        socket.emit('serverClickPiece', { x: data.x, y: data.y })
    }
});

客户端

客户端比较简单,直接贴几个关键的事件

socket.emit('applyConnect', socketId); //请求联机

//发送落子信息, this.ServerRole 为当前客户端棋盘角色
socket.emit('clickPiece', {
    x: x,
    y: y,
    deskId: vueHander.getDeskId(),
    role: this.ServerRole
});

//接收对手落子
socket.on('serverClickPiece', function (data) {
    vueHander.clickPiece(data.y, data.x);
});

Demo

主要是用来学习socket.io,所以很多细节方面没有处理,bug多了点

2017/11/25 posted in  网站开发