refactor: ♻️ 字典缓存加载方式修改为按需加载,添加字典实时更新通知
This commit is contained in:
9984
pnpm-lock.yaml
generated
9984
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -292,11 +292,14 @@ export interface DictItemForm {
|
|||||||
*/
|
*/
|
||||||
export interface DictItemOption {
|
export interface DictItemOption {
|
||||||
/** 字典数据值 */
|
/** 字典数据值 */
|
||||||
value: string;
|
value: string | number;
|
||||||
|
|
||||||
/** 字典数据标签 */
|
/** 字典数据标签 */
|
||||||
label: string;
|
label: string;
|
||||||
|
|
||||||
/** 标签类型 */
|
/** 标签类型 */
|
||||||
tagType: string;
|
tagType?: "" | "success" | "info" | "warning" | "danger" | "primary";
|
||||||
|
|
||||||
|
/** 允许其他属性 */
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,31 +19,44 @@ const props = defineProps({
|
|||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
type TagType = "success" | "warning" | "primary" | "danger" | "default";
|
|
||||||
const label = ref("");
|
const label = ref("");
|
||||||
const tagType = ref<TagType>();
|
const tagType = ref<string | undefined>();
|
||||||
|
|
||||||
const getLabelAndTagByValue = async (dictCode: string, value: any) => {
|
const getLabelAndTagByValue = async (dictCode: string, value: any) => {
|
||||||
// 先从本地缓存中获取字典数据
|
// 按需加载字典数据
|
||||||
const dictData = dictStore.getDictionary(dictCode);
|
await dictStore.loadDictItems(dictCode);
|
||||||
console.log("dictData", dictData);
|
|
||||||
|
// 从缓存中获取字典数据
|
||||||
|
const dictItems = dictStore.getDictItems(dictCode);
|
||||||
|
|
||||||
// 查找对应的字典项
|
// 查找对应的字典项
|
||||||
const dictEntry = dictData.find((item: any) => item.value == value);
|
const dictItem = dictItems.find((item) => item.value == value);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: dictEntry ? dictEntry.label : "",
|
label: dictItem?.label || "",
|
||||||
tag: dictEntry ? dictEntry.tagType : undefined,
|
tagType: dictItem?.tagType,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 监听字典数据变化,确保WebSocket更新时刷新标签
|
||||||
|
watch(
|
||||||
|
() => props.code && dictStore.getDictItems(props.code),
|
||||||
|
async () => {
|
||||||
|
if (props.code) {
|
||||||
|
await fetchLabelAndTag();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
// 监听 props 的变化,获取并更新 label 和 tag
|
// 监听 props 的变化,获取并更新 label 和 tag
|
||||||
const fetchLabelAndTag = async () => {
|
const fetchLabelAndTag = async () => {
|
||||||
const result = await getLabelAndTagByValue(props.code as string, props.modelValue);
|
if (!props.code || props.modelValue === undefined) return;
|
||||||
console.log("result", result);
|
|
||||||
|
const result = await getLabelAndTagByValue(props.code, props.modelValue);
|
||||||
label.value = result.label;
|
label.value = result.label;
|
||||||
if (result.tag === "info") {
|
tagType.value = result.tagType;
|
||||||
result.tag = "default";
|
|
||||||
}
|
|
||||||
tagType.value = result.tag as "success" | "warning" | "primary" | "danger" | "default";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 首次挂载时获取字典数据
|
// 首次挂载时获取字典数据
|
||||||
|
|||||||
@@ -114,13 +114,28 @@ function handleChange(val: any) {
|
|||||||
|
|
||||||
// 获取字典数据
|
// 获取字典数据
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!props.type) {
|
if (!props.code) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let dictData = dictStore.getDictionary(props.code);
|
// 按需加载字典数据
|
||||||
options.value = dictData.map((item) => ({
|
await dictStore.loadDictItems(props.code);
|
||||||
|
options.value = dictStore.getDictItems(props.code).map((item) => ({
|
||||||
label: item.label,
|
label: item.label,
|
||||||
value: item.value,
|
value: item.value,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 监听字典数据变化,确保WebSocket更新时刷新选项
|
||||||
|
watch(
|
||||||
|
() => dictStore.getDictItems(props.code),
|
||||||
|
(newItems) => {
|
||||||
|
if (newItems.length > 0) {
|
||||||
|
options.value = newItems.map((item) => ({
|
||||||
|
label: item.label,
|
||||||
|
value: item.value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
7
src/hooks/index.ts
Normal file
7
src/hooks/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* 全局Hooks入口文件
|
||||||
|
* 导出所有可用的Hooks
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 导出WebSocket相关Hook
|
||||||
|
export * from "./websocket";
|
||||||
285
src/hooks/websocket/core/useStomp.ts
Normal file
285
src/hooks/websocket/core/useStomp.ts
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
import { getToken as getAccessToken } from "@/utils/cache";
|
||||||
|
|
||||||
|
export interface UseStompOptions {
|
||||||
|
/** WebSocket 地址,不传时使用 VITE_APP_WS_ENDPOINT 环境变量 */
|
||||||
|
brokerURL?: string;
|
||||||
|
/** 重连延迟,单位毫秒,默认为 8000 */
|
||||||
|
reconnectDelay?: number;
|
||||||
|
/** 连接超时时间,单位毫秒,默认为 10000 */
|
||||||
|
connectionTimeout?: number;
|
||||||
|
/** 是否开启指数退避重连策略 */
|
||||||
|
useExponentialBackoff?: boolean;
|
||||||
|
/** 最大重连次数,默认为 5 */
|
||||||
|
maxReconnectAttempts?: number;
|
||||||
|
/** 最大重连延迟,单位毫秒,默认为 60000 */
|
||||||
|
maxReconnectDelay?: number;
|
||||||
|
/** 是否开启调试日志 */
|
||||||
|
debug?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STOMP WebSocket连接Hook (UniApp版本)
|
||||||
|
* 用于管理WebSocket连接的建立、断开、重连和消息订阅
|
||||||
|
*/
|
||||||
|
export function useStomp(options: UseStompOptions = {}) {
|
||||||
|
// 默认配置
|
||||||
|
const defaultBrokerURL = import.meta.env.VITE_APP_WS_ENDPOINT || "";
|
||||||
|
const brokerURL = ref(options.brokerURL ?? defaultBrokerURL);
|
||||||
|
const reconnectDelay = options.reconnectDelay ?? 8000;
|
||||||
|
const connectionTimeout = options.connectionTimeout ?? 10000;
|
||||||
|
const useExponentialBackoff = options.useExponentialBackoff ?? false;
|
||||||
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
|
||||||
|
const maxReconnectDelay = options.maxReconnectDelay ?? 60000;
|
||||||
|
|
||||||
|
// 连接状态和计数
|
||||||
|
const isConnected = ref(false);
|
||||||
|
const reconnectCount = ref(0);
|
||||||
|
let reconnectTimer: number | null = null;
|
||||||
|
let connectionTimeoutTimer: number | null = null;
|
||||||
|
|
||||||
|
// 存储所有订阅
|
||||||
|
const subscriptions = ref<Record<string, any>>({});
|
||||||
|
|
||||||
|
// WebSocket实例
|
||||||
|
let socketTask: any = null;
|
||||||
|
const client = ref<any>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建WebSocket连接
|
||||||
|
*/
|
||||||
|
const createSocketConnection = () => {
|
||||||
|
const token = getAccessToken();
|
||||||
|
if (!token) {
|
||||||
|
console.error("WebSocket连接失败:未找到有效token");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建WebSocket连接
|
||||||
|
try {
|
||||||
|
// 构建带有token的URL
|
||||||
|
let url = brokerURL.value;
|
||||||
|
if (url.indexOf("?") > -1) {
|
||||||
|
url += "&token=" + token;
|
||||||
|
} else {
|
||||||
|
url += "?token=" + token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建WebSocket连接
|
||||||
|
socketTask = uni.connectSocket({
|
||||||
|
url: url,
|
||||||
|
complete: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!socketTask) {
|
||||||
|
console.error("WebSocket连接创建失败");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置WebSocket事件处理函数
|
||||||
|
socketTask.onOpen(() => {
|
||||||
|
isConnected.value = true;
|
||||||
|
reconnectCount.value = 0;
|
||||||
|
if (connectionTimeoutTimer) clearTimeout(connectionTimeoutTimer);
|
||||||
|
console.log("WebSocket连接已建立");
|
||||||
|
});
|
||||||
|
|
||||||
|
socketTask.onClose(() => {
|
||||||
|
isConnected.value = false;
|
||||||
|
console.log("WebSocket连接已关闭");
|
||||||
|
|
||||||
|
// 如果使用指数退避重连策略,则处理重连
|
||||||
|
if (useExponentialBackoff && reconnectCount.value < maxReconnectAttempts) {
|
||||||
|
handleReconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socketTask.onError((error: any) => {
|
||||||
|
console.error("WebSocket连接错误:", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
socketTask.onMessage((res: any) => {
|
||||||
|
const message = JSON.parse(res.data);
|
||||||
|
// 处理订阅消息
|
||||||
|
if (message.subscription && subscriptions.value[message.subscription]) {
|
||||||
|
const subscription = subscriptions.value[message.subscription];
|
||||||
|
if (subscription.callback) {
|
||||||
|
subscription.callback(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return socketTask;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("创建WebSocket连接时出错:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理重连逻辑
|
||||||
|
*/
|
||||||
|
const handleReconnect = () => {
|
||||||
|
if (reconnectCount.value >= maxReconnectAttempts) {
|
||||||
|
console.error(`已达到最大重连次数(${maxReconnectAttempts}),停止重连`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reconnectCount.value++;
|
||||||
|
console.log(`尝试重连(${reconnectCount.value}/${maxReconnectAttempts})...`);
|
||||||
|
|
||||||
|
// 使用指数退避策略
|
||||||
|
const delay = useExponentialBackoff
|
||||||
|
? Math.min(reconnectDelay * Math.pow(2, reconnectCount.value - 1), maxReconnectDelay)
|
||||||
|
: reconnectDelay;
|
||||||
|
|
||||||
|
// 清除之前的计时器
|
||||||
|
if (reconnectTimer) {
|
||||||
|
clearTimeout(reconnectTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置重连计时器
|
||||||
|
reconnectTimer = setTimeout(() => {
|
||||||
|
if (!isConnected.value) {
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
}, delay) as unknown as number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 建立WebSocket连接
|
||||||
|
*/
|
||||||
|
const connect = () => {
|
||||||
|
if (isConnected.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建WebSocket连接
|
||||||
|
socketTask = createSocketConnection();
|
||||||
|
client.value = socketTask;
|
||||||
|
|
||||||
|
// 设置连接超时
|
||||||
|
if (connectionTimeoutTimer) {
|
||||||
|
clearTimeout(connectionTimeoutTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionTimeoutTimer = setTimeout(() => {
|
||||||
|
if (!isConnected.value) {
|
||||||
|
console.warn("WebSocket连接超时");
|
||||||
|
if (useExponentialBackoff) {
|
||||||
|
handleReconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, connectionTimeout) as unknown as number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅主题
|
||||||
|
* @param destination 主题地址
|
||||||
|
* @param callback 回调函数
|
||||||
|
* @returns 订阅ID
|
||||||
|
*/
|
||||||
|
const subscribe = (destination: string, callback: (message: any) => void): string => {
|
||||||
|
if (!socketTask || !isConnected.value) {
|
||||||
|
console.warn("WebSocket未连接,无法订阅:", destination);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成唯一订阅ID
|
||||||
|
const subscriptionId = "sub-" + Math.random().toString(36).substr(2, 9);
|
||||||
|
|
||||||
|
// 发送订阅消息
|
||||||
|
socketTask.send({
|
||||||
|
data: JSON.stringify({
|
||||||
|
command: "SUBSCRIBE",
|
||||||
|
headers: {
|
||||||
|
id: subscriptionId,
|
||||||
|
destination: destination,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
success: () => {
|
||||||
|
console.log(`订阅成功: ${destination}, ID: ${subscriptionId}`);
|
||||||
|
},
|
||||||
|
fail: (err: any) => {
|
||||||
|
console.error(`订阅失败(${destination}):`, err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保存订阅
|
||||||
|
subscriptions.value[subscriptionId] = {
|
||||||
|
destination,
|
||||||
|
callback,
|
||||||
|
};
|
||||||
|
|
||||||
|
return subscriptionId;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消订阅
|
||||||
|
* @param subscriptionId 订阅ID
|
||||||
|
*/
|
||||||
|
const unsubscribe = (subscriptionId: string) => {
|
||||||
|
if (!socketTask || !isConnected.value || !subscriptions.value[subscriptionId]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 发送取消订阅消息
|
||||||
|
socketTask.send({
|
||||||
|
data: JSON.stringify({
|
||||||
|
command: "UNSUBSCRIBE",
|
||||||
|
headers: {
|
||||||
|
id: subscriptionId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
success: () => {
|
||||||
|
console.log(`已取消订阅: ${subscriptionId}`);
|
||||||
|
},
|
||||||
|
fail: (err: any) => {
|
||||||
|
console.error(`取消订阅失败(${subscriptionId}):`, err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`取消订阅失败(${subscriptionId}):`, error);
|
||||||
|
} finally {
|
||||||
|
Reflect.deleteProperty(subscriptions.value, subscriptionId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 断开WebSocket连接
|
||||||
|
*/
|
||||||
|
const disconnect = () => {
|
||||||
|
if (!socketTask) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消所有订阅
|
||||||
|
Object.keys(subscriptions.value).forEach(unsubscribe);
|
||||||
|
|
||||||
|
// 断开WebSocket连接
|
||||||
|
try {
|
||||||
|
socketTask.close({
|
||||||
|
success: () => {
|
||||||
|
console.log("WebSocket连接已断开");
|
||||||
|
isConnected.value = false;
|
||||||
|
socketTask = null;
|
||||||
|
},
|
||||||
|
fail: (err: any) => {
|
||||||
|
console.error("断开WebSocket连接失败:", err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("断开WebSocket连接失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 返回公开的API
|
||||||
|
return {
|
||||||
|
isConnected,
|
||||||
|
client,
|
||||||
|
connect,
|
||||||
|
disconnect,
|
||||||
|
subscribe,
|
||||||
|
unsubscribe,
|
||||||
|
};
|
||||||
|
}
|
||||||
10
src/hooks/websocket/index.ts
Normal file
10
src/hooks/websocket/index.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* WebSocket相关Hook入口文件
|
||||||
|
* 统一导出所有WebSocket相关Hook
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 核心基础Hook
|
||||||
|
export { useStomp } from "./core/useStomp";
|
||||||
|
|
||||||
|
// 业务服务Hook
|
||||||
|
export { useDictSync } from "./services/useDictSync";
|
||||||
188
src/hooks/websocket/services/useDictSync.ts
Normal file
188
src/hooks/websocket/services/useDictSync.ts
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import { useDictStore } from "@/store/modules/dict";
|
||||||
|
import { useStomp } from "../core/useStomp";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
// 字典消息类型
|
||||||
|
export interface DictMessage {
|
||||||
|
dictCode: string;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字典事件回调类型
|
||||||
|
export type DictMessageCallback = (message: DictMessage) => void;
|
||||||
|
|
||||||
|
// 全局单例实例
|
||||||
|
let instance: ReturnType<typeof createDictSyncHook> | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建字典同步Hook
|
||||||
|
* 负责监听后端字典变更并同步到前端
|
||||||
|
*/
|
||||||
|
function createDictSyncHook() {
|
||||||
|
const dictStore = useDictStore();
|
||||||
|
|
||||||
|
// 使用现有的useStomp,配置适合字典场景的重连参数
|
||||||
|
const { isConnected, connect, subscribe, unsubscribe, disconnect } = useStomp({
|
||||||
|
reconnectDelay: 10000, // 使用更长的重连延迟 - 10秒
|
||||||
|
connectionTimeout: 15000, // 更长的连接超时时间 - 15秒
|
||||||
|
useExponentialBackoff: false, // 字典数据不需要指数退避策略
|
||||||
|
});
|
||||||
|
|
||||||
|
// 存储订阅ID
|
||||||
|
const subscriptionIds = ref<string[]>([]);
|
||||||
|
|
||||||
|
// 已订阅的主题
|
||||||
|
const subscribedTopics = ref<Set<string>>(new Set());
|
||||||
|
|
||||||
|
// 消息回调函数列表
|
||||||
|
const messageCallbacks = ref<DictMessageCallback[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册字典消息回调
|
||||||
|
* @param callback 回调函数
|
||||||
|
*/
|
||||||
|
const onDictMessage = (callback: DictMessageCallback) => {
|
||||||
|
messageCallbacks.value.push(callback);
|
||||||
|
return () => {
|
||||||
|
// 返回取消注册的函数
|
||||||
|
const index = messageCallbacks.value.indexOf(callback);
|
||||||
|
if (index !== -1) {
|
||||||
|
messageCallbacks.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化WebSocket
|
||||||
|
*/
|
||||||
|
const initWebSocket = async () => {
|
||||||
|
try {
|
||||||
|
// 连接WebSocket
|
||||||
|
connect();
|
||||||
|
|
||||||
|
// 设置字典订阅
|
||||||
|
setupDictSubscription();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[WebSocket] 初始化失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭WebSocket
|
||||||
|
*/
|
||||||
|
const closeWebSocket = () => {
|
||||||
|
// 取消所有订阅
|
||||||
|
subscriptionIds.value.forEach((id) => {
|
||||||
|
unsubscribe(id);
|
||||||
|
});
|
||||||
|
subscriptionIds.value = [];
|
||||||
|
subscribedTopics.value.clear();
|
||||||
|
|
||||||
|
// 断开连接
|
||||||
|
disconnect();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置字典订阅
|
||||||
|
*/
|
||||||
|
const setupDictSubscription = () => {
|
||||||
|
const topic = "/topic/dict";
|
||||||
|
|
||||||
|
// 防止重复订阅
|
||||||
|
if (subscribedTopics.value.has(topic)) {
|
||||||
|
console.log(`跳过重复订阅: ${topic}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`开始尝试订阅字典主题: ${topic}`);
|
||||||
|
|
||||||
|
// 使用简化的重试逻辑,依赖useStomp的连接管理
|
||||||
|
const attemptSubscribe = () => {
|
||||||
|
if (!isConnected.value) {
|
||||||
|
console.log("等待WebSocket连接建立...");
|
||||||
|
// 3秒后再次尝试
|
||||||
|
setTimeout(attemptSubscribe, 3000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已订阅
|
||||||
|
if (subscribedTopics.value.has(topic)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`连接已建立,开始订阅: ${topic}`);
|
||||||
|
|
||||||
|
// 订阅字典更新
|
||||||
|
const subId = subscribe(topic, (message: any) => {
|
||||||
|
handleDictEvent(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (subId) {
|
||||||
|
subscriptionIds.value.push(subId);
|
||||||
|
subscribedTopics.value.add(topic);
|
||||||
|
console.log(`字典主题订阅成功: ${topic}`);
|
||||||
|
} else {
|
||||||
|
console.warn(`字典主题订阅失败: ${topic}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始尝试订阅
|
||||||
|
attemptSubscribe();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理字典事件
|
||||||
|
* @param message STOMP消息
|
||||||
|
*/
|
||||||
|
const handleDictEvent = (message: any) => {
|
||||||
|
if (!message.body) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 记录接收到的消息
|
||||||
|
console.log(`收到字典更新消息: ${message.body}`);
|
||||||
|
|
||||||
|
// 尝试解析消息
|
||||||
|
const parsedData = JSON.parse(message.body) as DictMessage;
|
||||||
|
const dictCode = parsedData.dictCode;
|
||||||
|
|
||||||
|
if (!dictCode) return;
|
||||||
|
|
||||||
|
// 清除缓存,等待按需加载
|
||||||
|
dictStore.removeDictItem(dictCode);
|
||||||
|
console.log(`字典缓存已清除: ${dictCode}`);
|
||||||
|
|
||||||
|
// 调用所有注册的回调函数
|
||||||
|
messageCallbacks.value.forEach((callback) => {
|
||||||
|
try {
|
||||||
|
callback(parsedData);
|
||||||
|
} catch (callbackError) {
|
||||||
|
console.error("[WebSocket] 回调执行失败:", callbackError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示提示消息
|
||||||
|
console.info(`字典 ${dictCode} 已变更,将在下次使用时自动加载`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[WebSocket] 解析消息失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
isConnected,
|
||||||
|
initWebSocket,
|
||||||
|
closeWebSocket,
|
||||||
|
handleDictEvent,
|
||||||
|
onDictMessage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典同步Hook
|
||||||
|
* 用于监听后端字典变更并同步到前端
|
||||||
|
*/
|
||||||
|
export function useDictSync() {
|
||||||
|
if (!instance) {
|
||||||
|
instance = createDictSyncHook();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
@@ -1,38 +1,80 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import DictAPI, { type DictVO, type DictData } from "@/api/system/dict";
|
import DictAPI, { type DictItemOption } from "@/api/system/dict";
|
||||||
import { setDictCache, getDictCache } from "@/utils/cache";
|
|
||||||
|
const DICT_CACHE_KEY = "dict_cache";
|
||||||
|
|
||||||
export const useDictStore = defineStore("dict", () => {
|
export const useDictStore = defineStore("dict", () => {
|
||||||
const dictionary = ref<Record<string, DictData[]>>(getDictCache());
|
// 字典数据缓存
|
||||||
|
const dictCache = ref<Record<string, DictItemOption[]>>(uni.getStorageSync(DICT_CACHE_KEY) || {});
|
||||||
|
|
||||||
const setDictionary = (dict: DictVO) => {
|
// 监听dictCache变化,同步到本地存储
|
||||||
dictionary.value[dict.dictCode] = dict.dictDataList;
|
watch(
|
||||||
setDictCache(dictionary.value);
|
dictCache,
|
||||||
|
(newVal) => {
|
||||||
|
uni.setStorageSync(DICT_CACHE_KEY, newVal);
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 请求队列(防止重复请求)
|
||||||
|
const requestQueue: Record<string, Promise<void>> = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存字典数据
|
||||||
|
* @param dictCode 字典编码
|
||||||
|
* @param data 字典项列表
|
||||||
|
*/
|
||||||
|
const cacheDictItems = (dictCode: string, data: DictItemOption[]) => {
|
||||||
|
dictCache.value[dictCode] = data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadDictionaries = async () => {
|
/**
|
||||||
const dictList = await DictAPI.getList();
|
* 加载字典数据(如果缓存中没有则请求)
|
||||||
dictList.forEach(setDictionary);
|
* @param dictCode 字典编码
|
||||||
|
*/
|
||||||
|
const loadDictItems = async (dictCode: string) => {
|
||||||
|
if (dictCache.value[dictCode]) return;
|
||||||
|
|
||||||
|
// 防止重复请求
|
||||||
|
if (!requestQueue[dictCode]) {
|
||||||
|
requestQueue[dictCode] = DictAPI.getDictItems(dictCode).then((data) => {
|
||||||
|
cacheDictItems(dictCode, data);
|
||||||
|
Reflect.deleteProperty(requestQueue, dictCode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await requestQueue[dictCode];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDictionary = (dictCode: string): DictData[] => {
|
/**
|
||||||
return dictionary.value[dictCode] || [];
|
* 获取字典项列表
|
||||||
|
* @param dictCode 字典编码
|
||||||
|
* @returns 字典项列表
|
||||||
|
*/
|
||||||
|
const getDictItems = (dictCode: string): DictItemOption[] => {
|
||||||
|
return dictCache.value[dictCode] || [];
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearDictionaryCache = () => {
|
/**
|
||||||
dictionary.value = {};
|
* 移除指定字典项
|
||||||
|
* @param dictCode 字典编码
|
||||||
|
*/
|
||||||
|
const removeDictItem = (dictCode: string) => {
|
||||||
|
if (dictCache.value[dictCode]) {
|
||||||
|
Reflect.deleteProperty(dictCache.value, dictCode);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateDictionaryCache = async () => {
|
/**
|
||||||
clearDictionaryCache(); // 先清除旧缓存
|
* 清空字典缓存
|
||||||
await loadDictionaries(); // 重新加载最新字典数据
|
*/
|
||||||
|
const clearDictCache = () => {
|
||||||
|
dictCache.value = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dictionary,
|
loadDictItems,
|
||||||
setDictionary,
|
getDictItems,
|
||||||
loadDictionaries,
|
removeDictItem,
|
||||||
getDictionary,
|
clearDictCache,
|
||||||
clearDictionaryCache,
|
|
||||||
updateDictionaryCache,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
const TOKEN_KEY = "app-token";
|
const TOKEN_KEY = "app-token";
|
||||||
const USER_INFO_KEY = "user-info";
|
const USER_INFO_KEY = "user-info";
|
||||||
const DICT_KEY = "dict";
|
|
||||||
import { type DictData } from "@/api/system/dict";
|
|
||||||
|
|
||||||
// 设置 token
|
// 设置 token
|
||||||
export function setToken(token: string) {
|
export function setToken(token: string) {
|
||||||
@@ -33,24 +31,10 @@ export function clearUserInfo() {
|
|||||||
uni.removeStorageSync(USER_INFO_KEY);
|
uni.removeStorageSync(USER_INFO_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置字典缓存
|
|
||||||
export function setDictCache(dict: Record<string, DictData[]>) {
|
|
||||||
uni.setStorageSync(DICT_KEY, dict);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取字典缓存
|
|
||||||
export function getDictCache(): Record<string, DictData[]> {
|
|
||||||
return uni.getStorageSync(DICT_KEY) || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除字典缓存
|
|
||||||
export function clearDictCache() {
|
|
||||||
uni.removeStorageSync(DICT_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除所有缓存信息
|
// 清除所有缓存信息
|
||||||
export function clearAll() {
|
export function clearAll() {
|
||||||
clearToken();
|
clearToken();
|
||||||
clearUserInfo();
|
clearUserInfo();
|
||||||
clearDictCache();
|
// 清除字典缓存
|
||||||
|
uni.removeStorageSync("dict_cache");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user