Merge branch 'dev' of ssh://git.online.zzfzyc.com:10022/mp/spider_cloud_warehouse into 订单页

This commit is contained in:
Haiyi 2022-10-20 15:12:16 +08:00
commit c5b74e3dd9
11 changed files with 14339 additions and 314 deletions

View File

@ -1,6 +1,7 @@
import { usePropsValue } from '@/use/useCommon'
import { View } from '@tarojs/components' import { View } from '@tarojs/components'
import classnames from 'classnames' import classnames from 'classnames'
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react' import React, { forwardRef, SetStateAction, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'
import IconFont from '../iconfont/iconfont' import IconFont from '../iconfont/iconfont'
import styles from './index.module.scss' import styles from './index.module.scss'
@ -37,7 +38,25 @@ export default forwardRef((props: params, ref) => {
customTextClass = '', customTextClass = '',
hiddenCheckboxIcon = false, hiddenCheckboxIcon = false,
} = props } = props
const [selected, SetSelected] = useState(false) console.log('Rerender component: checkbox', status)
const [selected, setSelected] = usePropsValue({
value: status,
defaultValue: false
})
// const selectedRef = useRef(status)
// if (status !== undefined){
// selectedRef.current = status
// }
// const [, setForceUpdate] = useState({})
// const setSelected = useCallback((v: SetStateAction<boolean>) => {
// const nextValue = typeof v === 'function' ? (v as (prevValue: boolean) => boolean)(selectedRef.current) : v
// console.log('对比',nextValue, selectedRef.current)
// if (nextValue === selectedRef.current) return
// selectedRef.current = nextValue
// console.log('强制刷新', nextValue, selectedRef.current)
// setForceUpdate({})
// }, [])
const onSelectEven = () => { const onSelectEven = () => {
if (disabled) return false if (disabled) return false
let res = !selected let res = !selected
@ -46,11 +65,12 @@ export default forwardRef((props: params, ref) => {
} else { } else {
onClose?.() onClose?.()
} }
SetSelected(res) console.log('res', res)
setSelected(res)
} }
const handleClickChildren = (event) => { const handleClickChildren = event => {
if (!triggerLabel){ if (!triggerLabel) {
return event.stopPropagation() return event.stopPropagation()
} }
} }
@ -76,9 +96,9 @@ export default forwardRef((props: params, ref) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
onSelectEven, onSelectEven,
})) }))
useEffect(() => { // useEffect(() => {
SetSelected(status) // setSelected(status)
}, [status]) // }, [status])
return ( return (
<View className={classnames(customClassName, styles.checkbox)} style={customStyles} onClick={onSelectEven}> <View className={classnames(customClassName, styles.checkbox)} style={customStyles} onClick={onSelectEven}>
{!hiddenCheckboxIcon && ( {!hiddenCheckboxIcon && (

View File

@ -15,6 +15,7 @@ type params = {
disable?: boolean, //是否禁用 disable?: boolean, //是否禁用
} }
export default ({minNum = 0, maxNum = 10000, step=1, digits = 0, defaultNum = 0, onChange, onBlue, onClickBtn, unit = '', disable = false}: params) => { export default ({minNum = 0, maxNum = 10000, step=1, digits = 0, defaultNum = 0, onChange, onBlue, onClickBtn, unit = '', disable = false}: params) => {
console.log('Rerender component: Counter');
const [value, setValue] = useState<any>({count:defaultNum}) const [value, setValue] = useState<any>({count:defaultNum})
const onPlus = (event) => { const onPlus = (event) => {
@ -65,22 +66,26 @@ export default ({minNum = 0, maxNum = 10000, step=1, digits = 0, defaultNum = 0,
if(res === '') { if(res === '') {
setValue({...value, count:minNum}) setValue({...value, count:minNum})
onChange?.(minNum) onChange?.(minNum)
return minNum
} }
else if(!isNaN(Number(res))) { else if(!isNaN(Number(res))) {
let count = formatDigits(res) let count = formatDigits(res)
count = checkData(count) count = checkData(count)
setValue({...value, count}) setValue({...value, count})
onChange?.(parseFloat(count as string)) onChange?.(parseFloat(count as string))
return count
} else { } else {
let num = parseFloat(res) let num = parseFloat(res)
if(!isNaN(num)) { if (!isNaN(num)) {
let count = formatDigits(num) let count = formatDigits(num)
count = checkData(count) count = checkData(count)
setValue({...value, count}) setValue({ ...value, count })
onChange?.(count as number) onChange?.(count as number)
return count
} else { } else {
setValue({...value, count:defaultNum}) setValue({ ...value, count: defaultNum })
onChange?.(defaultNum) onChange?.(defaultNum)
return defaultNum
} }
} }
@ -88,14 +93,16 @@ export default ({minNum = 0, maxNum = 10000, step=1, digits = 0, defaultNum = 0,
const onBluerEven = () => { const onBluerEven = () => {
let num = parseFloat(value.count) let num = parseFloat(value.count)
if(!isNaN(num)) { if (!isNaN(num)) {
let count = formatDigits(num) let count = formatDigits(num)
count = checkData(count) count = checkData(count)
setValue({...value, count}) setValue({ ...value, count })
onBlue?.(count as number) onBlue?.(count as number)
return count
} else { } else {
setValue({...value, count:defaultNum}) setValue({ ...value, count: defaultNum })
onBlue?.(defaultNum) onBlue?.(defaultNum)
return defaultNum
} }
} }
const noop = (e) => { const noop = (e) => {

View File

@ -62,7 +62,6 @@ const Login: FC = () => {
account, account,
password, password,
}) })
console.log('state===>', res)
if (res.success) { if (res.success) {
alert.success('登陆成功') alert.success('登陆成功')
setToken(res.data.token) setToken(res.data.token)

View File

@ -1,33 +1,40 @@
import { formatPriceDiv } from '@/common/format' import { formatPriceDiv } from '@/common/format'
import NormalButton from '@/components/normalButton' import NormalButton from '@/components/normalButton'
import { View, Text } from '@tarojs/components' import { View, Text } from '@tarojs/components'
import { memo, useState } from 'react' import { memo, SetStateAction, useMemo, useRef, useState } from 'react'
import styles from './index.module.scss' import styles from './index.module.scss'
import MCheckbox from '@/components/checkbox' import MCheckbox from '@/components/checkbox'
import { usePropsValue } from '@/use/useCommon'
import { ShoppingDispatchType, useShoppingDispatch } from '../../context'
type PropsType = { type PropsType = {
onDelete?: Function onDelete?: Function
onSelectCheckbox?: (bool: boolean) => void onSelectCheckbox?: (bool: boolean) => void
disabled?: boolean disabled?: boolean
isSelectAll?: boolean
} }
export default memo<PropsType>(props => { export default (props: PropsType) => {
const { onDelete, onSelectCheckbox, disabled = false } = props const { onDelete, onSelectCheckbox, disabled = false, isSelectAll: selectAll = false } = props
const dispatch = useShoppingDispatch()
console.log('isSelectAll==>', selectAll)
const handleDelete = () => { const handleDelete = () => {
onDelete && onDelete() onDelete && onDelete()
} }
const selectCallBack = () => { const selectCallBack = () => {
onSelectCheckbox && onSelectCheckbox(true) onSelectCheckbox && onSelectCheckbox(true)
setSelectAll(true) dispatch({ type: ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION, data: true })
} }
const closeCallBack = () => { const closeCallBack = () => {
setSelectAll(false) dispatch({ type: ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION, data: false })
onSelectCheckbox && onSelectCheckbox(false) onSelectCheckbox && onSelectCheckbox(false)
} }
const [isSelectAll, setSelectAll] = useState(false) const [isSelectAll, setSelectAll] = usePropsValue({
value: selectAll,
defaultValue: false,
})
return ( return (
<View className={styles.bottomBar}> <View className={styles.bottomBar}>
@ -50,4 +57,4 @@ export default memo<PropsType>(props => {
</View> </View>
</View> </View>
) )
}) }

View File

@ -1,136 +1,130 @@
import { View, Text, Image } from '@tarojs/components' import { View, Text, Image } from '@tarojs/components'
import MCheckbox from '@/components/checkbox' import MCheckbox from '@/components/checkbox'
import Counter from '@/components/counter' import Counter from '@/components/counter'
import { FC, memo, ReactNode, useCallback, useEffect, useState } from 'react' import { FC, forwardRef, memo } from 'react'
import classnames from 'classnames' import classnames from 'classnames'
import styles from './index.module.scss' import styles from './index.module.scss'
import { debounce } from '@/common/util' import { debounce } from '@/common/util'
import { formatImgUrl, formatPriceDiv } from '@/common/format' import { formatImgUrl, formatPriceDiv } from '@/common/format'
import { EnumSaleMode } from '@/common/Enumerate' import { EnumSaleMode } from '@/common/Enumerate'
import { useNeedMemoCallback } from '@/use/useCommon'
import { selectList } from '../../config' import { selectList } from '../../config'
import { ShoppingDispatchType, useShoppingDispatch, useShoppingState } from '../../context' import { Goods, ShoppingDispatchType, ShoppingStateContextValue, useShoppingDispatch, useShoppingState } from '../../context'
type PropsType = { type PropsType = {
state?: Goods
purchaserId: number purchaserId: number
itemData: Record<string, any> & object itemData: Record<string, any> & object
orderType: EnumSaleMode orderType: EnumSaleMode
} }
// 注意:如果在表单控件内部使用 useWatch由于是直接从Context中取值memo也会直接失去作用。 let ColorKindItem: FC<PropsType> = props => {
console.log('Rerender component: ColorKindItem')
const { state: multipleSelection, purchaserId, itemData, orderType = EnumSaleMode.Bulk } = props
const dispatch = useShoppingDispatch()
// console.log('checked==>', checked)
const ColorKindItem: FC<PropsType> = memo( //格式化金额
props => { const formatPrice = (price: number) => {
console.log('Rerender component: ColorKindItem') return Number(formatPriceDiv(price))
const { purchaserId, itemData, orderType = EnumSaleMode.Bulk } = props }
const { colorStore } = useShoppingState()
const dispatch = useShoppingDispatch()
// @ts-ignore
const { checked } = colorStore[purchaserId]['goodsKind']![itemData.id] || { checked: false }
//格式化金额 //格式化数量
const formatPrice = (price: number) => { const formatCount = itemData => {
return Number(formatPriceDiv(price)) return itemData.sale_mode == EnumSaleMode.Bulk ? itemData.roll : itemData.length / 100
}
//格式化单位
const formatUnit = itemData => {
return itemData.sale_mode == EnumSaleMode.Bulk ? '条' : '米'
}
const handleSelect = () => {
dispatch({
type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX,
data: {
purchaserId: purchaserId,
multipleSelection: { ...multipleSelection, [itemData.id]: itemData },
},
})
}
const handleClose = () => {
const temp = multipleSelection
delete temp?.[itemData.id]
dispatch({
type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX,
data: {
purchaserId: purchaserId,
multipleSelection: temp,
},
})
}
// TODO需要新增调整条数/米数的接口 并在调整完成后重新请求整个购物车页面
const getInputValue = debounce(async (num, itemData) => {
if (itemData.sale_mode === EnumSaleMode.Bulk) {
itemData.roll = num
} else {
itemData.length = num
} }
//格式化数量 }, 260)
const formatCount = itemData => {
return itemData.sale_mode == EnumSaleMode.Bulk ? itemData.roll : itemData.length / 100
}
//格式化单位
const formatUnit = itemData => {
return itemData.sale_mode == EnumSaleMode.Bulk ? '条' : '米'
}
const handleSelect = () => {
// console.log('handleSelect')
dispatch({
type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX,
data: {
purchaserId: purchaserId,
goodsKind: { [itemData.id]: { ...colorStore[purchaserId].goodsKind?.[itemData.id]!, checked: true } },
},
})
// setChangedCheckbox({
// purchaserId: purchaserId,
// goodsKind: { [itemData.id]: { ...colorStore[purchaserId].goodsKind?.[itemData.id]!, checked: true } },
// })
}
const handleClose = () => {
dispatch({
type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX,
data: {
purchaserId: purchaserId,
goodsKind: { [itemData.id]: { ...colorStore[purchaserId].goodsKind?.[itemData.id]!, checked: false } },
},
})
// setChangedCheckbox({
// purchaserId: purchaserId,
// goodsKind: { [itemData.id]: { ...colorStore[purchaserId].goodsKind?.[itemData.id]!, checked: false } },
// })
}
const getInputValue = debounce(async (num, itemData) => { return (
if (itemData.sale_mode === EnumSaleMode.Bulk) { <MCheckbox
itemData.roll = num status={multipleSelection?.hasOwnProperty(itemData.id) || false}
} else { onSelect={handleSelect}
itemData.length = num onClose={handleClose}
} customClassName={classnames(styles.checkbox, multipleSelection?.hasOwnProperty(itemData.id) ? styles.selected : '')}
}, 300) customTextClass={styles.colorKindItem}>
<View className={styles['colorKindItem__left']}>
return ( <Image className={styles['colorKindItem__left--image']} mode='aspectFill' src={formatImgUrl(itemData.product_color_texture_url)}></Image>
<MCheckbox </View>
status={checked} <View className={styles['colorKindItem__center']}>
onSelect={handleSelect} <Text className={styles['colorKindItem__center--title']}>
onClose={handleClose} {itemData.product_code}# {itemData.product_name}
customClassName={classnames(styles.checkbox, checked ? styles.selected : '')} </Text>
customTextClass={styles.colorKindItem}> <View className={styles['colorKindItem__center--detail']}>
<View className={styles['colorKindItem__left']}> <Text className={styles['colorKindItem__center--ID']}>{itemData.product_color_code}</Text>
<Image className={styles['colorKindItem__left--image']} mode='aspectFill' src={formatImgUrl(itemData.product_color_texture_url)}></Image> <Text className={styles['colorKindItem__center--description']}>{itemData.product_color_name}</Text>
</View> </View>
<View className={styles['colorKindItem__center']}> </View>
<Text className={styles['colorKindItem__center--title']}> <View className={styles['colorKindItem__right']}>
{itemData.product_code}# {itemData.product_name} <View className={styles['colorKindItem__right--price']}> {formatPrice(itemData.sale_price)}/kg</View>
</Text> <View className={styles['colorKindItem__right--counter']}>
<View className={styles['colorKindItem__center--detail']}> <Counter
<Text className={styles['colorKindItem__center--ID']}>{itemData.product_color_code}</Text> onBlue={e => getInputValue(e, itemData)}
<Text className={styles['colorKindItem__center--description']}>{itemData.product_color_name}</Text> defaultNum={formatCount(itemData)}
</View> step={selectList[orderType].step}
digits={selectList[orderType].digits}
onClickBtn={e => getInputValue(e, itemData)}
unit={formatUnit(itemData)}
minNum={selectList[orderType].minNum}
maxNum={selectList[orderType].maxNum}
/>
</View> </View>
<View className={styles['colorKindItem__right']}> </View>
<View className={styles['colorKindItem__right--price']}> {formatPrice(itemData.sale_price)}/kg</View> </MCheckbox>
<View className={styles['colorKindItem__right--counter']}> )
<Counter }
onBlue={e => getInputValue(e, itemData)} // State 分割组件 思路就是把 context 直接通过 props 的形式传给组件,这样的话就解决了 context 强制刷新 memo 的问题了
defaultNum={formatCount(itemData)} // 那么当 context 内的 value 被更新的时候react 只会强制渲染 Wrapper
step={selectList[orderType].step} const withStateSlice = (comp, slice) => {
digits={selectList[orderType].digits} const MemoComp = memo(comp)
onClickBtn={e => getInputValue(e, itemData)} const Wrapper = (props, ref) => {
unit={formatUnit(itemData)} const state = useShoppingState()
minNum={selectList[orderType].minNum} return <MemoComp ref={ref} state={slice(state, props)} {...props} />
maxNum={selectList[orderType].maxNum} }
/> return memo(forwardRef(Wrapper))
</View> }
</View>
</MCheckbox> ColorKindItem = withStateSlice(
// <View>1111</View> ColorKindItem,
) (state: ShoppingStateContextValue, props: PropsType) => {
}, return state.colorStore[props.purchaserId]['multipleSelection']
(preProp, nextProp) => {
const stringifyPreProp = JSON.stringify(preProp.itemData)
const stringifyNextProp = JSON.stringify(nextProp.itemData)
// console.log('memo==>', preProp, nextProp)
let needMemoized = true
if (preProp.purchaserId !== nextProp.purchaserId) {
needMemoized = false
}
if (preProp.orderType !== nextProp.orderType) {
needMemoized = false
}
if (stringifyPreProp !== stringifyNextProp) {
needMemoized = false
}
return needMemoized
}, },
) )
export default ColorKindItem export default ColorKindItem

View File

@ -1,6 +1,13 @@
import { Dispatch, FC, ReactNode, useEffect, useMemo, useReducer, useRef, useState } from 'react' import { FC, ReactNode, useEffect, useReducer, useRef } from 'react'
import { ShoppingAction, ShoppingCartAction, ShoppingDispatchContext, ShoppingDispatchContextValue, ShoppingDispatchType, shoppingReducer, ShoppingStateContext } from '../../context' import {
import { GoodsMeta, ColorStore, ShoppingStateContextValue } from '../../context' ShoppingAction,
ShoppingDispatchContext,
ShoppingDispatchContextValue,
ShoppingDispatchType,
shoppingReducer,
ShoppingStateContext,
} from '../../context'
import { ColorStore, ShoppingStateContextValue } from '../../context'
export type TriggerCheckboxOptions = { export type TriggerCheckboxOptions = {
colorStore: ColorStore colorStore: ColorStore
@ -13,6 +20,7 @@ type InitialState = {
currentCheckedPurchaserId: number currentCheckedPurchaserId: number
currentCheckedSaleMode: number currentCheckedSaleMode: number
isManageStatus: boolean isManageStatus: boolean
isMultipleSelection: boolean
selectedAmount: number selectedAmount: number
} }
@ -33,17 +41,16 @@ export const ShoppingProvider: FC<ShoppingCartPropsType> = props => {
currentCheckedPurchaserId: -1, currentCheckedPurchaserId: -1,
currentCheckedSaleMode: 0, currentCheckedSaleMode: 0,
isManageStatus: false, isManageStatus: false,
isMultipleSelection: false,
selectedAmount: 0, selectedAmount: 0,
}) })
// 这里要在 useEffect 也就是刷新 state 后再调用,否则如果在 onFieldsChangeRef 修改值会覆盖掉上次修改 // 这里要在 useEffect 也就是刷新 state 后再调用,否则如果在 onFieldsChangeRef 修改值会覆盖掉上次修改
useEffect(() => { useEffect(() => {
onTriggerCheckboxRef.current?.({ onTriggerCheckboxRef.current?.({
colorStore: state.colorStore, colorStore: state.colorStore,
currentCheckedPurchaserId: state.currentCheckedPurchaserId, currentCheckedPurchaserId: state.currentCheckedPurchaserId,
setSelectedAmount: (amount) => dispatch({ type: ShoppingDispatchType.UPDATE_SELECTED_AMOUNT,data: amount }), setSelectedAmount: amount => dispatch({ type: ShoppingDispatchType.UPDATE_SELECTED_AMOUNT, data: amount }),
}) })
}, [state.colorStore, state.currentCheckedPurchaserId]) }, [state.colorStore, state.currentCheckedPurchaserId])

View File

@ -1,5 +1,5 @@
import { Text, View } from '@tarojs/components' import { Text, View } from '@tarojs/components'
import { FC, memo, useEffect, useMemo, useState, useTransition } from 'react' import { FC, forwardRef, memo, useEffect, useMemo, useState, useTransition } from 'react'
import styles from './index.module.scss' import styles from './index.module.scss'
import classnames from 'classnames' import classnames from 'classnames'
import { formatMeterDiv } from '@/common/format' import { formatMeterDiv } from '@/common/format'
@ -9,14 +9,29 @@ import Divider from '@/components/divider'
import ColorKindItem from '../colorKindItem' import ColorKindItem from '../colorKindItem'
import { EnumSaleMode } from '@/common/Enumerate' import { EnumSaleMode } from '@/common/Enumerate'
import { selectList } from '../../config' import { selectList } from '../../config'
import { Goods, ShoppingDispatchType, useShoppingDispatch, useShoppingState } from '../../context' import { Goods, GoodsMeta, ShoppingDispatchType, ShoppingStateContextValue, useShoppingDispatch, useShoppingState } from '../../context'
import IconFont from '@/components/iconfont/iconfont' import IconFont from '@/components/iconfont/iconfont'
import { isEmptyObject } from '@/common/common' import { isEmptyObject } from '@/common/common'
import classNames from 'classnames' import classNames from 'classnames'
import LoadingCard from '@/components/loadingCard' import LoadingCard from '@/components/loadingCard'
type PropsType = { interface ButtonPropsType {
itemData?: ShoppingCartData isActive: boolean
onClick?: Function
children?: React.ReactNode
customStyle?: React.CSSProperties
}
// 订单类型
const SaleModeButton: FC<ButtonPropsType> = props => {
const { onClick, children, isActive = false, customStyle } = props
const handleClick = () => {
onClick?.()
}
return (
<View className={classnames(styles.saleModeButton, isActive && styles['saleModeButton--active'])} style={customStyle} onClick={handleClick}>
<View className={classnames(styles['saleModeButton--text'])}>{children}</View>
</View>
)
} }
const DrawerButton = memo<{ isOpen: boolean }>(({ isOpen }) => { const DrawerButton = memo<{ isOpen: boolean }>(({ isOpen }) => {
@ -33,9 +48,17 @@ enum BackEndSaleModeListFieldMap {
weight_cut_color_list = 2, weight_cut_color_list = 2,
} }
export default memo<PropsType>(props => { type PropsType = {
const { itemData } = props itemData?: ShoppingCartData
state?: {
multipleSelection?: GoodsMeta['multipleSelection']
currentCheckedPurchaserId?: number
}
}
let ShoppingCartItem: FC<PropsType> = props => {
const { itemData, state } = props
const { multipleSelection, currentCheckedPurchaserId } = state!
const dispatch = useShoppingDispatch() const dispatch = useShoppingDispatch()
const [openDetail, setOpenDetail] = useState(false) const [openDetail, setOpenDetail] = useState(false)
@ -53,93 +76,59 @@ export default memo<PropsType>(props => {
dispatch({ type: ShoppingDispatchType.UPDATE_CURRENT_CHECKED_SALEMODE, data: type }) dispatch({ type: ShoppingDispatchType.UPDATE_CURRENT_CHECKED_SALEMODE, data: type })
// 重置预估金额 // 重置预估金额
dispatch({ type: ShoppingDispatchType.UPDATE_SELECTED_AMOUNT, data: 0 }) dispatch({ type: ShoppingDispatchType.UPDATE_SELECTED_AMOUNT, data: 0 })
dispatch({ type: ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION, data: false })
} }
const { currentCheckedPurchaserId, colorStore } = useShoppingState()
console.log('Rerender component: shoppingCartItem') console.log('Rerender component: shoppingCartItem')
// const memoList = useMemo(() => {
// console.log('ext useMemo')
// const component = itemData?.[BackEndSaleModeListFieldMap[selected]].map(item => {
// dispatch({
// type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX,
// data: {
// purchaserId: itemData?.purchaser_id!,
// goodsKind: {
// [item?.id]: {
// id: item?.id,
// estimate_amount: item.estimate_amount,
// checked: false,
// product_code: item.product_code,
// product_color_code: item.product_color_code,
// sale_mode: item.sale_mode,
// count: selected === EnumSaleMode.Bulk ? item.roll : Number(formatMeterDiv(item.length)),
// },
// },
// },
// })
// return <ColorKindItem purchaserId={itemData.purchaser_id} key={item.id} itemData={item} orderType={selected}></ColorKindItem>
// })
// // 向 context 中初始化数据
// const result = itemData?.[BackEndSaleModeListFieldMap[selected]].length !== 0 ? component : <View className={styles.noList}>暂无数据</View>
// return result
// }, [itemData, selected])
const handleClickLayout = () => { const handleClickLayout = () => {
if (currentCheckedPurchaserId === itemData?.purchaser_id) { if (currentCheckedPurchaserId === itemData?.purchaser_id) {
return return
} }
dispatch({ type: ShoppingDispatchType.UPDATE_SELECTED_AMOUNT, data: 0 }) dispatch({ type: ShoppingDispatchType.UPDATE_SELECTED_AMOUNT, data: 0 })
// setSelectedAmount(0)
// setCurrentCheckedSaleMode(selected)
dispatch({ type: ShoppingDispatchType.UPDATE_CURRENT_CHECKED_SALEMODE, data: selected }) dispatch({ type: ShoppingDispatchType.UPDATE_CURRENT_CHECKED_SALEMODE, data: selected })
dispatch({ type: ShoppingDispatchType.UPDATE_CURRENT_CHECKED_PURCHASERID, data: itemData?.purchaser_id as number }) dispatch({ type: ShoppingDispatchType.UPDATE_CURRENT_CHECKED_PURCHASERID, data: itemData?.purchaser_id as number })
// setCurrentCheckedPurchaserId(itemData?.purchaser_id as number) dispatch({ type: ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION, data: false })
} }
// 统计已选面料 // 统计已选面料
const materialChecked = useMemo(() => { const materialChecked = useMemo(() => {
const targetGoodsKind = colorStore?.[itemData?.purchaser_id!]?.['goodsKind'] if (!multipleSelection || isEmptyObject(multipleSelection)) return 0
if (!targetGoodsKind || isEmptyObject(targetGoodsKind)) return 0
return new Set( return new Set(
Object.values(targetGoodsKind)?.reduce((prev, item: Goods) => { Object.values(multipleSelection)?.reduce((prev, item: Goods) => {
if (item.checked && item.sale_mode === selected) { if (item.sale_mode === selected) {
return [...prev, item.product_code] return [...prev, item.product_code]
} }
return prev return prev
}, []), }, []),
).size ).size
}, [colorStore, currentCheckedPurchaserId, selected, itemData]) }, [multipleSelection, currentCheckedPurchaserId, selected, itemData])
// 统计已选颜色 // 统计已选颜色
const colorChecked = useMemo(() => { const colorChecked = useMemo(() => {
const targetGoodsKind = colorStore?.[itemData?.purchaser_id!]?.['goodsKind'] if (!multipleSelection || isEmptyObject(multipleSelection)) return 0
if (!targetGoodsKind || isEmptyObject(targetGoodsKind)) return 0
return new Set( return new Set(
Object.values(targetGoodsKind).reduce((prev, item: Goods) => { Object.values(multipleSelection).reduce((prev, item: Goods) => {
if (item.checked && item.sale_mode === selected) { if (item.sale_mode === selected) {
return [...prev, item.product_color_code] return [...prev, item.product_color_code]
} }
return prev return prev
}, []), }, []),
).size ).size
}, [colorStore, currentCheckedPurchaserId, selected, itemData]) }, [multipleSelection, currentCheckedPurchaserId, selected, itemData])
// 统计已选条数 / 米数 // 统计已选条数 / 米数
const lengthOrRollChecked = useMemo(() => { const lengthOrRollChecked = useMemo(() => {
const targetGoodsKind = colorStore?.[itemData?.purchaser_id!]?.['goodsKind'] if (!multipleSelection || isEmptyObject(multipleSelection)) return 0
if (!targetGoodsKind || isEmptyObject(targetGoodsKind)) return 0
return ( return (
Object.values(targetGoodsKind).reduce((prev, item: Goods) => { Object.values(multipleSelection).reduce((prev, item: Goods) => {
if (item.checked && item.sale_mode === selected) { if (item.sale_mode === selected) {
return prev + item.count return prev + item.count
} }
return prev return prev
}, 0) || 0 }, 0) || 0
) )
}, [colorStore, currentCheckedPurchaserId, selected, itemData]) }, [multipleSelection, currentCheckedPurchaserId, selected, itemData])
const [isPending, startTransition] = useTransition() const [isPending, startTransition] = useTransition()
@ -205,7 +194,6 @@ export default memo<PropsType>(props => {
</View> </View>
</View> </View>
<View className={styles.orderContainer}> <View className={styles.orderContainer}>
{/* <GoodsList itemData={itemData} selected={selected} /> */}
<GoodsList itemData={itemData} selected={selected} isPending={isPending} startTransition={startTransition} /> <GoodsList itemData={itemData} selected={selected} isPending={isPending} startTransition={startTransition} />
</View> </View>
</View> </View>
@ -213,7 +201,7 @@ export default memo<PropsType>(props => {
)} )}
</LayoutBlock> </LayoutBlock>
) )
}) }
interface GoodsListPropType { interface GoodsListPropType {
itemData?: ShoppingCartData itemData?: ShoppingCartData
@ -241,13 +229,13 @@ const GoodsList = memo<GoodsListPropType>(props => {
[item?.id]: { [item?.id]: {
id: item?.id, id: item?.id,
estimate_amount: item.estimate_amount, estimate_amount: item.estimate_amount,
checked: false,
product_code: item.product_code, product_code: item.product_code,
product_color_code: item.product_color_code, product_color_code: item.product_color_code,
sale_mode: item.sale_mode, sale_mode: item.sale_mode,
count: selected === EnumSaleMode.Bulk ? item.roll : Number(formatMeterDiv(item.length)), count: selected === EnumSaleMode.Bulk ? item.roll : Number(formatMeterDiv(item.length)),
}, },
}, },
multipleSelection: {},
}, },
}) })
return <ColorKindItem purchaserId={itemData.purchaser_id} key={item.id} itemData={item} orderType={selected}></ColorKindItem> return <ColorKindItem purchaserId={itemData.purchaser_id} key={item.id} itemData={item} orderType={selected}></ColorKindItem>
@ -271,22 +259,30 @@ const GoodsList = memo<GoodsListPropType>(props => {
</> </>
) )
}) })
// State 分割组件 思路就是把 context 直接通过 props 的形式传给组件,这样的话就解决了 context 强制刷新 memo 的问题了
interface ButtonPropsType { // 那么当 context 内的 value 被更新的时候react 只会强制渲染 Wrapper
isActive: boolean const withStateSlice = (comp, slice) => {
onClick?: Function const MemoComp = memo(comp, (prevProps, nextProps)=>{
children?: React.ReactNode let needMemo = true
customStyle?: React.CSSProperties if (JSON.stringify(prevProps.itemData) !== JSON.stringify(nextProps.itemData)) {
} needMemo = false
// 订单类型 }
const SaleModeButton: FC<ButtonPropsType> = props => { if(JSON.stringify(prevProps.state) !== JSON.stringify(nextProps.state)){
const { onClick, children, isActive = false, customStyle } = props needMemo = false
const handleClick = () => { }
onClick?.() return needMemo
})
const Wrapper = (props, ref) => {
const state = useShoppingState()
console.log('state===>', state)
return <MemoComp ref={ref} state={slice(state, props)} {...props} />
} }
return ( return memo(forwardRef(Wrapper))
<View className={classnames(styles.saleModeButton, isActive && styles['saleModeButton--active'])} style={customStyle} onClick={handleClick}>
<View className={classnames(styles['saleModeButton--text'])}>{children}</View>
</View>
)
} }
ShoppingCartItem = withStateSlice(ShoppingCartItem, (state: ShoppingStateContextValue, props) => ({
multipleSelection: state.colorStore?.[props.itemData?.purchaser_id]?.['multipleSelection'],
currentCheckedPurchaserId: state.currentCheckedPurchaserId,
}))
export default ShoppingCartItem

View File

@ -1,4 +1,5 @@
import React, { Dispatch, useRef } from 'react' import { EnumSaleMode } from '@/common/Enumerate'
import React, { Dispatch } from 'react'
import { useContext } from 'react' import { useContext } from 'react'
/** /**
@ -31,41 +32,46 @@ export type Goods = {
product_color_code: number // 颜色编号 product_color_code: number // 颜色编号
estimate_amount: number // 预估金额 estimate_amount: number // 预估金额
count: number // 已选的条数或米数 count: number // 已选的条数或米数
sale_mode: number sale_mode: EnumSaleMode
checked: boolean
} }
// 分组 // 分组
export interface GoodsMeta { export interface GoodsMeta {
purchaserId: number purchaserId: number
goodsKind?: { goodsKind?: {
[id: number]: Goods [id: Goods['id']]: Goods
}
multipleSelection: {
[id: Goods['id']]: Goods
} }
} }
export interface ShoppingStateContextValue { export interface ShoppingStateContextValue {
isManageStatus: boolean isManageStatus: boolean // 管理按钮的状态
isMultipleSelection: boolean // 全选按钮的状态
currentCheckedPurchaserId: number // 当前高亮的客户ID currentCheckedPurchaserId: number // 当前高亮的客户ID
currentCheckedSaleMode: number currentCheckedSaleMode: EnumSaleMode
colorStore: ColorStore colorStore: ColorStore
selectedAmount: number selectedAmount: number
} }
export enum ShoppingDispatchType { export enum ShoppingDispatchType {
UPDATE_MANAGE_STATUS = 'UPDATE_MANAGE_STATUS', UPDATE_MANAGE_STATUS = 'UPDATE_MANAGE_STATUS',
UPDATE_MULTIPLE_SELECTION = 'UPDATE_MULTIPLE_SELECTION',
UPDATE_CURRENT_CHECKED_PURCHASERID = 'UPDATE_CURRENT_CHECKED_PURCHASERID', UPDATE_CURRENT_CHECKED_PURCHASERID = 'UPDATE_CURRENT_CHECKED_PURCHASERID',
UPDATE_CURRENT_CHECKED_SALEMODE = 'UPDATE_CURRENT_CHECKED_SALEMODE', UPDATE_CURRENT_CHECKED_SALEMODE = 'UPDATE_CURRENT_CHECKED_SALEMODE',
UPDATE_COLOR_STORE = 'UPDATE_COLOR_STORE', UPDATE_COLOR_STORE = 'UPDATE_COLOR_STORE',
UPDATE_SELECTED_AMOUNT = 'UPDATE_SELECTED_AMOUNT', UPDATE_SELECTED_AMOUNT = 'UPDATE_SELECTED_AMOUNT',
UPDATE_CHANGED_CHECKBOX = 'UPDATE_CHANGED_CHECKBOX', UPDATE_CHANGED_CHECKBOX = 'UPDATE_CHANGED_CHECKBOX',
} }
// UPDATE_MultipleSelection
export interface ShoppingDispatchContextValue { export interface ShoppingDispatchContextValue {
[ShoppingDispatchType.UPDATE_MANAGE_STATUS]: (isManageStatus: boolean) => void [ShoppingDispatchType.UPDATE_MANAGE_STATUS]: (isManageStatus: ShoppingStateContextValue['isManageStatus']) => void
[ShoppingDispatchType.UPDATE_CURRENT_CHECKED_PURCHASERID]: (purchaserId: number) => void [ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION]: (isMultipleSelection: ShoppingStateContextValue['isMultipleSelection']) => void
[ShoppingDispatchType.UPDATE_CURRENT_CHECKED_SALEMODE]: (saleMode: number) => void [ShoppingDispatchType.UPDATE_CURRENT_CHECKED_PURCHASERID]: (purchaserId: ShoppingStateContextValue['currentCheckedPurchaserId']) => void
[ShoppingDispatchType.UPDATE_CURRENT_CHECKED_SALEMODE]: (saleMode: ShoppingStateContextValue['currentCheckedSaleMode']) => void
[ShoppingDispatchType.UPDATE_COLOR_STORE]: (colorStore: React.SetStateAction<ColorStore>) => void [ShoppingDispatchType.UPDATE_COLOR_STORE]: (colorStore: React.SetStateAction<ColorStore>) => void
[ShoppingDispatchType.UPDATE_SELECTED_AMOUNT]: (amount: React.SetStateAction<number>) => void [ShoppingDispatchType.UPDATE_SELECTED_AMOUNT]: (amount: React.SetStateAction<ShoppingStateContextValue['selectedAmount']>) => void
[ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX]: (amount: React.SetStateAction<number>) => void [ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX]: (amount: React.SetStateAction<ShoppingStateContextValue['selectedAmount']>) => void
} }
@ -80,6 +86,9 @@ export function shoppingReducer(state: ShoppingStateContextValue, action: Shoppi
case ShoppingDispatchType.UPDATE_MANAGE_STATUS: { case ShoppingDispatchType.UPDATE_MANAGE_STATUS: {
return { ...state, isManageStatus: data } return { ...state, isManageStatus: data }
} }
case ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION: {
return { ...state, isMultipleSelection: data }
}
case ShoppingDispatchType.UPDATE_CURRENT_CHECKED_PURCHASERID: { case ShoppingDispatchType.UPDATE_CURRENT_CHECKED_PURCHASERID: {
return { ...state, currentCheckedPurchaserId: data } return { ...state, currentCheckedPurchaserId: data }
} }
@ -100,6 +109,7 @@ export function shoppingReducer(state: ShoppingStateContextValue, action: Shoppi
[data.purchaserId as number]: { [data.purchaserId as number]: {
purchaserId: data.purchaserId, purchaserId: data.purchaserId,
goodsKind: { ...state.colorStore[data.purchaserId]?.goodsKind, ...data.goodsKind }, goodsKind: { ...state.colorStore[data.purchaserId]?.goodsKind, ...data.goodsKind },
multipleSelection: { ...data.multipleSelection },
}, },
}, },
} }

View File

@ -20,15 +20,12 @@ import LoadingCard from '@/components/loadingCard'
export const Shopping: FC = memo(() => { export const Shopping: FC = memo(() => {
// 计算总的预估金额 // 计算总的预估金额
const handleTriggerCheckbox = ({ colorStore, currentCheckedPurchaserId, setSelectedAmount }) => { const handleTriggerCheckbox = ({ colorStore, currentCheckedPurchaserId, setSelectedAmount }) => {
const targetGoodsKind = colorStore?.[currentCheckedPurchaserId]?.['goodsKind'] const multipleSelection = colorStore?.[currentCheckedPurchaserId]?.multipleSelection
console.log('handleTriggerCheckbox==>', colorStore) console.log('handleTriggerCheckbox==>', colorStore)
if (targetGoodsKind) { if (multipleSelection) {
const result = Object.values(targetGoodsKind).reduce((prev: number, value: Goods) => { const result = Object.values(multipleSelection).reduce((prev: number, value: Goods) => {
if (value.checked) { return prev + Number(formatPriceDiv(value.estimate_amount))
return prev + Number(formatPriceDiv(value.estimate_amount))
}
return prev
}, 0) as number }, 0) as number
console.log('result==>', result) console.log('result==>', result)
// 同步修改上下文的 预估金额 // 同步修改上下文的 预估金额
@ -49,12 +46,15 @@ interface InternalContainer {}
const ShoppingCartContainer: FC<InternalContainer> = () => { const ShoppingCartContainer: FC<InternalContainer> = () => {
let isFirst = useRef(true) let isFirst = useRef(true)
const { isManageStatus, selectedAmount, currentCheckedPurchaserId, currentCheckedSaleMode, colorStore } = useShoppingState() const { isMultipleSelection, isManageStatus, selectedAmount, currentCheckedPurchaserId, currentCheckedSaleMode, colorStore } = useShoppingState()
const dispatch = useShoppingDispatch() const dispatch = useShoppingDispatch()
// 管理 // 管理
const onStartToManage = () => { const onStartToManage = () => {
if(isManageStatus){
handleSelectAllCheckbox(false) // 取消全选
}
dispatch({ type: ShoppingDispatchType.UPDATE_MANAGE_STATUS, data: !isManageStatus }) dispatch({ type: ShoppingDispatchType.UPDATE_MANAGE_STATUS, data: !isManageStatus })
} }
@ -125,13 +125,10 @@ const ShoppingCartContainer: FC<InternalContainer> = () => {
// 结算 // 结算
const handleSettleAccount = debounce(() => { const handleSettleAccount = debounce(() => {
const targetGoodsKind = colorStore?.[currentCheckedPurchaserId]?.['goodsKind'] const multipleSelection = colorStore?.[currentCheckedPurchaserId]?.['multipleSelection']
if (!targetGoodsKind) return Taro.showToast({ title: '请先选择客户', icon: 'error' }) if (!multipleSelection) return Taro.showToast({ title: '请先选择客户', icon: 'error' })
const checkedGoodsKind = Object.values(targetGoodsKind).reduce((prev, item: Goods) => { const checkedGoodsKind = Object.values(multipleSelection).reduce((prev, item: Goods) => {
if (item.checked) { return [...prev, item.id]
return [...prev, item.id]
}
return prev
}, []) }, [])
if (checkedGoodsKind.length === 0) return Taro.showToast({ title: '请先选择商品', icon: 'error' }) if (checkedGoodsKind.length === 0) return Taro.showToast({ title: '请先选择商品', icon: 'error' })
goLink('/pages/submitOrder/index', { goLink('/pages/submitOrder/index', {
@ -146,13 +143,10 @@ const ShoppingCartContainer: FC<InternalContainer> = () => {
// 批量某个客户的删除商品 // 批量某个客户的删除商品
const handleDelete = async () => { const handleDelete = async () => {
const targetGoodsKind = colorStore?.[currentCheckedPurchaserId]?.['goodsKind'] const multipleSelection = colorStore?.[currentCheckedPurchaserId]?.['multipleSelection']
let checked: Goods[] = [] let checked: Goods[] = Object.values(multipleSelection)
if (targetGoodsKind) { if (checked.length === 0) {
checked = Object.values(targetGoodsKind).filter((item: Goods) => item.checked) return Taro.showToast({ title: '请选择商品', icon: 'error' })
if (checked.length === 0) {
return Taro.showToast({ title: '请选择商品', icon: 'error' })
}
} }
console.log('checked==>', checked) console.log('checked==>', checked)
Taro.showModal({ Taro.showModal({
@ -179,26 +173,15 @@ const ShoppingCartContainer: FC<InternalContainer> = () => {
if (!targetGoodsKind) return Taro.showToast({ title: '请先选择客户', icon: 'error' }) if (!targetGoodsKind) return Taro.showToast({ title: '请先选择客户', icon: 'error' })
console.log('targetGoodsKind==>', targetGoodsKind) console.log('targetGoodsKind==>', targetGoodsKind)
const tempObject = {} const tempObject = { ...targetGoodsKind }
Object.entries(targetGoodsKind).forEach(([key, value]) => {
tempObject[key] = {
...value,
checked: isSelectAll,
}
})
console.log('tempObject==>', tempObject) console.log('tempObject==>', tempObject)
dispatch({ dispatch({
type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX, type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX,
data: { data: {
purchaserId: currentCheckedPurchaserId, purchaserId: currentCheckedPurchaserId,
goodsKind: tempObject, multipleSelection: isSelectAll ? tempObject : {},
}, },
}) })
// setChangedCheckbox({
// purchaserId: currentCheckedPurchaserId,
// goodsKind: tempObject,
// })
} }
// 加载刷新数据 // 加载刷新数据
@ -250,6 +233,7 @@ const ShoppingCartContainer: FC<InternalContainer> = () => {
{isManageStatus ? ( {isManageStatus ? (
<BottomEditBar <BottomEditBar
disabled={currentCheckedPurchaserId < 0} disabled={currentCheckedPurchaserId < 0}
isSelectAll={isMultipleSelection}
onDelete={handleDelete} onDelete={handleDelete}
onSelectCheckbox={isAll => handleSelectAllCheckbox(isAll)}></BottomEditBar> onSelectCheckbox={isAll => handleSelectAllCheckbox(isAll)}></BottomEditBar>
) : ( ) : (

View File

@ -1,5 +1,5 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState, SetStateAction, useMemo, useCallback } from 'react'
//倒计时hook //倒计时hook
export const useTimeCountDown = () => { export const useTimeCountDown = () => {
@ -33,7 +33,7 @@ export const useTimeCountDown = () => {
setTimeStatus(() => 1) setTimeStatus(() => 1)
if (startData >= endDate) { if (startData >= endDate) {
clearInterval(timeObj.current) clearInterval(timeObj.current)
setShowTime((e) => ({ ...e, DD: '00', HH: '00', MM: '00', SS: '00' })) setShowTime(e => ({ ...e, DD: '00', HH: '00', MM: '00', SS: '00' }))
setTimeStatus(() => 2) setTimeStatus(() => 2)
return false return false
} }
@ -51,7 +51,7 @@ export const useTimeCountDown = () => {
var MM = ('00' + mm).slice(-2) var MM = ('00' + mm).slice(-2)
var SS = ('00' + ss).slice(-2) var SS = ('00' + ss).slice(-2)
// console.log('endTime::', `${DD}-${HH}-${MM}-${SS}`) // console.log('endTime::', `${DD}-${HH}-${MM}-${SS}`)
setShowTime((e) => ({ ...e, DD, HH, MM, SS })) setShowTime(e => ({ ...e, DD, HH, MM, SS }))
} }
return { return {
showTime, showTime,
@ -93,7 +93,7 @@ type Options = {
} }
const defaultOptions = { const defaultOptions = {
deep: false deep: false,
} }
/** /**
* memo * memo
@ -113,7 +113,7 @@ export const useNeedMemoCallback = (options: Options = defaultOptions) => {
// 循环props对象 // 循环props对象
const handleDiff = () => { const handleDiff = () => {
for (let [key, value] of Object.entries(prevProps)){ for (let [key, value] of Object.entries(prevProps)) {
if (!Object.is(value, nextProps[key])) { if (!Object.is(value, nextProps[key])) {
// console.log('函数不相等',value === nextProps[key]) // console.log('函数不相等',value === nextProps[key])
// console.log('asdfasdf',value) // console.log('asdfasdf',value)
@ -121,48 +121,56 @@ export const useNeedMemoCallback = (options: Options = defaultOptions) => {
break break
} }
} }
// Object.entries(prevProps).forEach(([key, value]) => { // Object.entries(prevProps).forEach(([key, value]) => {
// if (!nextProps.hasOwnProperty(key)) { // if (!nextProps.hasOwnProperty(key)) {
// needMemoized = false // needMemoized = false
// // 新增的属性 // // 新增的属性
// if (Object.prototype.toString.call(value) === '[object Object]') { // if (Object.prototype.toString.call(value) === '[object Object]') {
// parent[key] = {} // parent[key] = {}
// } else if (Object.prototype.toString.call(value) === '[object Array]') { // } else if (Object.prototype.toString.call(value) === '[object Array]') {
// parent[key] = [] // parent[key] = []
// } // }
// } // }
// if (typeof data[key] === 'object') { // if (typeof data[key] === 'object') {
// o[key] = internalSetData(parent[key], data[key]) // o[key] = internalSetData(parent[key], data[key])
// } else { // } else {
// // 核心处理逻辑 // // 核心处理逻辑
// // key == data第一层 如果要做到data第二层呢所以需要递归 // // key == data第一层 如果要做到data第二层呢所以需要递归
// if (parent[key] != data[key]) { // if (parent[key] != data[key]) {
// o[key] = data[key] // o[key] = data[key]
// changed = true // changed = true
// } // }
// } // }
// }) // })
} }
handleDiff() handleDiff()
return needMemoized return needMemoized
} }
} }
const needMemo = useNeedMemoCallback() type UsePropsValueOptions<T> = {
const hobby = { value?: T
play1: 1, defaultValue: T
play2: 2 onChange?: (v: T) => void
} }
const bool = needMemo(
{ export function usePropsValue<T>(options: UsePropsValueOptions<T>) {
name: '小明', const { value, defaultValue, onChange } = options
age: 18,
hobby: hobby, const [, setForceUpdate] = useState({})
},
{ const stateRef = useRef<T>(value !== undefined ? value : defaultValue)
name: '小明', if (value !== undefined) {
age: 18, stateRef.current = value
hobby: hobby, }
},
) const setState = useCallback((v: SetStateAction<T>, forceTrigger: boolean = false) => {
// console.log('bool==>', bool) // `forceTrigger` means trigger `onChange` even if `v` is the same as `stateRef.current`
const nextValue = typeof v === 'function' ? (v as (prevState: T) => T)(stateRef.current) : v
if (!forceTrigger && nextValue === stateRef.current) return
stateRef.current = nextValue
setForceUpdate({})
return onChange?.(nextValue)
}, [])
return [stateRef.current, setState] as const
}

13993
yarn-error.log Normal file

File diff suppressed because it is too large Load Diff