20260809223428
This commit is contained in:
1 parent
d72ca4193d
commit
c4891538a7
466 files changed
+21000
-3339
No files matched your search
@@ -1,57 +0,0 @@
|
||||
import { nextIdApi } from "@/services/api";
|
||||
|
||||
export function cloneData(value) {
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
}
|
||||
|
||||
export function diffRows(current, previous) {
|
||||
const previousById = new Map(previous.map((item) => [String(item.b_id), item]));
|
||||
const currentById = new Map(current.map((item) => [String(item.b_id), item]));
|
||||
return {
|
||||
inserts: current.filter((item) => !previousById.has(String(item.b_id))),
|
||||
updates: current.filter((item) => {
|
||||
const old = previousById.get(String(item.b_id));
|
||||
return old && JSON.stringify(item) !== JSON.stringify(old);
|
||||
}),
|
||||
deletes: previous
|
||||
.filter((item) => !currentById.has(String(item.b_id)))
|
||||
.map((item) => ({ b_id: item.b_id })),
|
||||
};
|
||||
}
|
||||
|
||||
export async function allocateTemporaryIds(rowGroups) {
|
||||
const temporaryIds = [
|
||||
...new Set(
|
||||
rowGroups
|
||||
.flat()
|
||||
.map((row) => String(row?.b_id ?? ""))
|
||||
.filter((id) => /^-\d+$/.test(id)),
|
||||
),
|
||||
];
|
||||
if (!temporaryIds.length) return new Map();
|
||||
const res = await nextIdApi(temporaryIds.length);
|
||||
const ids = Array.isArray(res.data) ? res.data : [res.data];
|
||||
const mapping = new Map(temporaryIds.map((id, index) => [id, String(ids[index])]));
|
||||
rowGroups.flat().forEach((row) => {
|
||||
const id = mapping.get(String(row?.b_id));
|
||||
if (id) row.b_id = id;
|
||||
});
|
||||
return mapping;
|
||||
}
|
||||
|
||||
export function remapForeignKeys(rows, fields, mapping) {
|
||||
rows.forEach((row) => {
|
||||
fields.forEach((field) => {
|
||||
const value = mapping.get(String(row[field]));
|
||||
if (value) row[field] = value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function tableChange(table, changes) {
|
||||
return { table, key_field: "b_id", ...changes };
|
||||
}
|
||||
|
||||
export function hasChanges(request) {
|
||||
return request.inserts?.length || request.updates?.length || request.deletes?.length;
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
export function validateModuleCode(code) {
|
||||
const value = String(code ?? "").trim();
|
||||
if (!value) return "编码不能为空";
|
||||
if (value.length < 2 || value.length > 50) return "编码长度需为 2-50 个字符";
|
||||
if (!/^[a-z][a-z0-9_]*$/.test(value)) {
|
||||
return "编码须以小写字母开头,且只能包含小写字母、数字和下划线";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function getI18nNamespace(key) {
|
||||
return String(key || "").split(".", 1)[0] || "common";
|
||||
}
|
||||
|
||||
export function isValidI18nKey(key) {
|
||||
return /^[A-Za-z][A-Za-z0-9_.:-]{1,149}$/.test(String(key || ""));
|
||||
}
|
||||
|
||||
export function buildModuleItemI18nKey(prefix, moduleCode, itemCode) {
|
||||
const normalizedModuleCode = String(moduleCode || "").trim();
|
||||
const normalizedItemCode = String(itemCode || "").trim();
|
||||
return normalizedModuleCode && normalizedItemCode
|
||||
? `${prefix}.${normalizedModuleCode}.${normalizedItemCode}`
|
||||
: null;
|
||||
}
|
||||
|
||||
export function buildModuleI18nKey(moduleType, moduleCode) {
|
||||
const normalizedModuleCode = String(moduleCode || "").trim();
|
||||
if (!normalizedModuleCode) return null;
|
||||
const prefix = moduleType === "data" ? "module" : "menu";
|
||||
return `${prefix}.${normalizedModuleCode}`;
|
||||
}
|
||||
|
||||
export function syncModuleItemI18nKeys(rows, prefix, moduleCode, codeField) {
|
||||
let changed = false;
|
||||
for (const row of rows || []) {
|
||||
const key = buildModuleItemI18nKey(prefix, moduleCode, row?.[codeField]);
|
||||
if (row.b_i18n === key) continue;
|
||||
row.b_i18n = key;
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
export function groupI18nRows(rows) {
|
||||
const groups = {};
|
||||
rows.forEach((item) => {
|
||||
const key = item.b_key;
|
||||
groups[key] ||= { key, values: {}, records: {} };
|
||||
groups[key].values[item.b_locale] = item.b_value;
|
||||
groups[key].records[item.b_locale] = item;
|
||||
});
|
||||
return groups;
|
||||
}
|
||||
|
||||
// 将同步字段生成的默认翻译条目并入透视行数组:
|
||||
// - 已存在同 b_key 的行则补全其空语言值(不覆盖用户已填内容);
|
||||
// - 否则新增一行;managedId 用于关联字段,使重命名字段时复用同一行(改名而非新增)。
|
||||
// 返回新的数组,不修改入参。
|
||||
export function mergeI18nDefaults(rows, entries) {
|
||||
const next = [...rows];
|
||||
const byKey = new Map(next.map((r) => [String(r.b_key), r]));
|
||||
const managedIds = new Set(
|
||||
next
|
||||
.map((r) => r._managedId)
|
||||
.filter(Boolean)
|
||||
.map(String),
|
||||
);
|
||||
for (const e of entries || []) {
|
||||
if (!e.key) continue;
|
||||
const existing = byKey.get(String(e.key));
|
||||
if (existing) {
|
||||
// 已存在同键行:仅补全空值,保留用户已配置的翻译
|
||||
if (existing[e.locale] == null || existing[e.locale] === "") existing[e.locale] = e.value;
|
||||
continue;
|
||||
}
|
||||
// 同键不存在,但该受管键已由其它行代表时不再新建,避免重复键
|
||||
if (e.managedId && managedIds.has(String(e.managedId))) continue;
|
||||
const row = {
|
||||
_id: "i18n_" + Math.random().toString(36).slice(2, 10),
|
||||
b_key: e.key,
|
||||
_localeIds: {},
|
||||
_managedId: e.managedId ? String(e.managedId) : undefined,
|
||||
_syncInjected: e.managedId ? true : undefined,
|
||||
[e.locale]: e.value,
|
||||
};
|
||||
next.push(row);
|
||||
byKey.set(String(e.key), row);
|
||||
if (e.managedId) managedIds.add(String(e.managedId));
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
// 合并同 b_key 的重复透视行,保证一个键仅一行:
|
||||
// 多行同键时保留各自非空的语言值,受管标记(_managedId)、_syncInjected、排序/默认值取优先者。
|
||||
// 返回新的数组,不修改入参。
|
||||
export function consolidatePivotRows(rows) {
|
||||
const byKey = new Map();
|
||||
const merged = [];
|
||||
for (const row of rows) {
|
||||
const key = String(row.b_key || "");
|
||||
const hit = byKey.get(key);
|
||||
if (hit) {
|
||||
for (const [k, v] of Object.entries(row)) {
|
||||
if (k === "_id" || k === "_localeIds") continue;
|
||||
// 仅当目标为空(null/undefined/空串)且来源非空时才补全,保留首个非空值
|
||||
if ((hit[k] == null || hit[k] === "") && v != null) hit[k] = v;
|
||||
}
|
||||
Object.assign(hit._localeIds || (hit._localeIds = {}), row._localeIds || {});
|
||||
if (row._managedId && !hit._managedId) hit._managedId = row._managedId;
|
||||
if (row._syncInjected && !hit._syncInjected) hit._syncInjected = row._syncInjected;
|
||||
if (row._defaultValue != null && hit._defaultValue == null)
|
||||
hit._defaultValue = row._defaultValue;
|
||||
if (row._sortType && !hit._sortType) hit._sortType = row._sortType;
|
||||
} else {
|
||||
byKey.set(key, row);
|
||||
merged.push(row);
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Lucide 图标注册表 — 懒加载所有图标并按名称查找。
|
||||
* 首次使用时动态 import('@lucide/vue'),之后缓存复用。
|
||||
* iconMap 为响应式 ref,加载完成后自动触发依赖它的 computed 重算。
|
||||
*/
|
||||
import { ref } from "vue";
|
||||
|
||||
const iconMap = ref(null);
|
||||
let loadingPromise = null;
|
||||
const KNOWN_NON_ICONS = new Set(["createLucideIcon"]);
|
||||
|
||||
async function ensureLoaded() {
|
||||
if (iconMap.value) return iconMap.value;
|
||||
if (!loadingPromise) {
|
||||
loadingPromise = import("@lucide/vue").then((mod) => {
|
||||
const map = {};
|
||||
for (const [key, component] of Object.entries(mod)) {
|
||||
if (!KNOWN_NON_ICONS.has(key) && key[0] === key[0].toUpperCase()) {
|
||||
map[key] = component;
|
||||
}
|
||||
}
|
||||
iconMap.value = map;
|
||||
return map;
|
||||
});
|
||||
}
|
||||
return loadingPromise;
|
||||
}
|
||||
|
||||
/** 获取所有图标名称(按字母排序),需要 await */
|
||||
export async function getIconNames() {
|
||||
const map = await ensureLoaded();
|
||||
return Object.keys(map).sort();
|
||||
}
|
||||
|
||||
/** 同步获取图标组件;响应式,加载完成后自动重算依赖的 computed */
|
||||
export function getIcon(name) {
|
||||
if (!iconMap.value || !name) return null;
|
||||
return iconMap.value[name] || null;
|
||||
}
|
||||
|
||||
/** 异步获取图标组件 */
|
||||
export async function getIconAsync(name) {
|
||||
const map = await ensureLoaded();
|
||||
return map[name] || null;
|
||||
}
|
||||
|
||||
/** 图标是否已加载完成 */
|
||||
export function isLoaded() {
|
||||
return iconMap.value !== null;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// 生成本地临时 ID,用于尚未保存到后端的子表新增行。
|
||||
// 使用负数以区别于后端生成的雪花 ID(恒为正),保存时后端会替换为真实 ID。
|
||||
let sequence = 0;
|
||||
|
||||
/**
|
||||
* 生成会话内唯一的临时 ID(负数字符串)。
|
||||
* @returns {string} 形如 "-1"、"-2" 的临时 ID。
|
||||
*/
|
||||
export function createTempId() {
|
||||
sequence += 1;
|
||||
return String(-sequence);
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* 将扁平数组构建为树结构
|
||||
* @param {Array} items - 扁平数据
|
||||
* @param {object} [options]
|
||||
* @param {string} [options.idKey='b_id'] - ID 字段名
|
||||
* @param {string} [options.parentKey='b_parent_id'] - 父级 ID 字段名
|
||||
* @param {string} [options.childrenKey='children'] - 子节点字段名
|
||||
* @param {Function} [options.mapNode] - 节点映射函数 (item) => node,用于创建新的树节点对象
|
||||
* @returns {Array} 树形结构
|
||||
*/
|
||||
export function buildTree(items, options = {}) {
|
||||
const { idKey = "b_id", parentKey = "b_parent_id", childrenKey = "children", mapNode } = options;
|
||||
const map = new Map();
|
||||
const roots = [];
|
||||
|
||||
for (const item of items) {
|
||||
const node = mapNode ? mapNode(item) : item;
|
||||
map.set(String(item[idKey]), node);
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
const node = map.get(String(item[idKey]));
|
||||
const parentId = item[parentKey] != null ? String(item[parentKey]) : null;
|
||||
if (parentId && map.has(parentId)) {
|
||||
const parent = map.get(parentId);
|
||||
if (!parent[childrenKey]) parent[childrenKey] = [];
|
||||
parent[childrenKey].push(node);
|
||||
} else {
|
||||
roots.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
return roots;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归排序树节点(默认按 b_xh 升序,b_id 升序)
|
||||
* @param {Array} nodes - 树节点
|
||||
* @param {object} [options]
|
||||
* @param {string} [options.sortKey='b_xh'] - 排序字段
|
||||
* @param {string} [options.idKey='b_id'] - 第二排序字段
|
||||
* @param {string} [options.childrenKey='children'] - 子节点字段名
|
||||
*/
|
||||
export function sortTree(
|
||||
nodes,
|
||||
{ sortKey = "b_xh", idKey = "b_id", childrenKey = "children" } = {},
|
||||
) {
|
||||
nodes.sort(
|
||||
(a, b) =>
|
||||
Number(a[sortKey] || 0) - Number(b[sortKey] || 0) ||
|
||||
String(a[idKey]).localeCompare(String(b[idKey])),
|
||||
);
|
||||
for (const node of nodes) {
|
||||
if (node[childrenKey]?.length) sortTree(node[childrenKey], { sortKey, idKey, childrenKey });
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user