第二周
主要实现了英雄的获取及拖拽展示逻辑等操作
实现目标
英雄的随机获取
比例按照腾讯官方给出的比例来(没有考虑7费拉克丝的出现情况)
接口
GET /api/heroes/getRealRandomHeroes
参数
| 名称 | 类型 | 其他 |
|---|---|---|
| level | number | 当前用户等级 |
| number | number | 当前获取的英雄数目 |
返回
{
"code": 0,
"data": [
{
"heroId": 163,
"price": 1,
"id": 97,
"equipment": "4004,3026,3107",
"hero_name": "塔莉垭",
"hero_tittle": "岩雀",
"imgUrl": "",
"job": 17,
"otherjob": 0,
"otherrace": 0,
"partner": "",
"race": "22",
"season_id": "2",
"skill_introduce": "塔莉垭在拥有最多当前法力值的敌人脚下引发岩突,对其造成魔法伤害,并将其晕眩2秒。此外,这个技能还会将远程英雄拉近,或将近战英雄推离。",
"skill_name": "岩突",
"skill_num": "伤害:150/350/550",
"skill_type": "主动",
"special_desc": "",
"special_heroid": "",
"createdAt": "2019-12-18T03:46:12.000Z",
"updatedAt": "2019-12-18T03:46:12.000Z",
"info": {
"CR": "25%",
"Mana": "80",
"StartingMana": "50",
"armor": "20",
"damage": "40",
"dps": "26",
"health": "500",
"magic_res": "20",
"name": "1",
"range": "3",
"speed": "0.65"
},
"leftHealth": "500",
"leftMana": "50"
},
...
],
"msg": "OK"
}
算法
其实很简单的。
const getRateResult = (arr) => {
if (arr.length === 1) {
return 1
}
let leng = 0;
let tmpArr = [];
for(let i=0; i<arr.length; i++){
leng+=arr[i];//获取总数
tmpArr[i + 1] = arr[i]; //重新封装奖项,从1开始
}
tmpArr[0] = 0;
for(let i=0; i<tmpArr.length; i++){
if(i > 0) {
tmpArr[i] += tmpArr[i - 1]; //计算每项中奖范围
}
}
let random = parseInt(Math.random()*leng); //获取 0-总数 之间的一个随随机整数
for(let i=1; i<tmpArr.length; i++){
if(tmpArr[i - 1] <= random && random < tmpArr[i]) {
return i; //返回中奖的项数(按概率的设置顺序)
}
}
}
利用这个方法,获取到应该是几费的英雄,之后利用lodash的sample方法,从该费下所有英雄中抽一个出来即可。
英雄分类
const heroes = {
hero1: _.filter(config.heroes, (hero) => hero.price === 1 && hero.season_id === '2'),
hero2: _.filter(config.heroes, (hero) => hero.price === 2 && hero.season_id === '2'),
hero3: _.filter(config.heroes, (hero) => hero.price === 3 && hero.season_id === '2'),
hero4: _.filter(config.heroes, (hero) => hero.price === 4 && hero.season_id === '2'),
hero5: _.filter(config.heroes, (hero) => hero.price === 5 && hero.season_id === '2')
}
随机英雄
const getRealRandomHeroes = async (ctx, next) => {
try {
const pickResult = [];
const { level, number } = ctx.query;
const rateArr = _.values(config.pickRate[level]);
for (let i = 0; i<number; i++) {
const star = getRateResult(rateArr);
pickResult.push(_.sample(heroes[`hero${star}`]))
}
ctx.response.body = resBeautiful.set(_.map(pickResult, (hero) => ({
..._.omit(hero, 'level'),
info: hero.level[0],
leftHealth: hero.level[0].health,
leftMana: hero.level[0].StartingMana
})));
} catch (error) {
ctx.app.emit('error', error, ctx);
}
}
英雄出现概率

拖拽逻辑
这里没有使用对应的拖拽插件什么的,感觉用起来不自由,使用的是H5的Drag系列API,感觉还挺好用的,理解起来也不是很费力。
这里是菜鸟教程的文档,W3school的文档不是很容易理解,推荐菜鸟的。
需要的函数有四个:
dragStart 开始拖拽
dragging 正在拖拽(鼠标点击了但是没有松开的时候)
allowDrop 是否允许放置(感觉没啥用)
drop 放置之后的回调
React中属性有五个(回调对应上面的函数):
onDrop={this.drop()} -> drop
onDragOver={this.allowDrop.bind(this)} -> allowDrop
onDragStart={this.dragStart()} -> dragStart
onDrag={this.dragging()} -> dragging
draggable="true" -> 当前元素是否可以拖动,可以设置为只能放置不能拖动
在本次的代码中,allowDrop和dragging其实没有用到,但是目测没法不填,没有e.preventDefault()无法正常拖拽。
而且,还有一个点需要注意的就是在拖拽过程中可以附带一些参数,e.dataTransfer.setData(name, content)即可为设置参数,然后使用e.dataTransfer.getData(name)获取参数,当然了,这里有个弊端,就是只能设置String类型的内容,其他类型的值需要使用JSON.stringify来转换一下。
示例代码如下:
heroTableItem.jsx
/**
* @description: 允许drop定义方法
* @param {DropEvent} event 拖拽事件
* @return: void
*/
allowDrop(event) {
event.preventDefault();
}
/**
* @description: 拖拽放置方法
* 拿到传递过来的hero, index, from参数
* 如果来自table,去掉旧的hero,新增新的hero
* 否则去掉waitting中hero,table新增hero
* @param {DropEvent} e 拖拽事件
* @return: void
*/
drop = () => e => {
e.preventDefault()
const { hero, index, from } = JSON.parse(e.dataTransfer.getData('info'));
...
}
/**
* @description: 开始拖拽
* 如果当前没有hero,直接return掉
* 如果有hero,封装hero, index, from参数进拖拽事件
* @param {DropEvent} e 拖拽事件
* @return: void
*/
dragStart = () => e => {
const hero = this.props.hero;
if (!hero) {
return e.preventDefault();
}
e.dataTransfer.setData('info', JSON.stringify({
hero: this.props.hero,
index: this.props.index,
from: 'table'
}));
}
/**
* @description: 拖动中
* @param {DropEvent} e 拖拽事件
* @return: void
*/
dragging = () => e => {
e.preventDefault()
}
大概齐就是这个意思了,使用原生API还有一个好处就是可以跨组件操作,也不用做任何操作,十分方便,甚至在拖动中可以自定义图片的内容,不过好像样式不好调节,不过问题不大。
遇到问题
React获取e
此e非彼e,如果按照正常的操作,我们是这么拿到e的:
绑定:
onDrop={(e) => this.drop(e)}
获取:
drop (e) {}
这种情况下的e其实是默认的事件,里面的内容也与本次拖拽毫无关系,要想拿到真正的e,得这么操作
绑定:
onDrop={this.drop()}
获取:
drop = () => e => {}
如此便能拿到drop真正的e了,而且也可以顺利获取到dataTransfer中带有的数据了,使用之前的方法压根就没有dataTransfer这个属性。
拖拽的逻辑修改
本来是从HeroList中直接拖拽到HeroWaitting上,后来发现这样的操作有些问题,所以改成了双击HeroList元素自动填充到HeroWaitting中,之后可以从HeroWaitting中拖拽到HeroTable中,当从HeroWaitting中拖动到HeroList上时,直接售卖掉这个Hero。之后从HeroTable中也可以拖动到HeroWaitting中。
主要就是这样的一个逻辑。
小结
这周的工作其实也还好,抽奖逻辑感觉还有些问题,但是问题不大,日后有时间再做改进,先实现主要功能再说