在现代 Web 应用中,用户个性化设置是一项常见需求,例如主题偏好、语言选择或时间格式等。这些设置需要持久化存储,并在用户界面上提供直观的配置选项。本文将基于一个典型的 Vue 3 项目,详细讲解如何实现全局配置管理,包括使用 Pinia 进行状态管理、与后端 API 交互、页面表单反显、更新配置以及支持重置和还原操作的完整流程。这种方法模块化、清晰易懂,是管理全局配置的最佳实践之一,方便在类似场景中复用。
整体流程与思想
实现全局配置管理的核心思想是将配置数据集中管理,并通过清晰的模块分工实现状态存储、数据获取与更新、以及用户界面的展示与交互。以下是整体流程的四个关键部分:
- 状态管理:使用 Pinia 创建一个全局 Store,集中存储用户的配置数据。
- API 交互:定义服务模块,与后端 API 通信,负责获取和更新配置数据。
- 逻辑封装:通过自定义 Hook 封装获取和更新配置的逻辑,供组件调用。
- 用户界面:基于表单组件展示配置,提供保存、重置和还原功能。
这种分层设计不仅提高了代码的可维护性,还能让每个模块职责单一,便于扩展和调试。
详细实现步骤
以下是基于所给代码的具体实现步骤,涵盖从状态管理到用户界面的完整流程。
1. 状态管理:使用 Pinia 存储配置
我们首先使用 Pinia 创建一个全局 Store,命名为 userSettingStore
,用于管理用户的配置数据。这里以时间相关的配置为例,包括 firstDayMode
(每年第一周是否包含 1 月 1 日)和 startOfWeek
(一周的起始天)。
// src/store/user-setting-store/index.ts
import { defineStore } from 'pinia';
import { FirstDayModeEnum, StartOfWeekEnum } from './typing';
export const useUserSettingStore = defineStore('userSettingStore', {
state: () => ({
userSetting: {
firstDayMode: FirstDayModeEnum.A, // 默认包含 1 月 1 日
startOfWeek: StartOfWeekEnum.MONDAY, // 默认周一为起始天
},
}),
actions: {
setUserSetting(userSetting: any) {
this.userSetting = userSetting; // 更新配置数据
},
},
});
为了确保类型安全,我们在 typing.ts
中定义了枚举和接口:
// src/store/user-setting-store/typing.ts
export enum FirstDayModeEnum {
A = 'A', // 包含 1 月 1 日
B = 'B', // 不包含 1 月 1 日
}
export enum StartOfWeekEnum {
MONDAY = '1', // 周一
TUESDAY = '2', // 周二
WEDNESDAY = '3', // 周三
THURSDAY = '4', // 周四
FRIDAY = '5', // 周五
SATURDAY = '6', // 周六
SUNDAY = '7', // 周日
}
export interface UserSettingForm {
firstDayMode?: FirstDayModeEnum;
startOfWeek?: StartOfWeekEnum;
}
思路:Pinia 提供了响应式的状态管理能力,通过 state
定义初始值,actions
提供更新方法。枚举的使用增强了代码的可读性和类型约束。
2. API 交互:与后端通信
接下来,我们定义服务模块 permission/index.ts
,包含两个函数:getUserHabit
用于获取用户配置,setUserHabit
用于更新配置。这两个函数通过 HTTP 请求与后端交互。
// src/service/permission/index.ts
import http from '@/utils/http';
export async function getUserHabit(param: any) {
return http.get(`/userHabit?code=${param.code}&module=${param.module}`, {
baseURL: import.meta.env.VITE_APP_API_PREFIX_USER_CENTER,
});
}
export async function setUserHabit(params: any) {
return http.post('/userHabit', params, {
baseURL: import.meta.env.VITE_APP_API_PREFIX_USER_CENTER,
});
}
思路:服务模块封装了网络请求逻辑,使用环境变量配置 API 前缀,确保灵活性和可维护性。getUserHabit
通过查询参数获取数据,setUserHabit
通过 POST 请求提交更新。
3. 自定义 Hook:封装逻辑
为了让组件更专注于展示逻辑,我们创建了一个自定义 Hook useUserHabit.ts
,封装了获取和更新配置的逻辑,并提供了加载状态和配置数据。
// src/hooks/useUserHabit.ts
import { ref, computed, onMounted } from 'vue';
import { getUserHabit, setUserHabit } from '@/service/permission';
import { useUserSettingStore } from '@/store/user-setting-store';
export const useUserHabit = () => {
const userSettingStore = useUserSettingStore();
const userSetting = computed(() => userSettingStore.userSetting); // 响应式配置数据
const isLoading = ref(false); // 加载状态
const fetchUserHabit = async () => {
isLoading.value = true;
const res = await getUserHabit({
code: 'userSetting',
module: 'yms',
});
const { data } = res;
userSettingStore.setUserSetting(JSON.parse(data.data)); // 更新 Store
isLoading.value = false;
};
const updateUserHabit = async (newUserSetting: any) => {
isLoading.value = true;
await setUserHabit({
code: 'userSetting',
module: 'yms',
habit: JSON.stringify(newUserSetting),
});
isLoading.value = false;
fetchUserHabit(); // 更新后重新获取数据
};
onMounted(() => {
fetchUserHabit(); // 组件挂载时获取数据
});
return {
isLoading,
userSetting,
fetchUserHabit,
updateUserHabit,
};
};
思路:Hook 使用 computed
提供响应式的配置数据,isLoading
管理加载状态。fetchUserHabit
和 updateUserHabit
分别处理数据获取和更新,onMounted
确保组件加载时自动获取最新配置。
4. 用户界面:表单展示与交互
最后,我们在 UserSetting.vue
中实现用户界面,使用 Element Plus 的表单组件展示配置,并提供保存、重置和还原按钮。逻辑通过 useUserSettingForm
Hook 管理。
组件代码
// src/views/user-setting/UserSetting.vue
<template>
<div class="user-setting">
<div class="layout-container">
<div class="right-panel">
<div class="setting-content">
<div class="setting-section">
<div class="section-h1">时间</div>
<div class="section-h2">自定义 Week</div>
<div class="section-h2-content">
<div class="line-item">
<div class="sub-label">每年第一周是否包含1月1日</div>
<el-radio-group v-model="form.firstDayMode">
<el-radio :label="FirstDayModeEnum.A">包含</el-radio>
<el-radio :label="FirstDayModeEnum.B">不包含</el-radio>
</el-radio-group>
</div>
<div class="line-item">
<div class="sub-label">第一周的起始天</div>
<el-radio-group v-model="form.startOfWeek">
<el-radio :label="StartOfWeekEnum.MONDAY">周一</el-radio>
<el-radio :label="StartOfWeekEnum.SUNDAY">周日</el-radio>
<!-- 其他选项省略 -->
</el-radio-group>
</div>
</div>
</div>
<div class="action-buttons">
<el-button type="primary" :loading="isLoading" @click="handleSave">保存设置</el-button>
<el-button :loading="isLoading" @click="initForm">重置表单</el-button>
<el-button type="danger" :loading="isLoading" @click="handleReset">还原设置</el-button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { FirstDayModeEnum, StartOfWeekEnum } from '@/store/user-setting-store/typing';
import { useUserSettingForm } from './hooks/useUserSettingForm';
const activeTab = ref('data');
const { form, isLoading, initForm, handleSave, handleReset } = useUserSettingForm();
</script>
表单逻辑 Hook
// src/views/user-setting/hooks/useUserSettingForm.ts
import { onMounted, ref, watch } from 'vue';
import { useUserHabit } from '@/hooks/useUserHabit';
import { UserSettingForm } from '@/store/user-setting-store/typing';
import { DEFAULT_USER_SETTING } from '../constants';
export const useUserSettingForm = () => {
const form = ref<UserSettingForm>(DEFAULT_USER_SETTING); // 表单数据
const { isLoading, userSetting, fetchUserHabit, updateUserHabit } = useUserHabit();
const initForm = () => {
form.value = userSetting.value || DEFAULT_USER_SETTING; // 重置表单为当前配置或默认值
};
const resetUserSetting = async () => {
await updateUserHabit(DEFAULT_USER_SETTING); // 还原为默认配置
};
const handleSave = async () => {
try {
await updateUserHabit(form.value); // 保存表单数据
ElMessage.success('设置保存成功');
} catch (error) {
ElMessage.error('设置保存失败');
}
};
const handleReset = async () => {
try {
await resetUserSetting();
ElMessage.success('设置还原成功');
} catch (error) {
ElMessage.error('设置还原失败');
}
};
watch(userSetting, () => {
initForm(); // 配置变化时同步表单
}, { deep: true });
onMounted(() => {
initForm(); // 初始化表单
});
return {
form,
isLoading,
userSetting,
initForm,
fetchUserHabit,
updateUserHabit,
resetUserSetting,
handleSave,
handleReset,
};
};
默认配置
// src/views/user-setting/constants.ts
import { FirstDayModeEnum, StartOfWeekEnum } from '@/store/user-setting-store/typing';
export const DEFAULT_USER_SETTING = {
firstDayMode: FirstDayModeEnum.A,
startOfWeek: StartOfWeekEnum.MONDAY,
};
思路:
- 界面:使用
el-radio-group
展示选项,双向绑定表单数据。 - 逻辑:
useUserSettingForm
管理表单状态,watch
监听配置变化以同步表单,handleSave
和handleReset
分别处理保存和还原操作。 - 功能:
- 保存设置:将表单数据提交到后端并刷新。
- 重置表单:将表单恢复为当前配置。
- 还原设置:将配置恢复为默认值并更新后端。
总结
通过以上步骤,我们实现了一个完整的全局配置管理方案:
- 状态管理:Pinia 集中存储配置数据,提供响应式支持。
- API 交互:服务模块封装与后端通信,保证数据持久化。
- 逻辑封装:自定义 Hook 解耦业务逻辑,提升复用性。
- 用户界面:表单直观展示配置,支持修改、保存、重置和还原。
这种架构清晰、模块化,易于维护和扩展,非常适合需要在 Vue 3 项目中管理用户个性化设置的场景。开发者可以根据具体需求调整配置项和界面样式,快速复用这一最佳实践。
Comments NOTHING