Files
vue3-element-admin/src/views/demo/dict-sync.vue
2026-02-12 21:01:48 +08:00

304 lines
9.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="app-container">
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>字典WebSocket实时更新演示</span>
<el-tag :type="wsConnected ? 'success' : 'danger'" size="small" class="ml-2">
WebSocket {{ wsStatusText }}
</el-tag>
</div>
</template>
<el-alert type="info" :closable="false" class="mb-4">
本示例展示WebSocket实时更新字典缓存的效果您可以编辑"男"性别字典项保存后后端将通过WebSocket通知所有客户端刷新缓存
</el-alert>
<el-row :gutter="16">
<el-col :span="8">
<el-card shadow="hover" class="dict-card">
<template #header>
<div class="flex justify-between items-center">
<span>性别字典值 - </span>
<el-button type="warning" size="small" @click="loadMaleDict">重新加载</el-button>
</div>
</template>
<div>
<div v-if="dictForm" class="dict-form">
<el-form :model="dictForm" label-width="80px">
<el-form-item label="字典编码">
<el-input v-model="dictForm.dictCode" disabled />
</el-form-item>
<el-form-item label="字典标签">
<el-input v-model="dictForm.label" />
</el-form-item>
<el-form-item label="字典值">
<el-input v-model="dictForm.value" disabled />
</el-form-item>
<el-form-item label="标记颜色">
<el-select
v-model="dictForm.tagType"
placeholder="选择标签类型"
style="width: 100%"
>
<el-option value="success" label="success">
<el-tag type="success">success</el-tag>
</el-option>
<el-option value="warning" label="warning">
<el-tag type="warning">warning</el-tag>
</el-option>
<el-option value="danger" label="danger">
<el-tag type="danger">danger</el-tag>
</el-option>
<el-option value="info" label="info">
<el-tag type="info">info</el-tag>
</el-option>
<el-option value="primary" label="primary">
<el-tag type="primary">primary</el-tag>
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="saveDict">保存</el-button>
<el-button @click="loadMaleDict">重置</el-button>
</el-form-item>
</el-form>
</div>
<el-empty v-else description="暂无字典数据" />
</div>
</el-card>
</el-col>
<!-- 卡2: 字典组件展示 -->
<el-col :span="8">
<el-card shadow="hover" class="dict-card">
<template #header>
<div class="flex justify-between items-center">
<span>字典组件展示</span>
<el-button type="primary" size="small" @click="refreshDictComponent">
手动刷新
</el-button>
</div>
</template>
<div class="dict-component-demo">
<h4 class="mt-4 mb-3">性别组件</h4>
<el-radio-group v-model="selectedGender">
<el-radio
v-for="item in dictStore.getDictItems('gender')"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</el-radio>
</el-radio-group>
<h4 class="mt-4 mb-3">性别标签</h4>
<div>
<el-tag
v-for="item in dictStore.getDictItems('gender')"
:key="item.value"
:type="item.tagType || undefined"
class="mr-2"
>
{{ item.label }}
</el-tag>
</div>
<div class="mt-4 pt-3 border-top">
<div class="text-muted mb-2">已选择 {{ selectedGender }}</div>
<div class="text-muted">最后更新 {{ lastUpdateTime }}</div>
</div>
</div>
</el-card>
</el-col>
<!-- 卡3: 字典缓存数据 -->
<el-col :span="8">
<el-card shadow="hover" class="dict-card">
<template #header>
<div class="flex justify-between items-center">
<span>字典缓存数据</span>
<div>
<el-tag v-if="dictCacheStatus" type="success" class="ml-2" size="small">
已缓存
</el-tag>
<el-tag v-else type="danger" class="ml-2" size="small">未缓存</el-tag>
</div>
</div>
</template>
<div class="cache-content">
<pre class="cache-data">{{
JSON.stringify(dictStore.getDictItems("gender"), null, 2)
}}</pre>
</div>
</el-card>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup lang="ts">
import { useDictStoreHook } from "@/store/modules/dict";
import { useDateFormat } from "@vueuse/core";
import DictAPI from "@/api/system/dict";
import type { DictItemForm } from "@/types/api";
import { useDictSync, DictMessage } from "@/composables";
// 性别字典编码
const DICT_CODE = "gender";
// 男性字典项ID
const MALE_ITEM_ID = "1";
// 字典store
const dictStore = useDictStoreHook();
// 保存状态
const saving = ref(false);
// 最后更新时间
const lastUpdateTime = ref("-");
// 字典表单数据
const dictForm = ref<DictItemForm | null>(null);
// 选中的性别
const selectedGender = ref("");
// 初始化WebSocket
const dictWebSocket = useDictSync();
// 获取连接状态
const wsConnected = computed(() => dictWebSocket.isConnected);
// WebSocket连接状态显示文本
const wsStatusText = computed(() => (wsConnected.value ? "已连接" : "未连接"));
// 保存WebSocket清理函数
let unregisterCallback: (() => void) | null = null;
// 当前选中字典的缓存状态
const dictCacheStatus = computed(() => {
// 检查字典是否在缓存中
return dictStore.getDictItems(DICT_CODE).length > 0;
});
// 设置WebSocket
const setupWebSocket = () => {
// 初始化WebSocket连接
dictWebSocket.initialize();
// 注册字典消息回调
unregisterCallback = dictWebSocket.onDictChange((message: DictMessage) => {
// 只有当消息是关于性别字典的更新时才处理
if (message.dictCode === DICT_CODE) {
// 更新最后更新时间
lastUpdateTime.value = useDateFormat(new Date(), "YYYY-MM-DD HH:mm:ss").value;
// 触发字典组件重新加载
nextTick(() => {
refreshDictComponent();
});
}
});
};
// 刷新字典组件,强制重新加载字典数据
const refreshDictComponent = async () => {
// 这里重新获取字典数据以触发按需加载
await dictStore.loadDictItems(DICT_CODE);
ElMessage.success("字典组件已刷新");
};
// 加载男性字典表单数据
const loadMaleDict = async () => {
// 获取男性字典项表单数据 - 使用接口 /dicts/gender/items/1/form
const data = await DictAPI.getDictItemFormData(DICT_CODE, MALE_ITEM_ID);
dictForm.value = data;
};
// 保存字典值
const saveDict = async () => {
if (!dictForm.value) return;
saving.value = true;
// dictForm的类型已经是DictItemForm直接传递
await DictAPI.updateDictItem(DICT_CODE, MALE_ITEM_ID, dictForm.value);
// 更新时间
lastUpdateTime.value = useDateFormat(new Date(), "YYYY-MM-DD HH:mm:ss").value;
ElMessage.success("保存成功后端将通过WebSocket通知所有客户端");
saving.value = false;
};
// 组件挂载时加载性别字典
onMounted(async () => {
await loadMaleDict();
// 加载初始字典数据
await dictStore.loadDictItems(DICT_CODE);
// 初始化选中性别为男
selectedGender.value = "1";
// 设置WebSocket
setupWebSocket();
});
// 组件卸载时清理WebSocket
onUnmounted(() => {
unregisterCallback?.();
});
</script>
<style scoped>
.dict-card {
display: flex;
flex-direction: column;
height: 600px;
overflow: hidden;
}
.dict-card :deep(.el-card__body) {
flex: 1;
overflow: auto;
}
.dict-component-demo {
display: flex;
flex-direction: column;
height: 100%;
padding: 12px;
}
.cache-content {
height: 100%;
overflow: hidden;
}
pre {
padding: 8px;
overflow-y: auto;
overflow-wrap: break-word;
white-space: pre-wrap;
background-color: var(--el-fill-color-light);
border-radius: 4px;
}
.cache-data {
height: 100%;
padding: 8px;
overflow-y: auto;
font-size: 12px;
background-color: var(--el-fill-color-light);
border-radius: 4px;
}
.dict-form {
margin-bottom: 20px;
}
.text-muted {
font-size: 0.9em;
color: var(--el-text-color-secondary);
}
.border-top {
border-top: 1px solid var(--el-border-color-light);
}
</style>