Skip to content

Commit

Permalink
📝 拖拽文章更新
Browse files Browse the repository at this point in the history
  • Loading branch information
Jimmylxue committed Dec 30, 2023
1 parent eb3f135 commit 5ee6d98
Show file tree
Hide file tree
Showing 2 changed files with 359 additions and 0 deletions.
108 changes: 108 additions & 0 deletions docs/front-end-case/UI效果/拖拽其实没有那么难.md
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就完成了,在不需要考虑兼容性的情况下还是可以放心大胆的使用的~😄
251 changes: 251 additions & 0 deletions docs/job/商品活动.md
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)

0 comments on commit 5ee6d98

Please sign in to comment.