1
This commit is contained in:
parent
15c1e1ed25
commit
0bf155bd97
@ -1,10 +1,17 @@
|
|||||||
import fs from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import path from 'path';
|
import { fileURLToPath } from 'url';
|
||||||
|
import { dirname, join } from 'path';
|
||||||
|
import { createInterface } from 'readline';
|
||||||
|
|
||||||
|
// 获取当前文件的目录
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
interface GenerateOptions {
|
interface GenerateOptions {
|
||||||
name: string; // 模块名称,如 user, role 等
|
name: string; // 模块名称,如 user, role 等
|
||||||
description: string; // 中文描述,如 "用户管理"
|
description: string; // 中文描述,如 "用户管理"
|
||||||
baseUrl: string; // API基础路径,如 "/api/v1/users"
|
baseUrl: string; // API基础路径,如 "/api/v1/users"
|
||||||
|
pageType?: 'list' | 'detail' | 'form'; // 页面类型,预留扩展
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成 types.ts
|
// 生成 types.ts
|
||||||
@ -12,261 +19,758 @@ function generateTypes(options: GenerateOptions): string {
|
|||||||
const { name } = options;
|
const { name } = options;
|
||||||
const typeName = name.charAt(0).toUpperCase() + name.slice(1);
|
const typeName = name.charAt(0).toUpperCase() + name.slice(1);
|
||||||
|
|
||||||
return `import { BaseResponse, BaseQuery } from '@/types/base';
|
return `import type { BaseResponse, BaseQuery } from '@/types/base';
|
||||||
|
|
||||||
export interface ${typeName}Response extends BaseResponse {
|
export interface ${typeName}Response extends BaseResponse {
|
||||||
name: string;
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
enabled: boolean;
|
||||||
|
sort: number;
|
||||||
// TODO: 添加其他字段
|
// TODO: 添加其他字段
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ${typeName}Query extends BaseQuery {
|
export interface ${typeName}Query extends BaseQuery {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
enabled?: boolean;
|
||||||
// TODO: 添加其他查询字段
|
// TODO: 添加其他查询字段
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ${typeName}Request {
|
export interface ${typeName}Request {
|
||||||
name: string;
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
enabled: boolean;
|
||||||
|
sort: number;
|
||||||
// TODO: 添加其他请求字段
|
// TODO: 添加其他请求字段
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成 schema.ts
|
||||||
|
function generateSchema(options: GenerateOptions): string {
|
||||||
|
const { name } = options;
|
||||||
|
const typeName = name.charAt(0).toUpperCase() + name.slice(1);
|
||||||
|
|
||||||
|
return `import * as z from "zod";
|
||||||
|
|
||||||
|
export const searchFormSchema = z.object({
|
||||||
|
name: z.string().optional(),
|
||||||
|
enabled: z.boolean().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ${typeName.toLowerCase()}FormSchema = z.object({
|
||||||
|
name: z.string().min(1, "请输入名称").max(50, "名称不能超过50个字符"),
|
||||||
|
description: z.string().max(200, "描述不能超过200个字符").optional(),
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
sort: z.number().min(0).default(0),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SearchFormValues = z.infer<typeof searchFormSchema>;
|
||||||
|
export type ${typeName}FormValues = z.infer<typeof ${typeName.toLowerCase()}FormSchema>;
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
// 生成 service.ts
|
// 生成 service.ts
|
||||||
function generateService(options: GenerateOptions): string {
|
function generateService(options: GenerateOptions): string {
|
||||||
const { name, baseUrl } = options;
|
const { name, baseUrl } = options;
|
||||||
const typeName = name.charAt(0).toUpperCase() + name.slice(1);
|
const typeName = name.charAt(0).toUpperCase() + name.slice(1);
|
||||||
|
|
||||||
return `import { http } from '@/utils/http';
|
return `import request from '@/utils/request';
|
||||||
|
import type { Page } from '@/types/base';
|
||||||
import type { ${typeName}Response, ${typeName}Query, ${typeName}Request } from './types';
|
import type { ${typeName}Response, ${typeName}Query, ${typeName}Request } from './types';
|
||||||
|
|
||||||
// 获取列表
|
const BASE_URL = '${baseUrl}';
|
||||||
export const getList = (params?: ${typeName}Query) =>
|
|
||||||
http.get<${typeName}Response[]>('${baseUrl}', { params });
|
|
||||||
|
|
||||||
// 获取详情
|
|
||||||
export const getDetail = (id: number) =>
|
|
||||||
http.get<${typeName}Response>(\`${baseUrl}/\${id}\`);
|
|
||||||
|
|
||||||
// 创建
|
// 创建
|
||||||
export const create = (data: ${typeName}Request) =>
|
export const create${typeName} = (data: ${typeName}Request) =>
|
||||||
http.post<${typeName}Response>('${baseUrl}', data);
|
request.post<void>(BASE_URL, data);
|
||||||
|
|
||||||
// 更新
|
// 更新
|
||||||
export const update = (id: number, data: ${typeName}Request) =>
|
export const update${typeName} = (id: number, data: ${typeName}Request) =>
|
||||||
http.put<${typeName}Response>(\`${baseUrl}/\${id}\`, data);
|
request.put<void>(\`\${BASE_URL}/\${id}\`, data);
|
||||||
|
|
||||||
// 删除
|
// 删除
|
||||||
export const remove = (id: number) =>
|
export const delete${typeName} = (id: number) =>
|
||||||
http.delete(\`${baseUrl}/\${id}\`);
|
request.delete<void>(\`\${BASE_URL}/\${id}\`);
|
||||||
|
|
||||||
// 批量删除
|
// 获取详情
|
||||||
export const batchRemove = (ids: number[]) =>
|
export const get${typeName} = (id: number) =>
|
||||||
http.post('${baseUrl}/batch-delete', { ids });
|
request.get<${typeName}Response>(\`\${BASE_URL}/\${id}\`);
|
||||||
|
|
||||||
|
// 分页查询列表
|
||||||
|
export const get${typeName}Page = (params?: ${typeName}Query) =>
|
||||||
|
request.get<Page<${typeName}Response>>(\`\${BASE_URL}/page\`, { params });
|
||||||
|
|
||||||
|
// 获取所有列表
|
||||||
|
export const get${typeName}List = () =>
|
||||||
|
request.get<${typeName}Response[]>(BASE_URL);
|
||||||
|
|
||||||
|
// 条件查询列表
|
||||||
|
export const get${typeName}ListByCondition = (params?: ${typeName}Query) =>
|
||||||
|
request.get<${typeName}Response[]>(\`\${BASE_URL}/list\`, { params });
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成页面组件
|
// 生成 Modal 组件
|
||||||
function generatePage(options: GenerateOptions): string {
|
function generateModal(options: GenerateOptions): string {
|
||||||
|
const { name } = options;
|
||||||
|
const typeName = name.charAt(0).toUpperCase() + name.slice(1);
|
||||||
|
|
||||||
|
return `import React, { useEffect } from 'react';
|
||||||
|
import type { ${typeName}Response } from '@/pages/${name}/List/types';
|
||||||
|
import { create${typeName}, update${typeName} } from '@/pages/${name}/List/service';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogFooter,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { ${typeName.toLowerCase()}FormSchema, type ${typeName}FormValues } from "@/pages/${name}/List/schema";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
|
||||||
|
interface ${typeName}ModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
onSuccess: () => void;
|
||||||
|
initialValues?: ${typeName}Response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ${typeName}Modal: React.FC<${typeName}ModalProps> = ({
|
||||||
|
open,
|
||||||
|
onCancel,
|
||||||
|
onSuccess,
|
||||||
|
initialValues,
|
||||||
|
}) => {
|
||||||
|
const { toast } = useToast();
|
||||||
|
const isEdit = !!initialValues?.id;
|
||||||
|
|
||||||
|
const form = useForm<${typeName}FormValues>({
|
||||||
|
resolver: zodResolver(${typeName.toLowerCase()}FormSchema),
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
enabled: true,
|
||||||
|
sort: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialValues) {
|
||||||
|
form.reset({
|
||||||
|
name: initialValues.name,
|
||||||
|
description: initialValues.description || "",
|
||||||
|
enabled: initialValues.enabled,
|
||||||
|
sort: initialValues.sort,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [initialValues, form]);
|
||||||
|
|
||||||
|
const handleSubmit = async (values: ${typeName}FormValues) => {
|
||||||
|
try {
|
||||||
|
if (isEdit) {
|
||||||
|
await update${typeName}(initialValues.id, values);
|
||||||
|
} else {
|
||||||
|
await create${typeName}(values);
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title: \`\${isEdit ? '更新' : '创建'}成功\`,
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
form.reset();
|
||||||
|
onSuccess();
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: \`\${isEdit ? '更新' : '创建'}失败\`,
|
||||||
|
description: error instanceof Error ? error.message : undefined,
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={(open) => !open && onCancel()}>
|
||||||
|
<DialogContent className="sm:max-w-[600px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{isEdit ? '编辑' : '新建'}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
render={({field}) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>名称</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="请输入名称"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage/>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="description"
|
||||||
|
render={({field}) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>描述</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
{...field}
|
||||||
|
placeholder="请输入描述"
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage/>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="enabled"
|
||||||
|
render={({field}) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>状态</FormLabel>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="sort"
|
||||||
|
render={({field}) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>排序</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage/>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button type="button" variant="outline" onClick={onCancel}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button type="submit">
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ${typeName}Modal;
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成列表页面组件
|
||||||
|
function generateListPage(options: GenerateOptions): string {
|
||||||
const { name, description } = options;
|
const { name, description } = options;
|
||||||
const typeName = name.charAt(0).toUpperCase() + name.slice(1);
|
const typeName = name.charAt(0).toUpperCase() + name.slice(1);
|
||||||
|
|
||||||
return `import { useState } from 'react';
|
return `import React, { useState, useEffect } from 'react';
|
||||||
import { Card, Table, Button, Space, Modal, message, Form, Input } from 'antd';
|
import { PageContainer } from '@/components/ui/page-container';
|
||||||
import type { ${typeName}Response, ${typeName}Query, ${typeName}Request } from './types';
|
import {
|
||||||
import * as service from './service';
|
PlusOutlined,
|
||||||
import { useRequest } from 'ahooks';
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableHeader,
|
||||||
|
TableBody,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
TableCell,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
} from "@/components/ui/alert-dialog";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { searchFormSchema, type SearchFormValues } from "@/pages/${name}/List/schema";
|
||||||
|
import { DataTablePagination } from "@/components/ui/pagination";
|
||||||
|
import type { ${typeName}Response, ${typeName}Query } from '@/pages/${name}/List/types';
|
||||||
|
import { get${typeName}Page, delete${typeName} } from '@/pages/${name}/List/service';
|
||||||
|
import ${typeName}Modal from '@/pages/${name}/List/components/${typeName}Modal';
|
||||||
|
|
||||||
const ${typeName}Page = () => {
|
interface Column {
|
||||||
const [query, setQuery] = useState<${typeName}Query>({});
|
accessorKey?: keyof ${typeName}Response;
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<number[]>([]);
|
id?: string;
|
||||||
const [editModalVisible, setEditModalVisible] = useState(false);
|
header: string;
|
||||||
const [editingRecord, setEditingRecord] = useState<${typeName}Response | null>(null);
|
size: number;
|
||||||
const [form] = Form.useForm();
|
cell?: (props: { row: { original: ${typeName}Response } }) => React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
// 获取列表数据
|
const ${typeName}List: React.FC = () => {
|
||||||
const { data, loading, refresh } = useRequest(() => service.getList(query), {
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
refreshDeps: [query]
|
const [currentRecord, setCurrentRecord] = useState<${typeName}Response>();
|
||||||
|
const [list, setList] = useState<${typeName}Response[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
totalElements: 0,
|
||||||
|
});
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const form = useForm<SearchFormValues>({
|
||||||
|
resolver: zodResolver(searchFormSchema),
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
enabled: undefined,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 删除操作
|
const loadData = async (params?: ${typeName}Query) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const queryParams: ${typeName}Query = {
|
||||||
|
...params,
|
||||||
|
pageNum: pagination.pageNum,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
};
|
||||||
|
const data = await get${typeName}Page(queryParams);
|
||||||
|
setList(data.content || []);
|
||||||
|
setPagination({
|
||||||
|
...pagination,
|
||||||
|
totalElements: data.totalElements,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "获取列表失败",
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
setPagination({
|
||||||
|
...pagination,
|
||||||
|
pageNum: page,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadData(form.getValues());
|
||||||
|
}, [pagination.pageNum, pagination.pageSize]);
|
||||||
|
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
Modal.confirm({
|
|
||||||
title: '确认删除',
|
|
||||||
content: '确定要删除这条记录吗?',
|
|
||||||
onOk: async () => {
|
|
||||||
try {
|
try {
|
||||||
await service.remove(id);
|
await delete${typeName}(id);
|
||||||
message.success('删除成功');
|
toast({
|
||||||
refresh();
|
title: "删除成功",
|
||||||
} catch (error) {
|
duration: 3000,
|
||||||
message.error('删除失败');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
loadData(form.getValues());
|
||||||
|
|
||||||
// 批量删除
|
|
||||||
const handleBatchDelete = () => {
|
|
||||||
if (!selectedRowKeys.length) {
|
|
||||||
message.warning('请选择要删除的记录');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Modal.confirm({
|
|
||||||
title: '确认删除',
|
|
||||||
content: \`确定要删除这\${selectedRowKeys.length}条记录吗?\`,
|
|
||||||
onOk: async () => {
|
|
||||||
try {
|
|
||||||
await service.batchRemove(selectedRowKeys);
|
|
||||||
message.success('删除成功');
|
|
||||||
setSelectedRowKeys([]);
|
|
||||||
refresh();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('删除失败');
|
toast({
|
||||||
}
|
variant: "destructive",
|
||||||
}
|
title: "删除失败",
|
||||||
|
duration: 3000,
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
// 打开编辑弹窗
|
|
||||||
const handleEdit = async (id: number) => {
|
|
||||||
try {
|
|
||||||
const detail = await service.getDetail(id);
|
|
||||||
setEditingRecord(detail);
|
|
||||||
form.setFieldsValue(detail);
|
|
||||||
setEditModalVisible(true);
|
|
||||||
} catch (error) {
|
|
||||||
message.error('获取详情失败');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 打开新增弹窗
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
setEditingRecord(null);
|
setCurrentRecord(undefined);
|
||||||
form.resetFields();
|
setModalVisible(true);
|
||||||
setEditModalVisible(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 保存表单
|
const handleEdit = (record: ${typeName}Response) => {
|
||||||
const handleSave = async () => {
|
setCurrentRecord(record);
|
||||||
try {
|
setModalVisible(true);
|
||||||
const values = await form.validateFields();
|
|
||||||
if (editingRecord) {
|
|
||||||
await service.update(editingRecord.id, values);
|
|
||||||
message.success('更新成功');
|
|
||||||
} else {
|
|
||||||
await service.create(values);
|
|
||||||
message.success('创建成功');
|
|
||||||
}
|
|
||||||
setEditModalVisible(false);
|
|
||||||
refresh();
|
|
||||||
} catch (error) {
|
|
||||||
message.error('操作失败');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
const handleModalClose = () => {
|
||||||
|
setModalVisible(false);
|
||||||
|
setCurrentRecord(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSuccess = () => {
|
||||||
|
setModalVisible(false);
|
||||||
|
setCurrentRecord(undefined);
|
||||||
|
loadData(form.getValues());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
form.reset({
|
||||||
|
name: "",
|
||||||
|
enabled: undefined,
|
||||||
|
});
|
||||||
|
loadData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: Column[] = [
|
||||||
{
|
{
|
||||||
title: '名称',
|
accessorKey: 'name',
|
||||||
dataIndex: 'name',
|
header: '名称',
|
||||||
key: 'name',
|
size: 180,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '创建时间',
|
accessorKey: 'description',
|
||||||
dataIndex: 'createTime',
|
header: '描述',
|
||||||
key: 'createTime',
|
size: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
accessorKey: 'enabled',
|
||||||
key: 'action',
|
header: '状态',
|
||||||
render: (_, record: ${typeName}Response) => (
|
size: 100,
|
||||||
<Space>
|
cell: ({row}) => (
|
||||||
<Button type="link" onClick={() => handleEdit(record.id)}>编辑</Button>
|
<Badge variant={row.original.enabled ? "outline" : "secondary"}>
|
||||||
<Button type="link" danger onClick={() => handleDelete(record.id)}>删除</Button>
|
{row.original.enabled ? '启用' : '禁用'}
|
||||||
</Space>
|
</Badge>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'sort',
|
||||||
|
header: '排序',
|
||||||
|
size: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'createTime',
|
||||||
|
header: '创建时间',
|
||||||
|
size: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
header: '操作',
|
||||||
|
size: 180,
|
||||||
|
cell: ({row}) => (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleEdit(row.original)}
|
||||||
|
>
|
||||||
|
<EditOutlined className="mr-1"/>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="text-destructive"
|
||||||
|
>
|
||||||
|
<DeleteOutlined className="mr-1"/>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>确定要删除吗?</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
删除后将无法恢复,请谨慎操作
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={() => handleDelete(row.original.id)}
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<PageContainer>
|
||||||
<Card title="${description}" extra={
|
<div className="flex items-center justify-between">
|
||||||
<Space>
|
<h2 className="text-3xl font-bold tracking-tight">${description}</h2>
|
||||||
<Button type="primary" onClick={handleAdd}>新增</Button>
|
<div className="flex items-center gap-4">
|
||||||
<Button danger onClick={handleBatchDelete}>批量删除</Button>
|
<Button onClick={handleAdd}>
|
||||||
</Space>
|
<PlusOutlined className="mr-1"/>
|
||||||
}>
|
新建
|
||||||
<Form layout="inline" style={{ marginBottom: 16 }}>
|
</Button>
|
||||||
<Form.Item label="名称" name="name">
|
</div>
|
||||||
<Input placeholder="请输入名称"
|
</div>
|
||||||
onChange={e => setQuery(prev => ({ ...prev, name: e.target.value }))}
|
|
||||||
/>
|
<Card>
|
||||||
</Form.Item>
|
<div className="p-6">
|
||||||
</Form>
|
<div className="flex items-center gap-4">
|
||||||
<Table
|
<Input
|
||||||
rowKey="id"
|
placeholder="名称"
|
||||||
columns={columns}
|
{...form.register('name')}
|
||||||
dataSource={data}
|
className="max-w-[200px]"
|
||||||
loading={loading}
|
|
||||||
rowSelection={{
|
|
||||||
selectedRowKeys,
|
|
||||||
onChange: (keys) => setSelectedRowKeys(keys as number[]),
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
<Select
|
||||||
|
value={form.getValues('enabled')?.toString()}
|
||||||
|
onValueChange={(value) => form.setValue('enabled', value === 'true')}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="max-w-[200px]">
|
||||||
|
<SelectValue placeholder="状态"/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="true">启用</SelectItem>
|
||||||
|
<SelectItem value="false">禁用</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Button variant="outline" onClick={handleReset}>
|
||||||
|
重置
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => loadData(form.getValues())}>
|
||||||
|
搜索
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Modal
|
<Card>
|
||||||
title={editingRecord ? '编辑' : '新增'}
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
open={editModalVisible}
|
<CardTitle>列表</CardTitle>
|
||||||
onOk={handleSave}
|
</CardHeader>
|
||||||
onCancel={() => setEditModalVisible(false)}
|
<CardContent>
|
||||||
|
<div className="rounded-md border">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
{columns.map((column) => (
|
||||||
|
<TableHead
|
||||||
|
key={column.accessorKey || column.id}
|
||||||
|
style={{width: column.size}}
|
||||||
>
|
>
|
||||||
<Form form={form} layout="vertical">
|
{column.header}
|
||||||
<Form.Item
|
</TableHead>
|
||||||
label="名称"
|
))}
|
||||||
name="name"
|
</TableRow>
|
||||||
rules={[{ required: true, message: '请输入名称' }]}
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{list.map((item) => (
|
||||||
|
<TableRow key={item.id}>
|
||||||
|
{columns.map((column) => (
|
||||||
|
<TableCell
|
||||||
|
key={column.accessorKey || column.id}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入名称" />
|
{column.cell
|
||||||
</Form.Item>
|
? column.cell({row: {original: item}})
|
||||||
{/* TODO: 添加其他表单项 */}
|
: item[column.accessorKey!]}
|
||||||
</Form>
|
</TableCell>
|
||||||
</Modal>
|
))}
|
||||||
</>
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<div className="flex justify-end border-t border-border bg-muted/40">
|
||||||
|
<DataTablePagination
|
||||||
|
pageIndex={pagination.pageNum}
|
||||||
|
pageSize={pagination.pageSize}
|
||||||
|
pageCount={Math.ceil(pagination.totalElements / pagination.pageSize)}
|
||||||
|
onPageChange={handlePageChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{modalVisible && (
|
||||||
|
<${typeName}Modal
|
||||||
|
open={modalVisible}
|
||||||
|
onCancel={handleModalClose}
|
||||||
|
onSuccess={handleSuccess}
|
||||||
|
initialValues={currentRecord}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ${typeName}Page;
|
export default ${typeName}List;
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成文件
|
// 生成文件
|
||||||
function generateFiles(options: GenerateOptions) {
|
async function generateFiles(options: GenerateOptions) {
|
||||||
const { name } = options;
|
const { name, pageType = 'list' } = options;
|
||||||
const basePath = path.join(process.cwd(), 'src/pages', name);
|
const basePath = join(process.cwd(), 'src/pages', name, pageType);
|
||||||
|
const componentsPath = join(basePath, 'components');
|
||||||
|
|
||||||
// 创建目录
|
// 创建目录
|
||||||
if (!fs.existsSync(basePath)) {
|
await fs.mkdir(basePath, { recursive: true });
|
||||||
fs.mkdirSync(basePath, { recursive: true });
|
await fs.mkdir(componentsPath, { recursive: true });
|
||||||
}
|
|
||||||
|
|
||||||
// 生成文件
|
// 生成文件
|
||||||
fs.writeFileSync(path.join(basePath, 'types.ts'), generateTypes(options));
|
switch (pageType) {
|
||||||
fs.writeFileSync(path.join(basePath, 'service.ts'), generateService(options));
|
case 'list':
|
||||||
fs.writeFileSync(path.join(basePath, 'index.tsx'), generatePage(options));
|
await fs.writeFile(join(basePath, 'types.ts'), generateTypes(options));
|
||||||
|
await fs.writeFile(join(basePath, 'schema.ts'), generateSchema(options));
|
||||||
console.log(`Successfully generated files for module "${name}" in ${basePath}`);
|
await fs.writeFile(join(basePath, 'service.ts'), generateService(options));
|
||||||
|
await fs.writeFile(join(basePath, 'index.tsx'), generateListPage(options));
|
||||||
|
await fs.writeFile(
|
||||||
|
join(componentsPath, `${options.name.charAt(0).toUpperCase() + options.name.slice(1)}Modal.tsx`),
|
||||||
|
generateModal(options)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'detail':
|
||||||
|
case 'form':
|
||||||
|
// 未来实现
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用示例
|
// 主函数
|
||||||
const options: GenerateOptions = {
|
async function generate(options: GenerateOptions) {
|
||||||
name: process.argv[2],
|
try {
|
||||||
description: process.argv[3] || '管理',
|
await generateFiles(options);
|
||||||
baseUrl: process.argv[4] || `/api/v1/${process.argv[2]}`,
|
console.log('✨ 页面生成成功!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 页面生成失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建命令行交互界面
|
||||||
|
const rl = createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
|
||||||
|
// 提示用户输入
|
||||||
|
const prompt = (question: string): Promise<string> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
rl.question(question, (answer) => {
|
||||||
|
resolve(answer);
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!options.name) {
|
// 交互式生成页面
|
||||||
console.error('Please provide a module name!');
|
async function generateInteractive() {
|
||||||
process.exit(1);
|
try {
|
||||||
|
console.log('👋 欢迎使用页面生成器!');
|
||||||
|
console.log('-------------------');
|
||||||
|
|
||||||
|
// 获取模块名称
|
||||||
|
const name = await prompt('请输入模块名称(如:user): ');
|
||||||
|
if (!name) {
|
||||||
|
throw new Error('模块名称不能为空!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取模块描述
|
||||||
|
const description = await prompt('请输入模块描述(如:用户管理): ');
|
||||||
|
if (!description) {
|
||||||
|
throw new Error('模块描述不能为空!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取API路径
|
||||||
|
const defaultBaseUrl = `/api/v1/${name.toLowerCase()}`;
|
||||||
|
const baseUrl = await prompt(`请输入API路径 (默认: ${defaultBaseUrl}): `) || defaultBaseUrl;
|
||||||
|
|
||||||
|
// 获取页面类型(预留)
|
||||||
|
const pageType = 'list'; // 目前固定为list
|
||||||
|
|
||||||
|
// 生成页面
|
||||||
|
generate({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
baseUrl,
|
||||||
|
pageType,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
console.log('✨ 页面生成成功!');
|
||||||
|
console.log('📁 生成的文件位置:');
|
||||||
|
console.log(`src/pages/${name}/list/`);
|
||||||
|
console.log('');
|
||||||
|
console.log('🎉 包含以下文件:');
|
||||||
|
console.log('- types.ts');
|
||||||
|
console.log('- schema.ts');
|
||||||
|
console.log('- service.ts');
|
||||||
|
console.log('- index.tsx');
|
||||||
|
console.log(`- components/${name.charAt(0).toUpperCase() + name.slice(1)}Modal.tsx`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 页面生成失败:', error);
|
||||||
|
} finally {
|
||||||
|
rl.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generateFiles(options);
|
// 根据命令行参数判断是否使用交互模式
|
||||||
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
if (args.length === 0) {
|
||||||
|
// 无参数时使用交互模式
|
||||||
|
generateInteractive();
|
||||||
|
} else {
|
||||||
|
// 有参数时使用命令行模式
|
||||||
|
generate({
|
||||||
|
name: args[0],
|
||||||
|
description: args[1] || '管理',
|
||||||
|
baseUrl: args[2] || `/api/v1/${args[0]}`,
|
||||||
|
pageType: 'list',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { generate, type GenerateOptions };
|
||||||
21
frontend/scripts/tsconfig.json
Normal file
21
frontend/scripts/tsconfig.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": ["ES2020"],
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"outDir": "../dist/scripts",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["../src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["./**/*"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
@ -15,7 +15,6 @@ import {getProjectGroupList} from '../../ProjectGroup/List/service';
|
|||||||
import type {Application, ApplicationQuery} from './types';
|
import type {Application, ApplicationQuery} from './types';
|
||||||
import {DevelopmentLanguageTypeEnum} from './types';
|
import {DevelopmentLanguageTypeEnum} from './types';
|
||||||
import type {ProjectGroup} from '../../ProjectGroup/List/types';
|
import type {ProjectGroup} from '../../ProjectGroup/List/types';
|
||||||
import {ProjectGroupTypeEnum} from '../../ProjectGroup/List/types';
|
|
||||||
import {getProjectTypeInfo} from '../../ProjectGroup/List/utils';
|
import {getProjectTypeInfo} from '../../ProjectGroup/List/utils';
|
||||||
import ApplicationModal from './components/ApplicationModal';
|
import ApplicationModal from './components/ApplicationModal';
|
||||||
import {
|
import {
|
||||||
@ -33,13 +32,6 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import {Button} from "@/components/ui/button";
|
import {Button} from "@/components/ui/button";
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
} from "@/components/ui/form";
|
|
||||||
import {Input} from "@/components/ui/input";
|
import {Input} from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user