Files
yunzerwebsiteallinone/platform/src/stores/dict.js
T
2026-09-28 23:16:26 +08:00

188 lines
6.0 KiB
JavaScript
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.
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
}
})