Files
digital-pilates/store/familyHealthSlice.ts

304 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 家庭健康管理 Redux Slice
*/
import * as healthProfileApi from '@/services/healthProfile';
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { RootState } from './index';
// ==================== State 类型定义 ====================
export interface FamilyHealthState {
// 家庭组信息
familyGroup: healthProfileApi.FamilyGroup | null;
// 家庭成员列表
members: healthProfileApi.FamilyMember[];
// 邀请码信息
inviteCode: healthProfileApi.InviteCode | null;
// 加载状态
loading: boolean;
membersLoading: boolean;
inviteLoading: boolean;
// 错误信息
error: string | null;
}
// ==================== 初始状态 ====================
const initialState: FamilyHealthState = {
familyGroup: null,
members: [],
inviteCode: null,
loading: false,
membersLoading: false,
inviteLoading: false,
error: null,
};
// ==================== Async Thunks ====================
/**
* 获取用户所属家庭组
*/
export const fetchFamilyGroup = createAsyncThunk(
'familyHealth/fetchGroup',
async (_, { rejectWithValue }) => {
try {
const data = await healthProfileApi.getFamilyGroup();
return data;
} catch (err: any) {
return rejectWithValue(err?.message ?? '获取家庭组失败');
}
}
);
/**
* 生成邀请码
*/
export const generateInviteCode = createAsyncThunk(
'familyHealth/generateInvite',
async (expiresInHours: number = 24, { rejectWithValue }) => {
try {
const data = await healthProfileApi.generateInviteCode(expiresInHours);
return data;
} catch (err: any) {
return rejectWithValue(err?.message ?? '生成邀请码失败');
}
}
);
/**
* 加入家庭组
*/
export const joinFamilyGroup = createAsyncThunk(
'familyHealth/joinGroup',
async (
{ inviteCode, relationship }: { inviteCode: string; relationship: string },
{ rejectWithValue }
) => {
try {
const data = await healthProfileApi.joinFamilyGroup(inviteCode, relationship);
return data;
} catch (err: any) {
return rejectWithValue(err?.message ?? '加入家庭组失败');
}
}
);
/**
* 获取家庭成员列表
*/
export const fetchFamilyMembers = createAsyncThunk(
'familyHealth/fetchMembers',
async (_, { rejectWithValue }) => {
try {
const data = await healthProfileApi.getFamilyMembers();
return data;
} catch (err: any) {
return rejectWithValue(err?.message ?? '获取家庭成员失败');
}
}
);
/**
* 更新家庭成员权限
*/
export const updateFamilyMember = createAsyncThunk(
'familyHealth/updateMember',
async (
{
memberId,
permissions,
}: {
memberId: string;
permissions: healthProfileApi.UpdateMemberPermissionsRequest;
},
{ rejectWithValue }
) => {
try {
const data = await healthProfileApi.updateFamilyMember(memberId, permissions);
return data;
} catch (err: any) {
return rejectWithValue(err?.message ?? '更新成员权限失败');
}
}
);
/**
* 移除家庭成员
*/
export const removeFamilyMember = createAsyncThunk(
'familyHealth/removeMember',
async (memberId: string, { rejectWithValue }) => {
try {
await healthProfileApi.removeFamilyMember(memberId);
return memberId;
} catch (err: any) {
return rejectWithValue(err?.message ?? '移除成员失败');
}
}
);
/**
* 退出家庭组
*/
export const leaveFamilyGroup = createAsyncThunk(
'familyHealth/leaveGroup',
async (_, { rejectWithValue }) => {
try {
await healthProfileApi.leaveFamilyGroup();
return true;
} catch (err: any) {
return rejectWithValue(err?.message ?? '退出家庭组失败');
}
}
);
// ==================== Slice ====================
const familyHealthSlice = createSlice({
name: 'familyHealth',
initialState,
reducers: {
// 清除错误
clearError: (state) => {
state.error = null;
},
// 清除邀请码
clearInviteCode: (state) => {
state.inviteCode = null;
},
// 重置状态(用于登出时)
resetFamilyHealth: () => initialState,
},
extraReducers: (builder) => {
builder
// 获取家庭组
.addCase(fetchFamilyGroup.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchFamilyGroup.fulfilled, (state, action) => {
state.loading = false;
state.familyGroup = action.payload;
})
.addCase(fetchFamilyGroup.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
})
// 生成邀请码
.addCase(generateInviteCode.pending, (state) => {
state.inviteLoading = true;
state.error = null;
})
.addCase(generateInviteCode.fulfilled, (state, action) => {
state.inviteLoading = false;
state.inviteCode = action.payload;
})
.addCase(generateInviteCode.rejected, (state, action) => {
state.inviteLoading = false;
state.error = action.payload as string;
})
// 加入家庭组
.addCase(joinFamilyGroup.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(joinFamilyGroup.fulfilled, (state, action) => {
state.loading = false;
state.familyGroup = action.payload;
})
.addCase(joinFamilyGroup.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
})
// 获取家庭成员
.addCase(fetchFamilyMembers.pending, (state) => {
state.membersLoading = true;
state.error = null;
})
.addCase(fetchFamilyMembers.fulfilled, (state, action) => {
state.membersLoading = false;
state.members = action.payload;
})
.addCase(fetchFamilyMembers.rejected, (state, action) => {
state.membersLoading = false;
state.error = action.payload as string;
})
// 更新成员权限
.addCase(updateFamilyMember.fulfilled, (state, action) => {
const updatedMember = action.payload;
const index = state.members.findIndex((m) => m.id === updatedMember.id);
if (index !== -1) {
state.members[index] = updatedMember;
}
})
.addCase(updateFamilyMember.rejected, (state, action) => {
state.error = action.payload as string;
})
// 移除成员
.addCase(removeFamilyMember.fulfilled, (state, action) => {
const memberId = action.payload;
state.members = state.members.filter((m) => m.id !== memberId);
if (state.familyGroup) {
state.familyGroup.memberCount = Math.max(0, state.familyGroup.memberCount - 1);
}
})
.addCase(removeFamilyMember.rejected, (state, action) => {
state.error = action.payload as string;
})
// 退出家庭组
.addCase(leaveFamilyGroup.fulfilled, (state) => {
state.familyGroup = null;
state.members = [];
state.inviteCode = null;
})
.addCase(leaveFamilyGroup.rejected, (state, action) => {
state.error = action.payload as string;
});
},
});
// ==================== Actions ====================
export const { clearError, clearInviteCode, resetFamilyHealth } = familyHealthSlice.actions;
// ==================== Selectors ====================
export const selectFamilyGroup = (state: RootState) => state.familyHealth.familyGroup;
export const selectFamilyMembers = (state: RootState) => state.familyHealth.members;
export const selectInviteCode = (state: RootState) => state.familyHealth.inviteCode;
export const selectFamilyHealthLoading = (state: RootState) => state.familyHealth.loading;
export const selectFamilyMembersLoading = (state: RootState) => state.familyHealth.membersLoading;
export const selectInviteLoading = (state: RootState) => state.familyHealth.inviteLoading;
export const selectFamilyHealthError = (state: RootState) => state.familyHealth.error;
// 判断当前用户是否是家庭组 owner
export const selectIsOwner = (state: RootState) => {
const currentUserId = state.user.profile?.id;
const familyGroup = state.familyHealth.familyGroup;
return currentUserId && familyGroup && familyGroup.ownerId === currentUserId;
};
// 判断当前用户是否是管理员owner 或 admin
export const selectIsAdmin = (state: RootState) => {
const currentUserId = state.user.profile?.id;
const members = state.familyHealth.members;
const currentMember = members.find((m) => m.userId === currentUserId);
return currentMember && (currentMember.role === 'owner' || currentMember.role === 'admin');
};
export default familyHealthSlice.reducer;