feat(ui): 添加底部标签栏自定义配置功能和药物堆叠展示
- 新增底部标签栏配置页面,支持切换标签显示/隐藏和恢复默认设置 - 实现已服用药物的堆叠卡片展示,优化药物列表视觉层次 - 集成Redux状态管理底部标签栏配置,支持本地持久化 - 优化个人中心页面背景渐变效果,移除装饰性圆圈元素 - 更新启动页和应用图标为新的品牌视觉 - 药物详情页AI分析加载动画替换为Lottie动画 - 调整药物卡片圆角半径提升视觉一致性 - 新增多语言支持(中英文)用于标签栏配置界面 主要改进: 1. 用户可以自定义底部导航栏显示内容 2. 已完成的药物以堆叠形式展示,节省空间 3. 配置数据通过AsyncStorage持久化保存 4. 支持默认配置恢复功能
This commit is contained in:
203
store/tabBarConfigSlice.ts
Normal file
203
store/tabBarConfigSlice.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import AsyncStorage from '@/utils/kvStore';
|
||||
import { logger } from '@/utils/logger';
|
||||
import { 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; // 显示顺序
|
||||
}
|
||||
|
||||
// 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: false,
|
||||
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: false,
|
||||
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 {
|
||||
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(configs));
|
||||
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 configs = JSON.parse(stored) as TabConfig[];
|
||||
|
||||
// 验证配置有效性
|
||||
if (Array.isArray(configs) && configs.length > 0) {
|
||||
// 合并默认配置,确保新增的 tab 也能显示
|
||||
const mergedConfigs = mergeWithDefaults(configs);
|
||||
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 = (storedConfigs: TabConfig[]): TabConfig[] => {
|
||||
const merged = [...storedConfigs];
|
||||
|
||||
// 检查是否有新增的默认 tab
|
||||
DEFAULT_TAB_CONFIGS.forEach(defaultConfig => {
|
||||
const exists = merged.find(c => c.id === defaultConfig.id);
|
||||
if (!exists) {
|
||||
// 新增的 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;
|
||||
export const selectEnabledTabs = (state: RootState) =>
|
||||
state.tabBarConfig.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;
|
||||
Reference in New Issue
Block a user