feat(config): 实现后端配置管理功能

- 添加 ApiUrl 和 theme 配置字段支持
- 新增根据 code 查询应用参数的 API 接口- 实现配置 store 管理网站配置数据
- 支持 API 地址优先级: 后台配置 > 本地配置- 配置数据自动存储到 localStorage 实现持久化- 添加配置管理说明文档 CONFIG_MANAGEMENT.md- 优化请求工具支持动态 API 地址切换- 移除无用的 openNew 工具函数引入
- 实现主题配置自动加载和存储功能
This commit is contained in:
2025-10-04 16:08:35 +08:00
parent 650c80f5fe
commit c00509e51b
8 changed files with 360 additions and 48 deletions

View File

@@ -105,6 +105,19 @@ export async function getCmsWebsiteField(id: number) {
return Promise.reject(new Error(res.data.message));
}
/**
* 根据code查询应用参数
*/
export async function getCmsWebsiteFieldByCode(code: string) {
const res = await request.get<ApiResult<CmsWebsiteField>>(
MODULES_API_URL + '/cms/cms-website-field/getByCode/' + code
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 恢复项目参数
*/

View File

@@ -58,4 +58,8 @@ export interface Config {
email?: string;
loginTitle?: string;
sysLogo?: string;
}
// 添加API地址配置项
ApiUrl?: string;
// 添加主题配置项
theme?: string;
}

View File

@@ -141,7 +141,7 @@ import {
FullscreenExitOutlined
} from '@ant-design/icons-vue';
import {storeToRefs} from 'pinia';
import {copyText, openNew, openUrl} from '@/utils/common';
import {copyText, openUrl} from '@/utils/common';
import {useThemeStore} from '@/store/modules/theme';
import HeaderNotice from './header-notice.vue';
import PasswordModal from './password-modal.vue';
@@ -152,6 +152,8 @@ import {listRoles} from '@/api/system/role';
import { useSiteStore } from '@/store/modules/site';
import Qrcode from "@/components/QrCode/index.vue";
import {AppInfo} from "@/api/cms/cmsWebsite/model";
import {getCmsWebsiteFieldByCode} from "@/api/cms/cmsWebsiteField";
import {API_BASE_URL} from "@/config/setting";
// 是否开启响应式布局
const themeStore = useThemeStore();
@@ -244,6 +246,15 @@ const reload = () => {
}
});
}
// 检查是否启动自定义接口
if(!localStorage.getItem('ApiUrl')){
localStorage.setItem('ApiUrl', `${API_BASE_URL}`)
getCmsWebsiteFieldByCode('ApiUrl').then(res => {
if(res){
localStorage.setItem('ApiUrl', `${res.value}`);
}
})
}
};
reload();

View File

@@ -0,0 +1,119 @@
/**
* 网站配置 store
*/
import { defineStore } from 'pinia';
import {configWebsiteField} from '@/api/cms/cmsWebsiteField';
import type { Config } from '@/api/cms/cmsWebsiteField/model';
export interface ConfigState {
config: Config | null;
loading: boolean;
error: Error | null;
}
export const useConfigStore = defineStore({
id: 'config',
state: (): ConfigState => ({
// 网站配置数据
config: null,
// 加载状态
loading: false,
// 错误信息
error: null
}),
getters: {
/**
* 获取网站配置
*/
getConfig(state): Config | null {
return state.config;
},
/**
* 获取API地址
*/
getApiUrl(state): string | undefined {
return state.config?.ApiUrl;
},
/**
* 获取网站名称
*/
getSiteName(state): string | undefined {
return state.config?.siteName;
},
/**
* 获取网站Logo
*/
getSiteLogo(state): string | undefined {
return state.config?.siteLogo;
}
},
actions: {
/**
* 获取网站配置数据
*/
async fetchConfig() {
try {
this.loading = true;
this.error = null;
const data = await configWebsiteField();
this.config = data;
// 保存到localStorage中供其他地方使用
localStorage.setItem('config', JSON.stringify(data));
// 设置主题
if (data.theme && !localStorage.getItem('user_theme')) {
localStorage.setItem('user_theme', data.theme);
}
return data;
} catch (err) {
this.error = err instanceof Error ? err : new Error('获取配置失败');
console.error('获取网站配置失败:', err);
throw err;
} finally {
this.loading = false;
}
},
/**
* 更新配置数据
*/
setConfig(value: Config) {
this.config = value;
// 同时更新localStorage
localStorage.setItem('config', JSON.stringify(value));
},
/**
* 重新获取配置数据
*/
async refetchConfig() {
try {
this.loading = true;
this.error = null;
const data = await configWebsiteField();
this.config = data;
localStorage.setItem('config', JSON.stringify(data));
return data;
} catch (err) {
this.error = err instanceof Error ? err : new Error('获取配置失败');
console.error('重新获取网站配置失败:', err);
throw err;
} finally {
this.loading = false;
}
},
/**
* 清除配置数据
*/
clearConfig() {
this.config = null;
localStorage.removeItem('config');
}
}
});

View File

@@ -1,8 +1,7 @@
/**
* 增强的 API 请求工具
*/
import axios, { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';
import { message } from 'ant-design-vue';
import axios, { AxiosRequestConfig, AxiosError } from 'axios';
import { apiPerformanceMonitor } from './performance';
import { memoryCache } from './cache-manager';
import { getToken } from './token-util';
@@ -34,29 +33,29 @@ interface EnhancedRequestConfig extends AxiosRequestConfig {
// 请求队列管理
class RequestQueue {
private pendingRequests = new Map<string, Promise<any>>();
// 生成请求键
private generateKey(config: AxiosRequestConfig): string {
const { method, url, params, data } = config;
return `${method}_${url}_${JSON.stringify(params)}_${JSON.stringify(data)}`;
}
// 添加请求到队列
add<T>(config: AxiosRequestConfig, executor: () => Promise<T>): Promise<T> {
const key = this.generateKey(config);
if (this.pendingRequests.has(key)) {
return this.pendingRequests.get(key);
}
const promise = executor().finally(() => {
this.pendingRequests.delete(key);
});
this.pendingRequests.set(key, promise);
return promise;
}
// 清除队列
clear(): void {
this.pendingRequests.clear();
@@ -70,13 +69,13 @@ class RetryManager {
config: { times: number; delay: number; condition?: (error: any) => boolean }
): Promise<T> {
let lastError: any;
for (let i = 0; i <= config.times; i++) {
try {
return await fn();
} catch (error) {
lastError = error;
// 检查是否应该重试
if (i < config.times && (!config.condition || config.condition(error as AxiosError))) {
await this.delay(config.delay * Math.pow(2, i)); // 指数退避
@@ -86,10 +85,10 @@ class RetryManager {
}
}
}
throw lastError;
}
private static delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
@@ -101,13 +100,13 @@ export class EnhancedRequest {
baseURL: API_BASE_URL,
timeout: 30000
});
private requestQueue = new RequestQueue();
constructor() {
this.setupInterceptors();
}
private setupInterceptors() {
// 请求拦截器
this.instance.interceptors.request.use(
@@ -117,17 +116,17 @@ export class EnhancedRequest {
if (token && config.headers) {
config.headers[TOKEN_HEADER_NAME] = token;
}
// 添加请求时间戳
(config as any).startTime = Date.now();
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
this.instance.interceptors.response.use(
(response) => {
@@ -137,7 +136,7 @@ export class EnhancedRequest {
const duration = Date.now() - config.startTime;
apiPerformanceMonitor.recordApiCall(config.url, duration);
}
return response;
},
(error) => {
@@ -147,12 +146,12 @@ export class EnhancedRequest {
const duration = Date.now() - config.startTime;
apiPerformanceMonitor.recordApiCall(config.url, duration);
}
return Promise.reject(error);
}
);
}
// 通用请求方法
async request<T = any>(config: EnhancedRequestConfig): Promise<T> {
const {
@@ -163,10 +162,10 @@ export class EnhancedRequest {
timeoutRetry = true,
...axiosConfig
} = config;
// 生成缓存键
const cacheKey = cache?.key || this.generateCacheKey(axiosConfig);
// 尝试从缓存获取
if (cache?.enabled) {
const cachedData = memoryCache.get<T>(cacheKey);
@@ -174,11 +173,11 @@ export class EnhancedRequest {
return cachedData;
}
}
// 请求执行器
const executor = async (): Promise<T> => {
const response = await this.instance.request<T>(axiosConfig);
// 缓存响应数据
if (cache?.enabled && response.data) {
memoryCache.set(
@@ -188,10 +187,10 @@ export class EnhancedRequest {
cache.tags
);
}
return response.data;
};
// 请求去重
if (dedupe) {
return this.requestQueue.add(axiosConfig, async () => {
@@ -202,11 +201,11 @@ export class EnhancedRequest {
condition: retry.condition || this.shouldRetry
});
}
return executor();
});
}
// 重试机制
if (retry) {
return RetryManager.retry(executor, {
@@ -214,10 +213,10 @@ export class EnhancedRequest {
condition: retry.condition || this.shouldRetry
});
}
return executor();
}
// GET 请求
get<T = any>(url: string, config?: EnhancedRequestConfig): Promise<T> {
return this.request<T>({
@@ -226,7 +225,7 @@ export class EnhancedRequest {
url
});
}
// POST 请求
post<T = any>(url: string, data?: any, config?: EnhancedRequestConfig): Promise<T> {
return this.request<T>({
@@ -236,7 +235,7 @@ export class EnhancedRequest {
data
});
}
// PUT 请求
put<T = any>(url: string, data?: any, config?: EnhancedRequestConfig): Promise<T> {
return this.request<T>({
@@ -246,7 +245,7 @@ export class EnhancedRequest {
data
});
}
// DELETE 请求
delete<T = any>(url: string, config?: EnhancedRequestConfig): Promise<T> {
return this.request<T>({
@@ -255,47 +254,47 @@ export class EnhancedRequest {
url
});
}
// 批量请求
async batch<T = any>(requests: EnhancedRequestConfig[]): Promise<T[]> {
const promises = requests.map(config => this.request<T>(config));
return Promise.all(promises);
}
// 并发控制请求
async concurrent<T = any>(
requests: EnhancedRequestConfig[],
limit: number = 5
): Promise<T[]> {
const results: T[] = [];
for (let i = 0; i < requests.length; i += limit) {
const batch = requests.slice(i, i + limit);
const batchResults = await this.batch<T>(batch);
results.push(...batchResults);
}
return results;
}
// 生成缓存键
private generateCacheKey(config: AxiosRequestConfig): string {
const { method, url, params, data } = config;
return `api_${method}_${url}_${JSON.stringify(params)}_${JSON.stringify(data)}`;
}
// 判断是否应该重试
private shouldRetry(error: AxiosError): boolean {
// 网络错误或超时错误重试
if (!error.response) {
return true;
}
// 5xx 服务器错误重试
const status = error.response.status;
return status >= 500 && status < 600;
}
// 清除缓存
clearCache(tags?: string[]): void {
if (tags) {
@@ -304,7 +303,7 @@ export class EnhancedRequest {
memoryCache.clear();
}
}
// 取消所有请求
cancelAll(): void {
this.requestQueue.clear();
@@ -326,7 +325,7 @@ export function cachedGet<T = any>(
}
): Promise<T> {
const { expiry = 5 * 60 * 1000, tags, ...restConfig } = config || {};
return enhancedRequest.get<T>(url, {
...restConfig,
cache: {

View File

@@ -13,8 +13,26 @@ import type { ApiResult } from '@/api';
import { getHostname, getTenantId } from '@/utils/domain';
import { getMerchantId } from "@/utils/merchant";
// 获取API基础地址的函数
const getBaseUrl = (): string => {
// 尝试从配置store获取后台配置的API地址
try {
// 如果store中没有则尝试从localStorage获取
const ApiUrl = localStorage.getItem('ApiUrl');
if (ApiUrl) {
return ApiUrl;
}
} catch (error) {
console.warn('获取后台配置API地址失败:', error);
}
// 如果后台没有配置API地址则使用本地配置
console.log('使用本地配置的API地址:', API_BASE_URL);
return API_BASE_URL;
};
const service = axios.create({
baseURL: API_BASE_URL
baseURL: getBaseUrl()
});
/**

View File

@@ -284,7 +284,6 @@ import {Config} from '@/api/cms/cmsWebsiteField/model';
import {phoneReg} from 'ele-admin-pro';
import router from "@/router";
import {listAdminsByPhoneAll} from "@/api/system/user";
import {getUserInfo} from "@/api/layout";
import {QrCodeStatusResponse} from "@/api/passport/qrLogin";
const useForm = Form.useForm;