編輯 src/utils/request/index.ts
文件:
import axios from 'axios';
import { MessagePlugin } from 'tdesign-vue-next';
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
});
// 請求攔截器
request.interceptors.request.use(
(config) => {
// 添加認證token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 添加其他請求頭
config.headers['X-Requested-With'] = 'XMLHttpRequest';
config.headers['Content-Type'] = 'application/json';
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 響應攔截器
request.interceptors.response.use(
(response) => {
const { code, data, message } = response.data;
// 根據您的后端API格式調整
if (code === 200 || code === 0) {
return data;
} else {
// 錯誤處理
MessagePlugin.error(message || '請求失敗');
return Promise.reject(new Error(message));
}
},
(error) => {
// 網(wǎng)絡(luò )錯誤處理
if (error.response?.status === 401) {
// 未授權,跳轉到登錄頁(yè)
localStorage.removeItem('token');
window.location.href = '/login';
} else if (error.response?.status === 403) {
MessagePlugin.error('無(wú)權限訪(fǎng)問(wèn)');
} else if (error.response?.status === 500) {
MessagePlugin.error('服務(wù)器內部錯誤');
} else {
MessagePlugin.error(error.message || '網(wǎng)絡(luò )錯誤');
}
return Promise.reject(error);
}
);
export default request;
在 src/api/
目錄下創(chuàng )建API模塊:
// src/api/user.ts
import request from '@/utils/request';
export interface User {
id: number;
username: string;
email: string;
avatar?: string;
roles: string[];
}
export interface UserListParams {
page: number;
size: number;
keyword?: string;
}
export interface UserListResponse {
list: User[];
total: number;
}
// 獲取用戶(hù)列表
export function getUserList(params: UserListParams) {
return request.get<UserListResponse>('/api/users', { params });
}
// 獲取用戶(hù)詳情
export function getUserDetail(id: number) {
return request.get<User>(`/api/users/${id}`);
}
// 創(chuàng )建用戶(hù)
export function createUser(data: Partial<User>) {
return request.post<User>('/api/users', data);
}
// 更新用戶(hù)
export function updateUser(id: number, data: Partial<User>) {
return request.put<User>(`/api/users/${id}`, data);
}
// 刪除用戶(hù)
export function deleteUser(id: number) {
return request.delete(`/api/users/${id}`);
}
// src/api/upload.ts
import request from '@/utils/request';
export interface UploadResponse {
url: string;
filename: string;
size: number;
}
export function uploadFile(file: File, onProgress?: (progress: number) => void) {
const formData = new FormData();
formData.append('file', file);
return request.post<UploadResponse>('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (progressEvent) => {
if (onProgress && progressEvent.total) {
const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
onProgress(progress);
}
},
});
}
// src/store/modules/user.ts
import { defineStore } from 'pinia';
import { getUserDetail, type User } from '@/api/user';
export interface UserState {
userInfo: User | null;
token: string;
permissions: string[];
roles: string[];
}
export const useUserStore = defineStore('user', {
state: (): UserState => ({
userInfo: null,
token: '',
permissions: [],
roles: [],
}),
getters: {
isLoggedIn: (state) => !!state.token,
hasRole: (state) => (role: string) => state.roles.includes(role),
hasPermission: (state) => (permission: string) => state.permissions.includes(permission),
avatar: (state) => state.userInfo?.avatar || '/default-avatar.png',
username: (state) => state.userInfo?.username || '',
},
actions: {
// 登錄
async login(credentials: { username: string; password: string }) {
try {
const response = await loginApi(credentials);
this.token = response.token;
this.userInfo = response.userInfo;
this.permissions = response.permissions;
this.roles = response.roles;
// 保存token到localStorage
localStorage.setItem('token', this.token);
return response;
} catch (error) {
throw error;
}
},
// 獲取用戶(hù)信息
async fetchUserInfo() {
if (!this.token) return;
try {
const userInfo = await getUserDetail();
this.userInfo = userInfo;
this.permissions = userInfo.permissions;
this.roles = userInfo.roles;
} catch (error) {
this.logout();
throw error;
}
},
// 登出
logout() {
this.userInfo = null;
this.token = '';
this.permissions = [];
this.roles = [];
localStorage.removeItem('token');
},
// 更新用戶(hù)信息
updateUserInfo(userInfo: Partial<User>) {
if (this.userInfo) {
this.userInfo = { ...this.userInfo, ...userInfo };
}
},
},
// 持久化配置
persist: {
key: 'user-store',
storage: localStorage,
paths: ['token', 'userInfo', 'permissions', 'roles'],
},
});
// src/store/modules/setting.ts
import { defineStore } from 'pinia';
export interface SettingState {
theme: 'light' | 'dark';
language: 'zh_CN' | 'en_US';
sidebarCollapsed: boolean;
primaryColor: string;
layout: 'side' | 'top' | 'mix';
}
export const useSettingStore = defineStore('setting', {
state: (): SettingState => ({
theme: 'light',
language: 'zh_CN',
sidebarCollapsed: false,
primaryColor: '#0052d9',
layout: 'side',
}),
getters: {
isDark: (state) => state.theme === 'dark',
isCollapsed: (state) => state.sidebarCollapsed,
},
actions: {
// 切換主題
toggleTheme() {
this.theme = this.theme === 'light' ? 'dark' : 'light';
this.applyTheme();
},
// 設置主題
setTheme(theme: 'light' | 'dark') {
this.theme = theme;
this.applyTheme();
},
// 應用主題
applyTheme() {
document.documentElement.setAttribute('theme-mode', this.theme);
},
// 切換側邊欄
toggleSidebar() {
this.sidebarCollapsed = !this.sidebarCollapsed;
},
// 設置語(yǔ)言
setLanguage(language: 'zh_CN' | 'en_US') {
this.language = language;
},
// 設置主色調
setPrimaryColor(color: string) {
this.primaryColor = color;
this.applyPrimaryColor();
},
// 應用主色調
applyPrimaryColor() {
document.documentElement.style.setProperty('--td-brand-color', this.primaryColor);
},
// 設置布局
setLayout(layout: 'side' | 'top' | 'mix') {
this.layout = layout;
},
},
persist: {
key: 'setting-store',
storage: localStorage,
},
});
<template>
<div class="user-profile">
<t-avatar :image="userStore.avatar" />
<span>{{ userStore.username }}</span>
<t-button @click="handleLogout">退出登錄</t-button>
</div>
</template>
<script setup lang="ts">
import { useUserStore } from '@/store/modules/user';
import { useRouter } from 'vue-router';
const userStore = useUserStore();
const router = useRouter();
const handleLogout = async () => {
userStore.logout();
router.push('/login');
};
</script>
// src/store/modules/cache.ts
import { defineStore } from 'pinia';
export interface CacheState {
dictData: Record<string, any[]>;
userList: any[];
lastFetchTime: Record<string, number>;
}
export const useCacheStore = defineStore('cache', {
state: (): CacheState => ({
dictData: {},
userList: [],
lastFetchTime: {},
}),
getters: {
// 獲取字典數據
getDictData: (state) => (key: string) => state.dictData[key] || [],
// 檢查緩存是否過(guò)期
isCacheExpired: (state) => (key: string, expireTime = 5 * 60 * 1000) => {
const lastTime = state.lastFetchTime[key];
return !lastTime || Date.now() - lastTime > expireTime;
},
},
actions: {
// 設置字典數據
setDictData(key: string, data: any[]) {
this.dictData[key] = data;
this.lastFetchTime[key] = Date.now();
},
// 清除緩存
clearCache(key?: string) {
if (key) {
delete this.dictData[key];
delete this.lastFetchTime[key];
} else {
this.dictData = {};
this.lastFetchTime = {};
}
},
// 獲取或刷新數據
async getOrFetchData(key: string, fetchFn: () => Promise<any[]>) {
if (!this.isCacheExpired(key)) {
return this.getDictData(key);
}
try {
const data = await fetchFn();
this.setDictData(key, data);
return data;
} catch (error) {
console.error(`獲取${key}數據失敗:`, error);
return this.getDictData(key);
}
},
},
});
// src/composables/useApi.ts
import { ref, reactive } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
export function useApi<T = any>(apiFn: (...args: any[]) => Promise<T>) {
const loading = ref(false);
const error = ref<string>('');
const data = ref<T | null>(null);
const execute = async (...args: any[]) => {
loading.value = true;
error.value = '';
try {
const result = await apiFn(...args);
data.value = result;
return result;
} catch (err: any) {
error.value = err.message || '請求失敗';
MessagePlugin.error(error.value);
throw err;
} finally {
loading.value = false;
}
};
return {
loading,
error,
data,
execute,
};
}
// 使用示例
const { loading, data, execute } = useApi(getUserList);
// 調用API
await execute({ page: 1, size: 10 });