230 lines
6.4 KiB
TypeScript
230 lines
6.4 KiB
TypeScript
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: 'statistics.tabs.health',
|
||
enabled: true,
|
||
canBeDisabled: false,
|
||
order: 1,
|
||
},
|
||
{
|
||
id: 'medications',
|
||
icon: 'pills.fill',
|
||
titleKey: 'statistics.tabs.medications',
|
||
enabled: true,
|
||
canBeDisabled: true, // 用药管理可以被关闭
|
||
order: 2,
|
||
},
|
||
{
|
||
id: 'fasting',
|
||
icon: 'timer',
|
||
titleKey: 'statistics.tabs.fasting',
|
||
enabled: true,
|
||
canBeDisabled: true, // 断食可以被关闭
|
||
order: 3,
|
||
},
|
||
{
|
||
id: 'challenges',
|
||
icon: 'trophy.fill',
|
||
titleKey: 'statistics.tabs.challenges',
|
||
enabled: true,
|
||
canBeDisabled: true, // 挑战可以被关闭
|
||
order: 4,
|
||
},
|
||
{
|
||
id: 'personal',
|
||
icon: 'person.fill',
|
||
titleKey: 'statistics.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; |