diff --git a/global.d.ts b/global.d.ts index 6981aec..7dc913e 100644 --- a/global.d.ts +++ b/global.d.ts @@ -20,4 +20,5 @@ declare namespace NodeJS { declare const CURRENT_VERSION: string declare const CURRENT_GITHASH: string declare const CURRENT_ENV: string +declare const CURRENT_BASE_URL: string diff --git a/src/api/shopping/index.ts b/src/api/shopping/index.ts index 604a5e8..dc0dbe5 100644 --- a/src/api/shopping/index.ts +++ b/src/api/shopping/index.ts @@ -1,7 +1,7 @@ -import { useRequest } from "@/use/useHttp" +import { useRequest } from '@/use/useHttp' /** * 修改购物车 - * @returns + * @returns */ export const ShoppingCartUpdateApi = () => { return useRequest({ @@ -11,21 +11,31 @@ export const ShoppingCartUpdateApi = () => { } /** * 删除购物车商品 - * @returns + * @returns */ - export const ShoppingCartDeleteApi = () => { - return useRequest({ - url: `/v2/mp/shoppingCart/productColor`, - method: "delete", - }) +export const ShoppingCartDeleteApi = () => { + return useRequest({ + url: `/v2/mp/shoppingCart/productColor`, + method: 'delete', + }) } /** * 获取购物车商品列表 - * @returns + * @returns */ - export const ShoppingCartListApi = () => { - return useRequest({ - url: `/v2/mp/shoppingCart/productColor`, - method: "get", - }) +export const ShoppingCartListApi = () => { + return useRequest({ + url: `/v2/mp/shoppingCart/productColor`, + method: 'get', + }) +} +/** + * 调整购物车商品数量 + * @returns + */ +export const AdjestShoppingCartApi = () => { + return useRequest({ + url: `/v2/mp/shoppingCart/productColor/list`, + method: 'post', + }) } diff --git a/src/common/constant.js b/src/common/constant.ts similarity index 99% rename from src/common/constant.js rename to src/common/constant.ts index 1647e3d..edec91f 100644 --- a/src/common/constant.js +++ b/src/common/constant.ts @@ -38,4 +38,4 @@ export const SCENE = { SearchScene: 0, //商城面料搜索 } //支付码单跳转链接 -export const PAY_H5_CODE_URL = CURRENT_ENV.includes('production') ? 'https://www.zzfzyc.com/cashier' : 'https://test.zzfzyc.com/cashier' \ No newline at end of file +export const PAY_H5_CODE_URL = CURRENT_ENV.includes('production') ? 'https://www.zzfzyc.com/cashier' : 'https://test.zzfzyc.com/cashier' diff --git a/src/pages/shopping/README.md b/src/pages/shopping/README.md new file mode 100644 index 0000000..a5eeaa6 --- /dev/null +++ b/src/pages/shopping/README.md @@ -0,0 +1,16 @@ +购物页面的 组件结构 + +> Shopping +> +> > ShoppingProvider +> > +> > > ShoppingCartContainer +> > > +> > > > ShoppingCartItem +> > > > +> > > > > GoodsList +> > > > > +> > > > > > ColorKindItem + +还用到发布订阅者模式 通知祖先组件请求接口更新数据 + diff --git a/src/pages/shopping/components/colorKindItem/index.tsx b/src/pages/shopping/components/colorKindItem/index.tsx index 8c8d85d..45411ec 100644 --- a/src/pages/shopping/components/colorKindItem/index.tsx +++ b/src/pages/shopping/components/colorKindItem/index.tsx @@ -8,18 +8,23 @@ import { debounce } from '@/common/util' import { formatImgUrl, formatPriceDiv } from '@/common/format' import { EnumSaleMode } from '@/common/Enumerate' import { selectList } from '../../config' +import { AdjestShoppingCartApi } from '@/api/shopping/index' import { Goods, ShoppingDispatchType, ShoppingStateContextValue, useShoppingDispatch, useShoppingState } from '../../context' +import { ShoppingStore } from '../../context/shoppingStore' type PropsType = { - state?: Goods + state?: { + multipleSelection: Goods[] + Observer: ShoppingStore + } purchaserId: number itemData: Record & object orderType: EnumSaleMode } let ColorKindItem: FC = props => { - console.log('Rerender component: ColorKindItem') - const { state: multipleSelection, purchaserId, itemData, orderType = EnumSaleMode.Bulk } = props + const { state, purchaserId, itemData, orderType = EnumSaleMode.Bulk } = props + console.log('Rerender component: ColorKindItem', itemData.id) const dispatch = useShoppingDispatch() // console.log('checked==>', checked) @@ -43,12 +48,12 @@ let ColorKindItem: FC = props => { type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX, data: { purchaserId: purchaserId, - multipleSelection: { ...multipleSelection, [itemData.id]: itemData }, + multipleSelection: { ...state?.multipleSelection, [itemData.id]: itemData }, }, }) } const handleClose = () => { - const temp = multipleSelection + const temp = state?.multipleSelection delete temp?.[itemData.id] dispatch({ type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX, @@ -58,26 +63,39 @@ let ColorKindItem: FC = props => { }, }) } + const { fetchData } = AdjestShoppingCartApi() - - // TODO:需要新增调整条数/米数的接口 并在调整完成后重新请求整个购物车页面 + // 调整条数/米数的接口 并在调整完成后重新请求整个购物车页面 const getInputValue = debounce(async (num, itemData) => { + const targetColor: Record = { + product_color_id: itemData.product_color_id, + roll: 0, + length: 0, + } if (itemData.sale_mode === EnumSaleMode.Bulk) { itemData.roll = num + targetColor.roll = num } else { itemData.length = num + targetColor.length = num } - - }, 260) - - + const res = await fetchData({ + color_list: [targetColor], + purchaser_id: purchaserId, + sale_mode: itemData.sale_mode, + sale_offset: itemData.sale_offset, + }) + if (res.success) { + state?.Observer?.notify(purchaserId) + } + }, 400) return ( @@ -101,8 +119,8 @@ let ColorKindItem: FC = props => { digits={selectList[orderType].digits} onClickBtn={e => getInputValue(e, itemData)} unit={formatUnit(itemData)} - minNum={selectList[orderType].minNum} - maxNum={selectList[orderType].maxNum} + minNum={itemData.min_num} + maxNum={itemData.max_num} /> @@ -120,11 +138,11 @@ const withStateSlice = (comp, slice) => { return memo(forwardRef(Wrapper)) } -ColorKindItem = withStateSlice( - ColorKindItem, - (state: ShoppingStateContextValue, props: PropsType) => { - return state.colorStore[props.purchaserId]['multipleSelection'] - }, -) +ColorKindItem = withStateSlice(ColorKindItem, (state: ShoppingStateContextValue, props: PropsType) => { + return { + multipleSelection: state.colorStore[props.purchaserId]['multipleSelection'], + Observer: state.Observer, + } +}) export default ColorKindItem diff --git a/src/pages/shopping/components/shoppingCart/index.tsx b/src/pages/shopping/components/shoppingCart/index.tsx index be53cdf..a16abff 100644 --- a/src/pages/shopping/components/shoppingCart/index.tsx +++ b/src/pages/shopping/components/shoppingCart/index.tsx @@ -1,4 +1,4 @@ -import { FC, ReactNode, useEffect, useReducer, useRef } from 'react' +import { FC, ReactNode, useEffect, useMemo, useReducer, useRef } from 'react' import { ShoppingAction, ShoppingDispatchContext, @@ -8,6 +8,7 @@ import { ShoppingStateContext, } from '../../context' import { ColorStore, ShoppingStateContextValue } from '../../context' +import { ShoppingStore } from '../../context/shoppingStore' export type TriggerCheckboxOptions = { colorStore: ColorStore @@ -15,15 +16,6 @@ export type TriggerCheckboxOptions = { setSelectedAmount: ShoppingDispatchContextValue['UPDATE_SELECTED_AMOUNT'] } -type InitialState = { - colorStore: ColorStore - currentCheckedPurchaserId: number - currentCheckedSaleMode: number - isManageStatus: boolean - isMultipleSelection: boolean - selectedAmount: number -} - export interface ShoppingCartPropsType { initialValues?: ColorStore onTriggerCheckbox?: (options: TriggerCheckboxOptions) => void @@ -35,18 +27,22 @@ export const ShoppingProvider: FC = props => { const onTriggerCheckboxRef = useRef(onTriggerCheckbox) onTriggerCheckboxRef.current = onTriggerCheckbox + // 发布订阅 + const Observer = useMemo(() => new ShoppingStore(), []) - const [state, dispatch] = useReducer<(state: ShoppingStateContextValue, action: ShoppingAction) => InitialState>(shoppingReducer, { + const [state, dispatch] = useReducer<(state: ShoppingStateContextValue, action: ShoppingAction) => ShoppingStateContextValue>(shoppingReducer, { colorStore: initialValues || {}, currentCheckedPurchaserId: -1, currentCheckedSaleMode: 0, isManageStatus: false, isMultipleSelection: false, selectedAmount: 0, + Observer, }) // 这里要在 useEffect 也就是刷新 state 后再调用,否则如果在 onFieldsChangeRef 修改值会覆盖掉上次修改 useEffect(() => { + console.log('onTriggerCheckboxRef start run') onTriggerCheckboxRef.current?.({ colorStore: state.colorStore, currentCheckedPurchaserId: state.currentCheckedPurchaserId, @@ -54,6 +50,19 @@ export const ShoppingProvider: FC = props => { }) }, [state.colorStore, state.currentCheckedPurchaserId]) + // 加入一个监听,为 onFieldsChange 回调服务 + // useEffect(() => { + // const unsubscribe = Observer.subscribe(() => { + // console.log('onTriggerCheckboxRef start run') + // onTriggerCheckboxRef.current?.({ + // colorStore: state.colorStore, + // currentCheckedPurchaserId: state.currentCheckedPurchaserId, + // setSelectedAmount: amount => dispatch({ type: ShoppingDispatchType.UPDATE_SELECTED_AMOUNT, data: amount }), + // }) + // }) + // return unsubscribe + // }, [Observer]) + return ( {children} diff --git a/src/pages/shopping/components/shoppingCartItem/index.tsx b/src/pages/shopping/components/shoppingCartItem/index.tsx index 247f2dd..11ae848 100644 --- a/src/pages/shopping/components/shoppingCartItem/index.tsx +++ b/src/pages/shopping/components/shoppingCartItem/index.tsx @@ -1,5 +1,5 @@ import { Text, View } from '@tarojs/components' -import { FC, forwardRef, memo, useEffect, useMemo, useState, useTransition } from 'react' +import { FC, forwardRef, memo, useEffect, useMemo, useRef, useState, useTransition } from 'react' import styles from './index.module.scss' import classnames from 'classnames' import { formatMeterDiv } from '@/common/format' @@ -14,6 +14,8 @@ import IconFont from '@/components/iconfont/iconfont' import { isEmptyObject } from '@/common/common' import classNames from 'classnames' import LoadingCard from '@/components/loadingCard' +import { ShoppingCartListApi } from '@/api' +import { ShoppingStore } from '../../context/shoppingStore' interface ButtonPropsType { isActive: boolean @@ -53,11 +55,13 @@ type PropsType = { state?: { multipleSelection?: GoodsMeta['multipleSelection'] currentCheckedPurchaserId?: number + Observer?: ShoppingStore } } let ShoppingCartItem: FC = props => { - const { itemData, state } = props + const { state } = props + const [itemData, setItemData] = useState(props.itemData) const { multipleSelection, currentCheckedPurchaserId } = state! const dispatch = useShoppingDispatch() @@ -87,7 +91,10 @@ let ShoppingCartItem: FC = props => { } dispatch({ type: ShoppingDispatchType.UPDATE_SELECTED_AMOUNT, data: 0 }) 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, + }) dispatch({ type: ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION, data: false }) } @@ -131,6 +138,58 @@ let ShoppingCartItem: FC = props => { }, [multipleSelection, currentCheckedPurchaserId, selected, itemData]) const [isPending, startTransition] = useTransition() + const { fetchData } = ShoppingCartListApi() + + // 发布订阅 + + useEffect(() => { + const unsubscribe = state?.Observer?.subscribe(async id => { + if (itemData?.purchaser_id !== id) return + console.log('request new data start run') + const res = await fetchData({ + purchaser_id: id, + }) + console.log('res===>', res) + if (res.success) { + const newGoodsKind = Object.fromEntries( + res.data[0]?.[BackEndSaleModeListFieldMap[selected]].map(item => [ + item?.id, + { + id: item?.id, + estimate_amount: item.estimate_amount, + 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)), + }, + ]), + ) + dispatch({ + type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX, + data: { + purchaserId: id, + goodsKind: newGoodsKind, + multipleSelection: Object.fromEntries( + Object.keys(multipleSelection!).map(id => [ + id, + { + id, + estimate_amount: newGoodsKind[id].estimate_amount, + product_code: newGoodsKind[id].product_code, + product_color_code: newGoodsKind[id].product_color_code, + sale_mode: newGoodsKind[id].sale_mode, + count: selected === EnumSaleMode.Bulk ? newGoodsKind[id].roll : Number(formatMeterDiv(newGoodsKind[id].length)), + }, + ]), + ), + }, + }) + setItemData(res.data[0]) + } + }) + // 取消订阅 + return unsubscribe + }, [multipleSelection]) return ( = props => { - + @@ -205,52 +270,78 @@ let ShoppingCartItem: FC = props => { interface GoodsListPropType { itemData?: ShoppingCartData + multipleSelection?: GoodsMeta['multipleSelection'] selected: EnumSaleMode isPending: boolean startTransition: React.TransitionStartFunction } const GoodsList = memo(props => { - console.log('Rerender component: GoodsList') - const { itemData, selected, isPending, startTransition } = props + console.log('Rerender component: GoodsList', props.multipleSelection) + const { itemData, selected, isPending, startTransition, multipleSelection } = props + + const currentSelected = useRef(null) + const dispatch = useShoppingDispatch() const [component, setComponent] = useState(null) + // 更新 GoodsList 组件 + const updateComponent = () => { + setComponent( + itemData?.[BackEndSaleModeListFieldMap[selected]].length !== 0 ? ( + itemData?.[BackEndSaleModeListFieldMap[selected]].map(item => { + return + }) + ) : ( + 暂无数据 + ), + ) + } + useEffect(() => { - startTransition(() => { - setComponent( - itemData?.[BackEndSaleModeListFieldMap[selected]].length !== 0 ? ( - 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, - 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)), - }, - }, - multipleSelection: {}, - }, - }) - return - }) - ) : ( - 暂无数据 - ), - ) - }) - }, [itemData, selected]) + const newGoodsKind = Object.fromEntries( + itemData?.[BackEndSaleModeListFieldMap[selected]].map(item => [ + item?.id, + { + id: item?.id, + estimate_amount: item.estimate_amount, + 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)), + }, + ]), + ) + // 这里做一层比较是为了 重新渲染的时候如果没有切换订单类型的话就不让面料的选中状态初始化 + if (currentSelected.current === selected) { + dispatch({ + type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX, + data: { + purchaserId: itemData?.purchaser_id!, + goodsKind: newGoodsKind, + multipleSelection: multipleSelection, + }, + }) + updateComponent() + } else { + // 重新把当前的选中状态赋值给ref 作为下一次比较的旧状态 + currentSelected.current = selected + dispatch({ + type: ShoppingDispatchType.UPDATE_CHANGED_CHECKBOX, + data: { + purchaserId: itemData?.purchaser_id!, + goodsKind: newGoodsKind, + multipleSelection: {}, + }, + }) + startTransition(updateComponent) + } + }, [itemData, selected, multipleSelection]) return ( <> {isPending ? ( - + ) : ( @@ -262,12 +353,12 @@ const GoodsList = memo(props => { // State 分割组件 思路就是把 context 直接通过 props 的形式传给组件,这样的话就解决了 context 强制刷新 memo 的问题了 // 那么当 context 内的 value 被更新的时候,react 只会强制渲染 Wrapper const withStateSlice = (comp, slice) => { - const MemoComp = memo(comp, (prevProps, nextProps)=>{ + const MemoComp = memo(comp, (prevProps, nextProps) => { let needMemo = true if (JSON.stringify(prevProps.itemData) !== JSON.stringify(nextProps.itemData)) { needMemo = false } - if(JSON.stringify(prevProps.state) !== JSON.stringify(nextProps.state)){ + if (JSON.stringify(prevProps.state) !== JSON.stringify(nextProps.state)) { needMemo = false } return needMemo @@ -283,6 +374,7 @@ const withStateSlice = (comp, slice) => { ShoppingCartItem = withStateSlice(ShoppingCartItem, (state: ShoppingStateContextValue, props) => ({ multipleSelection: state.colorStore?.[props.itemData?.purchaser_id]?.['multipleSelection'], currentCheckedPurchaserId: state.currentCheckedPurchaserId, + Observer: state.Observer, })) export default ShoppingCartItem diff --git a/src/pages/shopping/context/index.ts b/src/pages/shopping/context/index.ts index d04e673..592136f 100644 --- a/src/pages/shopping/context/index.ts +++ b/src/pages/shopping/context/index.ts @@ -1,6 +1,7 @@ import { EnumSaleMode } from '@/common/Enumerate' import React, { Dispatch } from 'react' import { useContext } from 'react' +import { ShoppingStore } from './shoppingStore' /** * 456: { @@ -8,7 +9,13 @@ import { useContext } from 'react' * colorKind: { * 4562: { * id: 4562, - * checked: false + * ... + * } + * }, + * multipleSelection: { + * 4562: { + * id: 4562, + * ... * } * } * }, @@ -17,7 +24,7 @@ import { useContext } from 'react' * colorKind: { * 4562: { * id: 4562, - * checked: false + * ... * } * } * } @@ -40,9 +47,7 @@ export interface GoodsMeta { goodsKind?: { [id: Goods['id']]: Goods } - multipleSelection: { - [id: Goods['id']]: Goods - } + multipleSelection?: GoodsMeta['goodsKind'] } export interface ShoppingStateContextValue { @@ -52,6 +57,7 @@ export interface ShoppingStateContextValue { currentCheckedSaleMode: EnumSaleMode colorStore: ColorStore selectedAmount: number + Observer: ShoppingStore } export enum ShoppingDispatchType { @@ -63,7 +69,7 @@ export enum ShoppingDispatchType { UPDATE_SELECTED_AMOUNT = 'UPDATE_SELECTED_AMOUNT', UPDATE_CHANGED_CHECKBOX = 'UPDATE_CHANGED_CHECKBOX', } -// UPDATE_MultipleSelection + export interface ShoppingDispatchContextValue { [ShoppingDispatchType.UPDATE_MANAGE_STATUS]: (isManageStatus: ShoppingStateContextValue['isManageStatus']) => void [ShoppingDispatchType.UPDATE_MULTIPLE_SELECTION]: (isMultipleSelection: ShoppingStateContextValue['isMultipleSelection']) => void @@ -109,7 +115,7 @@ export function shoppingReducer(state: ShoppingStateContextValue, action: Shoppi [data.purchaserId as number]: { purchaserId: data.purchaserId, goodsKind: { ...state.colorStore[data.purchaserId]?.goodsKind, ...data.goodsKind }, - multipleSelection: { ...data.multipleSelection }, + multipleSelection: data?.multipleSelection ? data.multipleSelection : state.colorStore[data.purchaserId]?.multipleSelection, }, }, } diff --git a/src/pages/shopping/context/shoppingStore.ts b/src/pages/shopping/context/shoppingStore.ts index b5b5a9f..e93151a 100644 --- a/src/pages/shopping/context/shoppingStore.ts +++ b/src/pages/shopping/context/shoppingStore.ts @@ -1,7 +1,9 @@ -import { GoodsMeta, ColorStore } from '.' + // 用于优化数据流 结合发布订阅 更新组件内部状态可以组件自己处理 -export type SubscribeCallback = (changedGoods: GoodsMeta) => void +import { ColorStore } from "." + +export type SubscribeCallback = (changedGoods: any) => void export class ShoppingStore { // 全局缓存 @@ -9,7 +11,7 @@ export class ShoppingStore { // 监听器数组 private observers: SubscribeCallback[] = [] - constructor(initialValue: ColorStore) { + constructor(initialValue?: any) { initialValue && this.updateStore(initialValue) } // 更新全局缓存 @@ -27,9 +29,9 @@ export class ShoppingStore { } } // 通知 - notify(changedGoods: GoodsMeta) { + notify(changedGoods: any) { // 循环调用 - this.observers.forEach((callback) => { + this.observers.forEach(callback => { callback(changedGoods) }) } diff --git a/src/pages/shopping/index.tsx b/src/pages/shopping/index.tsx index 5390982..07cd63b 100644 --- a/src/pages/shopping/index.tsx +++ b/src/pages/shopping/index.tsx @@ -21,7 +21,7 @@ export const Shopping: FC = memo(() => { // 计算总的预估金额 const handleTriggerCheckbox = ({ colorStore, currentCheckedPurchaserId, setSelectedAmount }) => { const multipleSelection = colorStore?.[currentCheckedPurchaserId]?.multipleSelection - console.log('handleTriggerCheckbox==>', colorStore) + console.log('handleTriggerCheckbox==>', multipleSelection) if (multipleSelection) { const result = Object.values(multipleSelection).reduce((prev: number, value: Goods) => { @@ -84,11 +84,18 @@ const ShoppingCartContainer: FC = () => { setSearchOptions(prev => ({ ...prev, short_name_or_phone: e })) }, []) - const [shoppingCartData, setShoppingCartData] = useState<{ list: ShoppingCartData[]; total: number }>({ list: [], total: 0 }) + const [shoppingCartData, setShoppingCartData] = useState<{ + list: ShoppingCartData[] + total: number + }>({ list: [], total: 0 }) //数据加载状态 const statusMore = useMemo(() => { - const status = dataLoadingStatus({ list: shoppingCartData.list, total: shoppingCartData.total, status: state.loading }) + const status = dataLoadingStatus({ + list: shoppingCartData.list, + total: shoppingCartData.total, + status: state.loading, + }) console.log('status==>', status) return status }, [shoppingCartData, state]) @@ -144,7 +151,7 @@ const ShoppingCartContainer: FC = () => { // 批量某个客户的删除商品 const handleDelete = async () => { const multipleSelection = colorStore?.[currentCheckedPurchaserId]?.['multipleSelection'] - let checked: Goods[] = Object.values(multipleSelection) + let checked: Goods[] = Object.values(multipleSelection!) if (checked.length === 0) { return Taro.showToast({ title: '请选择商品', icon: 'error' }) } @@ -201,6 +208,7 @@ const ShoppingCartContainer: FC = () => { setRefreshStatus(false) } } + return (