diff --git a/packages/flow-approval-presenter/CHANGELOG.md b/packages/flow-approval-presenter/CHANGELOG.md index e70a35d..5c9299f 100644 --- a/packages/flow-approval-presenter/CHANGELOG.md +++ b/packages/flow-approval-presenter/CHANGELOG.md @@ -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 diff --git a/packages/flow-approval-presenter/package.json b/packages/flow-approval-presenter/package.json index 1d926c0..20ba118 100644 --- a/packages/flow-approval-presenter/package.json +++ b/packages/flow-approval-presenter/package.json @@ -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": [ diff --git a/packages/flow-core/CHANGELOG.md b/packages/flow-core/CHANGELOG.md index 0e4da67..1c07fb1 100644 --- a/packages/flow-core/CHANGELOG.md +++ b/packages/flow-core/CHANGELOG.md @@ -1,5 +1,7 @@ # @coding-flow/flow-core +## 0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/flow-core/package.json b/packages/flow-core/package.json index 5892865..7d9e7ab 100644 --- a/packages/flow-core/package.json +++ b/packages/flow-core/package.json @@ -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": [ diff --git a/packages/flow-design/CHANGELOG.md b/packages/flow-design/CHANGELOG.md index 336a91b..96fc255 100644 --- a/packages/flow-design/CHANGELOG.md +++ b/packages/flow-design/CHANGELOG.md @@ -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 diff --git a/packages/flow-design/package.json b/packages/flow-design/package.json index 84cc3d5..3b59d77 100644 --- a/packages/flow-design/package.json +++ b/packages/flow-design/package.json @@ -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": [ diff --git a/packages/flow-design/src/api/workflow.ts b/packages/flow-design/src/api/workflow.ts index 0cb8b78..5f70c23 100644 --- a/packages/flow-design/src/api/workflow.ts +++ b/packages/flow-design/src/api/workflow.ts @@ -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}); +} diff --git a/packages/flow-design/src/components/design-import/design_import.module.scss b/packages/flow-design/src/components/design-import/design_import.module.scss new file mode 100644 index 0000000..9466c56 --- /dev/null +++ b/packages/flow-design/src/components/design-import/design_import.module.scss @@ -0,0 +1,3 @@ +.notice { + margin-bottom: 16px; +} diff --git a/packages/flow-design/src/components/design-import/import-file.ts b/packages/flow-design/src/components/design-import/import-file.ts new file mode 100644 index 0000000..ffdd968 --- /dev/null +++ b/packages/flow-design/src/components/design-import/import-file.ts @@ -0,0 +1,59 @@ +type JsonObject = Record; + +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; +}; diff --git a/packages/flow-design/src/components/design-import/index.tsx b/packages/flow-design/src/components/design-import/index.tsx index a9ba9e8..e2a774e 100644 --- a/packages/flow-design/src/components/design-import/index.tsx +++ b/packages/flow-design/src/components/design-import/index.tsx @@ -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; + export const DesignImport:React.FC = (props) => { const [form] = Form.useForm(); + const [submitting, setSubmitting] = React.useState(false); + const [fileInfo, setFileInfo] = React.useState(); + 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) => { + 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 ( { form.submit(); }} > +
{ - importWorkflow(values).then((res)=>{ - if(res.success){ - message.success( - FlowMessageRegistry.getInstance().get(FlowMessageKey.DESIGN_IMPORT_SUCCESS) - ); - props.onClose(); - } - }) - }} + onFinish={submitImport} + onValuesChange={handleFileChange} > - + {fileInfo && ( + + )}
) -} \ No newline at end of file +} diff --git a/packages/flow-design/src/components/design-import/upload.tsx b/packages/flow-design/src/components/design-import/upload.tsx index 185493c..dc3d6b4 100644 --- a/packages/flow-design/src/components/design-import/upload.tsx +++ b/packages/flow-design/src/components/design-import/upload.tsx @@ -22,11 +22,14 @@ export const Upload:React.FC = (props)=>{ { + 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({ @@ -39,4 +42,4 @@ export const Upload:React.FC = (props)=>{ ) -} \ No newline at end of file +} diff --git a/packages/flow-design/src/components/design-panel/layout/header.tsx b/packages/flow-design/src/components/design-panel/layout/header.tsx index e47149c..bf0390b 100644 --- a/packages/flow-design/src/components/design-panel/layout/header.tsx +++ b/packages/flow-design/src/components/design-panel/layout/header.tsx @@ -1,10 +1,11 @@ import React from "react"; -import {Button, Form, Input, message, Popover, Space, Tabs} from "antd"; +import {Button, Divider, Form, Input, message, Popover, Space, Tabs} from "antd"; import {LayoutHeaderHeight, TabPanelType} from "../types"; import {useDesignContext} from "../hooks/use-design-context"; -import {CloseOutlined, DownloadOutlined, SaveOutlined} from "@ant-design/icons"; +import {CloseOutlined, ExportOutlined, ImportOutlined, SaveOutlined} from "@ant-design/icons"; import {EventBus, FlowMessageKey, FlowMessageRegistry} from "@coding-flow/flow-core"; import {exportWorkflow} from "@/api/workflow"; +import {DesignImport} from "@/components/design-import"; const Left = () => { return ( @@ -101,12 +102,27 @@ const SaveAsButton = () => { const Right = () => { const {state,context} = useDesignContext(); + const [resetImportVisible, setResetImportVisible] = React.useState(false); return ( - + <> + + + + - - + + { + context.getPresenter().loadDesign(workId); + }} + onClose={() => { + setResetImportVisible(false); + }} + /> + ) } @@ -176,4 +198,4 @@ export const Header = () => { }} /> ) -} \ No newline at end of file +} diff --git a/packages/flow-design/tests/components/design-import/import-file.test.ts b/packages/flow-design/tests/components/design-import/import-file.test.ts new file mode 100644 index 0000000..6072d81 --- /dev/null +++ b/packages/flow-design/tests/components/design-import/import-file.test.ts @@ -0,0 +1,73 @@ +import {describe, expect, it} from '@rstest/core'; +import { + readImportWorkflowCode, + readImportWorkflowInfo, + validateResetImportCode +} from '@/components/design-import/import-file'; + +const toDataUrl = (value: unknown): string => { + const json = JSON.stringify(value); + const bytes = new TextEncoder().encode(json); + let binary = ''; + bytes.forEach(byte => { + binary += String.fromCharCode(byte); + }); + return `data:application/json;base64,${globalThis.btoa(binary)}`; +}; + +describe('流程导入文件编码校验', () => { + it('读取包含多版本和脚本资源的V1迁移包编码', () => { + const file = toDataUrl({ + format: 'flow-engine-workflow', + schemaVersion: 1, + workflow: { + code: 'expense-approval', + title: '费用审批流程', + currentVersion: 'v3.0' + }, + versions: [ + {versionName: 'v1.0', nodes: [{id: 'start'}, {id: 'approval-a'}]}, + {versionName: 'v2.0', nodes: [{id: 'start'}, {id: 'condition'}, {id: 'approval-b'}]}, + {versionName: 'v3.0', nodes: [{id: 'start'}, {id: 'parallel'}, {id: 'end'}]} + ], + groovyScripts: { + 'script-condition': 'def run(request) { return true }', + 'script-operator': 'def run(request) { return [1L] }' + } + }); + + expect(readImportWorkflowInfo(file)).toEqual({ + code: 'expense-approval', + title: '费用审批流程' + }); + expect(readImportWorkflowCode(file)).toBe('expense-approval'); + expect(validateResetImportCode(file, 'expense-approval')).toBe('expense-approval'); + }); + + it('兼容读取历史单流程文件编码', () => { + const file = toDataUrl({ + id: 'workflow-1', + code: 'legacy-leave', + title: '历史请假流程', + nodes: [{id: 'start'}, {id: 'approval'}, {id: 'end'}] + }); + + expect(readImportWorkflowInfo(file)).toEqual({ + code: 'legacy-leave', + title: '历史请假流程' + }); + }); + + it('拒绝使用不同流程编码执行重置导入', () => { + const file = toDataUrl({ + format: 'flow-engine-workflow', + schemaVersion: 1, + workflow: {code: 'source-workflow'}, + versions: [{versionName: 'v1.0'}], + groovyScripts: {} + }); + + expect(() => validateResetImportCode(file, 'current-workflow')) + .toThrow('导入流程编码“source-workflow”与当前流程编码“current-workflow”不一致'); + }); +}); diff --git a/packages/flow-icons/CHANGELOG.md b/packages/flow-icons/CHANGELOG.md index 99cb22c..ba87852 100644 --- a/packages/flow-icons/CHANGELOG.md +++ b/packages/flow-icons/CHANGELOG.md @@ -1,5 +1,11 @@ # @coding-flow/flow-icons +## 0.2.4 + +### Patch Changes + +- @coding-flow/flow-core@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/flow-icons/package.json b/packages/flow-icons/package.json index 8cf252c..a0f3e37 100644 --- a/packages/flow-icons/package.json +++ b/packages/flow-icons/package.json @@ -1,6 +1,6 @@ { "name": "@coding-flow/flow-icons", - "version": "0.2.3", + "version": "0.2.4", "description": "flow-engine icons ", "type": "module", "sideEffects": [ diff --git a/packages/flow-mobile/flow-mobile-approval/CHANGELOG.md b/packages/flow-mobile/flow-mobile-approval/CHANGELOG.md index 6558d3f..02d8fef 100644 --- a/packages/flow-mobile/flow-mobile-approval/CHANGELOG.md +++ b/packages/flow-mobile/flow-mobile-approval/CHANGELOG.md @@ -1,5 +1,16 @@ # @coding-flow/flow-mobile-approval +## 0.2.4 + +### Patch Changes + +- @coding-flow/flow-approval-presenter@0.2.4 +- @coding-flow/flow-core@0.2.4 +- @coding-flow/flow-icons@0.2.4 +- @coding-flow/flow-mobile-form@0.2.4 +- @coding-flow/flow-mobile-ui@0.2.4 +- @coding-flow/flow-types@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/flow-mobile/flow-mobile-approval/package.json b/packages/flow-mobile/flow-mobile-approval/package.json index 0e63530..f963a16 100644 --- a/packages/flow-mobile/flow-mobile-approval/package.json +++ b/packages/flow-mobile/flow-mobile-approval/package.json @@ -1,6 +1,6 @@ { "name": "@coding-flow/flow-mobile-approval", - "version": "0.2.3", + "version": "0.2.4", "description": "flow-engine pc mobile approval components", "type": "module", "sideEffects": [ diff --git a/packages/flow-mobile/flow-mobile-form/CHANGELOG.md b/packages/flow-mobile/flow-mobile-form/CHANGELOG.md index 10d44c9..6010720 100644 --- a/packages/flow-mobile/flow-mobile-form/CHANGELOG.md +++ b/packages/flow-mobile/flow-mobile-form/CHANGELOG.md @@ -1,5 +1,12 @@ # @coding-flow/flow-mobile-form +## 0.2.4 + +### Patch Changes + +- @coding-flow/flow-core@0.2.4 +- @coding-flow/flow-types@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/flow-mobile/flow-mobile-form/package.json b/packages/flow-mobile/flow-mobile-form/package.json index a8c3ccf..00a343a 100644 --- a/packages/flow-mobile/flow-mobile-form/package.json +++ b/packages/flow-mobile/flow-mobile-form/package.json @@ -1,6 +1,6 @@ { "name": "@coding-flow/flow-mobile-form", - "version": "0.2.3", + "version": "0.2.4", "description": "flow-engine mobile form engine", "type": "module", "sideEffects": [ diff --git a/packages/flow-mobile/flow-mobile-ui/CHANGELOG.md b/packages/flow-mobile/flow-mobile-ui/CHANGELOG.md index 91c11c9..41d123a 100644 --- a/packages/flow-mobile/flow-mobile-ui/CHANGELOG.md +++ b/packages/flow-mobile/flow-mobile-ui/CHANGELOG.md @@ -1,5 +1,11 @@ # @coding-flow/flow-mobile-ui +## 0.2.4 + +### Patch Changes + +- @coding-flow/flow-core@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/flow-mobile/flow-mobile-ui/package.json b/packages/flow-mobile/flow-mobile-ui/package.json index e23474a..40097c1 100644 --- a/packages/flow-mobile/flow-mobile-ui/package.json +++ b/packages/flow-mobile/flow-mobile-ui/package.json @@ -1,6 +1,6 @@ { "name": "@coding-flow/flow-mobile-ui", - "version": "0.2.3", + "version": "0.2.4", "description": "flow-engine mobile ui components", "type": "module", "sideEffects": [ diff --git a/packages/flow-pc/flow-pc-approval/CHANGELOG.md b/packages/flow-pc/flow-pc-approval/CHANGELOG.md index 44c37be..fb258bf 100644 --- a/packages/flow-pc/flow-pc-approval/CHANGELOG.md +++ b/packages/flow-pc/flow-pc-approval/CHANGELOG.md @@ -1,5 +1,16 @@ # @coding-flow/flow-pc-approval +## 0.2.4 + +### Patch Changes + +- @coding-flow/flow-approval-presenter@0.2.4 +- @coding-flow/flow-core@0.2.4 +- @coding-flow/flow-icons@0.2.4 +- @coding-flow/flow-pc-form@0.2.4 +- @coding-flow/flow-pc-ui@0.2.4 +- @coding-flow/flow-types@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/flow-pc/flow-pc-approval/package.json b/packages/flow-pc/flow-pc-approval/package.json index b8f1302..6937121 100644 --- a/packages/flow-pc/flow-pc-approval/package.json +++ b/packages/flow-pc/flow-pc-approval/package.json @@ -1,6 +1,6 @@ { "name": "@coding-flow/flow-pc-approval", - "version": "0.2.3", + "version": "0.2.4", "description": "flow-engine pc form approval components", "type": "module", "sideEffects": [ diff --git a/packages/flow-pc/flow-pc-form/CHANGELOG.md b/packages/flow-pc/flow-pc-form/CHANGELOG.md index 6a9c78e..242dbe7 100644 --- a/packages/flow-pc/flow-pc-form/CHANGELOG.md +++ b/packages/flow-pc/flow-pc-form/CHANGELOG.md @@ -1,5 +1,12 @@ # @coding-flow/flow-pc-form +## 0.2.4 + +### Patch Changes + +- @coding-flow/flow-core@0.2.4 +- @coding-flow/flow-types@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/flow-pc/flow-pc-form/package.json b/packages/flow-pc/flow-pc-form/package.json index f4e5468..d2c7b21 100644 --- a/packages/flow-pc/flow-pc-form/package.json +++ b/packages/flow-pc/flow-pc-form/package.json @@ -1,6 +1,6 @@ { "name": "@coding-flow/flow-pc-form", - "version": "0.2.3", + "version": "0.2.4", "description": "flow-engine pc form engine", "type": "module", "sideEffects": [ diff --git a/packages/flow-pc/flow-pc-ui/CHANGELOG.md b/packages/flow-pc/flow-pc-ui/CHANGELOG.md index ba78e93..643e602 100644 --- a/packages/flow-pc/flow-pc-ui/CHANGELOG.md +++ b/packages/flow-pc/flow-pc-ui/CHANGELOG.md @@ -1,5 +1,11 @@ # @coding-flow/flow-pc-ui +## 0.2.4 + +### Patch Changes + +- @coding-flow/flow-core@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/flow-pc/flow-pc-ui/package.json b/packages/flow-pc/flow-pc-ui/package.json index 5760e06..bb48db1 100644 --- a/packages/flow-pc/flow-pc-ui/package.json +++ b/packages/flow-pc/flow-pc-ui/package.json @@ -1,6 +1,6 @@ { "name": "@coding-flow/flow-pc-ui", - "version": "0.2.3", + "version": "0.2.4", "description": "flow-engine pc ui components", "type": "module", "sideEffects": [ diff --git a/packages/flow-types/CHANGELOG.md b/packages/flow-types/CHANGELOG.md index 104b503..7fdb601 100644 --- a/packages/flow-types/CHANGELOG.md +++ b/packages/flow-types/CHANGELOG.md @@ -1,5 +1,11 @@ # @coding-flow/flow-types +## 0.2.4 + +### Patch Changes + +- @coding-flow/flow-core@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/flow-types/package.json b/packages/flow-types/package.json index c083e72..bef12dc 100644 --- a/packages/flow-types/package.json +++ b/packages/flow-types/package.json @@ -1,6 +1,6 @@ { "name": "@coding-flow/flow-types", - "version": "0.2.3", + "version": "0.2.4", "description": "flow-engine types", "type": "module", "sideEffects": [