feat(product): 新增产品管理模块与字典组件功能
- 新增产品管理相关路由和页面(dashboard、list、requirement、setting) - 实现产品基础信息编辑弹窗组件(base-info-dialog.vue) - 添加运行时字典功能(dict-select、dict-text、dict-tag组件) - 集成字典管理store和API调用 - 规范ID类型定义为string避免精度丢失问题 - 完善国际化资源文件支持中英文对照 - 新增对象上下文业务域入口页导航实现说明 - 添加Vue DevTools浮动入口注释说明 - 统一权限控制支持全局和对象作用域区分 - 规范分页查询参数类型定义与使用方式
This commit is contained in:
210
src/store/modules/dict/index.ts
Normal file
210
src/store/modules/dict/index.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
import { ref } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { RDMS_OBJECT_DIRECTION_DICT_CODE, RDMS_OBJECT_DIRECTION_LEGACY_DICT_CODE } from '@/constants/dict';
|
||||
import { 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'));
|
||||
}
|
||||
|
||||
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,
|
||||
remark: null,
|
||||
createTime: 0
|
||||
}));
|
||||
|
||||
return sortDictData(normalizedList);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
function resetDictCache() {
|
||||
dictTypes.value = [];
|
||||
dictDataMap.value = {};
|
||||
loadedAt.value = null;
|
||||
initialized.value = false;
|
||||
initPromise = null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
resetDictCache,
|
||||
getDictData,
|
||||
getDictOptions,
|
||||
getDictItem,
|
||||
getDictLabel,
|
||||
getDictLabels,
|
||||
hasDictValue
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user