This commit is contained in:
dengqichen 2025-10-20 13:18:42 +08:00
parent 07b99aac31
commit df07edc18f
10 changed files with 1228 additions and 1181 deletions

View File

@ -1,762 +0,0 @@
import { promises as fs } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { createInterface } from 'readline';
// 获取当前文件的目录
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// 生成 types.ts
function generateTypes(options) {
const { name } = options;
const typeName = name.charAt(0).toUpperCase() + name.slice(1);
return `import type { BaseResponse, BaseQuery } from '@/types/base';
export interface ${typeName}Response extends BaseResponse {
name: string;
description?: string;
enabled: boolean;
sort: number;
// TODO: 添加其他字段
}
export interface ${typeName}Query extends BaseQuery {
name?: string;
enabled?: boolean;
// TODO: 添加其他查询字段
}
export interface ${typeName}Request {
name: string;
description?: string;
enabled: boolean;
sort: number;
// TODO: 添加其他请求字段
}
`;
}
// 生成 schema.ts
function generateSchema(options) {
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
function generateService(options) {
const { name, baseUrl } = options;
const typeName = name.charAt(0).toUpperCase() + name.slice(1);
return `import request from '@/utils/request';
import type { Page } from '@/types/base';
import type { ${typeName}Response, ${typeName}Query, ${typeName}Request } from './types';
const BASE_URL = '${baseUrl}';
// 创建
export const create${typeName} = (data: ${typeName}Request) =>
request.post<void>(BASE_URL, data);
// 更新
export const update${typeName} = (id: number, data: ${typeName}Request) =>
request.put<void>(\`\${BASE_URL}/\${id}\`, data);
// 删除
export const delete${typeName} = (id: number) =>
request.delete<void>(\`\${BASE_URL}/\${id}\`);
// 获取详情
export const get${typeName} = (id: number) =>
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 generateModal(options) {
const { name } = options;
const typeName = name.charAt(0).toUpperCase() + name.slice(1);
return `import React, { useEffect } from 'react';
import type { ${typeName}Response } from '../types';
import { create${typeName}, update${typeName} } from '../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 '../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) {
const { name, description } = options;
const typeName = name.charAt(0).toUpperCase() + name.slice(1);
return `import React, { useState, useEffect } from 'react';
import { PageContainer } from '@/components/ui/page-container';
import { Plus, Pencil, Trash2, Loader2 } from 'lucide-react';
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 './schema';
import { DataTablePagination } from "@/components/ui/pagination";
import type { ${typeName}Response, ${typeName}Query } from './types';
import { get${typeName}Page, delete${typeName} } from './service';
import ${typeName}Modal from './components/${typeName}Modal';
interface Column {
accessorKey?: keyof ${typeName}Response;
id?: string;
header: string;
size: number;
cell?: (props: { row: { original: ${typeName}Response } }) => React.ReactNode;
}
const ${typeName}List: React.FC = () => {
const [modalVisible, setModalVisible] = useState(false);
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) => {
try {
await delete${typeName}(id);
toast({
title: "删除成功",
duration: 3000,
});
loadData(form.getValues());
} catch (error) {
toast({
variant: "destructive",
title: "删除失败",
duration: 3000,
});
}
};
const handleAdd = () => {
setCurrentRecord(undefined);
setModalVisible(true);
};
const handleEdit = (record: ${typeName}Response) => {
setCurrentRecord(record);
setModalVisible(true);
};
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[] = [
{
accessorKey: 'name',
header: '名称',
size: 180,
},
{
accessorKey: 'description',
header: '描述',
size: 200,
},
{
accessorKey: 'enabled',
header: '状态',
size: 100,
cell: ({row}) => (
<Badge variant={row.original.enabled ? "outline" : "secondary"}>
{row.original.enabled ? '启用' : '禁用'}
</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)}
>
<Pencil className="mr-2 h-4 w-4" />
编辑
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="ghost"
size="sm"
className="text-destructive"
>
<Trash2 className="mr-2 h-4 w-4" />
删除
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>确定要删除吗</AlertDialogTitle>
<AlertDialogDescription>
删除后将无法恢复请谨慎操作
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>取消</AlertDialogCancel>
<AlertDialogAction
onClick={() => handleDelete(row.original.id)}
>
确定
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
),
},
];
return (
<PageContainer>
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold tracking-tight">${description}</h2>
<div className="flex items-center gap-4">
<Button onClick={handleAdd}>
<Plus className="mr-2 h-4 w-4" />
新建
</Button>
</div>
</div>
<Card>
<div className="p-6">
<div className="flex items-center gap-4">
<Input
placeholder="名称"
{...form.register('name')}
className="max-w-[200px]"
/>
<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>
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>列表</CardTitle>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
{columns.map((column) => (
<TableHead
key={column.accessorKey || column.id}
style={{width: column.size}}
>
{column.header}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
<div className="flex justify-center items-center">
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
加载中...
</div>
</TableCell>
</TableRow>
) : list.length === 0 ? (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
暂无数据
</TableCell>
</TableRow>
) : (
list.map((item) => (
<TableRow key={item.id}>
{columns.map((column) => (
<TableCell
key={column.accessorKey || column.id}
>
{column.cell
? column.cell({row: {original: item}})
: item[column.accessorKey!]}
</TableCell>
))}
</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}List;
`;
}
// 生成文件
async function generateFiles(options) {
const { name, pageType = 'list' } = options;
const basePath = join(process.cwd(), 'src/pages', name, 'List');
const componentsPath = join(basePath, 'components');
// 创建目录
await fs.mkdir(basePath, { recursive: true });
await fs.mkdir(componentsPath, { recursive: true });
// 生成文件
switch (pageType) {
case 'list':
await fs.writeFile(join(basePath, 'types.ts'), generateTypes(options));
await fs.writeFile(join(basePath, 'schema.ts'), generateSchema(options));
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;
}
}
// 主函数
async function generate(options) {
try {
await generateFiles(options);
console.log('✨ 页面生成成功!');
}
catch (error) {
console.error('❌ 页面生成失败:', error);
}
}
// 创建命令行交互界面
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
// 提示用户输入
const prompt = (question) => {
return new Promise((resolve) => {
rl.question(question, (answer) => {
resolve(answer);
});
});
};
// 交互式生成页面
async function generateInteractive() {
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();
}
}
// 根据命令行参数判断是否使用交互模式
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 };

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ import {
DialogFooter, DialogFooter,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { BetaSchemaForm } from '@ant-design/pro-form'; import { BetaSchemaForm } from '@ant-design/pro-components';
import { convertJsonSchemaToColumns } from '@/utils/jsonSchemaUtils'; import { convertJsonSchemaToColumns } from '@/utils/jsonSchemaUtils';
import { message } from 'antd'; import { message } from 'antd';
import type { DeploymentConfig } from '@/pages/Deploy/Deployment/List/types'; import type { DeploymentConfig } from '@/pages/Deploy/Deployment/List/types';

View File

@ -5,7 +5,7 @@ import type {DeploymentConfig, DeployConfigTemplate, CreateDeploymentConfigReque
import {createDeploymentConfig, updateDeploymentConfig, getDeployConfigTemplates} from '../service'; import {createDeploymentConfig, updateDeploymentConfig, getDeployConfigTemplates} from '../service';
import {getApplicationList} from '../../../Application/List/service'; import {getApplicationList} from '../../../Application/List/service';
import type {Application} from '../../../Application/List/types'; import type {Application} from '../../../Application/List/types';
import {BetaSchemaForm} from '@ant-design/pro-form'; import {BetaSchemaForm} from '@ant-design/pro-components';
import {convertJsonSchemaToColumns} from '@/utils/jsonSchemaUtils'; import {convertJsonSchemaToColumns} from '@/utils/jsonSchemaUtils';
import {Editor} from '@/components/Editor'; import {Editor} from '@/components/Editor';
import type {JsonNode} from '@/types/common'; import type {JsonNode} from '@/types/common';

View File

@ -1,5 +1,5 @@
import React, {useState, useEffect} from 'react'; import React, {useState, useEffect} from 'react';
import {PageContainer} from '@ant-design/pro-layout'; import {PageContainer} from '@ant-design/pro-components';
import {Button, message, Popconfirm, Select, Space, Modal, Tooltip} from 'antd'; import {Button, message, Popconfirm, Select, Space, Modal, Tooltip} from 'antd';
import {PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined, FullscreenOutlined} from '@ant-design/icons'; import {PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined, FullscreenOutlined} from '@ant-design/icons';
import {getDeploymentConfigPage, deleteDeploymentConfig, getDeployConfigTemplates} from './service'; import {getDeploymentConfigPage, deleteDeploymentConfig, getDeployConfigTemplates} from './service';

View File

@ -1,5 +1,5 @@
import React, {useState} from 'react'; import React, {useState} from 'react';
import {PageContainer} from '@ant-design/pro-layout'; import {PageContainer} from '@ant-design/pro-components';
import {Button, Space, Popconfirm, Tag, App, Select} from 'antd'; import {Button, Space, Popconfirm, Tag, App, Select} from 'antd';
import {PlusOutlined, EditOutlined, DeleteOutlined} from '@ant-design/icons'; import {PlusOutlined, EditOutlined, DeleteOutlined} from '@ant-design/icons';
import {getEnvironmentPage, deleteEnvironment} from './service'; import {getEnvironmentPage, deleteEnvironment} from './service';

View File

@ -1,164 +1,165 @@
.workflow-design { .workflow-design {
position: relative; position: relative;
height: calc(100vh - 184px); // 120px + 24px * 2 + 16px height: calc(100vh - 184px); // 120px + 24px * 2 + 16px
display: flex;
flex-direction: column;
background: #fff;
.header {
padding: 16px;
border-bottom: 1px solid #f0f0f0;
display: flex; display: flex;
flex-direction: column; justify-content: space-between;
background: #fff; align-items: center;
flex-shrink: 0;
.header { .back-button {
padding: 16px; margin-right: 16px;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
.back-button {
margin-right: 16px;
}
.actions {
.ant-space-compact {
margin-right: 8px;
}
}
} }
.content { .actions {
.ant-space-compact {
margin-right: 8px;
}
}
}
.content {
flex: 1;
min-height: 0;
display: flex;
padding: 16px;
gap: 16px;
overflow: hidden; // 防止外层出现滚动条
.sidebar {
width: 280px;
flex-shrink: 0;
border-radius: 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
overflow: hidden; // 防止sidebar本身出现滚动条
:global {
.ant-collapse {
border: none;
background: transparent;
flex: 1;
overflow-y: auto; // 只在折叠面板内部显示滚动条
.ant-collapse-item {
border-radius: 0;
.ant-collapse-header {
padding: 8px 16px;
}
.ant-collapse-content-box {
padding: 0;
}
}
}
}
.node-item {
padding: 8px 16px;
margin: 4px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
cursor: move;
transition: all 0.3s;
&:hover {
background: #f5f5f5;
border-color: #1890ff;
}
}
}
.main-area {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
.workflow-container {
flex: 1; flex: 1;
min-height: 0; position: relative;
display: flex; border: 1px solid #d9d9d9;
padding: 16px; border-radius: 4px;
gap: 16px; background: #f5f5f5;
overflow: hidden; // 防止外层出现滚动条 overflow: hidden;
.sidebar { .workflow-canvas {
width: 280px; width: 100%;
flex-shrink: 0; height: 100%;
border-radius: 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
overflow: hidden; // 防止sidebar本身出现滚动条
:global {
.ant-collapse {
border: none;
background: transparent;
flex: 1;
overflow-y: auto; // 只在折叠面板内部显示滚动条
.ant-collapse-item {
border-radius: 0;
.ant-collapse-header {
padding: 8px 16px;
}
.ant-collapse-content-box {
padding: 0;
}
}
}
}
.node-item {
padding: 8px 16px;
margin: 4px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
cursor: move;
transition: all 0.3s;
&:hover {
background: #f5f5f5;
border-color: #1890ff;
}
}
} }
.main-area { .minimap-container {
flex: 1; position: absolute;
display: flex; right: 20px;
flex-direction: column; bottom: 20px;
min-width: 0; width: var(--minimap-width);
height: var(--minimap-height);
.workflow-container { border: 1px solid #f0f0f0;
flex: 1; border-radius: 4px;
position: relative; background: #fff;
border: 1px solid #d9d9d9; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border-radius: 4px; z-index: 1;
background: #f5f5f5; overflow: hidden;
overflow: hidden;
.workflow-canvas {
width: 100%;
height: 100%;
}
.minimap-container {
position: absolute;
right: 20px;
bottom: 20px;
width: var(--minimap-width);
height: var(--minimap-height);
border: 1px solid #f0f0f0;
border-radius: 4px;
background: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
z-index: 1;
overflow: hidden;
}
}
} }
}
}
}
:global {
.node-selected {
> rect {
stroke: #1890ff;
stroke-width: 2px;
}
> path {
stroke: #1890ff;
stroke-width: 2px;
}
} }
:global { .x6-node-selected {
.node-selected { rect {
> rect { stroke: #1890ff;
stroke: #1890ff; stroke-width: 2px;
stroke-width: 2px; }
}
> path {
stroke: #1890ff;
stroke-width: 2px;
}
}
.x6-node-selected {
rect {
stroke: #1890ff;
stroke-width: 2px;
}
}
.x6-edge-selected {
path {
stroke: #1890ff;
stroke-width: 2px !important;
}
}
// 右键菜单样式
.x6-context-menu {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
padding: 4px 0;
min-width: 120px;
&-item {
padding: 5px 16px;
cursor: pointer;
user-select: none;
transition: all 0.3s;
color: rgba(0, 0, 0, 0.85);
font-size: 14px;
&:hover {
background: #f5f5f5;
}
}
}
} }
.x6-edge-selected {
path {
stroke: #1890ff;
stroke-width: 2px !important;
}
}
// 右键菜单样式
.x6-context-menu {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
padding: 4px 0;
min-width: 120px;
&-item {
padding: 5px 16px;
cursor: pointer;
user-select: none;
transition: all 0.3s;
color: rgba(0, 0, 0, 0.85);
font-size: 14px;
&:hover {
background: #f5f5f5;
}
}
}
}
} }

View File

@ -4,18 +4,19 @@ import type { ColumnsType } from 'antd/es/table';
import { getWorkflowInstances } from './service'; import { getWorkflowInstances } from './service';
import { WorkflowTemplateWithInstances } from './types'; import { WorkflowTemplateWithInstances } from './types';
import { Page } from '@/types/base'; import { Page } from '@/types/base';
import DetailModal from './components/DetailModal'; // DetailModal 暂时移除,因为数据结构不匹配 (需要 WorkflowHistoricalInstance 而不是 WorkflowTemplateWithInstances)
import HistoryModal from './components/HistoryModal'; import HistoryModal from './components/HistoryModal';
const WorkflowInstanceList: React.FC = () => { const WorkflowInstanceList: React.FC = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [data, setData] = useState<Page<WorkflowTemplateWithInstances[]>>(null); const [data, setData] = useState<Page<WorkflowTemplateWithInstances> | null>(null);
const [query, setQuery] = useState({ const [query, setQuery] = useState({
current: 1, current: 1,
pageSize: 10, pageSize: 10,
}); });
const [detailVisible, setDetailVisible] = useState(false); // 移除未使用的详情弹窗相关状态
const [selectedInstance, setSelectedInstance] = useState<WorkflowTemplateWithInstances>(); // const [detailVisible, setDetailVisible] = useState(false);
// const [selectedInstance, setSelectedInstance] = useState<WorkflowTemplateWithInstances>();
const [historyVisible, setHistoryVisible] = useState(false); const [historyVisible, setHistoryVisible] = useState(false);
const [selectedWorkflowDefinitionId, setSelectedWorkflowDefinitionId] = useState<number>(); const [selectedWorkflowDefinitionId, setSelectedWorkflowDefinitionId] = useState<number>();
@ -102,7 +103,7 @@ const WorkflowInstanceList: React.FC = () => {
<Card title="流程实例"> <Card title="流程实例">
<Table <Table
columns={columns} columns={columns}
dataSource={data.content} dataSource={data?.content || []}
loading={loading} loading={loading}
rowKey="id" rowKey="id"
scroll={{ x: 1200 }} scroll={{ x: 1200 }}
@ -112,17 +113,18 @@ const WorkflowInstanceList: React.FC = () => {
pagination={{ pagination={{
current: query.current, current: query.current,
pageSize: query.pageSize, pageSize: query.pageSize,
total: data?.length || 0, total: data?.totalElements || 0,
showSizeChanger: true, showSizeChanger: true,
showQuickJumper: true, showQuickJumper: true,
}} }}
onChange={handleTableChange} onChange={handleTableChange}
/> />
<DetailModal {/* DetailModal 暂时移除,因为数据结构不匹配 */}
{/* <DetailModal
visible={detailVisible} visible={detailVisible}
onCancel={() => setDetailVisible(false)} onCancel={() => setDetailVisible(false)}
instanceData={selectedInstance} instanceData={selectedInstance}
/> /> */}
<HistoryModal <HistoryModal
visible={historyVisible} visible={historyVisible}
onCancel={() => setHistoryVisible(false)} onCancel={() => setHistoryVisible(false)}

View File

@ -1,5 +1,5 @@
import React, {useState, useEffect} from 'react'; import React, {useState, useEffect} from 'react';
import {PageContainer} from '@ant-design/pro-layout'; import {PageContainer} from '@ant-design/pro-components';
import {Button, Card, Form, Input, InputNumber, Select, Switch, Tabs, Row, Col, message, ColorPicker} from 'antd'; import {Button, Card, Form, Input, InputNumber, Select, Switch, Tabs, Row, Col, message, ColorPicker} from 'antd';
import type {NodeDesignDataResponse} from './types'; import type {NodeDesignDataResponse} from './types';
import * as service from './service'; import * as service from './service';

View File

@ -1,4 +1,4 @@
import type {ProFormColumnsType} from '@ant-design/pro-form'; import type {ProFormColumnsType} from '@ant-design/pro-components';
import request from '@/utils/request'; import request from '@/utils/request';
interface EditorConfig { interface EditorConfig {