做字典管理

This commit is contained in:
2026-09-28 23:16:26 +08:00
parent 7c75edcc61
commit c49e8d0d3f
59 changed files with 5005 additions and 4673 deletions
+187
View File
@@ -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
}
})