refactor(api): 统一CMS接口URL配置

- 在环境变量中新增CMS接口URL配置项 VITE_CMS_API_URL
- 将所有CMS相关API接口调用的基地址由MODULES_API_URL或SERVER_API_URL替换为CMS_API_BASE_URL
- 更新cmsAd、cmsAdRecord、cmsArticle及相关cms模块的接口地址引用
- 统一管理CMS API基础路径,提高代码维护性和可配置性
This commit is contained in:
2026-07-27 12:42:51 +08:00
parent 45fc45680f
commit 4a8b0a957c
32 changed files with 380 additions and 208 deletions
+67 -2
View File
@@ -23,10 +23,11 @@
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'title'">
<a-space :style="{ paddingLeft: ((depthMap.get(record.categoryId || 0) || 0) * 20) + 'px' }">
<div style="display: flex; align-items: center; gap: 6px;">
<span v-html="renderTreePrefix(record)"></span>
<img v-if="record.image" :src="getCompressedImageUrl(record.image, { width: 48, quality: 90 })" style="width: 24px; height: 24px; object-fit: cover; border-radius: 4px;" />
<span class="font-bold">{{ record.title }}</span>
</a-space>
</div>
</template>
<template v-if="column.key === 'type'">
<a-tag :color="record.type === 0 ? 'blue' : 'orange'">
@@ -113,6 +114,10 @@
const flatCategoryList = ref<ShopGoodsCategory[]>([]);
// 分类层级深度映射
const depthMap = ref<Map<number, number>>(new Map());
// 分类节点映射(categoryId -> 节点),用于回溯祖先链
const nodeMap = ref<Map<number, ShopGoodsCategory>>(new Map());
// 是否为父级最后一个子节点(用于绘制树形连接线的拐角 / 引导竖线)
const isLastMap = ref<Map<number, boolean>>(new Map());
// 计算每个分类的层级深度
const computeDepth = (list: ShopGoodsCategory[]) => {
@@ -133,16 +138,76 @@
depthMap.value = map;
};
// 构建树形元信息:节点映射 + 是否末位子节点
const buildTreeMeta = (list: ShopGoodsCategory[]) => {
const byParent = new Map<number, ShopGoodsCategory[]>();
const nMap = new Map<number, ShopGoodsCategory>();
list.forEach((c) => {
if (c.categoryId != null) nMap.set(c.categoryId, c);
const pid = c.parentId || 0;
if (!byParent.has(pid)) byParent.set(pid, []);
byParent.get(pid)!.push(c);
});
const lastMap = new Map<number, boolean>();
byParent.forEach((children) => {
children.forEach((c, idx) => {
if (c.categoryId != null) {
lastMap.set(c.categoryId, idx === children.length - 1);
}
});
});
nodeMap.value = nMap;
isLastMap.value = lastMap;
};
// 表格数据源(不分页,全量展示便于查看层级)
const datasource: DatasourceFunction = ({ where, orders }) => {
return listShopGoodsCategory({ ...where, ...orders }).then((res) => {
const list = res || [];
flatCategoryList.value = list;
computeDepth(list);
buildTreeMeta(list);
return { list, count: list.length };
});
};
// 生成树形连接线 SVG:顶级不画;二级/三级根据祖先链绘制引导竖线与拐角
const renderTreePrefix = (record: ShopGoodsCategory): string => {
const depth = depthMap.value.get(record.categoryId || 0) || 0;
if (depth === 0) return '';
const W = 20;
const H = 40;
const mid = H / 2;
// 回溯祖先链:chain[0]=顶级 ... chain[depth]=当前节点
const chain: number[] = [];
let cur: number | undefined = record.categoryId;
while (cur != null) {
chain.unshift(cur);
cur = nodeMap.value.get(cur)?.parentId || undefined;
if (chain.length > 30) break;
}
let svg = `<svg width="${depth * W}" height="${H}" viewBox="0 0 ${depth * W} ${H}" fill="none" style="flex-shrink:0;display:block;">`;
const stroke = '#d0d0d0';
for (let k = 0; k < depth; k++) {
const x = k * W + Math.floor(W / 2);
if (k < depth - 1) {
// 祖先节点不是末位子节点时,画贯穿引导竖线
if (!isLastMap.value.get(chain[k])) {
svg += `<line x1="${x}" y1="0" x2="${x}" y2="${H}" stroke="${stroke}" stroke-width="1"/>`;
}
} else {
// 末段:竖线到中线 + 横线到右侧
svg += `<line x1="${x}" y1="0" x2="${x}" y2="${mid}" stroke="${stroke}" stroke-width="1"/>`;
if (!isLastMap.value.get(record.categoryId || 0)) {
svg += `<line x1="${x}" y1="${mid}" x2="${x}" y2="${H}" stroke="${stroke}" stroke-width="1"/>`;
}
svg += `<line x1="${x}" y1="${mid}" x2="${(k + 1) * W}" y2="${mid}" stroke="${stroke}" stroke-width="1"/>`;
}
}
svg += `</svg>`;
return svg;
};
// 获取父级分类名称
const getParentName = (parentId?: number) => {
if (!parentId || parentId === 0) return '顶级分类';