Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/flow-approval-presenter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @coding-flow/flow-approval-presenter

## 0.2.4

### Patch Changes

- @coding-flow/flow-core@0.2.4
- @coding-flow/flow-types@0.2.4

## 0.2.3

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/flow-approval-presenter/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coding-flow/flow-approval-presenter",
"version": "0.2.3",
"version": "0.2.4",
"description": "flow-engine approval presenter framework ",
"type": "module",
"sideEffects": [
Expand Down
2 changes: 2 additions & 0 deletions packages/flow-core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# @coding-flow/flow-core

## 0.2.4

## 0.2.3

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/flow-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coding-flow/flow-core",
"version": "0.2.3",
"version": "0.2.4",
"description": "flow-engine core lib framework ",
"type": "module",
"sideEffects": [
Expand Down
10 changes: 10 additions & 0 deletions packages/flow-design/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# @coding-flow/flow-design

## 0.2.4

### Patch Changes

- 优化流程导入与导出交互:导入文件后展示流程名称和编码,仅在编码冲突时确认创建新流程;设计器新增带编码校验和覆盖确认的重置流程入口,并调整导入导出按钮的名称、图标与布局。
- @coding-flow/flow-core@0.2.4
- @coding-flow/flow-icons@0.2.4
- @coding-flow/flow-pc-ui@0.2.4
- @coding-flow/flow-types@0.2.4

## 0.2.3

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/flow-design/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coding-flow/flow-design",
"version": "0.2.3",
"version": "0.2.4",
"description": "flow-engine design components ",
"type": "module",
"sideEffects": [
Expand Down
12 changes: 10 additions & 2 deletions packages/flow-design/src/api/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,15 @@ export const exportWorkflow = (id:string) => {
return httpClient.download('/api/cmd/workflow/export?id='+id,filename);
}

export const importWorkflow = (file:string) => {
return httpClient.post('/api/cmd/workflow/import',file);
export interface WorkflowImportRequest {
file: string;
mode: 'REPLACE' | 'INCREMENTAL';
}

export const importWorkflow = (request: WorkflowImportRequest) => {
return httpClient.post('/api/cmd/workflow/import',request);
}

export const workflowCodeExists = (code: string) => {
return httpClient.get('/api/cmd/workflow/exists', {code});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.notice {
margin-bottom: 16px;
}
59 changes: 59 additions & 0 deletions packages/flow-design/src/components/design-import/import-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
type JsonObject = Record<string, unknown>;

export interface WorkflowImportFileInfo {
code: string;
title: string;
}

const isJsonObject = (value: unknown): value is JsonObject => {
return typeof value === 'object' && value !== null && !Array.isArray(value);
};

const decodeBase64File = (file: string): string => {
const separatorIndex = file.indexOf(',');
const encoded = separatorIndex >= 0 ? file.substring(separatorIndex + 1) : file;
const binary = globalThis.atob(encoded);
const bytes = Uint8Array.from(binary, character => character.charCodeAt(0));
return new TextDecoder().decode(bytes);
};

/**
* 从V1迁移包或历史单流程文件中读取流程编码。
*/
export const readImportWorkflowInfo = (file: string): WorkflowImportFileInfo => {
let parsed: unknown;
try {
parsed = JSON.parse(decodeBase64File(file));
} catch {
throw new Error('无法解析导入文件,请确认文件为有效的流程JSON文件');
}
if (!isJsonObject(parsed)) {
throw new Error('导入文件内容必须为JSON对象');
}

const workflow = parsed.workflow;
const workflowData = isJsonObject(workflow) ? workflow : parsed;
const code = workflowData.code;
if (typeof code !== 'string' || code.trim().length === 0) {
throw new Error('导入文件中缺少流程编码');
}
const title = workflowData.title;
return {
code,
title: typeof title === 'string' && title.trim().length > 0 ? title : '未命名流程'
};
};

export const readImportWorkflowCode = (file: string): string => {
return readImportWorkflowInfo(file).code;
};

export const validateResetImportCode = (file: string, currentWorkflowCode: string): string => {
const importedCode = readImportWorkflowCode(file);
if (importedCode !== currentWorkflowCode) {
throw new Error(
`导入流程编码“${importedCode}”与当前流程编码“${currentWorkflowCode}”不一致,无法重置导入`
);
}
return importedCode;
};
143 changes: 128 additions & 15 deletions packages/flow-design/src/components/design-import/index.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,166 @@
import {Form, message, Modal} from "antd";
import {Alert, Descriptions, Form, message, Modal} from "antd";
import React from "react";
import {Upload} from "./upload";
import {importWorkflow} from "@/api/workflow";
import {importWorkflow, type WorkflowImportRequest, workflowCodeExists} from "@/api/workflow";
import {FlowMessageKey, FlowMessageRegistry} from "@coding-flow/flow-core";
import {
readImportWorkflowInfo,
validateResetImportCode,
type WorkflowImportFileInfo
} from "./import-file";
import styles from "./design_import.module.scss";

export type WorkflowImportScene = 'MANAGEMENT' | 'RESET';

interface DesignImportProps{
open: boolean;
onClose: () => void;
scene?: WorkflowImportScene;
currentWorkflowCode?: string;
onImported?: (workId: string) => void;
}

type ImportFormValues = Pick<WorkflowImportRequest, 'file'>;

export const DesignImport:React.FC<DesignImportProps> = (props) => {

const [form] = Form.useForm();
const [submitting, setSubmitting] = React.useState(false);
const [fileInfo, setFileInfo] = React.useState<WorkflowImportFileInfo>();
const resetImport = props.scene === 'RESET';

React.useEffect(() => {
if (!props.open) {
form.resetFields();
setFileInfo(undefined);
}
}, [form, props.open]);

const executeImport = async (values: ImportFormValues) => {
setSubmitting(true);
try {
const result = await importWorkflow({
file: values.file,
mode: resetImport ? 'REPLACE' : 'INCREMENTAL'
});
if (result.success) {
message.success(
FlowMessageRegistry.getInstance().get(FlowMessageKey.DESIGN_IMPORT_SUCCESS)
);
props.onImported?.(result.data as string);
props.onClose();
}
} finally {
setSubmitting(false);
}
};

const confirmImport = (values: ImportFormValues, info: WorkflowImportFileInfo) => {
Modal.confirm({
title: resetImport ? '确认重置导入' : '确认导入流程',
content: resetImport
? `重置导入将使用“${info.title}(${info.code})”完全覆盖当前流程的全部版本和配置。该操作不可撤销,确认继续吗?`
: `流程编码“${info.code}”已存在,系统将自动生成新编码并创建一条独立流程。确认继续吗?`,
okText: resetImport ? '确认重置' : '确认导入',
cancelText: '取消',
okButtonProps: resetImport ? {danger: true} : undefined,
onOk: () => executeImport(values)
});
};

const submitImport = async (values: ImportFormValues) => {
let info: WorkflowImportFileInfo;
try {
info = readImportWorkflowInfo(values.file);
if (resetImport) {
validateResetImportCode(values.file, props.currentWorkflowCode || '');
}
} catch (error) {
message.error(error instanceof Error ? error.message : '无法解析导入流程信息');
return;
}

if (resetImport) {
confirmImport(values, info);
return;
}

setSubmitting(true);
try {
const existsResult = await workflowCodeExists(info.code);
if (existsResult.data === true) {
confirmImport(values, info);
return;
}
} finally {
setSubmitting(false);
}
await executeImport(values);
};

const handleFileChange = (changedValues: Partial<ImportFormValues>) => {
if (changedValues.file === undefined) {
return;
}
if (!changedValues.file) {
setFileInfo(undefined);
return;
}
try {
setFileInfo(readImportWorkflowInfo(changedValues.file));
} catch (error) {
setFileInfo(undefined);
message.error(error instanceof Error ? error.message : '无法解析导入流程信息');
}
};

return (
<Modal
title={"流程导入"}
title={resetImport ? '重置导入' : '流程导入'}
open={props.open}
onCancel={props.onClose}
destroyOnHidden={true}
okText={resetImport ? '重置导入' : '导入'}
confirmLoading={submitting}
onOk={()=>{
form.submit();
}}
>
<Alert
className={styles.notice}
type={resetImport ? 'warning' : 'info'}
showIcon={true}
message={resetImport ? '重置导入说明' : '流程导入说明'}
description={resetImport
? '仅允许导入与当前流程编码一致的文件。确认后,当前流程的全部版本和配置将被导入数据覆盖。'
: '系统会检测导入流程的编码。编码未占用时保留原编码;编码已存在时自动生成新编码,并创建一条独立流程。'}
/>
<Form
form={form}
layout="vertical"
onFinish={(values)=>{
importWorkflow(values).then((res)=>{
if(res.success){
message.success(
FlowMessageRegistry.getInstance().get(FlowMessageKey.DESIGN_IMPORT_SUCCESS)
);
props.onClose();
}
})
}}
onFinish={submitImport}
onValuesChange={handleFileChange}
>
<Form.Item
name="file"
label={"流程设计文件"}
help={"请选择导出的设计文件,文件格式为json格式"}
rules={[{required: true, message: '请选择流程设计文件'}]}
>
<Upload/>
</Form.Item>

{fileInfo && (
<Descriptions
size="small"
bordered={true}
column={1}
items={[
{key: 'title', label: '流程名称', children: fileInfo.title},
{key: 'code', label: '流程编码', children: fileInfo.code}
]}
/>
)}
</Form>
</Modal>
)
}
}
7 changes: 5 additions & 2 deletions packages/flow-design/src/components/design-import/upload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ export const Upload:React.FC<UploadProps> = (props)=>{
<AntdUpload
maxCount={1}
accept=".json,application/json"
onRemove={() => {
props.onChange?.('');
return true;
}}
customRequest={async ({file, onSuccess}) => {
const currentFile = file as RcFile;
const base64 = await fileToBase64(currentFile);
props.onChange?.(base64);
const filename = currentFile.name;
const url = URL.createObjectURL(currentFile);
// @ts-ignore
onSuccess({
Expand All @@ -39,4 +42,4 @@ export const Upload:React.FC<UploadProps> = (props)=>{
<Button>选择文件</Button>
</AntdUpload>
)
}
}
Loading
Loading