Files
digital-pilates/store/tabBarConfigSlice.ts
richarjiang 4836058d56 feat(health): 新增手腕温度监测和经期双向同步功能
新增手腕温度健康数据追踪,支持Apple Watch睡眠手腕温度数据展示和30天历史趋势分析
实现经期数据与HealthKit的完整双向同步,支持读取、写入和删除经期记录
优化经期预测算法,基于历史数据计算更准确的周期和排卵日预测
重构经期UI组件为模块化结构,提升代码可维护性
添加完整的中英文国际化支持,覆盖所有新增功能界面
2025-12-18 08:40:08 +08:00

231 lines
6.3 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.

import AsyncStorage from '@/utils/kvStore';
import { logger } from '@/utils/logger';
import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
import type { RootState } from './index';
// Tab 配置接口
export interface TabConfig {
id: string; // tab 标识符
icon: string; // SF Symbol 图标名
titleKey: string; // i18n 翻译 key
enabled: boolean; // 是否启用
canBeDisabled: boolean; // 是否可以被禁用(系统级配置,不持久化)
order: number; // 显示顺序
}
// 用户可持久化的配置(只包含用户可控制的属性)
interface UserTabConfig {
id: string;
enabled: boolean;
order: number;
}
// State 接口
interface TabBarConfigState {
configs: TabConfig[];
isInitialized: boolean;
}
// 默认配置
export const DEFAULT_TAB_CONFIGS: TabConfig[] = [
{
id: 'statistics',
icon: 'chart.pie.fill',
titleKey: 'health.tabs.health',
enabled: true,
canBeDisabled: false,
order: 1,
},
{
id: 'medications',
icon: 'pills.fill',
titleKey: 'health.tabs.medications',
enabled: true,
canBeDisabled: true, // 用药管理可以被关闭
order: 2,
},
{
id: 'fasting',
icon: 'timer',
titleKey: 'health.tabs.fasting',
enabled: true,
canBeDisabled: true, // 断食可以被关闭
order: 3,
},
{
id: 'challenges',
icon: 'trophy.fill',
titleKey: 'health.tabs.challenges',
enabled: true,
canBeDisabled: true, // 挑战可以被关闭
order: 4,
},
{
id: 'personal',
icon: 'person.fill',
titleKey: 'health.tabs.personal',
enabled: true,
canBeDisabled: false,
order: 5,
},
];
// AsyncStorage key
const STORAGE_KEY = 'tab_bar_config';
// 初始状态
const initialState: TabBarConfigState = {
configs: DEFAULT_TAB_CONFIGS,
isInitialized: false,
};
const tabBarConfigSlice = createSlice({
name: 'tabBarConfig',
initialState,
reducers: {
// 设置配置(用于从 AsyncStorage 恢复)
setConfigs: (state, action: PayloadAction<TabConfig[]>) => {
state.configs = action.payload;
state.isInitialized = true;
},
// 切换 tab 启用状态
toggleTabEnabled: (state, action: PayloadAction<string>) => {
const tabId = action.payload;
const config = state.configs.find(c => c.id === tabId);
if (config && config.canBeDisabled) {
config.enabled = !config.enabled;
// 自动持久化到 AsyncStorage
saveConfigsToStorage(state.configs);
}
},
// 更新 tab 顺序(拖拽后)
reorderTabs: (state, action: PayloadAction<TabConfig[]>) => {
// 更新顺序,同时保持其他属性不变
const newConfigs = action.payload.map((config, index) => ({
...config,
order: index + 1,
}));
state.configs = newConfigs;
// 自动持久化到 AsyncStorage
saveConfigsToStorage(newConfigs);
},
// 恢复默认配置
resetToDefault: (state) => {
state.configs = DEFAULT_TAB_CONFIGS;
// 持久化到 AsyncStorage
saveConfigsToStorage(DEFAULT_TAB_CONFIGS);
},
// 标记已初始化
markInitialized: (state) => {
state.isInitialized = true;
},
},
});
// 持久化配置到 AsyncStorage只保存用户可控制的属性
const saveConfigsToStorage = async (configs: TabConfig[]) => {
try {
// 只保存用户可控制的属性enabled 和 order
const userConfigs: UserTabConfig[] = configs.map(config => ({
id: config.id,
enabled: config.enabled,
order: config.order,
}));
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(userConfigs));
logger.info('底部栏配置已保存');
} catch (error) {
logger.error('保存底部栏配置失败:', error);
}
};
// 从 AsyncStorage 加载配置
export const loadTabBarConfigs = () => async (dispatch: any) => {
try {
const stored = await AsyncStorage.getItem(STORAGE_KEY);
if (stored) {
const userConfigs = JSON.parse(stored) as UserTabConfig[];
// 验证配置有效性
if (Array.isArray(userConfigs) && userConfigs.length > 0) {
// 合并用户配置和默认配置
const mergedConfigs = mergeWithDefaults(userConfigs);
dispatch(setConfigs(mergedConfigs));
logger.info('底部栏配置已加载');
return;
}
}
// 如果没有存储或无效,使用默认配置
dispatch(setConfigs(DEFAULT_TAB_CONFIGS));
dispatch(markInitialized());
} catch (error) {
logger.error('加载底部栏配置失败:', error);
// 出错时使用默认配置
dispatch(setConfigs(DEFAULT_TAB_CONFIGS));
dispatch(markInitialized());
}
};
// 合并用户配置和默认配置
const mergeWithDefaults = (userConfigs: UserTabConfig[]): TabConfig[] => {
const merged: TabConfig[] = [];
// 遍历默认配置,将用户的 enabled 和 order 合并进来
DEFAULT_TAB_CONFIGS.forEach(defaultConfig => {
const userConfig = userConfigs.find(c => c.id === defaultConfig.id);
if (userConfig) {
// 合并系统配置icon, titleKey, canBeDisabled从默认配置读取
// 用户配置enabled, order从用户配置读取
merged.push({
...defaultConfig,
enabled: userConfig.enabled,
order: userConfig.order,
});
} else {
// 新增的 tab使用默认配置
merged.push({
...defaultConfig,
order: merged.length + 1,
});
}
});
// 按 order 排序
return merged.sort((a, b) => a.order - b.order);
};
// Actions
export const {
setConfigs,
toggleTabEnabled,
reorderTabs,
resetToDefault,
markInitialized,
} = tabBarConfigSlice.actions;
// Selectors
export const selectTabBarConfigs = (state: RootState) => state.tabBarConfig.configs;
// ✅ 使用 createSelector 进行记忆化,避免不必要的重渲染
export const selectEnabledTabs = createSelector(
[selectTabBarConfigs],
(configs) => configs
.filter(config => config.enabled)
.sort((a, b) => a.order - b.order)
);
export const selectIsInitialized = (state: RootState) => state.tabBarConfig.isInitialized;
// 按 id 获取配置
export const selectTabConfigById = (tabId: string) => (state: RootState) =>
state.tabBarConfig.configs.find(config => config.id === tabId);
export default tabBarConfigSlice.reducer;