做字典管理
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
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
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user