-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
359 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# 拖拽其实没有那么难 | ||
|
||
## 前言 | ||
|
||
大家是否在个人开发或者公司项目中遇到过拖拽相关的需求呢?之前我从未处理过这块的内容,碰巧上回接到了这么一个拖拽的需求。当时心里一咯噔:“完了!这个我没有处理过,不会啊!😭”。 | ||
|
||
不过在真正去了解了一下这块的内容,发现其实拖拽也没有我们想象的那么难(对于web端来说)。现在原生对于拖拽的支持已经非常好了。 | ||
|
||
## 效果 | ||
|
||
我们来看这么一个效果: | ||
|
||
![Kapture 2023-12-30 at 10.46.24](https://image.jimmyxuexue.top/img/202312301048064.gif) | ||
|
||
第一眼看着是不是觉得还有点复杂,监听鼠标按下事件,监听鼠标移动,监听鼠标放开,想想就害怕,但其实并不是,代码只有这么几行: | ||
|
||
```html | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>拖拽demo</title> | ||
<style> | ||
body { | ||
display: flex; | ||
} | ||
.box1, | ||
.box2 { | ||
width: 300px; | ||
height: 600px; | ||
border: 1px solid #ccc; | ||
} | ||
.box2 { | ||
margin-left: 50px; | ||
} | ||
.item { | ||
width: 100px; | ||
height: 50px; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
background-color: skyblue; | ||
margin-top: 10px; | ||
margin-left: 10px; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div class="box1"> | ||
<div class="item" draggable="true" ondragstart="dragStart(event,'手机')"> | ||
手机 | ||
</div> | ||
<div class="item" draggable="true" ondragstart="dragStart(event,'电脑')"> | ||
电脑 | ||
</div> | ||
<div class="item" draggable="true" ondragstart="dragStart(event,'耳机')"> | ||
耳机 | ||
</div> | ||
</div> | ||
<div | ||
class="box2" | ||
ondrop="dropEnd(event)" | ||
></div> | ||
</body> | ||
|
||
<script> | ||
const box2 = document.querySelector('.box2') | ||
function dragStart(e, device) { | ||
e.dataTransfer.setData('device', device) | ||
} | ||
function dropEnd(e) { | ||
var transferredDevice = e.dataTransfer.getData('device') | ||
const div = document.createElement('div') | ||
div.textContent = transferredDevice | ||
div.classList.add('item') | ||
box2.appendChild(div) | ||
} | ||
</script> | ||
</html> | ||
``` | ||
|
||
核心就是这两步: | ||
|
||
- 给需要拖拽的元素加上`draggable = true` 的属性,同时设置`ondragstart`的事件处理函数 | ||
|
||
可以通过句柄`e`中以`key,value`存储我们想要存储的数据 | ||
|
||
`e.dataTransfer.setData('device', device)` | ||
|
||
- 给可放下拖拽的容器设置`ondrop`放手事件处理函数 | ||
|
||
可以通过句柄e获取我们之前存储的数据 | ||
|
||
基于以上的两步我就可以完成一个拖拽的任务了。 | ||
|
||
## 兼容性 | ||
|
||
在[caniuse](https://caniuse.com/?search=drag)中我们也得知,大部分主流的浏览器其实都是已经支持的了。 | ||
|
||
![image-20231230104249719](https://image.jimmyxuexue.top/img/202312301042869.png) | ||
|
||
|
||
|
||
## 总结 | ||
|
||
拖拽其实没有我们想象的那么难了,之前没有这个api之前可能就相对复杂要处理各种鼠标事件,现在一个api就完成了,在不需要考虑兼容性的情况下还是可以放心大胆的使用的~😄 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
# 小程序商品活动金额计算 | ||
|
||
## 核心流程 | ||
|
||
小程序进入预下单页面之前,涉及的所有金额的计算均有前端进行计算。 | ||
|
||
进入预下单页面之后的金额由后端的`preAddOrder`接口计算。接口返回与上一期[pos 支付](https://517tastien.feishu.cn/wiki/H3XpwgEnRixdAxk04PlcsWornRd)的内容是一致的。 这块不是本次核心讨论点。 | ||
|
||
### 为什么要如此设计 | ||
|
||
- 少调用几次接口,减轻后端压力 | ||
|
||
如果放在后端计算,那么每次购物车变动,都会调用一次接口,造成的请求数量将是成指数级别的上升,根本顶不住。 | ||
|
||
- 能够让页面操作更加流程 | ||
|
||
如果每次购物车变动都需要等待几百毫秒才能计算出价格,整个操作流程是非常卡顿和有割裂感的。放前端计算能够让用户在操作的时候更加丝滑。 | ||
|
||
### app 和 小程序下单流程是类似的,app 为什么不采用这种模式 | ||
|
||
- app 体量还是相对比较小,全国就几千家门店,接口完全顶得住。 | ||
|
||
- 维护成本变高,一旦逻辑变更,需要两端的开发同学一起同步更进维护,在体量小的情况下,恶心的事情一个人做就好了。 | ||
|
||
- 都是自己人,下单的流程金额出现的慢一点没事。 | ||
|
||
## 商品活动类型 | ||
|
||
商品活动主要有是三种: | ||
|
||
> 后续 CodeReview 主要也针对这块代码进行 CodeReview | ||
- 限时折扣 `STRAIGHT_AT` | ||
|
||
> 没有任何门槛,好比于一个特价 6 元汉堡 🍔,直接购买就是 6 元。 | ||
🌰: | ||
|
||
只要购物车有这个 🍔,价格均以 6 元进行计算 | ||
|
||
- 满减折扣 `FULL` | ||
|
||
> 满足一定数量,才支持折扣,如:够买 N 件 其中 M 件享受优惠 🍔 | ||
> | ||
> 注意: | ||
> | ||
> **这里的满减折扣指的不是金额的满减,而是商品数量级别的满减** | ||
🌰: | ||
|
||
🍟 满 3 件,其中 1 件五折 | ||
|
||
- 购买 2 件,均原价 | ||
- 购买 3 件,1 件五折,2 件原价 | ||
- 购买 10 件,1 件五折,9 件原价 | ||
|
||
- 每购买 N 件,其中 M 件享折扣 `EVERY_FULL` | ||
|
||
> 这个场景就相对复杂一点。 | ||
🌰: | ||
|
||
🍔 每满 3 件,其中 2 件享五折。 | ||
|
||
- 购买 2 件,则均原价 | ||
- 购买 3 件,2 件五折,1 件原价 | ||
- 购物 5 件,2 件五折,1 件原价 | ||
- 购买 7 件,4 件五折,3 件原价 | ||
|
||
## 核心参数 | ||
|
||
这里我们以小程序视角来看: | ||
|
||
![image-20231225094555009](https://image.jimmyxuexue.top/img/202312250946821.png) | ||
|
||
商品所有的数据均来源于小程序端的`store/product/mini/query`这个接口,活动信息存放在 activitys 这个数组中。 | ||
|
||
> 虽然是数组,但是这块好像是只会返回一条数据,因为一个商品只会享受一个活动。 | ||
其中最为关键的数据是`activityId`和`extJson`字段。 | ||
|
||
`activityId`这个字段用于判断这个商品是否所属于哪个活动。 | ||
|
||
> 不同的商品可以共享同一个活动 | ||
🌰: | ||
|
||
🍔和🍟都共同参与满二,其中一件半价的活动。所以这时时候只要这么情况就可以触发这个活动: | ||
|
||
- 两个🍔 | ||
- 两个🍟 | ||
- 一个🍔和一个🍟 | ||
|
||
**** | ||
|
||
`extJson` 这个字段,其返回的是一个JSON化的字符串,需要我们手动`JSON.parse`处理一下。 | ||
|
||
> q:为啥要这样设计?是为了数据量小一点加快请求速度吗? | ||
这个字段是一个还是相对大的数据: | ||
|
||
```ts | ||
{"activityCanBuyTotal":2147483647,"addPriceFlag":false,"channel":"1,3,2","cornerMark":null,"cornerMarkBackgroundColor":null,"cornerMarkFlag":false,"cornerMarkFontColor":null,"discountConditionType":"STRAIGHT_AT","discountConditionValue":1,"joinActivityLimit":null,"joinDayLimit":5,"originalPrice":null,"originalPriceFlag":true,"price":33.80,"priceSort":"","priceType":"SPECIAL_PRICE","productLimit":"SINGLE","productQuantityLimit":5,"showActivityPrice":33.80,"todayCanBuyTotal":5,"userType":"CARD"} | ||
``` | ||
|
||
解析后得到这么一串数据: | ||
|
||
```ts | ||
{ | ||
"activityCanBuyTotal":2147483647, // 活动的商品总数量 | ||
"addPriceFlag":false, // 加料是否算进优惠 | ||
"channel":"1,3,2", | ||
"cornerMark":null, | ||
"cornerMarkBackgroundColor":null, | ||
"cornerMarkFlag":false, | ||
"cornerMarkFontColor":null, | ||
"discountConditionType":"STRAIGHT_AT", // 折扣类型 | ||
"discountConditionValue":1, | ||
"joinActivityLimit":null, | ||
"joinDayLimit":5, | ||
"originalPrice":null, | ||
"originalPriceFlag":true, | ||
"price":33.8, | ||
"priceSort":"ASC", // 商品排序的顺序 | ||
"priceType":"SPECIAL_PRICE", | ||
"productLimit":"SINGLE", // 活动是否支持跨商品 | ||
"productQuantityLimit":5, // 商品可享受的数量 | ||
"showActivityPrice":33.8, | ||
"todayCanBuyTotal":5, // 用户当天最多可以买的数量 | ||
"userType":"CARD" | ||
} | ||
``` | ||
|
||
这里最为关键的是这么六个字段: | ||
|
||
- discountConditionType | ||
|
||
商品活动类型:限时折扣、N减折扣 | ||
|
||
> 上面详细介绍过,这里不过过多介绍 | ||
- productLimit | ||
|
||
商品限制:"SINGLE"||"MULTI" | ||
|
||
商品活动是否支持跨商品 | ||
|
||
> 当🍔和🍟都支持5折活动。且满3减,其中2件参与互动 | ||
- SINGLE | ||
|
||
只有至少3件🍔,或者3件🍟才能满足活动,如果1个🍔两个🍟是不能享受活动的。 | ||
|
||
- MULTI | ||
|
||
🍔或者🍟,只要二者加起来有3件就可以参与活动。如1个🍔和2个🍟。 | ||
|
||
- priceSort | ||
|
||
价格排序:"ASC"||"DESC" | ||
|
||
> 当🍔(10元)和🍟(7元)都享受这个活动时,这时候到底用哪个商品参与活动呢?就是根据这个字段进行决定的 | ||
- ASC | ||
|
||
从高到底参与活动。则此时🍔参与活动 | ||
|
||
- DESC | ||
|
||
从低到高参与活动。则此时🍟参与活动 | ||
|
||
- addPriceFlag | ||
|
||
加料是否算进优惠 | ||
|
||
> 这个是给其他品牌打工的字段~ 我们涉及的加料的好像只有一两家门店。 | ||
🌰: | ||
|
||
一杯普通奶茶(10元)可以加小料,如:+珍珠(2元)、+椰果(3元)、+芋圆(3元)。 | ||
|
||
奶茶设置了特价五折的活动。 | ||
|
||
- 当加料算进优惠 | ||
- 奶茶 = 10/2 = 5 | ||
- 奶茶+珍珠 = (10+2)/2 = 6 | ||
- 奶茶+珍珠+椰果 = (10+2+3)/2 = 7.5 | ||
- 当加料不算进优惠 | ||
- 奶茶 = 10/2 = 5 | ||
- 奶茶+珍珠 = (10/2)+2 = 7 | ||
- 奶茶+珍珠+椰果 = (10/2)+2+3 = 10 | ||
|
||
- productQuantityLimit | ||
|
||
商品可参与活动的限制 | ||
|
||
🍔有5折限时折扣活动。 | ||
|
||
如这个限制是5,则就算是限时折扣活动,就算加购了10个🍔,最多也只有5个🍔能够参与活动。 | ||
|
||
- todayCanBuyTotal | ||
|
||
对于用户角度,今天还可以享受的活动次数。 | ||
|
||
> 与 productQuantityLimit 解析类似 | ||
- activityCanBuyTotal | ||
|
||
对这个活动总共可以卖的🍔数量。 | ||
|
||
以上的字段单独来看有的就有一些复杂了,但是他们如果组合起来,那个它的总体活动就机制复杂了。 | ||
|
||
核心计算逻辑如下: | ||
|
||
> 维护一个 ActivityMap,key: cartItem.id value: 可参与活动的数量 | ||
```ts | ||
export let ActivityMap: { [key in string]: number } = {} | ||
|
||
export function clearActivityMap() { | ||
ActivityMap = {} | ||
} | ||
``` | ||
|
||
1. 获取购物车中相同类型商品的数量 `getSameTypeCartItems` | ||
|
||
2. 获取每个项可参与活动的数量 | ||
|
||
> 这一块的处理前端是比后端更复杂的,如+珍珠和奶茶和+椰果的奶茶,在前端的购物车中是两项,而对于后端来说是同一个商品。 | ||
![image-20231225152022224](https://image.jimmyxuexue.top/img/202312251520272.png) | ||
|
||
3. 计算金额 | ||
|
||
### 小试牛刀: | ||
|
||
> 了解了字段之后,我们来小看一个🌰,一起来计算下,看看一个相对复杂的场景应该怎么算。 | ||
🌰: | ||
|
||
🍔(10元)支持(+加肉饼5元)和🍟(8元)支持(+10根3元)都参与每满3件其中2件5折的活动,且最多一个只能5个商品享受,此时购物车加购了4个🍔,4个🍟。 | ||
|
||
# 后台相关 | ||
|
||
活动相关的配置在:营销 => 提升客单 => 限时折扣||购N件享折扣 | ||
|
||
![image-20231225144928486](https://image.jimmyxuexue.top/img/202312251449596.png) | ||
|
||
其中相关配置均在此处进行设置: | ||
|
||
![image-20231225150309742](https://image.jimmyxuexue.top/img/202312251503774.png) |