diff --git a/src/types/api/codegen.ts b/src/types/api/codegen.ts
index 2686a681..cea667ba 100644
--- a/src/types/api/codegen.ts
+++ b/src/types/api/codegen.ts
@@ -12,6 +12,10 @@ export interface GeneratorPreviewItem {
fileName: string;
/** 文件内容 */
content: string;
+ /** 文件范围(frontend/backend) */
+ scope: "frontend" | "backend";
+ /** 文件语言(扩展名) */
+ language: string;
}
/** 数据表分页查询参数 */
diff --git a/src/views/codegen/index.vue b/src/views/codegen/index.vue
index 5cf47b8c..d398b3f2 100644
--- a/src/views/codegen/index.vue
+++ b/src/views/codegen/index.vue
@@ -388,7 +388,7 @@
@node-click="handleFileTreeNodeClick"
>
-
+
{{ data.label }}
@@ -525,7 +525,13 @@ import type { EditorConfiguration } from "codemirror";
import { FormTypeEnum, QueryTypeEnum } from "@/enums/codegen";
import GeneratorAPI from "@/api/codegen";
-import type { FieldConfig, GenConfigForm, TableQueryParams, TableItem } from "@/api/types";
+import type {
+ FieldConfig,
+ GenConfigForm,
+ TableQueryParams,
+ TableItem,
+ GeneratorPreviewItem,
+} from "@/api/types";
import { ElLoading } from "element-plus";
import DictAPI from "@/api/system/dict";
@@ -535,43 +541,37 @@ interface TreeNode {
label: string;
content?: string;
children?: TreeNode[];
+ scope?: "frontend" | "backend";
+ language?: string;
}
const treeData = ref([]);
const previewScope = ref<"all" | "frontend" | "backend">("all");
-const previewTypeOptions = ["ts", "vue", "java", "xml"];
-const previewTypes = ref([...previewTypeOptions]);
+const previewTypeOptions = ref([]);
+const previewTypes = ref([]);
const frontendType = "ts";
const filteredTreeData = computed(() => {
if (!treeData.value.length) return [];
// 基于原树 scope/types 过滤叶子节点
- const match = (label: string, parentPath: string[]): boolean => {
- // scope 过滤:根据路径初步判断
- const pathStr = parentPath.join("/");
+ const match = (node: TreeNode): boolean => {
if (previewScope.value !== "all") {
- const isBackend = /(^|\/)src\/main\//.test(pathStr) || /(^|\/)java\//.test(pathStr);
- const scopeOfNode = isBackend ? "backend" : "frontend";
- if (scopeOfNode !== previewScope.value) return false;
+ if (node.scope !== previewScope.value) return false;
}
- // 类型过滤:根据后缀
- const ext = label.split(".").pop() || "";
- return previewTypes.value.includes(ext);
+ if (!previewTypes.value.length) return true;
+ const language = node.language || node.label.split(".").pop() || "";
+ return previewTypes.value.includes(language);
};
- const cloneFilter = (node: TreeNode, parents: string[] = []): TreeNode | null => {
+ const cloneFilter = (node: TreeNode): TreeNode | null => {
if (!node.children || node.children.length === 0) {
- return match(node.label, parents) ? { ...node } : null;
+ return match(node) ? { ...node } : null;
}
- const nextParents = [...parents, node.label];
- const children = (node.children || [])
- .map((c) => cloneFilter(c, nextParents))
- .filter(Boolean) as TreeNode[];
+ const children = (node.children || []).map((c) => cloneFilter(c)).filter(Boolean) as TreeNode[];
if (!children.length) return null;
return { label: node.label, children };
};
- const filtered = treeData.value.map((n) => cloneFilter(n)).filter(Boolean) as TreeNode[];
- return filtered;
+ return treeData.value.map((n) => cloneFilter(n)).filter(Boolean) as TreeNode[];
});
const queryFormRef = ref();
@@ -647,12 +647,12 @@ const backendDirHandle = ref(null);
const frontendDirName = ref("");
const backendDirName = ref("");
// 预览的原始文件列表(用于写盘)
-const lastPreviewFiles = ref<{ path: string; fileName: string; content: string }[]>([]);
+const lastPreviewFiles = ref([]);
const needFrontend = computed(() =>
- lastPreviewFiles.value.some((f) => resolveRootForPath(f.path) === "frontend")
+ lastPreviewFiles.value.some((f) => resolveRootForItem(f) === "frontend")
);
const needBackend = computed(() =>
- lastPreviewFiles.value.some((f) => resolveRootForPath(f.path) === "backend")
+ lastPreviewFiles.value.some((f) => resolveRootForItem(f) === "backend")
);
const canWriteToLocal = computed(() => {
if (!lastPreviewFiles.value.length) return false;
@@ -913,8 +913,19 @@ async function handlePreview(tableName: string) {
frontendType
);
dialog.title = `代码生成 ${tableName}`;
- const tree = buildTree(data);
- lastPreviewFiles.value = data || [];
+ const previewList = data || [];
+ const typeOptions = Array.from(
+ new Set(
+ previewList
+ .map((item) => item.language || item.fileName.split(".").pop() || "")
+ .filter(Boolean)
+ )
+ );
+ previewTypeOptions.value = typeOptions;
+ previewTypes.value = [...typeOptions];
+
+ const tree = buildTree(previewList);
+ lastPreviewFiles.value = previewList;
treeData.value = tree?.children ? [...tree.children] : [];
const firstLeafNode = findFirstLeafNode(tree);
@@ -933,52 +944,17 @@ async function handlePreview(tableName: string) {
* @param data - 数据数组
* @returns 树形结构根节点
*/
-function buildTree(data: { path: string; fileName: string; content: string }[]): TreeNode {
+function buildTree(data: GeneratorPreviewItem[]): TreeNode {
// 动态获取根节点
const root: TreeNode = { label: "前后端代码", children: [] };
data.forEach((item) => {
- // 将路径分成数组
- const separator = item.path.includes("/") ? "/" : "\\";
- const parts = item.path.split(separator);
-
- // 定义特殊路径
- const specialPaths = [
- "src" + separator + "main",
- "java",
- genConfigFormData.value.backendAppName,
- genConfigFormData.value.frontendAppName,
- (genConfigFormData.value.packageName + "." + genConfigFormData.value.moduleName).replace(
- /\./g,
- separator
- ),
- ];
-
- // 检查路径中的特殊部分并合并它们
- const mergedParts: string[] = [];
- let buffer: string[] = [];
-
- parts.forEach((part) => {
- buffer.push(part);
- const currentPath = buffer.join(separator);
- if (specialPaths.includes(currentPath)) {
- mergedParts.push(currentPath);
- buffer = [];
- }
- });
-
- // 将 mergedParts 路径中的分隔符 \ 替换为 /
- mergedParts.forEach((part, index) => {
- mergedParts[index] = part.replace(/\\/g, "/");
- });
-
- if (buffer.length > 0) {
- mergedParts.push(...buffer);
- }
+ const normalizedPath = item.path.replace(/\\/g, "/");
+ const parts = normalizedPath.split("/").filter(Boolean);
let currentNode = root;
- mergedParts.forEach((part) => {
+ parts.forEach((part) => {
// 查找或创建当前部分的子节点
let node = currentNode.children?.find((child) => child.label === part);
if (!node) {
@@ -992,6 +968,8 @@ function buildTree(data: { path: string; fileName: string; content: string }[]):
currentNode.children?.push({
label: item.fileName,
content: item?.content,
+ scope: item.scope,
+ language: item.language,
});
});
@@ -1024,22 +1002,26 @@ function handleFileTreeNodeClick(data: TreeNode) {
}
/** 获取文件树节点图标 */
-function getFileTreeNodeIcon(label: string) {
- if (label.endsWith(".java")) {
+function getFileTreeNodeIcon(node: TreeNode) {
+ const ext = (node.language || node.label.split(".").pop() || "").toLowerCase();
+ if (ext === "java") {
return "java";
}
- if (label.endsWith(".html")) {
+ if (ext === "html") {
return "html";
}
- if (label.endsWith(".vue")) {
+ if (ext === "vue") {
return "vue";
}
- if (label.endsWith(".ts")) {
+ if (ext === "ts") {
return "typescript";
}
- if (label.endsWith(".xml")) {
+ if (ext === "xml") {
return "xml";
}
+ if (["cs", "go", "py", "php", "js"].includes(ext)) {
+ return "code";
+ }
return "file";
}
@@ -1138,23 +1120,11 @@ async function isSameFile(dirHandle: any, filePath: string, content: string): Pr
}
}
-// 将模板中 path 映射到前/后端根目录
-function resolveRootForPath(p: string) {
- const normalized = p.replace(/\\/g, "/");
- const frontApp = genConfigFormData.value.frontendAppName;
- const backApp = genConfigFormData.value.backendAppName;
- if (
- (backApp && normalized.startsWith(`${backApp}/`)) ||
- normalized.includes("/src/main/") ||
- normalized.startsWith("src/main/") ||
- normalized.startsWith("java/")
- ) {
+// 将预览条目映射到前/后端根目录(由后端给出 scope)
+function resolveRootForItem(item: GeneratorPreviewItem) {
+ if (item.scope === "backend") {
return "backend" as const;
}
- if ((frontApp && normalized.startsWith(`${frontApp}/`)) || normalized.startsWith("src/")) {
- return "frontend" as const;
- }
- // 默认前端
return "frontend" as const;
}
@@ -1204,7 +1174,7 @@ const writeGeneratedCode = async () => {
let backCount = 0;
const failed: string[] = [];
const files = lastPreviewFiles.value.filter((f) => {
- const root = resolveRootForPath(f.path);
+ const root = resolveRootForItem(f);
return writeScope.value === "all" || root === writeScope.value;
});
writeProgress.total = files.length;
@@ -1220,7 +1190,7 @@ const writeGeneratedCode = async () => {
while (queue.length) {
const item = queue.shift()!;
try {
- const root = resolveRootForPath(item.path);
+ const root = resolveRootForItem(item);
const relativePath = stripProjectRoot(`${item.path}/${item.fileName}`);
writeProgress.current = relativePath;
if (overwriteMode.value === "ifChanged") {