304 lines
8.6 KiB
TypeScript
304 lines
8.6 KiB
TypeScript
/**
|
||
* 家庭健康管理 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;
|