280 lines
7.6 KiB
TypeScript
280 lines
7.6 KiB
TypeScript
import { ref } from 'vue';
|
||
import { defineStore } from 'pinia';
|
||
import { RDMS_OBJECT_DIRECTION_DICT_CODE, RDMS_OBJECT_DIRECTION_LEGACY_DICT_CODE } from '@/constants/dict';
|
||
import { fetchGetDictDataByCode, fetchGetFrontendDictCache } from '@/service/api';
|
||
import { SetupStoreId } from '@/enum';
|
||
|
||
type DictValue = string | number | null | undefined;
|
||
type DictFilterOptions = {
|
||
onlyEnabled?: boolean;
|
||
};
|
||
type DictLabelOptions = DictFilterOptions & {
|
||
fallback?: string;
|
||
};
|
||
type DictLabelsOptions = DictLabelOptions & {
|
||
separator?: string;
|
||
};
|
||
|
||
function sortDictData(list: Api.Dict.DictData[]) {
|
||
return list.slice().sort((left, right) => left.sort - right.sort || left.label.localeCompare(right.label, 'zh-CN'));
|
||
}
|
||
|
||
// hex 色值兜底校验:仅接受 #RRGGBB(6 位);其他格式(含 #RGB 简写 / rgb())一律视为无效回落到默认渲染
|
||
const HEX_COLOR_PATTERN = /^#[0-9a-f]{6}$/i;
|
||
|
||
function normalizeColorType(raw: unknown): string | null {
|
||
if (typeof raw !== 'string') return null;
|
||
const trimmed = raw.trim().toLowerCase();
|
||
return HEX_COLOR_PATTERN.test(trimmed) ? trimmed : null;
|
||
}
|
||
|
||
function normalizeFrontendDictData(
|
||
dictType: string,
|
||
list: Api.Dict.FrontendDictData[],
|
||
dictIndex: number
|
||
): Api.Dict.DictData[] {
|
||
const normalizedList = list.map((item, itemIndex) => ({
|
||
id: -((dictIndex + 1) * 100000 + itemIndex + 1),
|
||
label: item.label,
|
||
value: item.value,
|
||
dictType: item.dictType || dictType,
|
||
sort: item.sort,
|
||
status: item.status ?? 0,
|
||
colorType: normalizeColorType(item.colorType),
|
||
remark: item.remark ?? null,
|
||
createTime: 0
|
||
}));
|
||
|
||
return sortDictData(normalizedList);
|
||
}
|
||
|
||
function normalizeDictDataItem(item: Api.Dict.DictData, dictType: string): Api.Dict.DictData {
|
||
return {
|
||
...item,
|
||
value: String(item.value),
|
||
dictType: item.dictType || dictType,
|
||
status: item.status ?? 0,
|
||
colorType: normalizeColorType(item.colorType),
|
||
remark: item.remark ?? null
|
||
};
|
||
}
|
||
|
||
function normalizeFrontendDictCache(cache: Api.Dict.FrontendDictCache) {
|
||
const entries = Object.entries(cache);
|
||
|
||
return Object.fromEntries(
|
||
entries.map(([dictType, list], index) => [dictType, normalizeFrontendDictData(dictType, list, index)])
|
||
);
|
||
}
|
||
|
||
function applyDictTypeAliases(dictDataMap: Record<string, Api.Dict.DictData[]>) {
|
||
const nextDictDataMap = { ...dictDataMap };
|
||
|
||
// 兼容后端尚未切换完成的过渡期:旧编码仍返回时,前端统一映射到新编码。
|
||
if (!nextDictDataMap[RDMS_OBJECT_DIRECTION_DICT_CODE] && nextDictDataMap[RDMS_OBJECT_DIRECTION_LEGACY_DICT_CODE]) {
|
||
nextDictDataMap[RDMS_OBJECT_DIRECTION_DICT_CODE] = nextDictDataMap[RDMS_OBJECT_DIRECTION_LEGACY_DICT_CODE].map(
|
||
item => ({
|
||
...item,
|
||
dictType: RDMS_OBJECT_DIRECTION_DICT_CODE
|
||
})
|
||
);
|
||
}
|
||
|
||
return nextDictDataMap;
|
||
}
|
||
|
||
function createRuntimeDictTypes(dictDataMap: Record<string, Api.Dict.DictData[]>) {
|
||
return Object.keys(dictDataMap).map((dictType, index) => ({
|
||
id: -(index + 1),
|
||
name: dictType,
|
||
type: dictType,
|
||
status: 0 as const,
|
||
remark: null,
|
||
createTime: 0
|
||
}));
|
||
}
|
||
|
||
function findDictItem(list: Api.Dict.DictData[], value?: DictValue) {
|
||
if (value === null || value === undefined || value === '') {
|
||
return undefined;
|
||
}
|
||
|
||
return list.find(item => item.value === String(value));
|
||
}
|
||
|
||
export const useDictStore = defineStore(SetupStoreId.Dict, () => {
|
||
const loading = ref(false);
|
||
const initialized = ref(false);
|
||
const dictTypes = ref<Api.Dict.DictType[]>([]);
|
||
const dictDataMap = ref<Record<string, Api.Dict.DictData[]>>({});
|
||
const loadedAt = ref<number | null>(null);
|
||
|
||
let initPromise: Promise<boolean> | null = null;
|
||
const dictDataLoadPromises = new Map<string, Promise<boolean>>();
|
||
|
||
function resetDictCache() {
|
||
dictTypes.value = [];
|
||
dictDataMap.value = {};
|
||
loadedAt.value = null;
|
||
initialized.value = false;
|
||
initPromise = null;
|
||
dictDataLoadPromises.clear();
|
||
}
|
||
|
||
async function initDictCache(force = false) {
|
||
if (initialized.value && !force) {
|
||
return true;
|
||
}
|
||
|
||
if (initPromise && !force) {
|
||
return initPromise;
|
||
}
|
||
|
||
if (force) {
|
||
resetDictCache();
|
||
}
|
||
|
||
initPromise = (async () => {
|
||
loading.value = true;
|
||
|
||
const result = await fetchGetFrontendDictCache();
|
||
|
||
loading.value = false;
|
||
|
||
if (result.error) {
|
||
initPromise = null;
|
||
return false;
|
||
}
|
||
|
||
const normalizedDictDataMap = applyDictTypeAliases(normalizeFrontendDictCache(result.data || {}));
|
||
|
||
dictTypes.value = createRuntimeDictTypes(normalizedDictDataMap);
|
||
dictDataMap.value = normalizedDictDataMap;
|
||
loadedAt.value = Date.now();
|
||
initialized.value = true;
|
||
initPromise = null;
|
||
|
||
return true;
|
||
})();
|
||
|
||
return initPromise;
|
||
}
|
||
|
||
async function ensureDictData(dictType: string, force = false) {
|
||
if (!dictType) {
|
||
return false;
|
||
}
|
||
|
||
if (!initialized.value) {
|
||
await initDictCache();
|
||
}
|
||
|
||
if (!force && getDictData(dictType).length > 0) {
|
||
return true;
|
||
}
|
||
|
||
const pending = dictDataLoadPromises.get(dictType);
|
||
if (pending && !force) {
|
||
return pending;
|
||
}
|
||
|
||
const promise = (async () => {
|
||
const result = await fetchGetDictDataByCode(dictType);
|
||
|
||
if (result.error || !result.data?.list?.length) {
|
||
return false;
|
||
}
|
||
|
||
dictDataMap.value = {
|
||
...dictDataMap.value,
|
||
[dictType]: sortDictData(result.data.list.map(item => normalizeDictDataItem(item, dictType)))
|
||
};
|
||
dictTypes.value = createRuntimeDictTypes(dictDataMap.value);
|
||
|
||
return true;
|
||
})();
|
||
|
||
dictDataLoadPromises.set(dictType, promise);
|
||
|
||
try {
|
||
return await promise;
|
||
} finally {
|
||
if (dictDataLoadPromises.get(dictType) === promise) {
|
||
dictDataLoadPromises.delete(dictType);
|
||
}
|
||
}
|
||
}
|
||
|
||
function getDictData(dictType: string, onlyEnabled = false) {
|
||
if (!dictType) {
|
||
return [];
|
||
}
|
||
|
||
const list = dictDataMap.value[dictType] || [];
|
||
|
||
if (!onlyEnabled) {
|
||
return list;
|
||
}
|
||
|
||
return list.filter(item => item.status === 0);
|
||
}
|
||
|
||
function getDictOptions(dictType: string, onlyEnabled = true) {
|
||
return getDictData(dictType, onlyEnabled).map(item => ({
|
||
label: item.label,
|
||
value: item.value
|
||
}));
|
||
}
|
||
|
||
function getDictItem(dictType: string, value?: DictValue, options: DictFilterOptions = {}) {
|
||
return findDictItem(getDictData(dictType, options.onlyEnabled), value);
|
||
}
|
||
|
||
function getDictLabel(dictType: string, value?: DictValue, options: DictLabelOptions = {}) {
|
||
const { fallback = '--', onlyEnabled = false } = options;
|
||
|
||
if (value === null || value === undefined || value === '') {
|
||
return fallback;
|
||
}
|
||
|
||
const matched = getDictItem(dictType, value, { onlyEnabled });
|
||
|
||
return matched?.label || String(value);
|
||
}
|
||
|
||
function getDictLabels(dictType: string, values?: Array<DictValue> | null, options: DictLabelsOptions = {}) {
|
||
const { fallback = '--', separator = ' / ', onlyEnabled = false } = options;
|
||
|
||
if (!values?.length) {
|
||
return fallback;
|
||
}
|
||
|
||
const labels = values
|
||
.filter(value => value !== null && value !== undefined && value !== '')
|
||
.map(value => getDictLabel(dictType, value, { fallback: String(value), onlyEnabled }));
|
||
|
||
return labels.length ? labels.join(separator) : fallback;
|
||
}
|
||
|
||
function hasDictValue(dictType: string, value?: DictValue, options: DictFilterOptions = {}) {
|
||
return Boolean(getDictItem(dictType, value, options));
|
||
}
|
||
|
||
return {
|
||
loading,
|
||
initialized,
|
||
dictTypes,
|
||
dictDataMap,
|
||
loadedAt,
|
||
initDictCache,
|
||
ensureDictData,
|
||
resetDictCache,
|
||
getDictData,
|
||
getDictOptions,
|
||
getDictItem,
|
||
getDictLabel,
|
||
getDictLabels,
|
||
hasDictValue
|
||
};
|
||
});
|