import { defineStore } from 'pinia' import { ref } from 'vue' import { getDictOptions, getDictOptionsBatch, getAllDictOptions } from '@/api/dict' /** * 字典 Store(平台端) * * 全部字典数据都来自数据库,前端不维护编码清单: * - 登录后调用一次 loadAll(),把库里所有「启用」字典读进缓存; * - 页面按库里真实的 dict_code 取值即可(字符串直接写,无需常量文件); * - 之后在字典管理页新增的字典,第一次被引用时会按需自动加载。 * * 用法: * const dictStore = useDictStore() * dictStore.getDictLabel('payment_order_status', row.status) // 文案 * dictStore.getDictTagType('payment_order_status', row.status) // 标签色 * dictStore.getSelectOptions('payment_channel') // 下拉选项 */ export const useDictStore = defineStore('dict', () => { /** dict_code -> 字典项数组(渲染依赖它,必须是响应式的) */ const dictMap = ref({}) /** 已加载过的编码(仅用于去重,不参与渲染) */ let loaded = {} /** 正在请求中的编码 -> Promise(并发调用复用同一个请求) */ const pending = new Map() /** 是否已完成全量预热 */ let allLoaded = false let allPending = null function setItems(code, list) { dictMap.value = { ...dictMap.value, [code]: Array.isArray(list) ? list : [] } } function itemsOf(code) { return dictMap.value[code] || [] } /** 把入参规范成去重后的编码数组 */ function normalizeCodes(codes) { return [...new Set((codes || []).map((c) => String(c || '').trim()).filter(Boolean))] } /** * 加载字典(带缓存与并发去重)。 * 请求失败也会标记为已加载,避免页面反复重试;需要重试请先 clearCache。 */ async function load(codes) { const list = normalizeCodes(codes) const missing = list.filter((c) => !loaded[c] && !pending.has(c)) if (missing.length) { const promise = (async () => { if (missing.length === 1) { // 单个字典走专用接口 const code = missing[0] try { const res = await getDictOptions(code) setItems(code, res?.code === 200 ? res.data?.list || [] : []) } catch { setItems(code, []) } } else { // 多个字典合并成一次请求 try { const res = await getDictOptionsBatch(missing) const dicts = res?.code === 200 ? res.data?.dicts || {} : {} missing.forEach((code) => setItems(code, dicts[code] || [])) } catch { missing.forEach((code) => setItems(code, [])) } } })().finally(() => { missing.forEach((code) => { loaded[code] = true pending.delete(code) }) }) missing.forEach((code) => pending.set(code, promise)) } await Promise.all(list.map((c) => pending.get(c) || Promise.resolve())) return list.flatMap((c) => itemsOf(c)) } /** * 全量预热:一次请求把库里所有启用字典读进缓存(登录后调用一次即可)。 * 之后再新增的字典仍会走 load() 按需加载。 */ function loadAll() { if (allLoaded) return Promise.resolve() if (allPending) return allPending allPending = (async () => { try { const res = await getAllDictOptions() const dicts = res?.code === 200 ? res.data?.dicts || {} : {} const next = { ...dictMap.value } Object.entries(dicts).forEach(([code, list]) => { loaded[code] = true next[code] = Array.isArray(list) ? list : [] }) dictMap.value = next } catch { // 预热失败不影响使用:后续按需加载 } finally { allLoaded = true allPending = null } })() return allPending } /** 同步触发加载(不等待),用于组件 setup/onMounted 里“先渲染后填充” */ function ensure(code) { const list = normalizeCodes([code]) if (list.some((c) => !loaded[c] && !pending.has(c))) { load(list) } } /** 同步取字典项;未加载时顺带触发加载,拿到后由响应式自动刷新 */ function getDictItemsSync(code) { ensure(code) return itemsOf(code) } /** 值 → 字典项(值按字符串比较,兼容 1 / '1' 混用) */ function getDictItem(code, value) { if (value === undefined || value === null || value === '') return null const key = String(value) return itemsOf(code).find((it) => String(it.item_value) === key) || null } /** 值 → 文案;取不到时返回 fallback,未传则原样返回取值 */ function getDictLabel(code, value, fallback) { const hit = getDictItem(code, value) if (hit) return hit.item_label if (fallback !== undefined) return fallback return value === undefined || value === null ? '' : String(value) } /** 值 → el-tag 的 type;取不到时返回 fallback(默认 info) */ function getDictTagType(code, value, fallback = 'info') { const hit = getDictItem(code, value) return hit && hit.tag_type ? hit.tag_type : fallback } /** 值 → 下拉选项 { label, value },供 el-select / el-option 直接使用 */ function getSelectOptions(code) { return getDictItemsSync(code).map((it) => ({ label: it.item_label, value: it.item_value, tag_type: it.tag_type })) } /** 预加载多个字典(一次批量请求) */ function preloadDicts(codes) { return load(codes) } /** 清空缓存:传编码清单个,不传清全部(同时允许重新全量预热) */ function clearCache(code) { if (code) { delete loaded[code] const next = { ...dictMap.value } delete next[code] dictMap.value = next return } loaded = {} dictMap.value = {} allLoaded = false } return { dictMap, ensure, loadAll, getDictItems: load, getDictItemsSync, getDictItem, getDictLabel, getDictTagType, getSelectOptions, preloadDicts, clearCache } })