cart-slice.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import makeClient from "@/lib/ApolloClientBrowser";
  2. import { type PayloadAction, createSlice, createAsyncThunk } from "@reduxjs/toolkit";
  3. import type {CartDetail} from "@/types/cart/type";
  4. import { GET_CART_ITEM } from "@/graphql";
  5. import { formatCartDetail } from "@/utils/cartDetailTools";
  6. /*
  7. idle 尚未初始化
  8. loading 正在请求购物车
  9. succeeded 请求成功,即使购物车为空也算成功
  10. failed 请求失败
  11. */
  12. export type CartStatus =
  13. | "idle"
  14. | "loading"
  15. | "succeeded"
  16. | "failed";
  17. export interface CartState {
  18. cart: CartDetail | null;
  19. loading: boolean;
  20. error: string | null;
  21. status: CartStatus;
  22. }
  23. // 定义异步 action
  24. export const fetchCartDetail = createAsyncThunk<
  25. ReturnType<typeof formatCartDetail> | null,
  26. void,
  27. {
  28. state: { cartDetail: CartState };
  29. rejectValue: string;
  30. }
  31. >(
  32. 'cartDetail/fetchCartDetail',
  33. async (_, { rejectWithValue }) => { //_ 占位,表示不需要参数
  34. try {
  35. const apolloClient = makeClient();
  36. const res = await apolloClient.mutate({
  37. mutation: GET_CART_ITEM,
  38. fetchPolicy: 'network-only', // 按需调整
  39. });
  40. const responseData = res.data?.createReadCart?.readCart;
  41. return responseData ? formatCartDetail(responseData) : null;
  42. } catch (error) {
  43. let errorMessage: string;
  44. if (error instanceof Error) {
  45. errorMessage = error.message;
  46. } else {
  47. errorMessage = String(error);
  48. }
  49. return rejectWithValue(errorMessage);
  50. }
  51. },
  52. {
  53. condition: (_, { getState }) => {
  54. const state = getState();
  55. const fetchStatus = state.cartDetail.status;
  56. if (fetchStatus === 'succeeded' || fetchStatus === 'loading') {
  57. // Already fetched or in progress, don't need to re-fetch
  58. return false
  59. }
  60. },
  61. },
  62. );
  63. const initialState: CartState = {
  64. cart: null,
  65. loading: false,
  66. status: "idle",
  67. error: null,
  68. };
  69. const cartSlice = createSlice({
  70. name: "cartDetail",
  71. initialState,
  72. reducers: {
  73. addItem: (state, action: PayloadAction<CartDetail>) => {
  74. state.cart = { ...action.payload };
  75. },
  76. updateCart: (state, action: PayloadAction<Partial<CartDetail>>) => {
  77. if (state.cart) {
  78. state.cart = { ...state.cart, ...action.payload };
  79. }
  80. },
  81. clearCart(state) {
  82. state.cart = null;
  83. state.loading = false;
  84. state.error = null;
  85. },
  86. },
  87. extraReducers: (builder) => {
  88. builder
  89. .addCase(fetchCartDetail.pending, (state) => {
  90. state.status = "loading";
  91. state.loading = true;
  92. state.error = null;
  93. })
  94. .addCase(fetchCartDetail.fulfilled, (state, action) => {
  95. state.status = "succeeded";
  96. state.loading = false;
  97. state.cart = action.payload;
  98. })
  99. .addCase(fetchCartDetail.rejected, (state, action) => {
  100. state.status = "failed";
  101. state.loading = false;
  102. state.error = (action.payload as string | undefined) ?? action.error.message ?? "Get cart detail failed!";
  103. });
  104. },
  105. });
  106. export const {
  107. addItem,
  108. updateCart,
  109. clearCart,
  110. } = cartSlice.actions;
  111. export default cartSlice.reducer;