222
schangxiang@126.com
2025-04-30 9bec4dcae002f36aa23231da11cb03a156b40110
222
已添加252个文件
已修改86个文件
55348 ■■■■ 文件已修改
PipeLineLems/server/src/CMS.Plugin.PipeLineLems.Application.Contracts/Dtos/WorkPlan/MesOrderResponse.cs 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/server/src/CMS.Plugin.PipeLineLems.Application.Contracts/Dtos/WorkPlan/WorkPlanInput.cs 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/server/src/CMS.Plugin.PipeLineLems.Application.Contracts/Services/IMesAppService.cs 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/server/src/CMS.Plugin.PipeLineLems.Application/Implements/MesAppService.cs 378 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/server/src/CMS.Plugin.PipeLineLems/CMSPluginEntry.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/server/src/CMS.Plugin.PipeLineLems/Controller/WorkPlanController.cs 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/.build 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/.build.prod 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/.env 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/.env.development 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/.env.production 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/.gitignore 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/README.md 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/app/build.sh 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/app/build/.npmrc 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/app/build/package.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/app/controllers/home.go 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/app/dto/config.go 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/app/go.mod 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/app/go.sum 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/app/main.go 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/app/routes/routes.go 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/app/service/home_service.go 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/app/utils/index.go 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/checkout.sh 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/.env 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/.env.development 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/.vitepress/config.mts 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/.vitepress/theme/index.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/.vitepress/theme/style.css 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/Layout/Layout.scss 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/Layout/Layout.tsx 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/api-examples.md 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/assets/image/table.png 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/BaseContent.md 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/BaseDialog.md 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/BaseDrawer.md 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/BaseInput.md 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/ConfirmBox.md 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/Container.md 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/Content.md 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/DialogPreView.md 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/Dyform.md 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/Empty.md 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/Flow.md 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/Icon.md 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/IconButton.md 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/Pdf.md 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/PreviewDialog.md 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/Search.md 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/SearchInput.md 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/Tab.md 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/Table.md 248 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/TableFilter.md 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/Tag.md 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/TdButton.md 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/Text.md 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/Title.md 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/TouchScale.md 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/Upload.md 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/Variable.md 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/components/index.md 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/index.md 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/markdown-examples.md 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/postcss.confg.mts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/public/lems.png 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/public/tt.png 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/quick-start.md 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/docs/vite.config.js 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/env.d.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/index.html 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/package-lock.json 22689 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/package.json 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/public/language/Common.en-US.json 2147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/public/language/MesSuite.en-US.json 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/public/language/ProcessManagement.en-US.json 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/public/language/ProductManagement.en-US.json 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/public/language/lmes.en-US.json 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/public/mitm/mitm.html 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/public/mitm/sw.js 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/pull.sh 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/release.sh 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/script/ZipAFolder.js 174 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/script/build.js 158 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/script/filterExternal.ts 318 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/script/generateMenu.js 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/script/plugins/vite-plugin-development-filter.ts 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/script/plugins/vite-plugin-image-filter.ts 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/script/plugins/vite-plugin-widget-provider.ts 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/script/replace.ts 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/script/tag.js 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/App.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/api/common-enum.ts 137 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/api/file.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/api/index.ts 239 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/api/logic-flow.ts 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/api/period-setting.ts 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/api/period-setting.type.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/assets/images/add-p.png 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/assets/images/icon_process.png 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/.npmrc 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BarcodeAnalysisDialog/BarcodeAnalysisDialog.module.scss 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BarcodeAnalysisDialog/BarcodeAnalysisDialog.tsx 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BarcodeGenerateDialog/BarcodeGenerateDialog.module.scss 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BarcodeGenerateDialog/BarcodeGenerateDialog.tsx 190 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseConfigProvider/BaseConfigProvider.tsx 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseContent/BaseContent.module.scss 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseContent/BaseContent.tsx 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseDialog/BaseDialog.scss 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseDialog/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseDrawer/BaseDrawer.tsx 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseInput/BaseInput.module.scss 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseInput/BaseInput.tsx 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseTable/AutoTooltip.tsx 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseTable/BaseTable.d.ts 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseTable/BaseTable.module.scss 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseTable/BaseTable.tsx 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseTable/Props.ts 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseTable/useColumns.tsx 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseTable/useEvent.ts 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseTable/useHook.ts 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseTable/useState.ts 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/BaseTable/useUtils.ts 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Button/index.vue 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/CanvasTableS2/CanvasTableS2.scss 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/CanvasTableS2/CanvasTableS2.tsx 272 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/CommonTable/CommonTable.module.scss 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/CommonTable/CommonTable.tsx 589 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/ConfirmBox/ConfirmBox.module.scss 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/ConfirmBox/ConfirmBox.tsx 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Container/Container.module.scss 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Container/Container.tsx 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Content/Content.tsx 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/CsTree/CsTree.scss 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/CsTree/CsTree.tsx 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/DateTimePickRange/DateTimePickRange.tsx 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/DialogPreView/Chart.tsx 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/DialogPreView/DialogPreView.module.scss 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/DialogPreView/DialogPreView.tsx 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/DialogPreView/Picture.tsx 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/DyDatePicker/DyDatePicker.tsx 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/DyForm/DyForm.d.ts 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/DyForm/DyForm.module.scss 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/DyForm/DyForm.tsx 317 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/EllipsisTooltip/EllipsisTooltip.vue 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Flow/Flow.module.scss 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Flow/Flow.tsx 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/FlowContextDialog/FlowContextDialog.module.scss 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/FlowContextDialog/FlowContextDialog.tsx 199 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/FlowContextDialog/config.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/G6Flow.module.scss 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/G6Flow.tsx 295 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Canvas/Canvas.module.scss 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Canvas/Canvas.tsx 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/ConditionDialog/ConditionDialog.module.scss 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/ConditionDialog/ConditionDialog.tsx 253 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/EdgeDrawer/EdgeDrawer.module.scss 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/EdgeDrawer/EdgeDrawer.tsx 492 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Menu/Menu.module.scss 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Menu/Menu.tsx 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Menu/index.scss 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Models/CreateFormItem.ts 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Models/WidgetTypeByEnum.ts 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/NodeDialog/NodeDialog.module.scss 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/NodeDialog/NodeDialog.tsx 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/NodeDrawer/NodeDrawer.module.scss 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/NodeDrawer/NodeDrawer.tsx 283 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Nodes/EndNode.tsx 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Nodes/Node.tsx 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Nodes/OrdinaryNode.tsx 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Nodes/StartNode.tsx 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Nodes/index.module.scss 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Nodes/index.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Renderer/Renderer.module.scss 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Renderer/Renderer.tsx 332 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/ToolBar/ToolBar.module.scss 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/ToolBar/ToolBar.tsx 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/ToolBar/ToolBarDefine.tsx 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Tools/Tools.module.scss 159 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Tools/Tools.tsx 247 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Tooltip/Tooltip.scss 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/components/Tooltip/Tooltip.tsx 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/core/Core.ts 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/core/GraphEvent.ts 235 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/core/behavior.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/core/enum.ts 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/core/store.ts 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/core/transformHelp.ts 527 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/G6Flow/type/index.d.ts 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Icon/Icon.tsx 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/IconButton/IconButton.module.scss 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/IconButton/IconButton.tsx 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Image/Image.tsx 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/ImportProcessDialog/ImportProcessDialog.module.scss 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/ImportProcessDialog/ImportProcessDialog.tsx 229 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Input/Input.tsx 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LabelDialog/LabelDialog.module.scss 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LabelDialog/LabelDialog.tsx 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LabelDialog/hook.ts 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/LogicFlow.module.scss 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/LogicFlow.tsx 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Canvas/Canvas.module.scss 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Canvas/Canvas.tsx 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/EdgeDrawer/EdgeDrawer.module.scss 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/EdgeDrawer/EdgeDrawer.tsx 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Edges/Curve.ts 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/NodeDrawer/NodeDrawer.module.scss 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/NodeDrawer/NodeDrawer.tsx 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/BaseNode.tsx 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/EndNode.tsx 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/FlowNode.tsx 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/Node.tsx 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/OrdinaryNode.tsx 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/StartNode.tsx 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/BusinessNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/DetermineProcessNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/DuplicateCodeDetectionNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/InboundInitializeNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/LocalQualificationJudgmentNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/MaterialAssociationNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/MissingProcessDetectionNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/OutboundInitializeNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/PLCQualificationJudgmentNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/ParameterCollectNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/ParameterSaveNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/ProductStateDetectionNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/TrayAssociationNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/VariableMonitorNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/VariableReadNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/VariableWriteNode.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/index.module.scss 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Nodes/index.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Renderer/Renderer.module.scss 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Renderer/Renderer.tsx 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/components/Theme/Theme.tsx 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/core/bak.ts 342 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/core/dagre.ts 343 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/core/enum.ts 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/core/event.ts 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/core/layout.ts 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/core/store.ts 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/core/transformHelp.ts 265 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/LogicFlow/type/index.d.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Menu/index.vue 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/MyPages/index.vue 282 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Pdf/index.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/PrintDialog/PrintDialog.module.scss 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/PrintDialog/PrintDialog.tsx 171 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/ProcessRouterDialog/ProcessRouterDialog.module.scss 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/ProcessRouterDialog/ProcessRouterDialog.tsx 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/ProcessRouterDialog/api.ts 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/ProcessRouterDialog/hook.ts 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/ProcessRoutes/ProcessRoutes.module.scss 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/ProcessRoutes/ProcessRoutes.tsx 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/ProductSelectDialog/ProductSelectDialog.module.scss 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/ProductSelectDialog/ProductSelectDialog.tsx 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/ProductSelectDialog/hook.ts 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/ProjectConfig/ProjectConfig.module.scss 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/ProjectConfig/ProjectConfig.tsx 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Radio/Radio.module.scss 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Radio/Radio.tsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/RelationFlowDialog/RelationFlowDialog.module.scss 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/RelationFlowDialog/RelationFlowDialog.tsx 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/RelationFlowDialog/app.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/RelationFlowDialog/enum.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Search/Search.tsx 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/SearchSelect/Option.tsx 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/SearchSelect/SearchSelect.tsx 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/SearchSelect/Select.module.scss 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Segment/segment.module.scss 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Segment/segment.tsx 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Select/Option.tsx 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Select/Select.module.scss 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Select/Select.tsx 43 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Select/Select1.tsx 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/SelectInput/SelectInput.tsx 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Setting/Setting.tsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/SvgIcon/SvgIcon.module.scss 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/SvgIcon/SvgIcon.tsx 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Tab/Tab.tsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Tab/TabPane.tsx 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Table/index.module.scss 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/TableArray/TableArray.module.scss 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/TableArray/TableArray.tsx 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/TableFilter/TableFilter.module.scss 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/TableFilter/TableFilter.tsx 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Tag/Tag.tsx 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/TdButton/TdButton.module.scss 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/TdButton/TdButton.tsx 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Text/Text.module.scss 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Text/Text.tsx 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Title/Title.module.scss 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Title/Title.tsx 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Variable/Variable.module.scss 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/Variable/Variable.tsx 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/WorkSectionDialog/WorkSectionDialog.module.scss 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/WorkSectionDialog/WorkSectionDialog.tsx 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/WorkSectionDialog/hook.ts 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/WorkSectionParams/WorkSectionParams.module.scss 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/WorkSectionParams/WorkSectionParams.tsx 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/WorkSectionParams/api.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/WorkStationDialog/WorkStationDialog.module.scss 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/WorkStationDialog/WorkStationDialog.tsx 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/WorkStationDialog/hook.ts 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/index.d.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/index.ts 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/package.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/components/vue3-context-menu/ContextMenuItem.vue 363 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/hooks/Dialog.ts 447 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/hooks/File.ts 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/hooks/drawer.ts 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/index.d.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/libs/Base/Base.ts 89 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/libs/DownloadFile/DownLoadFile.ts 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/libs/Permission/Permission.d.ts 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/libs/Permission/Permission.ts 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/libs/Provider/Provider.ts 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/libs/Socket/Socket.ts 267 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/libs/Socket/index.ts 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/libs/Socket/toast.ts 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/libs/Store/Store.d.ts 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/libs/Store/Store.ts 385 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/libs/Store/globalState.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/libs/system-enum.ts 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/main.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/provider/index.vue 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/utils/client.ts 287 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/utils/columnConfig.ts 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/utils/index.ts 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/utils/request.ts 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/utils/storage.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/utils/util.ts 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/widgets/MyPluginName/Views/MyPluginName.tsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/src/widgets/MyPluginName/index.ts 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/vite.build.config.ts 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/vite.config.ts 135 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/vite.lib.config.ts 74 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/web/yarn.lock 6459 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PipeLineLems/server/src/CMS.Plugin.PipeLineLems.Application.Contracts/Dtos/WorkPlan/MesOrderResponse.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace CMS.Plugin.PipeLineLems.Application.Contracts.Dtos.WorkPlan
{
    public class MesOrderResponse
    {
        [JsonPropertyName("code")]
        public string Code { get; set; }
        [JsonPropertyName("data")]
        public object Data { get; set; }
        [JsonPropertyName("fail")]
        public bool Fail { get; set; }
        [JsonPropertyName("mesg")]
        public string Mesg { get; set; }
        [JsonPropertyName("success")]
        public bool Success { get; set; }
        [JsonPropertyName("time")]
        public DateTime Time { get; set; }
    }
}
PipeLineLems/server/src/CMS.Plugin.PipeLineLems.Application.Contracts/Dtos/WorkPlan/WorkPlanInput.cs
@@ -17,6 +17,12 @@
        public string TaskCode { get; set; }
        /// <summary>
        /// åŽŸæ–™ç¼–å·
        /// </summary>
        public string OrgMaterialCode { get; set; }
        /// <summary>
        /// å·¥åŽ‚ä»£ç ï¼ˆç”Ÿäº§å·¥åŽ‚æ ‡è¯†ï¼‰
        /// </summary>
        public string FactoryCode { get; set; }
@@ -52,12 +58,12 @@
        public string PipeFittingCode { get; set; }
        /// <summary>
        /// é¢„序号(预处理序列号)
        /// é¡ºåºå·ï¼ˆé¢„处理序列号)
        /// </summary>
        public string PreSerialNumber { get; set; }
        /// <summary>
        /// èµ„料标识(数据区分标识)
        /// åŽŸæ–™æ ‡è¯†ï¼ˆæ•°æ®åŒºåˆ†æ ‡è¯†ï¼‰
        /// </summary>
        public string DataIdentifier { get; set; }
PipeLineLems/server/src/CMS.Plugin.PipeLineLems.Application.Contracts/Services/IMesAppService.cs
@@ -1,4 +1,5 @@
using CMS.Plugin.PipeLineLems.Application.Contracts.Dtos.MyTestEntityNames;
using CMS.Plugin.PipeLineLems.Application.Contracts.Dtos.WorkPlan;
using Volo.Abp.Application.Services;
namespace CMS.Plugin.PipeLineLems.Application.Contracts.Services;
@@ -6,14 +7,7 @@
/// <summary>
/// MES应用服务
/// </summary>
public interface IMesAppService : ICrudAppService<MyTestEntityNameDto, Guid, GetMyTestEntityNamesInput, MyTestEntityNameCreateDto, MyTestEntityNameUpdateDto>
public interface IMesAppService
{
    /// <summary>
    /// Clones the asynchronous.
    /// </summary>
    /// <param name="ids">The ids.</param>
    /// <returns></returns>
    Task<List<MyTestEntityNameDto>> CloneAsync(IEnumerable<Guid> ids);
    Task<MesOrderResponse> CreateAsync(List<WorkPlanInput> input);
}
PipeLineLems/server/src/CMS.Plugin.PipeLineLems.Application/Implements/MesAppService.cs
@@ -1,308 +1,158 @@
using CMS.Plugin.FormulaManagement.Abstractions;
using CMS.Plugin.MesSuite.Abstractions.Events;
using CMS.Plugin.OrderManagement.Abstractions.Enums;
using CMS.Plugin.OrderManagement.Abstractions.Models;
using CMS.Plugin.OrderManagement.Abstractions;
using CMS.Plugin.PipeLineLems.Application.Contracts.Dtos.MyTestEntityNames;
using CMS.Plugin.PipeLineLems.Application.Contracts.Services;
using CMS.Plugin.PipeLineLems.Domain.MyTestEntityNames;
using CMS.Plugin.PipeLineLems.Domain.Shared;
using CMS.Plugin.PipeLineLems.Domain.Shared.MyTestEntityNames;
using CMS.Plugin.ProductManagement.Abstractions;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Data;
using Volo.Abp.ObjectExtending;
using CMS.Plugin.PipeLineLems.Application.Contracts.Dtos.WorkPlan;
using Volo.Abp.EventBus;
namespace CMS.Plugin.PipeLineLems.Application.Implements;
/// <inheritdoc />
public class MesAppService : CMSPluginAppService, IMesAppService
public class MesAppService : IMesAppService
{
    private readonly IMyTestEntityNameRepository _mytestentitynameRepository;
    private readonly IServiceProvider _serviceProvider;
    /// <summary>
    /// Initializes a new instance of the <see cref="MyTestEntityNameAppService"/> class.
    /// </summary>
    /// <param name="mytestentitynameRepository">The task job repository.</param>
    public MesAppService(IMyTestEntityNameRepository mytestentitynameRepository)
    //private readonly IEventBus _eventBus;
    public MesAppService(IServiceProvider serviceProvider
        //, IEventBus eventBus
        )
    {
        _mytestentitynameRepository = mytestentitynameRepository;
        _serviceProvider = serviceProvider;
        // _eventBus = eventBus;
    }
    /// <inheritdoc />
    public virtual async Task<MyTestEntityNameDto> GetAsync(Guid id)
    public async Task<MesOrderResponse> CreateAsync(List<WorkPlanInput> input)
    {
        return ObjectMapper.Map<MyTestEntityName, MyTestEntityNameDto>(await _mytestentitynameRepository.GetAsync(id));
    }
    /// <inheritdoc />
    public virtual async Task<PagedResultDto<MyTestEntityNameDto>> GetListAsync(GetMyTestEntityNamesInput input)
    {
        Check.NotNull(input, nameof(input));
        if (input.Sorting.IsNullOrWhiteSpace())
        if (input == null)
        {
            input.Sorting = nameof(MyTestEntityName.Sort);
            throw new UserFriendlyException("输入参数不能为空");
        }
        var specification = new MyTestEntityNameSpecification(input.Name);
        var count = await _mytestentitynameRepository.GetCountAsync(input.Filter, specification);
        var list = await _mytestentitynameRepository.GetListAsync(input.Sorting, input.MaxResultCount,  input.SkipCount, input.Filter, specification);
        return new PagedResultDto<MyTestEntityNameDto>(count, ObjectMapper.Map<List<MyTestEntityName>, List<MyTestEntityNameDto>>(list));
    }
    /// <inheritdoc />
    public virtual async Task<MyTestEntityNameDto> CreateAsync(MyTestEntityNameCreateDto input)
    {
        await CheckCreateOrUpdateDtoAsync(input);
        var exist = await _mytestentitynameRepository.NameExistAsync(input.Name);
        if (exist)
        if (input.Count == 0)
        {
            throw new UserFriendlyException(L[CMSPluginDomainErrorCodes.NameAlreadyExists, input.Name]);
            throw new UserFriendlyException("输入参数Data不能为空");
        }
        var maxSort = await _mytestentitynameRepository.GetMaxSortAsync();
        var sort = input.Sort ?? maxSort;
        var mytestentityname = new MyTestEntityName(GuidGenerator.Create(), input.Code, input.Name, sort, input.Remark);
        input.MapExtraPropertiesTo(mytestentityname, MappingPropertyDefinitionChecks.None);
        await _mytestentitynameRepository.InsertAsync(mytestentityname);
        if (input.Sort.HasValue && mytestentityname.Sort != maxSort)
        var orderManager = _serviceProvider.GetRequiredService<IOrderManager>();
        var productProvider = _serviceProvider.GetRequiredService<IProductProvider>();
        var formulaProvider = _serviceProvider.GetRequiredService<IFormulaProvider>();
        List<OrderModel> orderModels = new List<OrderModel>();
        //按照任务编号分组
        var groupTask = input.GroupBy(x => x.TaskCode);
        foreach (var gTask in groupTask)
        {
            await AdjustSortAsync(mytestentityname.Id, mytestentityname.Sort);
        }
        return ObjectMapper.Map<MyTestEntityName, MyTestEntityNameDto>(mytestentityname);
    }
    /// <inheritdoc />
    public virtual async Task<MyTestEntityNameDto> UpdateAsync(Guid id, MyTestEntityNameUpdateDto input)
    {
        await CheckCreateOrUpdateDtoAsync(input);
        var mytestentityname = await _mytestentitynameRepository.GetAsync(id);
        var exist = await _mytestentitynameRepository.NameExistAsync(input.Name, mytestentityname.Id);
        if (exist)
        {
            throw new UserFriendlyException(L[CMSPluginDomainErrorCodes.NameAlreadyExists, input.Name]);
        }
        mytestentityname.SetConcurrencyStampIfNotNull(input.ConcurrencyStamp);
        input.MapExtraPropertiesTo(mytestentityname, MappingPropertyDefinitionChecks.None);
        mytestentityname.Update(input.Code, input.Name, input.Remark);
        await _mytestentitynameRepository.UpdateAsync(mytestentityname);
        return ObjectMapper.Map<MyTestEntityName, MyTestEntityNameDto>(mytestentityname);
    }
    /// <inheritdoc />
    public async Task<List<MyTestEntityNameDto>> CloneAsync(IEnumerable<Guid> ids)
    {
        var mytestentitynames = new List<MyTestEntityName>();
        if (ids != null)
        {
            var sort = await _mytestentitynameRepository.GetMaxSortAsync();
            foreach (var id in ids)
            var taskCode = gTask.Key;
            var order = await orderManager.GetByCodeAsync(taskCode);
            if (order != null)
            {
                var mytestentityname = await _mytestentitynameRepository.FindAsync(id);
                if (mytestentityname != null)
                {
                    var name = mytestentityname.Name + MyTestEntityNameConsts.CloneTag;
                    var notExist = false;
                    while (!notExist)
                    {
                        var exist = await _mytestentitynameRepository.NameExistAsync(name);
                        if (exist || mytestentitynames.Any(x => x.Name == name))
                        {
                            name += MyTestEntityNameConsts.CloneTag;
                            continue;
                        }
                        notExist = true;
                    }
                    mytestentityname = await _mytestentitynameRepository.InsertAsync(mytestentityname.Clone(GuidGenerator.Create(), name, sort++));
                    mytestentitynames.Add(mytestentityname);
                }
            }
        }
        return ObjectMapper.Map<List<MyTestEntityName>, List<MyTestEntityNameDto>>(mytestentitynames);
    }
    /// <inheritdoc />
    public virtual Task DeleteAsync(Guid id)
    {
        return _mytestentitynameRepository.DeleteAsync(id);
    }
    /// <inheritdoc />
    public async Task DeleteManyAsync(IEnumerable<Guid> ids)
    {
        foreach (var id in ids)
        {
            await DeleteAsync(id);
        }
    }
    /// <inheritdoc />
    public virtual async Task AdjustSortAsync(Guid id, int sort)
    {
        var list = await _mytestentitynameRepository.GetListAsync(nameof(MyTestEntityName.Sort));
        if (list != null && list.Any())
        {
            var initSort = 1;
            list.ForEach(x => x.AdjustSort(initSort++));
            var entity = list.FirstOrDefault(x => x.Id == id);
            if (entity != null)
            {
                if (sort == 1)
                {
                    list.Where(x => x.Id != id).ToList()?.ForEach(x => x.AdjustSort(x.Sort + 1));
                }
                else if (entity.Sort > sort)
                {
                    list.Where(x => x.Id != id && x.Sort >= sort).ToList()?.ForEach(x => x.AdjustSort(x.Sort + 1));
                    list.Where(x => x.Id != id && x.Sort < sort).ToList()?.ForEach(x => x.AdjustSort(x.Sort - 1));
                }
                else if (entity.Sort < sort)
                {
                    list.Where(x => x.Id != id && x.Sort > sort).ToList()?.ForEach(x => x.AdjustSort(x.Sort + 1));
                    list.Where(x => x.Id != id && x.Sort <= sort).ToList()?.ForEach(x => x.AdjustSort(x.Sort - 1));
                }
                entity.AdjustSort(sort);
            }
        }
        await _mytestentitynameRepository.UpdateManyAsync(list);
    }
    /// <inheritdoc />
    public async Task ImportAsync(MyTestEntityNamesImportModel input)
    {
        Check.NotNull(input, nameof(input));
        var mytestentitynameCreateDtos = new List<(int RowIndex, MyTestEntityNameCreateDto Item)>();
        var mytestentitynameUpdateDtos = new List<(int RowIndex, Guid Id, MyTestEntityNameUpdateDto Item)>();
        var mytestentitynames = input.MyTestEntityNames;
        if (mytestentitynames != null && mytestentitynames.Any())
        {
            #region å¯¼å…¥æ ¡éªŒ
            // åˆ¤æ–­åç§°æ˜¯å¦é‡å¤ï¼Œå¹¶è¾“出第几行重复
            var duplicateMyTestEntityNames = mytestentitynames.GroupBy(x => x.Name).Where(x => x.Count() > 1).ToList();
            if (duplicateMyTestEntityNames?.Any() == true)
            {
                var duplicateMyTestEntityNameMsgs = duplicateMyTestEntityNames.Select(x => $"第 {string.Join(",", x.Select(x => x.RowIndex))} è¡Œï¼š{x.Key}  åç§°é‡å¤");
                var errorMsg = $"导入失败!配置, {string.Join(",", duplicateMyTestEntityNameMsgs)},终止导入";
                throw new UserFriendlyException(errorMsg);
                throw new UserFriendlyException($"工单[{taskCode}]已存在");
            }
            #endregion
            foreach (var mytestentityname in mytestentitynames)
            //var product = await productProvider.FindByNameAsync(orderItem.PipeSectionName);
            //if (product == null)
            //{
            //    throw new UserFriendlyException($"产品名称[{orderItem.PipeSectionName}]不存在");
            //}
            //var formula = await formulaProvider.GetFormulaAsync(product.Id);
            //if (formula == null)
            //{
            //    throw new UserFriendlyException($"产品型号[{orderItem.MaterialCode}]无关联配方");
            //}
            //首先要创建 æ‰“码切割的工单
            var productForCut = await productProvider.FindByNameAsync("切割原料管");
            if (productForCut == null)
            {
                if (mytestentityname.Code.IsNullOrWhiteSpace() && mytestentityname.Name.IsNullOrWhiteSpace())
                throw new UserFriendlyException($"产品名称[切割原料管]不存在");
            }
            var formulaForCut = await formulaProvider.GetFormulaAsync(productForCut.Id);
            if (formulaForCut == null)
            {
                throw new UserFriendlyException($"产品名称[切割原料管]无关联配方");
            }
            //分组数据
            var group = gTask.ToList().GroupBy(x => x.DataIdentifier);
            foreach (var item in group)
            {
                OrderModel orderModelForCut = new OrderModel()
                {
                    continue;
                    Id = Guid.NewGuid(),
                    Code = "Cut_" + taskCode,
                    Source = "APS推送",
                    PlanStartTime = gTask.ToList().First().PlannedStartTime,
                    PlanFinishTime = gTask.ToList().First().PlannedEndTime,
                    PlanQty = (ulong)item.ToList().Count,
                    Status = OrderStatus.NotActive,
                    Product = new AssociationProductModel() { Id = productForCut.Id, Name = productForCut.Name, Model = productForCut.Model, ShortNumber = productForCut.ShortNumber },
                    Formula = new AssociationFormulaModel() { Id = formulaForCut.Id, Code = formulaForCut.Code, Name = formulaForCut.Name }
                };
                orderModelForCut.ExtraProperties["OuterDiameter"] = gTask.ToList().First().OuterDiameter;//外径
                orderModelForCut.ExtraProperties["Material"] = gTask.ToList().First().Material;//材质
                orderModelForCut.ExtraProperties["Length"] = gTask.ToList().First().Length;//长度
                var orderForCut = await orderManager.GetByCodeAsync(orderModelForCut.Code);
                if (orderForCut != null)
                {
                    //throw new UserFriendlyException($"工单[{orderModelForCut.Code}]已存在");
                    //不再抛异常,直接跳过
                    break;
                }
                if (mytestentityname.Name.IsNullOrWhiteSpace())
                var orderModelResultForCut = await orderManager.CreateAsync(orderModelForCut);
                if (orderModelResultForCut == null)
                {
                    var errorMsg = $"导入失败!配置,第{mytestentityname.RowIndex}行:MyTestEntityName名称不能为空";
                    throw new UserFriendlyException(errorMsg);
                    throw new UserFriendlyException($"工单[{orderModelForCut.Code}]创建失败");
                }
                orderModels.Add(orderModelResultForCut);
                var oldMyTestEntityName = await _mytestentitynameRepository.FindByNameAsync(mytestentityname.Name);
                if (oldMyTestEntityName != null)
                {
                    var mytestentitynameUpdateDto = new MyTestEntityNameUpdateDto
                    {
                        Code = mytestentityname.Code,
                        Name = mytestentityname.Name,
                        Remark = mytestentityname.Remark,
                    };
                    mytestentitynameUpdateDtos.Add((mytestentityname.RowIndex, oldMyTestEntityName.Id, mytestentitynameUpdateDto));
                }
                else
                {
                    var mytestentitynameCreateDto = new MyTestEntityNameCreateDto
                    {
                        Code = mytestentityname.Code,
                        Name = mytestentityname.Name,
                        Remark = mytestentityname.Remark,
                    };
                    mytestentitynameCreateDtos.Add((mytestentityname.RowIndex, mytestentitynameCreateDto));
                }
                //OrderModel orderModel = new OrderModel();
                //orderModel.Id = Guid.NewGuid();
                //orderModel.Code = orderItem.TaskCode;
                //orderModel.Source = "APS推送";
                //orderModel.Product = new AssociationProductModel() { Id = product.Id, Name = product.Name, Model = product.Model, ShortNumber = product.ShortNumber };
                ////orderModel.Formula = new AssociationFormulaModel() { Id = formula.Id, Code = formula.Code, Name = formula.Name };
                //orderModel.PlanStartTime = orderItem.PlannedStartTime;
                //orderModel.PlanFinishTime = orderItem.PlannedEndTime;
                //orderModel.PlanQty = (ulong?)orderItem.OrderQty;
                //orderModel.Status = OrderStatus.NotActive;
                //orderModel.ExtraProperties["Source"] = "ddd";
                //var source = orderModel.ExtraProperties["Source"];
                //var orderModelResult = await orderManager.CreateAsync(orderModel);
                //if (orderModelResult == null)
                //{
                //    throw new UserFriendlyException($"工单[{orderItem.TaskCode}]创建失败");
                //}
                //orderModels.Add(orderModelResult);
            }
        }
        // æ–°å¢ž
        foreach (var mytestentitynameDto in mytestentitynameCreateDtos)
        // å‘布事件
        //await _eventBus.PublishAsync(new EntityChangedEto("MESCREATE", input, null, EntityChangeType.Add, true));
        var response = new MesOrderResponse
        {
            try
            {
                await CreateAsync(mytestentitynameDto.Item);
            }
            catch (Exception e)
            {
                var errorMsg = $"导入失败!配置,第{mytestentitynameDto.RowIndex}行:{e.Message},终止导入";
                throw new UserFriendlyException(errorMsg);
            }
        }
        // æ›´æ–°
        foreach (var mytestentitynameDto in mytestentitynameUpdateDtos)
        {
            try
            {
                await UpdateAsync(mytestentitynameDto.Id, mytestentitynameDto.Item);
            }
            catch (Exception e)
            {
                var errorMsg = $"导入失败!配置,第{mytestentitynameDto.RowIndex}行:{e.Message},终止导入";
                throw new UserFriendlyException(errorMsg);
            }
        }
    }
    /// <inheritdoc />
    public async Task<(Dictionary<string, object> Sheets, string FileName)> ExportAsync(GetMyTestEntityNamesInput input)
    {
        Check.NotNull(input, nameof(input));
        if (input.Sorting.IsNullOrWhiteSpace())
        {
            input.Sorting = nameof(MyTestEntityName.Sort);
        }
        var specification = new MyTestEntityNameSpecification(input.Name);
        var list = await _mytestentitynameRepository.GetListAsync(input.Sorting, input.MaxResultCount, input.SkipCount, input.Filter, specification, includeDetails: true);
        var result = ObjectMapper.Map<List<MyTestEntityName>, List<MyTestEntityNameDto>>(list);
        var sheets = new Dictionary<string, object>
        {
            ["配置"] = result.Select(x => x.GetExportData()).ToList(),
            Code = "000000",
            Data = orderModels,
            Fail = false,
            Mesg = "处理成功",
            Success = true,
            Time = DateTime.UtcNow
        };
        var fileName = result.Count > 1 ? "MyTestEntityName列表" : result.Count == 1 ? result.First()?.Name : "MyTestEntityName模版";
        return (sheets, fileName);
    }
    /// <summary>
    /// Checks the create or update dto asynchronous.
    /// </summary>
    /// <param name="input">The input.</param>
    protected Task CheckCreateOrUpdateDtoAsync(MyTestEntityNameCreateOrUpdateDtoBase input)
    {
        Check.NotNull(input, nameof(input));
        Check.NotNullOrWhiteSpace(input.Code, "编号", MyTestEntityNameConsts.MaxCodeLength);
        Check.NotNullOrWhiteSpace(input.Name, "名称", MyTestEntityNameConsts.MaxNameLength);
        Check.Length(input.Remark, "备注", MyTestEntityNameConsts.MaxRemarkLength);
        return Task.CompletedTask;
        return response;
    }
}
PipeLineLems/server/src/CMS.Plugin.PipeLineLems/CMSPluginEntry.cs
@@ -22,6 +22,8 @@
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Modularity.PlugIns;
using CMS.Plugin.PipeLineLems.Application.Contracts.Services;
using CMS.Plugin.PipeLineLems.Application.Implements;
namespace CMS.Plugin.PipeLineLems;
@@ -48,6 +50,8 @@
        context.Services.AddScoped<IProjectRuntimeMigrator, CMSPluginRuntimeMigrator>();
        context.Services.AddSingleton<IProjectService, PipeLineLemsProjectService>();
        context.Services.AddScoped<IMesAppService, MesAppService>();
        context.Services.AddScoped<IEFDataProvider>(p =>
        {
            var cfg = p.GetRequiredService<IDataRuntimeConfig>();
PipeLineLems/server/src/CMS.Plugin.PipeLineLems/Controller/WorkPlanController.cs
@@ -1,5 +1,6 @@
using CMS.Extensions.Abp.AspNetCore.Mvc.Filters;
using CMS.Plugin.PipeLineLems.Application.Contracts.Dtos.WorkPlan;
using CMS.Plugin.PipeLineLems.Application.Contracts.Services;
using Microsoft.AspNetCore.Mvc;
using System.Reflection;
@@ -17,14 +18,14 @@
    public class WorkPlanController : ControllerBase
    {
        private readonly IMesAppService _mesAppService;
        /// <summary>
        /// Initializes a new instance of the <see cref="TestEntityNameController"/> class.
        /// </summary>
        /// <param name="testentitynameAppService">The testentityname application service.</param>
        public WorkPlanController()
        public WorkPlanController(IMesAppService mesAppService)
        {
            _mesAppService = mesAppService;
        }
        /// <summary>
@@ -32,11 +33,10 @@
        /// </summary>
        /// <param name="input">标识符.</param>
        /// <returns></returns>
        [HttpGet]
        public virtual Task GetWorkPlanAsync([FromQuery] List<WorkPlanInput> input)
        [HttpPost]
        public virtual async Task<MesOrderResponse> GetWorkPlanAsync([FromBody] List<WorkPlanInput> input)
        {
            // å†…部业务逻辑暂时为空
            return null;
            return await _mesAppService.CreateAsync(input);
        }
PipeLineLems/web/.build
PipeLineLems/web/.build.prod
PipeLineLems/web/.env
@@ -4,3 +4,9 @@
VITE_APP_TITLE= CMS
VITE_API_URL = 'http://192.168.5.94:18000' # å¼€å‘版-育航
# production只能设置为空,静态host资源服务地址
VITE_STATIC_URL=''
VITE_PROJECT_API_URL='http://127.0.0.1:8800'
PipeLineLems/web/.env.development
@@ -10,7 +10,7 @@
VITE_APP_NAMESPACE= 'cs'
# VITE_API_URL = 'http://192.168.2.13:18000'
VITE_API_URL = 'http://192.168.1.18:18000'
VITE_API_URL = 'http://localhost:18000'
VITE_PROJECT_API_URL='http://127.0.0.1:8800'
VITE_STATIC_URL = 'http://192.168.2.206:18000'
# VITE_APP_PROJECT_ID = 2
PipeLineLems/web/.env.production
@@ -6,4 +6,7 @@
VITE_APP_NAMESPACE= 'cs'
VITE_APP_VERSION = 'beta-9'
VITE_APP_VERSION = '2.0.0-release'
# production只能设置为空,静态host资源服务地址
VITE_STATIC_URL=''
PipeLineLems/web/.gitignore
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
auto-imports.d.ts
components.d.ts
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
/widgets
*.ts.timestamp*
wwwroot
src/config/menu.ts
cms_back
rust/target
.argv
docs/.vitepress/cache
publish
information-ui
widgets_bak
src/widgets/**/*
src/widgets
.build.local
*.zip
src/assets/**/*
src/assets
cross-env/*
rust
app/tmp/*
app/build/www
app/build/www.exe
PipeLineLems/web/README.md
@@ -1,20 +1,6 @@
# information-standard-tpl
# information-standard
## å®‰è£…环境
安装[nodeJs](https://nodejs.cn/)环境,下载 nodejs,安装完成后,
```js
node - v
```
## å®‰è£… yarn
```js
npm install -g yarn
```
## å®‰è£…依赖
## å®‰è£…
```js
yarn
@@ -57,6 +43,30 @@
|------hook.ts //钩子
|------...
index.html
```
## å¿«é€Ÿå¼€å§‹
### å¼€å‘分为主仓库基座: information-base å’Œæ ‡å‡†ç»„件及定制化组件
开发流程为:
#### 1、拉取基座仓库
```bash
git clone https://gitlab.syc-cms.com/lmes-plugin/web/information-base.git
```
#### 2、拉取子仓库组件
```bash
yarn pull
```
#### 3、按需要是否将所有组件切换到指定分支
```
yarn checkout ã€åˆ†æ”¯åã€‘
```
## åŠŸèƒ½
@@ -105,7 +115,7 @@
  onClick={onAddProcess}
  type="primary"
>
  æ·»åŠ MyEntityName
  æ·»åŠ å·¥åº
</IconButton>
```
PipeLineLems/web/app/build.sh
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,5 @@
#!/bin/bash
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o ./build/www main.go
# GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -o ./build/www_arm main.go
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o ./build/www.exe main.go
PipeLineLems/web/app/build/.npmrc
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
registry=https://registry.npmjs.org/
PipeLineLems/web/app/build/package.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
{
  "name": "lmes-create-widget",
  "version": "1.0.3",
  "main": "./www",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "description": ""
}
PipeLineLems/web/app/controllers/home.go
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
package controllers
import (
    "app/dto"
    "app/service"
    "app/utils"
    "net/http"
    "os"
    "path/filepath"
    "github.com/gin-gonic/gin"
)
// HomeHandler å¤„理首页请求
func HomeHandler(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "message": "Hello, World!",
    })
}
/**
* è¯»å–文件
**/
func FileHandler(c *gin.Context) {
    query := c.Request.URL.Query()
    absPath, err := filepath.Abs("../" + query.Get("name"))
    if err != nil {
        c.JSON(http.StatusOK, gin.H{
            "message": err,
        })
    } else {
        path := filepath.ToSlash(absPath)
        envLocal, err := os.ReadFile(path)
        if err != nil {
            c.JSON(http.StatusOK, gin.H{
                "message": err,
            })
        } else {
            c.JSON(http.StatusOK, gin.H{
                "data": string(envLocal),
            })
        }
    }
}
// åˆ›å»ºç»„ä»¶
func CreateWidget(c *gin.Context) {
    var body dto.RequestBody
    err := c.ShouldBindJSON(&body)
    if err != nil {
        utils.HandlerErr(c, err)
        return
    }
    service.CreateService(c, body)
}
PipeLineLems/web/app/dto/config.go
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package dto
type RequestBody struct {
    WidgetName string                            `json:"widgetName"`
    WidgetId   string                            `json:"widgetId"`
    Menu       []map[string]interface{}          `json:"menu"`
    MenuMap    map[string]map[string]interface{} `json:"menuMap"`
    Type       int                               `json:"type"`
}
type MenuConfig struct {
    Menu    []map[string]interface{}
    MenuMap map[string]map[string]interface{}
}
type ErrorType struct {
    string
    int
    error
}
PipeLineLems/web/app/go.mod
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
module app
go 1.23.0
require github.com/gin-gonic/gin v1.10.0
require (
    github.com/bytedance/sonic v1.11.6 // indirect
    github.com/bytedance/sonic/loader v0.1.1 // indirect
    github.com/cloudwego/base64x v0.1.4 // indirect
    github.com/cloudwego/iasm v0.2.0 // indirect
    github.com/gabriel-vasile/mimetype v1.4.3 // indirect
    github.com/gin-contrib/sse v0.1.0 // indirect
    github.com/go-playground/locales v0.14.1 // indirect
    github.com/go-playground/universal-translator v0.18.1 // indirect
    github.com/go-playground/validator/v10 v10.20.0 // indirect
    github.com/goccy/go-json v0.10.2 // indirect
    github.com/json-iterator/go v1.1.12 // indirect
    github.com/klauspost/cpuid/v2 v2.2.7 // indirect
    github.com/leodido/go-urn v1.4.0 // indirect
    github.com/mattn/go-isatty v0.0.20 // indirect
    github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
    github.com/modern-go/reflect2 v1.0.2 // indirect
    github.com/otiai10/copy v1.14.1 // indirect
    github.com/otiai10/mint v1.6.3 // indirect
    github.com/pelletier/go-toml/v2 v2.2.2 // indirect
    github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
    github.com/ugorji/go/codec v1.2.12 // indirect
    golang.org/x/arch v0.8.0 // indirect
    golang.org/x/crypto v0.23.0 // indirect
    golang.org/x/net v0.25.0 // indirect
    golang.org/x/sync v0.8.0 // indirect
    golang.org/x/sys v0.24.0 // indirect
    golang.org/x/text v0.15.0 // indirect
    google.golang.org/protobuf v1.34.1 // indirect
    gopkg.in/yaml.v3 v3.0.1 // indirect
)
PipeLineLems/web/app/go.sum
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,97 @@
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
PipeLineLems/web/app/main.go
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package main
import (
    "app/routes"
    "os"
    "github.com/gin-gonic/gin"
)
func main() {
    env := os.Getenv("ENV")
    if env == "production" {
        gin.SetMode(gin.ReleaseMode) // ç”Ÿäº§çŽ¯å¢ƒ
    } else {
        gin.SetMode(gin.DebugMode) // å¼€å‘环境
    }
    r := gin.Default()
    // æ³¨å†Œè·¯ç”±
    routes.RegisterRoutes(r)
    // è¿è¡ŒæœåС噍
    r.Run(":8800")
}
PipeLineLems/web/app/routes/routes.go
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package routes
import (
    "app/controllers"
    "github.com/gin-gonic/gin"
)
// RegisterRoutes æ³¨å†Œæ‰€æœ‰è·¯ç”±
func RegisterRoutes(r *gin.Engine) {
    prefixRoute := r.Group("/projectApi")
    prefixRoute.GET("/", controllers.HomeHandler)    // ä¸»é¡µ
    prefixRoute.GET("/env", controllers.FileHandler) // æ–‡ä»¶
    prefixRoute.POST("create", controllers.CreateWidget)
}
PipeLineLems/web/app/service/home_service.go
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,153 @@
package service
import (
    "app/dto"
    "app/utils"
    "encoding/json"
    "fmt"
    "os"
    "path/filepath"
    "strings"
    "github.com/gin-gonic/gin"
    cp "github.com/otiai10/copy"
)
// åˆ›å»ºservice
func CreateService(c *gin.Context, body dto.RequestBody) {
    // å¤åˆ¶æ–‡ä»¶åˆ°ä¸Šä¸€å±‚
    absPath, _ := filepath.Abs("../src")
    tplAbsPath, _ := filepath.Abs("../src/MyPluginName")
    sourceAbsPath, _ := filepath.Abs("./template")
    menuAbsPath, _ := filepath.Abs("../src/config/menu.ts")
    widgetPath := filepath.ToSlash(absPath)
    sourcePath := filepath.ToSlash(sourceAbsPath)
    tplPath := filepath.ToSlash(tplAbsPath)
    upWidget := utils.CapitalizeFirstLetter(body.WidgetId)
    s, _ := filepath.Abs("../src/" + upWidget)
    t, _ := filepath.Abs("../src/widgets/" + upWidget)
    widgetSourcePath := filepath.ToSlash(s)
    widgetTargetPath := filepath.ToSlash(t)
    menuPath := filepath.ToSlash(menuAbsPath)
    cpErr := cp.Copy(sourcePath, widgetPath)
    if cpErr != nil {
        utils.HandlerErr(c, cpErr)
        return
    }
    // æ‰«æå¤åˆ¶çš„æ–‡ä»¶å¤¹
    var matchDirs []string
    filepath.WalkDir(tplPath, func(path string, d os.DirEntry, err error) error {
        if err != nil {
            utils.HandlerErr(c, err)
            return err
        }
        matchDirs = append(matchDirs, path)
        return nil
    })
    entityName := body.WidgetId + "Entity"
    for _, dir := range matchDirs {
        // æ›¿æ¢æ–‡ä»¶å†…容
        utils.ReplaceFileContent(dir, []string{body.WidgetId, entityName, "MyPluginName", "MyEntityName"}, body.WidgetName)
        // ä¿®æ”¹æ–‡ä»¶åç§°
        if strings.Contains(dir, "MyPluginName") {
            if !strings.Contains(dir, "MyEntityName") {
                utils.RenameFile(dir, body.WidgetId, "MyPluginName")
            } else {
                utils.RenameFile(dir, body.WidgetId, "MyPluginName", entityName, "MyEntityName")
            }
        }
    }
    // åˆ é™¤ç›®å½•
    fileErr := os.RemoveAll(tplAbsPath)
    if fileErr != nil {
        utils.HandlerErr(c, fileErr)
        return
    }
    // ç§»åŠ¨ç›®å½•ï¼Œ
    // err := os.Rename(widgetSourcePath, widgetTargetPath)
    err := cp.Copy(widgetSourcePath, widgetTargetPath)
<<<<<<< HEAD
    fmt.Print(widgetSourcePath,widgetTargetPath,err,"================================")
=======
    if err != nil {
        utils.HandlerErr(c, err)
        return
    }
    // åˆ é™¤ç›®å½•
    moveErr := os.RemoveAll(widgetSourcePath)
    if moveErr != nil {
        utils.HandlerErr(c, moveErr)
        return
    }
>>>>>>> 3e0cd197c07476292ecb2980eec427fa8e40d1a0
    // æ›´æ–° menu.ts
    newMenuMap := map[string]interface{}{
        "icon":      "p",
        "name":      body.WidgetName,
        "notPage":   false,
        "patchName": body.WidgetId,
        "path":      "/information-base/" + body.WidgetId,
    }
    body.Menu = append(body.Menu, newMenuMap)
    body.MenuMap[body.WidgetId] = newMenuMap
    menuStr, menuErr := generateMenuStr(body.Menu, 1)
    if menuErr != nil {
        utils.HandlerErr(c, menuErr)
    }
    menuMapStr, menuMapErr := generateMenuStr(body.MenuMap, 2)
    if menuMapErr != nil {
        utils.HandlerErr(c, menuMapErr)
    }
    menuSumStr := menuStr + menuMapStr
    // å†™å…¥æ–‡ä»¶
    os.WriteFile(menuPath, []byte(menuSumStr), 0777)
    appendFileContent(body.WidgetId, body.Type)
}
func generateMenuStr[T any](menuConfig T, menuType int) (string, error) {
    menuJson, menuErr := json.Marshal(menuConfig)
    var menuKey string
    var menuTypeKey string
    if menuType == 1 {
        menuKey = "menu"
        menuTypeKey = "Record<string,any>[]"
    } else {
        menuKey = "menuMap"
        menuTypeKey = "Record<string,any>"
    }
    str := fmt.Sprintf("export const %s: %s = ", menuKey, menuTypeKey)
    menuStr := str + string(menuJson) + "\n"
    return menuStr, menuErr
}
func appendFileContent(widgetId string, fileType int) (string, error) {
    absPath, _ := filepath.Abs("../.build.local")
    prodPath, _ := filepath.Abs("../.build.prod")
    local := filepath.ToSlash(absPath)
    prod := filepath.ToSlash(prodPath)
    var name string
    if fileType == 1 {
        name = local
    } else {
        name = prod
    }
    // æ‰“开文件,如果文件不存在则创建
    file, err := os.OpenFile(name, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0777)
    if err != nil {
        return widgetId, err
    }
    defer file.Close()
    // è¿½åŠ å†…å®¹åˆ°æ–‡ä»¶
    _, err = file.WriteString("\n" + widgetId + "\n")
    if err != nil {
        return widgetId, err
    }
    return widgetId, err
}
PipeLineLems/web/app/utils/index.go
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,103 @@
package utils
import (
    "net/http"
    "os"
    "path/filepath"
    "strings"
    "unicode"
    "github.com/gin-gonic/gin"
    cp "github.com/otiai10/copy"
)
// ç»Ÿä¸€å¤„理错误
func HandlerErr(c *gin.Context, err error) error {
    c.JSON(http.StatusBadRequest, gin.H{"message": err})
    return err
}
// é¦–字母大写
func CapitalizeFirstLetter(s string) string {
    if s == "" {
        return s
    }
    runes := []rune(s)
    runes[0] = unicode.ToUpper(runes[0])
    return string(runes)
}
// é¦–字母小写
func DecapitalizeFirstLetter(s string) string {
    if s == "" {
        return s
    }
    runes := []rune(s)
    runes[0] = unicode.ToLower(runes[0])
    return string(runes)
}
func isDirectory(path string) bool {
    info, err := os.Stat(path)
    if err != nil {
        return false // å‡ºé”™ï¼ˆä¾‹å¦‚路径不存在),默认返回 false
    }
    return info.IsDir() // å¦‚果是目录,返回 true
}
// è¯»å–文件后,并替换文件内容,同步
func ReplaceFileContent(sourceFile string, configArr []string, widgetName string) {
    name := configArr[0]
    entityName := configArr[1]
    oldName := configArr[2]
    oldEntityName := configArr[3]
    sFile, _ := filepath.Abs(sourceFile)
    // æ’件名
    upName := CapitalizeFirstLetter(name)
    lowName := DecapitalizeFirstLetter(name)
    // æ—§æ’件名
    upOldName := CapitalizeFirstLetter(oldName)
    lowOldName := DecapitalizeFirstLetter(oldName)
    // æ–°å®žä½“名
    upEntityName := CapitalizeFirstLetter(entityName)
    lowEntityName := DecapitalizeFirstLetter(entityName)
    // æ—§å®žä½“名
    upOldEntityName := CapitalizeFirstLetter(oldEntityName)
    lowOldEntityName := DecapitalizeFirstLetter(oldEntityName)
    // tFile, _ := filepath.Abs(targetFile)
    content, err := os.ReadFile(sFile)
    if err != nil {
        return
    }
    newContentStr := strings.ReplaceAll(string(content), "${{widgetName}}", widgetName)
    newStr := strings.ReplaceAll(string(newContentStr), upOldName, upName)
    pluginStr := strings.ReplaceAll(newStr, lowOldName, lowName)
    entityStr := strings.ReplaceAll(pluginStr, upOldEntityName, upEntityName)
    entityLowStr := strings.ReplaceAll(entityStr, lowOldEntityName, lowEntityName)
    // å†™å…¥åˆ°æ–‡ä»¶ä¸­
    os.WriteFile(sourceFile, []byte(entityLowStr), 0777)
}
// ä¿®æ”¹æ–‡ä»¶åç§°
func RenameFile(dir string, targetName string, oldName string, entity ...string) {
    slashDir := filepath.ToSlash(dir)
    targetDir := strings.ReplaceAll(slashDir, oldName, targetName)
    if len(entity) == 0 {
        if strings.Contains(targetDir, "MyPluginName") || strings.Contains(targetDir, "MyEntityName") {
            return
        }
        if !isDirectory(slashDir) {
            cp.Copy(slashDir, targetDir)
        }
    } else {
        targetEntityDir := strings.ReplaceAll(targetDir, entity[1], entity[0])
        if strings.Contains(targetEntityDir, "MyPluginName") || strings.Contains(targetEntityDir, "MyEntityName") {
            return
        }
        if !isDirectory(slashDir) {
            cp.Copy(slashDir, targetEntityDir)
        }
    }
}
PipeLineLems/web/checkout.sh
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,84 @@
#!/bin/bash
# é»˜è®¤ç»„件数组
widget_Array=()
# æŒ‡å®šåˆ†æ”¯åä½œä¸ºå‚æ•°ä¼ å…¥
target_branch=$1
if [[ -z "$target_branch" ]]; then
    echo "请指定目标分支,例如: bash $0 feature/wg/test"
    exit 1
fi
# è¯»å– .build.prod æ–‡ä»¶å†…容
if [[ -f ./.build.prod ]]; then
    while IFS= read -r line || [ -n "$line" ]; do
        widget_Array+=("$line")
    done < ./.build.prod
fi
# è¯»å– .build æ–‡ä»¶å†…容
if [[ -f ./.build ]]; then
    while IFS= read -r line || [ -n "$line" ]; do
        widget_Array+=("$line")
    done < ./.build
fi
# è¯»å– .build æ–‡ä»¶å†…容
if [[ -f ./.build.local ]]; then
    while IFS= read -r line || [ -n "$line" ]; do
        widget_Array+=("$line")
    done < ./.build.local
fi
# åŽ»é‡
widget_Array=($(echo "${widget_Array[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
# å­˜å‚¨åˆ‡æ¢ä¿¡æ¯çš„æ•°ç»„
declare -a switch_info
# å¾ªçŽ¯éåŽ†ç»„ä»¶æ•°ç»„
for widgetName in "${widget_Array[@]}"; do
    # æ£€æŸ¥ç»„件目录是否存在
    if [[ -d "src/widgets/$widgetName" ]]; then
        # è¿›å…¥ç»„件目录
        cd "src/widgets/$widgetName" || { echo "无法进入目录 src/widgets/$widgetName"; continue; }
        # èŽ·å–å½“å‰åˆ†æ”¯å
        current_branch=$(git symbolic-ref --short HEAD)
        # å¦‚果当前分支代码有变动,提交变更
        if [[ -n "$(git status --porcelain)" ]]; then
            git add .
            git commit -m 'merge'
        fi
        # å°è¯•切换到指定分支
        if git show-ref --verify --quiet refs/heads/"$target_branch"; then
            git checkout "$target_branch"
        else
            # å¦‚果没有指定分支,创建一个
            git checkout -b "$target_branch"
        fi
        # èŽ·å–åˆ‡æ¢åŽçš„åˆ†æ”¯å
        new_branch=$(git symbolic-ref --short HEAD)
        # æŽ¨é€æŒ‡å®šåˆ†æ”¯åˆ°è¿œç¨‹
        # git push --set-upstream origin "$target_branch"
        # æ·»åŠ åˆ‡æ¢ä¿¡æ¯åˆ°æ•°ç»„
        switch_info+=("$widgetName: $current_branch -> $new_branch")
        # è¿”回到上一级目录
        cd - > /dev/null || exit
    else
        echo "目录 src/widgets/$widgetName ä¸å­˜åœ¨"
    fi
done
# ç»Ÿä¸€è¾“出切换信息
echo "切换信息:"
for info in "${switch_info[@]}"; do
    echo "$info"
done
PipeLineLems/web/docs/.env
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,5 @@
# # port ç«¯å£å·
VITE_PORT = 5173
VITE_API_URL = 'http://192.168.1.18:18000' # å¼€å‘版-育航
VITE_APP_NAMESPACE= 'cs'
PipeLineLems/web/docs/.env.development
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
# #
# # è¯·å¤åˆ¶æ­¤æ–‡ä»¶å¹¶å‘½åä¸º `.env.development.local`
# # ä¸ªäººå¼€å‘请在 `.env.development.local` æ–‡ä»¶ä¸Šæ”¹åЍ
# #
# # æœ¬åœ°çŽ¯å¢ƒ
# ENV = 'development'
# VITE_APP_TITLE= CMS
# VITE_APP_NAMESPACE= 'cs'
# # VITE_API_URL = 'http://192.168.2.13:18000'
# VITE_API_URL = 'http://192.168.2.158:18000'
PipeLineLems/web/docs/.vitepress/config.mts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,133 @@
import { defineConfig } from 'vitepress'
export default defineConfig({
  title: '信息化组件库',
  description: '前端信息化组件库',
  base: '/info',
  themeConfig: {
    nav: [
      { text: 'Home', link: '/' },
      { text: '快速开始', link: '/quick-start' },
    ],
    sidebar: {
      '/components': [
        {
          text: 'BaseContent å†…容框',
          link: '/components/BaseContent',
        },
        {
          text: 'BaseDialog å¯¹è¯æ¡†',
          link: '/components/BaseDialog',
        },
        {
          text: 'BaseDrawer æŠ½å±‰',
          link: '/components/BaseDrawer',
        },
        {
          text: 'BaseInput è¾“入框',
          link: '/components/BaseInput',
        },
        {
          text: 'ConfirmBox ç¡®è®¤æ¡†',
          link: '/components/ConfirmBox',
        },
        {
          text: 'Container å®¹å™¨',
          link: '/components/Container',
        },
        {
          text: 'Content å†…容区',
          link: '/components/Content',
        },
        {
          text: 'DialogPreView å¼¹çª—预览',
          link: '/components/DialogPreView',
        },
        {
          text: 'Dyform åŠ¨æ€è¡¨å•',
          link: '/components/Dyform',
        },
        {
          text: 'Empty ç©º',
          link: '/components/Empty',
        },
        {
          text: 'Flow æ˜¾ç¤º',
          link: '/components/Flow',
        },
        {
          text: 'Icon å›¾æ ‡',
          link: '/components/Icon',
        },
        {
          text: 'IconButon å›¾æ ‡æŒ‰é’®',
          link: '/components/IconButton',
        },
        {
          text: 'Pdf Pdf',
          link: '/components/Pdf',
        },
        {
          text: 'PreviewDialog é¢„览弹窗iframe',
          link: '/components/PreviewDialog',
        },
        {
          text: 'Search æœç´¢',
          link: '/components/Search',
        },
        {
          text: 'SearchInput é€‰æ‹©è¾“入组件',
          link: '/components/SearchInput',
        },
        {
          text: 'Tag æ ‡ç­¾',
          link: '/components/Tag',
        },
        {
          text: 'Tab æœç´¢',
          link: '/components/Tab',
        },
        {
          text: 'Table è¡¨æ ¼',
          link: '/components/Table',
        },
        {
          text: 'TableFilter è¡¨æ ¼ç­›é€‰',
          link: '/components/TableFilter',
        },
        {
          text: 'TdButton td按钮',
          link: '/components/TdButton',
        },
        {
          text: 'Text æ–‡æœ¬',
          link: '/components/Text',
        },
        {
          text: 'Title æ ‡é¢˜',
          link: '/components/Title',
        },
        {
          text: 'TouchScale ç¼©æ”¾',
          link: '/components/TouchScale',
        },
        {
          text: 'Upload ä¸Šä¼ ',
          link: '/components/Upload',
        },
        {
          text: 'Variable å˜é‡',
          link: '/components/Variable',
        },
      ],
    },
    socialLinks: [
      {
        icon: 'github',
        link: 'https://gitlab.syc-cms.com:8443/lmes-plugin/web/information-ui',
      },
    ],
  },
})
PipeLineLems/web/docs/.vitepress/theme/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
import type { Theme } from 'vitepress'
import Layout from '../../Layout/Layout.tsx'
import DefaultTheme from 'vitepress/theme'
import './style.css'
export default {
  extends: DefaultTheme,
  enhanceApp({ app }) {
    // æ³¨å†Œè‡ªå®šä¹‰å…¨å±€ç»„ä»¶
    // @ts-ignore
    app.component('Layout', Layout)
  },
} satisfies Theme
PipeLineLems/web/docs/.vitepress/theme/style.css
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
:root {
  /* æ ‡é¢˜ */
  --vp-home-hero-name-color: transparent;
  --vp-home-hero-name-background: linear-gradient(
    135deg,
    #fff886 10%,
    #f072b6 100%
  );
  /* å›¾æ ‡èƒŒæ™¯ */
  --vp-home-hero-image-background-image: linear-gradient(
    135deg,
    #fff886 10%,
    #f072b6 100%
  );
  --vp-home-hero-image-filter: blur(150px);
  --vp-c-indigo-1: #f072b6;
  --vp-c-indigo-2: #f072b6;
  --vp-c-indigo-3: #f072b6;
  /* å›¾æ ‡èƒŒæ™¯ */
  --vp-home-hero-image-background-image: linear-gradient(
    135deg,
    #f6ceec 10%,
    #d939cd 100%
  );
  --vp-home-hero-image-filter: blur(150px);
}
PipeLineLems/web/docs/Layout/Layout.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
.el-message {
  display: none!important;
}
.hover {
  &:hover {
    background-color: #ddd;
  }
  &:active {
    background-color: #ccc;
  }
}
PipeLineLems/web/docs/Layout/Layout.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
import { defineComponent } from 'vue'
import './Layout.scss'
import 'vxe-table/lib/style.css'
export default defineComponent({
  name: 'layout',
  setup(props, { slots, attrs }) {
    return () => {
      return (
        <el-config-provider {...attrs} namespace="cs">
          <ClientOnly>{slots.default?.()}</ClientOnly>
        </el-config-provider>
      )
    }
  },
})
PipeLineLems/web/docs/api-examples.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
---
outline: deep
---
# Runtime API Examples
This page demonstrates usage of some of the runtime APIs provided by VitePress.
The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files:
```md
<script setup>
import { useData } from 'vitepress'
const { theme, page, frontmatter } = useData()
</script>
## Results
### Theme Data
<pre>{{ theme }}</pre>
### Page Data
<pre>{{ page }}</pre>
### Page Frontmatter
<pre>{{ frontmatter }}</pre>
```
<script setup>
import { useData } from 'vitepress'
const { site, theme, page, frontmatter } = useData()
</script>
## Results
### Theme Data
<pre>{{ theme }}</pre>
### Page Data
<pre>{{ page }}</pre>
### Page Frontmatter
<pre>{{ frontmatter }}</pre>
## More
Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata).
PipeLineLems/web/docs/assets/image/table.png
PipeLineLems/web/docs/components/BaseContent.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
# BaseContent
`BaseContent` ç»„件用于显示带有标题、内容区域和可选页脚的内容。
## ç¤ºä¾‹ç”¨æ³•
<script setup>
import BaseContent from '@/components/BaseContent/BaseContent.tsx'
</script>
<BaseContent title='测试' icon='banben'>
    <div style="width: 100%;height: 500px">
      å†…容区
    </div>
</BaseContent>
```vue
<template>
  <BaseContent title="系统配置" icon="settings">
    <div>
      <!-- è¿™é‡Œæ”¾ç½®ä½ çš„内容 -->
    </div>
    <template #footer>
      <div>
        <!-- è¿™é‡Œæ”¾ç½®é¡µè„šå†…容 -->
      </div>
    </template>
  </BaseContent>
</template>
<script>
import { defineComponent } from 'vue'
import BaseContent from '@/components/BaseContent'
export default defineComponent({
  components: {
    BaseContent,
  },
  // å…¶ä»–组件选项...
})
</script>
```
在上面的示例中,我们使用了 `BaseContent` ç»„件,标题为 "系统配置",图标为 "settings"。我们还在默认插槽中提供了内容,在页脚插槽中提供了页脚内容。
## å±žæ€§
| å±žæ€§  | æè¿°                       | ç±»åž‹   | é»˜è®¤å€¼ | ç¤ºä¾‹å€¼     |
| ----- | -------------------------- | ------ | ------ | ---------- |
| title | åœ¨æ ‡é¢˜æ ä¸­æ˜¾ç¤ºçš„æ ‡é¢˜       | String | '标题' | '系统配置' |
| icon  | åœ¨æ ‡é¢˜æ ä¸­æ˜¾ç¤ºçš„图标的名称 | String | ''     | 'settings' |
## æ’æ§½
| åç§°    | æè¿°                         |
| ------- | ---------------------------- |
| default | è¦æ˜¾ç¤ºåœ¨å†…容区域中的主要内容 |
| footer  | å¯é€‰çš„页脚内容               |
PipeLineLems/web/docs/components/BaseDialog.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
# BaseDialog
在保留当前页面状态的情况下,告知用户并承载相关操作。
基础属性与 element-plus å±žæ€§ç›¸åŒ
## ç¤ºä¾‹
<script setup>
  import BaseDialog from '@/components/BaseDialog/index.vue'
  import { ref } from 'vue'
  const visible = ref(false)
</script>
<Layout>
  <el-button type='primary' @click="visible=true">打开</el-button>
  <BaseDialog
    v-model="visible"
    @close="visible = false"
    @confirm="visible = true"
    width="500"
    height="300">
    æˆ‘是对话框
  </BaseDialog>
</Layout>
```js-vue
<template>
  <el-button type='primary' @click="visible=true">打开</el-button>
  <BaseDialog
    v-model="visible"
    @close="visible = false"
    @confirm="visible = true"
    width="500"
    height="300">
    æˆ‘是对话框
  </BaseDialog>
</template>
<script setup>
  import BaseDialog from '@/components/BaseDialog/index.vue'
  import { ref } from 'vue'
  const visible = ref(false)
</script>
```
## å±žæ€§
| å±žæ€§          | ç±»åž‹     | è¯´æ˜Ž                                 |
| ------------- | -------- | ------------------------------------ |
| className     | è®¡ç®—属性 | è®¾ç½®å¯¹è¯æ¡†çš„自定义类名。             |
| attrs         | è®¡ç®—属性 | èŽ·å–ä¼ é€’ç»™å¯¹è¯æ¡†ç»„ä»¶çš„æ‰€æœ‰å±žæ€§ã€‚     |
| props         | å±žæ€§å®šä¹‰ | å¯¹è¯æ¡†ç»„ä»¶çš„ props å¯¹è±¡ã€‚            |
| footer        | è®¡ç®—属性 | æ£€æµ‹æ˜¯å¦å­˜åœ¨å¯¹è¯æ¡†åº•部的自定义内容。 |
| currentHeight | è®¡ç®—属性 | å½“前对话框的高度。                   |
## äº‹ä»¶
| äº‹ä»¶    | è¯´æ˜Ž         |
| ------- | ------------ |
| close   | å…³é—­å¯¹è¯æ¡†ã€‚ |
| confirm | ç¡®è®¤å¯¹è¯æ¡†ã€‚ |
| open    | æ‰“开对话框。 |
PipeLineLems/web/docs/components/BaseDrawer.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
# BaseDrawer
有些时候, Dialog ç»„件并不满足我们的需求, æ¯”如你的表单很长, äº¦æˆ–是你需要临时展示一些文档, Drawer æ‹¥æœ‰å’Œ Dialog å‡ ä¹Žç›¸åŒçš„ API, åœ¨ UI ä¸Šå¸¦æ¥ä¸ä¸€æ ·çš„体验.
基础属性与 element-plus å±žæ€§ç›¸åŒ
## æ–‡æ¡£
这个示例展示了一个简单的抽屉组件。当用户点击"打开抽屉"按钮时,`showDrawer` æ–¹æ³•会被调用,显示一个名为 "示例抽屉" çš„æŠ½å±‰ï¼Œç”¨æˆ·å¯ä»¥åœ¨æŠ½å±‰ä¸­ç‚¹å‡»ç¡®å®šæˆ–取消按钮,分别触发对应的事件处理函数 `handleConfirm` å’Œ `closeDrawer`。
## ç¤ºä¾‹
<script setup>
import BaseDrawer from '@/components/BaseDrawer/BaseDrawer.tsx'
import { ref } from 'vue'
const drawerVisible = ref(false)
const showDrawer = () => {
  drawerVisible.value = true
}
const closeDrawer = () => {
  drawerVisible.value = false
}
const handleConfirm = () => {
  console.log('用户点击了确认按钮')
  closeDrawer()
}
</script>
<Layout>
  <el-button type="primary" @click="showDrawer">打开抽屉</el-button>
  <BaseDrawer width="40%" title="示例抽屉" v-model="drawerVisible" @close="closeDrawer" @confirm="handleConfirm">
    <p>这是抽屉的内容。</p>
  </BaseDrawer>
</Layout>
```js-vue
<template>
  <div>
    <el-button type="primary" @click="showDrawer">打开抽屉</el-button>
    <BaseDrawer title="示例抽屉" v-model="drawerVisible" @close="closeDrawer" @confirm="handleConfirm">
      <p>这是抽屉的内容。</p>
    </BaseDrawer>
  </div>
</template>
<script setup>
import BaseDrawer from './BaseDrawer.vue'
import { ref } from 'vue'
const drawerVisible = ref(false)
const showDrawer = () => {
  drawerVisible.value = true
}
const closeDrawer = () => {
  drawerVisible.value = false
}
const handleConfirm = () => {
  console.log('用户点击了确认按钮')
  closeDrawer()
}
</script>
```
## å±žæ€§
| å±žæ€§      | ç±»åž‹    | é»˜è®¤å€¼ | è¯´æ˜Ž           |
| --------- | ------- | ------ | -------------- |
| clickable | Boolean | false  | é®ç½©æ˜¯å¦å¯ç‚¹å‡» |
| title     | String  | ''     | æŠ½å±‰æ ‡é¢˜       |
| width     | String  | ''     | æŠ½å±‰å®½åº¦       |
## äº‹ä»¶
| äº‹ä»¶              | è¯´æ˜Ž             |
| ----------------- | ---------------- |
| close             | å…³é—­æŠ½å±‰         |
| confirm           | ç¡®è®¤æŠ½å±‰         |
| update:modelValue | æ›´æ–° modelValue  |
| open              | æ‰“开抽屉         |
| beforeClose       | å…³é—­æŠ½å±‰å‰çš„事件 |
PipeLineLems/web/docs/components/BaseInput.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
# BaseInput
基础 input,不包含样式
## ç¤ºä¾‹
<script setup>
import BaseInput from '@/components/BaseInput/BaseInput.tsx'
import { ref } from 'vue'
const value = ref('')
</script>
<Layout>
  <BaseInput v-model="value" placeholder="请输入" style="border: 1px solid #ccc"/>
</Layout>
```js-vue
<template>
  <Layout>
    <Base v-model="value" style="border: 1px solid #ccc"/>
  </Layout>
</template>
<script>
  import BaseDrawer from '@/components/BaseInput/BaseInput.tsx'
  import { ref } from 'vue'
  const value = ref('')
</script>
```
## å±žæ€§
| å±žæ€§        | ç±»åž‹          | é»˜è®¤å€¼   | è¯´æ˜Ž           |
| ----------- | ------------- | -------- | -------------- |
| modelValue  | String/Number | ''       | è¾“入框的值     |
| placeholder | String        | '请输入' | è¾“入框的占位符 |
## äº‹ä»¶
| äº‹ä»¶              | è¯´æ˜Ž                   |
| ----------------- | ---------------------- |
| update:modelValue | æ›´æ–°è¾“入框的值         |
| click             | ç‚¹å‡»è¾“入框时触发的事件 |
PipeLineLems/web/docs/components/ConfirmBox.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
# ConfirmBox
## ä½¿ç”¨æ–‡æ¡£
确认弹窗
## ç¤ºä¾‹
<script setup>
import { ConfirmBox } from '@/components/ConfirmBox/ConfirmBox.tsx'
import { ref } from 'vue'
const handleConfirm = async () => {
  try {
    const result = await ConfirmBox('确定要删除吗?', '删除确认')
    if (result) {
      console.log('用户点击了确认按钮')
      // æ‰§è¡Œåˆ é™¤æ“ä½œ
    } else {
      console.log('用户点击了取消按钮')
    }
  } catch (error) {
    console.log('对话框关闭或出现错误:', error)
  }
}
</script>
<Layout>
  <el-button type='primary' @click="handleConfirm">打开</el-button>
</Layout>
```js-vue
<script setup>
import ConfirmBox from '@/components/ConfirmBox/ConfirmBox.tsx'
import { ref } from 'vue'
const handleConfirm = async () => {
  try {
    const result = await ConfirmBox('确定要删除吗?', '删除确认')
    if (result) {
      console.log('用户点击了确认按钮')
      // æ‰§è¡Œåˆ é™¤æ“ä½œ
    } else {
      console.log('用户点击了取消按钮')
    }
  } catch (error) {
    console.log('对话框关闭或出现错误:', error)
  }
}
</script>
<template>
  <Layout>
    <el-button @click="handleConfirm">打开</el-button>
  </Layout>
</template>
```
这个示例展示了如何使用 `ConfirmBox` å‡½æ•°æ¥æ˜¾ç¤ºä¸€ä¸ªå¸¦æœ‰ç¡®è®¤å’Œå–消按钮的对话框,并根据用户的操作执行相应的操作。
## å±žæ€§
| å±žæ€§å        | ç±»åž‹          | é»˜è®¤å€¼   | è¯´æ˜Ž                                          |
| ------------- | ------------- | -------- | --------------------------------------------- |
| `modelValue`  | Boolean       | false    | æŽ§åˆ¶å¯¹è¯æ¡†çš„æ˜¾ç¤ºä¸Žéšè—ï¼Œä½¿ç”¨ `v-model` ç»‘定。 |
| `title`       | String        | ''       | å¯¹è¯æ¡†çš„æ ‡é¢˜ã€‚                                |
| `width`       | String        | ''       | å¯¹è¯æ¡†çš„宽度。                                |
| `clickable`   | Boolean       | false    | é®ç½©æ˜¯å¦å¯ç‚¹å‡»ã€‚                              |
| `placeholder` | String        | '请输入' | è¾“入框的占位符。                              |
| `modelValue`  | String/Number | ''       | è¾“入框的值。                                  |
## äº‹ä»¶
| äº‹ä»¶å              | å‚æ•°             | è¯´æ˜Ž                     |
| ------------------- | ---------------- | ------------------------ |
| `update:modelValue` | `value: boolean` | æ›´æ–°å¯¹è¯æ¡†çš„æ˜¾ç¤ºçŠ¶æ€ã€‚   |
| `close`             | -                | å…³é—­å¯¹è¯æ¡†æ—¶è§¦å‘。       |
| `confirm`           | -                | ç”¨æˆ·ç¡®è®¤å¯¹è¯æ¡†æ—¶è§¦å‘。   |
| `open`              | -                | æ‰“开对话框时触发。       |
| `beforeClose`       | -                | å…³é—­å¯¹è¯æ¡†å‰è§¦å‘的事件。 |
| `click`             | `event: Event`   | ç‚¹å‡»è¾“入框时触发的事件。 |
PipeLineLems/web/docs/components/Container.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,83 @@
# Container å®¹å™¨
该组件用于显示一个通用的页面头部,可以包含标题和搜索功能。
## ç¤ºä¾‹
<Layout>
  <Container
  title="通用头部示例"
  :placeholder="'搜索内容'"
  :modelValue="searchValue"
  @confirm="handleSearch">
      <!-- è¿™é‡Œæ”¾å…¶ä»–内容 -->
  </Container>
</Layout>
<script setup>
import { ref } from 'vue'
import Container from '@/components/Container/Container.tsx'
const searchValue = ref('')
const handleSearch = (value) => {
  console.log('用户搜索:', value)
  // æ‰§è¡Œæœç´¢æ“ä½œ
}
</script>
```vue
<template>
  <Container
    title="通用头部示例"
    :placeholder="'搜索内容'"
    :modelValue="searchValue"
    @confirm="handleSearch"
  >
    <!-- è¿™é‡Œæ”¾å…¶ä»–内容 -->
  </Container>
</template>
<script>
import { ref } from 'vue'
import Container from '@/components/Container/Container.tsx'
export default {
  components: {
    Container,
  },
  setup() {
    const searchValue = ref('')
    const handleSearch = (value) => {
      console.log('用户搜索:', value)
      // æ‰§è¡Œæœç´¢æ“ä½œ
    }
    return {
      searchValue,
      handleSearch,
    }
  },
}
</script>
```
在该示例中,`UniversalHeader` ç»„件被使用,并通过属性传递了标题、搜索框的占位符以及搜索框的值。当用户确认搜索时,`confirm` äº‹ä»¶è¢«è§¦å‘,调用 `handleSearch` æ–¹æ³•执行搜索操作。
## å±žæ€§
| å±žæ€§å      | ç±»åž‹    | é»˜è®¤å€¼       | å¿…å¡« | æè¿°               |
| ----------- | ------- | ------------ | ---- | ------------------ |
| title       | String  | -            | æ˜¯   | å¤´éƒ¨æ ‡é¢˜           |
| placeholder | String  | '请输入搜索' | å¦   | æœç´¢è¾“入框的占位符 |
| modelValue  | String  | ''           | å¦   | æœç´¢è¾“入框的值     |
| isSearch    | Boolean | true         | å¦   | æ˜¯å¦æ˜¾ç¤ºæœç´¢è¾“入框 |
## äº‹ä»¶
| äº‹ä»¶å  | å‚æ•°          | æè¿°                           |
| ------- | ------------- | ------------------------------ |
| confirm | value: String | å½“用户按下回车键确认搜索时触发 |
PipeLineLems/web/docs/components/Content.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
# Content å†…容组件
## ç¤ºä¾‹
<Layout>
  <Content title="通用内容示例">
    <div style="width:100%;height:500px">
      <!-- è¿™é‡Œæ”¾å†…容 -->
      æˆ‘是内容
    </div>
  </Content>
</Layout>
<script setup>
import Content from '@/components/Content/Content.tsx'
</script>
```vue
<template>
  <Content title="通用内容示例">
    <div style="width:100%;height:500px">
      <!-- è¿™é‡Œæ”¾å†…容 -->
      æˆ‘是内容
    </div>
  </Content>
</template>
<script setup>
import Content from '@/components/Content/Content.tsx'
export default {
  components: {
    Content,
  },
}
</script>
```
在该示例中,`Content` ç»„件被使用,并通过属性传递了标题。在组件内部,可以通过插槽放置其他内容。
该组件用于显示通用的内容区域,包含标题和内容。
## å±žæ€§
| å±žæ€§å | ç±»åž‹   | é»˜è®¤å€¼ | å¿…å¡« | æè¿°     |
| ------ | ------ | ------ | ---- | -------- |
| title  | String | '标题' | å¦   | å†…容标题 |
PipeLineLems/web/docs/components/DialogPreView.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,165 @@
# DialogPreView å¯¹è¯æ¡†é¢„览组件
该组件用于显示对话框预览,可以展示图表或者图片列表。
## ç¤ºä¾‹
<Layout>
  <el-button @click="dialogVisible = true" type='primary'>echart打开</el-button>
<DialogPreView
title="对话框预览示例"
:isChart="true"
:chartOptions="chartOptions"
:picList="picList"
v-model="dialogVisible"
@close="handleClose"
@confirm="handleConfirm"></DialogPreView>
```vue
<template>
  <Layout>
    <DialogPreView
      title="对话框预览示例"
      :isChart="isChart"
      :chartOptions="chartOptions"
      :picList="picList"
      v-model:visible="dialogVisible"
      @close="handleClose"
      @confirm="handleConfirm"
    >
    </DialogPreView>
  </Layout>
</template>
<script setup>
import { ref } from 'vue'
import DialogPreView from '@/components/DialogPreView/DialogPreView.vue'
const dialogVisible = ref(false)
const isChart = ref(true)
const chartOptions = ref({
  xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
  },
  yAxis: {
    type: 'value',
  },
  series: [
    {
      data: [120, 200, 150, 80, 70, 110, 130],
      type: 'bar',
    },
  ],
})
const picList = ref([])
const handleClose = () => {
  console.log('对话框关闭')
}
const handleConfirm = () => {
  console.log('确认按钮被点击')
}
</script>
```
<el-button @click="dialogVisible1 = true" type='primary'>图片打开</el-button>
<DialogPreView
title="对话框预览示例"
:isChart="false"
:chartOptions="chartOptions"
:picList="picList"
v-model="dialogVisible1"
@close="handleClose"
@confirm="handleConfirm"></DialogPreView>
</Layout>
<script setup>
import { ref } from 'vue'
import DialogPreView from '@/components/DialogPreView/DialogPreView.tsx'
const ROOT_PATH = 'https://echarts.apache.org/examples';
const dialogVisible = ref(false)
const dialogVisible1 = ref(false)
const isChart = ref(true)
const chartOptions = ref({
  xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
  },
  yAxis: {
    type: 'value',
  },
  series: [
    {
      data: [120, 200, 150, 80, 70, 110, 130],
      type: 'bar',
    },
  ],
})
const picList = ref(['https://copyright.bdstatic.com/vcg/creative/cc9c744cf9f7c864889c563cbdeddce6.jpg@h_1280'])
const handleClose = () => {
  console.log('对话框关闭')
}
const handleConfirm = () => {
  console.log('确认按钮被点击')
}
</script>
```vue
<template>
  <Layout>
    <DialogPreView
      title="对话框预览示例"
      :isChart="isChart"
      :chartOptions="chartOptions"
      :picList="picList"
      v-model:visible="dialogVisible"
      @close="handleClose"
      @confirm="handleConfirm"
    >
    </DialogPreView>
  </Layout>
</template>
<script setup>
import { ref } from 'vue'
import DialogPreView from '@/components/DialogPreView/DialogPreView.vue'
const dialogVisible = ref(false)
const isChart = ref(true)
const chartOptions = ref({})
const picList = ref([
  'https://copyright.bdstatic.com/vcg/creative/cc9c744cf9f7c864889c563cbdeddce6.jpg@h_1280',
])
const handleClose = () => {
  console.log('对话框关闭')
}
const handleConfirm = () => {
  console.log('确认按钮被点击')
}
</script>
```
## å±žæ€§
| å±žæ€§å       | ç±»åž‹            | é»˜è®¤å€¼ | å¿…å¡« | æè¿°           |
| ------------ | --------------- | ------ | ---- | -------------- |
| title        | String          | '预览' | å¦   | å¯¹è¯æ¡†æ ‡é¢˜     |
| isChart      | Boolean         | false  | å¦   | æ˜¯å¦ä¸ºå›¾è¡¨é¢„览 |
| chartOptions | Object          | {}     | å¦   | å›¾è¡¨é…ç½®é¡¹     |
| picList      | Array\<string\> | []     | å¦   | å›¾ç‰‡åˆ—表       |
## äº‹ä»¶
| äº‹ä»¶å            | å‚æ•°    | æè¿°                             |
| ----------------- | ------- | -------------------------------- |
| update:modelValue | Boolean | å½“对话框的显示状态发生改变时触发 |
| close             | -       | å½“对话框关闭时触发               |
| confirm           | -       | å½“确认按钮被点击时触发           |
PipeLineLems/web/docs/components/Dyform.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,165 @@
# DyForm åŠ¨æ€è¡¨å•ç»„ä»¶
该组件用于生成动态表单,可以根据传入的配置项渲染不同的表单项。
## ç¤ºä¾‹
<Layout>
  <DyForm
    labelWidth="100px"
    labelPosition="left"
    ref='formRef'
    v-model:formData="formData"
    :formItemProps="formItemProps"
    :inLine="false"
  />
  <el-button @click="valid" type='primary'>提交</el-button>
</Layout>
<script setup>
import { ref, h } from 'vue'
import DyForm from '@/components/DyForm/DyForm.tsx'
const formRef = ref()
const formData = ref({})
const formItemProps = ref([
{
label: '姓名',
prop: 'name',
el: 'input',
placeholder: '请输入姓名',
rules: [{ required: true, message: '姓名', trigger: 'blur' }],
},
{
label: '编码',
prop: 'code',
el: 'input',
placeholder: '请输入编码',
rules: [
{ required: true, message: '不能为空或空白字符!', trigger: 'blur' },
],
},
{
label: '地址',
prop: 'addr',
el: 'select',
placeholder: '请选择',
options: [{
label: '广东',
value: '222'
}]
},
{
label: '公司',
prop: 'company',
el: (props, {attrs}) => h('div', null, '盛原成'),
}
])
const handleUpdateFormData = (data) => {
console.log('表单数据已更新:', data)
}
const valid = () => {
formRef.value?.validate()
}
// å¯æ ¹æ®éœ€è¦è®¾ç½® formData å’Œ formItemProps
</script>
```vue
<template>
  <Layout>
    <DyForm
      labelWidth="100px"
      labelPosition="left"
      ref="formRef"
      v-model:formData="formData"
      :formItemProps="formItemProps"
      :inLine="false"
    />
    <el-button @click="valid" type="primary">提交</el-button>
    />
  </Layout>
</template>
<script setup>
import { ref } from 'vue'
import DyForm from '@/components/DyForm/DyForm.tsx'
const formRef = ref()
const formData = ref({})
const formItemProps = ref([
  {
    label: '姓名',
    prop: 'name',
    el: 'input',
    placeholder: '请输入姓名',
    rules: [{ required: true, message: '姓名', trigger: 'blur' }],
  },
  {
    label: '编码',
    prop: 'code',
    el: 'input',
    placeholder: '请输入编码',
    rules: [
      { required: true, message: '不能为空或空白字符!', trigger: 'blur' },
    ],
  },
    {
    label: '地址',
    prop: 'addr',
    el: 'select',
    placeholder: '请选择',
    options: [{
      label: 'xxx'
      value: '222'
    }]
  },
])
const handleUpdateFormData = (data) => {
  console.log('表单数据已更新:', data)
}
const valid = () => {
  formRef.value?.validate()
}
</script>
```
在该示例中,`DyForm` ç»„件被使用,并通过属性传递了表单数据对象 `formData` å’Œè¡¨å•项配置数组 `formItemProps`。当表单数据对象发生改变时,触发 `update:formData` äº‹ä»¶ï¼Œè°ƒç”¨ `handleUpdateFormData` æ–¹æ³•更新表单数据。
## å±žæ€§
| å±žæ€§å        | ç±»åž‹    | é»˜è®¤å€¼  | å¿…å¡« | æè¿°               |
| ------------- | ------- | ------- | ---- | ------------------ |
| labelWidth    | String  | '100px' | å¦   | è¡¨å•项标签宽度     |
| labelPosition | String  | 'left'  | å¦   | è¡¨å•项标签位置     |
| formData      | Object  | {}      | å¦   | è¡¨å•数据对象       |
| formItemProps | Array   | []      | å¦   | è¡¨å•项配置数组     |
| inLine        | Boolean | false   | å¦   | æ˜¯å¦ä¸ºè¡Œå†…表单布局 |
## è¡¨å•项配置属性(formItemProps)
| å±žæ€§å    | ç±»åž‹                | é»˜è®¤å€¼ | å¿…å¡« | æè¿°                                         |
| --------- | ------------------- | ------ | ---- | -------------------------------------------- |
| el        | String or Component | -      | æ˜¯   | è¡¨å•项类型,支持预设的字符串类型或自定义组件 |
| prop      | String              | -      | æ˜¯   | è¡¨å•项数据对象中的属性名                     |
| label     | String              | -      | å¦   | è¡¨å•项标签文字                               |
| rules     | Array               | []     | å¦   | è¡¨å•项验证规则                               |
| width     | String              | -      | å¦   | è¡¨å•项宽度                                   |
| height    | String              | -      | å¦   | è¡¨å•项高度                                   |
| labelIcon | String              | -      | å¦   | è¡¨å•项标签图标                               |
| options   | Array               | []     | å¦   | é€‰é¡¹ç±»åž‹è¡¨å•项的选项数组                     |
| isHide    | Boolean             | false  | å¦   | æ˜¯å¦éšè—è¡¨å•项                               |
| isTitle   | Boolean             | false  | å¦   | æ˜¯å¦ä¸ºæ ‡é¢˜                                   |
| title     | String or Component | -      | å¦   | æ ‡é¢˜å†…容,仅当 isTitle ä¸º true æ—¶ç”Ÿæ•ˆ        |
## æ–¹æ³•
| æ–¹æ³•名    | å‚æ•° | è¿”回值  | æè¿°                   |
| --------- | ---- | ------- | ---------------------- |
| validate  | -    | Promise | éªŒè¯è¡¨å•项是否通过验证 |
| resetForm | -    | -       | é‡ç½®è¡¨å•项的值         |
PipeLineLems/web/docs/components/Empty.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
# Empty ç©ºæ•°æ®ç»„ä»¶
该组件用于在页面中展示暂无数据的提示信息。
## å±žæ€§
| å±žæ€§å | ç±»åž‹   | é»˜è®¤å€¼     | å¿…å¡« | æè¿°         |
| ------ | ------ | ---------- | ---- | ------------ |
| text   | String | '暂无数据' | å¦   | æç¤ºä¿¡æ¯æ–‡æœ¬ |
## ç¤ºä¾‹
<Empty text="暂无数据,请稍后再试" />
<script setup>
import Empty from '@/components/Empty/Empty.tsx'
</script>
```vue
<template>
  <Empty text="暂无数据,请稍后再试" />
</template>
<script setup>
import Empty from '@/components/Empty/Empty.tsx'
</script>
```
在该示例中,`Empty` ç»„件被使用,并通过属性设置了提示信息文本为 "暂无数据,请稍后再试"。
PipeLineLems/web/docs/components/Flow.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
# Flow æµç¨‹æ˜¾ç¤ºæ ‡ç­¾ç»„ä»¶
该组件用于显示流程标签,支持根据传入的数据渲染标签。
## å±žæ€§
| å±žæ€§å     | ç±»åž‹    | é»˜è®¤å€¼ | å¿…å¡« | æè¿°         |
| ---------- | ------- | ------ | ---- | ------------ |
| modelValue | Array   | []     | å¦   | æµç¨‹æ•°æ®æ•°ç»„ |
| disabled   | Boolean | false  | å¦   | æ˜¯å¦ç¦ç”¨ç»„ä»¶ |
## äº‹ä»¶
| äº‹ä»¶å | å‚æ•° | æè¿°             |
| ------ | ---- | ---------------- |
| click  | -    | å½“点击标签时触发 |
## ç¤ºä¾‹
<Layout>
  <Flow v-model="flowData" :disabled="isDisabled" @click="handleClick" />
</Layout>
<script setup>
import { ref } from 'vue'
import Flow from '@/components/Flow/Flow.tsx'
const flowData = ref([
  { name: '流程1', description: '这是流程1' },
  { name: '流程2', description: '这是流程2' },
  { name: '流程3', description: '这是流程3' },
])
const isDisabled = ref(false)
const handleClick = () => {
  console.log('点击了流程标签')
}
</script>
```vue
<template>
  <Flow v-model="flowData" :disabled="isDisabled" @click="handleClick" />
</template>
<script setup>
import Flow from '@/components/Flow/Flow.tsx'
const flowData = ref([
  { name: '流程1', description: '这是流程1' },
  { name: '流程2', description: '这是流程2' },
  { name: '流程3', description: '这是流程3' },
])
const isDisabled = ref(false)
const handleClick = () => {
  console.log('点击了流程标签')
}
</script>
```
在该示例中,`Flow` ç»„件被使用,并通过属性传递了流程数据数组 `flowData` å’Œç¦ç”¨çŠ¶æ€ `isDisabled`。当点击标签时,触发 `click` äº‹ä»¶ï¼Œè°ƒç”¨ `handleClick` æ–¹æ³•。
PipeLineLems/web/docs/components/Icon.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,126 @@
# Icon å›¾æ ‡ç»„ä»¶
该组件用于显示图标,支持自定义图标名称、宽度和高度,并可以响应点击事件。
## ç¤ºä¾‹
<Layout>
  <div style="
    display: flex;
    justify-content: flex-start;
    align-items: center;
    flex-wrap: wrap;
  ">
    <span
      :title="name"
      style="
        flex-shrink: 0;
        margin-right: 10px;
        cursor: pointer;
        display: flex;
        justify-content: space-between;
        padding: 20px 0;
        align-items: center;
        flex-direction: column;
        width: 100px;
        height: 100px;
        overflow:hidden;
      "
      v-for="name in icons"
      class='hover'
      @click="() => handleClick(name)"
      :key="name">
      <Icon :icon="name" :width="20" :height="20"  />
      <span>{{name}}</span>
    </span>
  </div>
</Layout>
<script setup>
import { ref } from 'vue'
import Icon from '@/components/Icon/Icon.tsx'
const iconMap = import.meta.glob('../../src/assets/images/*.png', {
  eager: true,
})
const icons = ref([])
const extractFileNameWithoutExtension = (filePath) => {
  const parts = filePath.split('/')
  const fileNameWithExtension = parts[parts.length - 1]
  const fileNameParts = fileNameWithExtension.split('.')
  return fileNameParts[0]
}
const  fallbackCopyTextToClipboard = (text) => {
  var textArea = document.createElement("textarea");
  textArea.value = text;
  // Avoid scrolling to bottom
  textArea.style.top = "0";
  textArea.style.left = "0";
  textArea.style.position = "fixed";
  document.body.appendChild(textArea);
  textArea.focus();
  textArea.select();
  try {
    var successful = document.execCommand('copy');
    var msg = successful ? 'successful' : 'unsuccessful';
    console.log('Fallback: Copying text command was ' + msg);
  } catch (err) {
    console.error('Fallback: Oops, unable to copy', err);
  }
  document.body.removeChild(textArea);
}
const copyTextToClipboard = (text) => {
  if (!navigator.clipboard) {
    fallbackCopyTextToClipboard(text);
    return;
  }
  navigator.clipboard.writeText(text).then(() => {
    console.log('复制成功');
  }, function(err) {
    console.error('复制失败', err);
  });
}
Object.entries(iconMap).forEach(([key, v]) => {
  const name = extractFileNameWithoutExtension(key)
  icons.value.push(name)
})
const handleClick = (name) => {
  copyTextToClipboard(`<Icon :icon="${name}" :width="24" :height="24"  />`)
  alert('复制成功')
}
</script>
```vue
<template>
  <Icon icon="logo" width="24" height="24" @click="handleClick" />
</template>
<script setup>
import Icon from '@/components/Icon/Icon.tsx'
const handleClick = (evt) => {
  console.log('点击了图标')
}
</script>
```
在该示例中,`Icon` ç»„件被使用,并通过属性设置了图标名称为 "logo",宽度为 24,高度为 24。当点击图标时,触发 `click` äº‹ä»¶ï¼Œè°ƒç”¨ `handleClick` æ–¹æ³•。
## å±žæ€§
| å±žæ€§å | ç±»åž‹   | é»˜è®¤å€¼ | å¿…å¡« | æè¿°     |
| ------ | ------ | ------ | ---- | -------- |
| icon   | String | ''     | å¦   | å›¾æ ‡åç§° |
| width  | Number | 12     | å¦   | å›¾æ ‡å®½åº¦ |
| height | Number | 12     | å¦   | å›¾æ ‡é«˜åº¦ |
## äº‹ä»¶
| äº‹ä»¶å | å‚æ•°  | æè¿°             |
| ------ | ----- | ---------------- |
| click  | Event | å½“点击图标时触发 |
PipeLineLems/web/docs/components/IconButton.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
# IconButton å›¾æ ‡æŒ‰é’®ç»„ä»¶
该组件用于显示带图标的按钮,支持自定义图标、按钮类型、弹出框宽度等属性,并可以响应点击事件。
<Layout>
  <IconButton icon="search" type="primary" @click="handleClick">
    <template #content>
      å†…容
    </template>
    åˆ†ç»„
  </IconButton>
  <IconButton icon="g">
    å†…容
  </IconButton>
</Layout>
<script setup>
import IconButton from '@/components/IconButton/IconButton.tsx'
const handleClick = (evt) => {
  console.log('点击了图标按钮')
}
</script>
## ç¤ºä¾‹
```vue
<template>
  <IconButton icon="search" type="primary" @click="handleClick">
    <template #content> å†…容 </template>
    æœç´¢
  </IconButton>
  <IconButton icon="g"> åˆ†ç»„ </IconButton>
</template>
<script setup>
import IconButton from '@/components/IconButton/IconButton.tsx'
const handleClick = (evt) => {
  console.log('点击了图标按钮')
}
</script>
```
在该示例中,`IconButton` ç»„件被使用,并通过属性设置了图标名称为 "search",按钮类型为 "primary"。当点击按钮时,触发 `click` äº‹ä»¶ï¼Œè°ƒç”¨ `handleClick` æ–¹æ³•。
## å±žæ€§
| å±žæ€§å       | ç±»åž‹   | é»˜è®¤å€¼ | å¿…å¡« | æè¿°         |
| ------------ | ------ | ------ | ---- | ------------ |
| icon         | String | -      | å¦   | å›¾æ ‡åç§°     |
| type         | String | -      | å¦   | æŒ‰é’®ç±»åž‹     |
| popoverWidth | Number | -      | å¦   | å¼¹å‡ºæ¡†å®½åº¦   |
| content      | slots  | -      | å¦   | å¼¹çª—         |
| å…¶ä»–         | any    | -      | å¦   | å…¶ä»–按钮属性 |
## äº‹ä»¶
| äº‹ä»¶å | å‚æ•°  | æè¿°             |
| ------ | ----- | ---------------- |
| click  | Event | å½“点击按钮时触发 |
PipeLineLems/web/docs/components/Pdf.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
# PDFViewer ç»„ä»¶
该组件用于显示 PDF æ–‡ä»¶çš„预览。支持在对话框中展示 PDF æ–‡ä»¶å†…容,并提供关闭和确认功能。
## ç¤ºä¾‹
<Layout>
  <el-button type='primary' @click="showDialog = true">pdf弹窗 </el-button>
  <PDFViewer v-model="showDialog" pdfSrc="/path/to/pdf/file.pdf" />
</Layout>
<script setup>
import PDFViewer from '@/components/Pdf/index.vue'
import { ref } from 'vue'
const showDialog = ref(false)
</script>
```vue
<template>
  <PDFViewer :v-model="showDialog" pdfSrc="/path/to/pdf/file.pdf" />
</template>
<script setup>
import PDFViewer from '@/components/Pdf/index.vue'
import { ref } from 'vue'
const showDialog = ref(false)
</script>
```
在该示例中,`PDFViewer` ç»„件被使用,并传递了 `modelValue` æŽ§åˆ¶å¯¹è¯æ¡†çš„æ˜¾ç¤ºçŠ¶æ€ï¼Œä»¥åŠ `pdfSrc` å±žæ€§æŒ‡å®š PDF æ–‡ä»¶çš„路径。
## å±žæ€§
| å±žæ€§å     | ç±»åž‹    | é»˜è®¤å€¼ | å¿…å¡« | æè¿°           |
| ---------- | ------- | ------ | ---- | -------------- |
| modelValue | Boolean | -      | æ˜¯   | æŽ§åˆ¶å¯¹è¯æ¡†æ˜¾ç¤º |
| pdfSrc     | String  | -      | æ˜¯   | PDF æ–‡ä»¶çš„路径 |
## äº‹ä»¶
| äº‹ä»¶å            | å‚æ•°    | æè¿°               |
| ----------------- | ------- | ------------------ |
| update:modelValue | Boolean | æ›´æ–°å¯¹è¯æ¡†æ˜¾ç¤ºçŠ¶æ€ |
| close             | -       | å…³é—­å¯¹è¯æ¡†æ—¶è§¦å‘   |
| confirm           | -       | ç¡®è®¤æ“ä½œæ—¶è§¦å‘     |
PipeLineLems/web/docs/components/PreviewDialog.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
# PreviewDialog é¢„览对话框组件
该组件用于显示预览内容,支持在对话框中展示指定 URL çš„内容,并提供关闭功能。
## ç¤ºä¾‹
<Layout>
  <el-button type='primary' @click="showDialog = true">弹窗 </el-button>
  <PreviewDialog v-model="showDialog" url="https://www.shengyc.com/" />
</Layout>
<script setup>
import PreviewDialog from '@/components/PreviewDialog/index.vue'
import { ref } from 'vue'
const showDialog = ref(false)
</script>
```vue
<template>
  <PreviewDialog v-model="showDialog" url="https://www.shengyc.com/" />
</template>
<script setup>
import PreviewDialog from '@/components/PreviewDialog/index.vue'
import { ref } from 'vue'
const showDialog = ref(false)
</script>
```
在该示例中,`PreviewDialog` ç»„件被使用,并传递了 `modelValue` æŽ§åˆ¶å¯¹è¯æ¡†çš„æ˜¾ç¤ºçŠ¶æ€ï¼Œä»¥åŠ `url` å±žæ€§æŒ‡å®šé¢„览内容的 URL。
## å±žæ€§
| å±žæ€§å         | ç±»åž‹    | é»˜è®¤å€¼                 | å¿…å¡« | æè¿°                           |
| -------------- | ------- | ---------------------- | ---- | ------------------------------ |
| modelValue     | Boolean | -                      | æ˜¯   | æŽ§åˆ¶å¯¹è¯æ¡†æ˜¾ç¤º                 |
| url            | String  | 'http://www.baidu.com' | å¦   | é¢„览内容的 URL                 |
| append-to-body | Boolean | true                   | å¦   | æ˜¯å¦å°†å¯¹è¯æ¡†æ’入到 body å…ƒç´ å†… |
| width          | Number  | 1200                   | å¦   | å¯¹è¯æ¡†å®½åº¦                     |
| show-close     | Boolean | true                   | å¦   | æ˜¯å¦æ˜¾ç¤ºå…³é—­æŒ‰é’®               |
## äº‹ä»¶
| äº‹ä»¶å            | å‚æ•°    | æè¿°               |
| ----------------- | ------- | ------------------ |
| update:modelValue | Boolean | æ›´æ–°å¯¹è¯æ¡†æ˜¾ç¤ºçŠ¶æ€ |
| close             | -       | å…³é—­å¯¹è¯æ¡†æ—¶è§¦å‘   |
PipeLineLems/web/docs/components/Search.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
# Search æœç´¢è¾“入组件
该组件用于展示一个搜索输入框,支持用户输入关键字进行搜索,并提供确认搜索功能。
## ç¤ºä¾‹
<Layout>
  <Search v-model="searchValue" placeholder="搜索内容" :tableRef="tableRef" field="Name" @confirm="handleSearch" />
</Layout>
<script setup>
import Search from '@/components/Search/Search.tsx'
import { ref } from 'vue'
const searchValue = ref('')
const tableRef = ref(null)
const handleSearch = (value) => {
  console.log('用户搜索:', value)
  // æ‰§è¡Œæœç´¢æ“ä½œ
  // æ­¤å¤„演示
  if (tableRef.value) {
    tableRef.value.getList({ Name: value })
  }
}
</script>
```vue
<template>
  <Search
    :modelValue="searchValue"
    placeholder="搜索内容"
    :tableRef="tableRef"
    field="Name"
    @confirm="handleSearch"
  />
</template>
<script setup>
import Search from '@/components/Search/Search.tsx'
import { ref } from 'vue'
const searchValue = ref('')
const tableRef = ref(null)
const handleSearch = (value) => {
  console.log('用户搜索:', value)
  // æ‰§è¡Œæœç´¢æ“ä½œ
  // æ­¤å¤„演示
  if (tableRef.value) {
    tableRef.value.getList({ Name: value })
  }
}
</script>
```
在该示例中,`Search` ç»„件被使用,并通过属性传递了搜索框的占位符、表格的引用以及执行搜索操作时指定的字段名。当用户确认搜索时,`confirm` äº‹ä»¶è¢«è§¦å‘,调用 `handleSearch` æ–¹æ³•执行搜索操作。
## å±žæ€§
| å±žæ€§å      | ç±»åž‹   | é»˜è®¤å€¼       | å¿…å¡« | æè¿°                         |
| ----------- | ------ | ------------ | ---- | ---------------------------- |
| placeholder | String | '请输入搜索' | å¦   | æœç´¢è¾“入框的占位符           |
| modelValue  | String | ''           | å¦   | æœç´¢è¾“入框的值               |
| tableRef    | Object | null         | å¦   | è¡¨æ ¼çš„引用,用于执行搜索操作 |
| field       | String | ''           | å¦   | æ‰§è¡Œæœç´¢æ“ä½œæ—¶æŒ‡å®šçš„字段名   |
## äº‹ä»¶
| äº‹ä»¶å  | å‚æ•°   | æè¿°                                 |
| ------- | ------ | ------------------------------------ |
| confirm | string | ç”¨æˆ·ç¡®è®¤æœç´¢æ—¶è§¦å‘,参数为搜索关键字 |
PipeLineLems/web/docs/components/SearchInput.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
# SelectInput é€‰æ‹©è¾“入组件
该组件用于展示一个带有选择按钮的输入框,同时可以展示选择的标签。
## ç¤ºä¾‹
<Layout>
  <SelectInput v-model="selectedValues" @click="handleClick" />
</Layout>
<script setup>
import SelectInput from '@/components/SelectInput/SelectInput.tsx'
import { ref } from 'vue'
const selectedValues = ref([])
const handleClick = () => {
  // å¤„理点击选择按钮的逻辑
}
</script>
```vue
<template>
  <SelectInput v-model="selectedValues" @click="handleClick" />
</template>
<script setup>
import SelectInput from '@/components/SelectInput/SelectInput.tsx'
import { ref } from 'vue'
const selectedValues = ref([])
const handleClick = () => {
  // å¤„理点击选择按钮的逻辑
}
</script>
```
在该示例中,`SelectInput` ç»„件被使用,并通过 `v-model` åŒå‘绑定了选择输入框的值。当点击选择按钮时,会触发 `click` äº‹ä»¶ï¼Œè°ƒç”¨ `handleClick` æ–¹æ³•处理点击事件。
## å±žæ€§
| å±žæ€§å     | ç±»åž‹  | é»˜è®¤å€¼ | å¿…å¡« | æè¿°           |
| ---------- | ----- | ------ | ---- | -------------- |
| modelValue | Array | []     | å¦   | é€‰æ‹©è¾“入框的值 |
## äº‹ä»¶
| äº‹ä»¶å            | å‚æ•°  | æè¿°                     |
| ----------------- | ----- | ------------------------ |
| click             | -     | ç‚¹å‡»é€‰æ‹©æŒ‰é’®æ—¶è§¦å‘       |
| update:modelValue | Array | æ›´æ–°é€‰æ‹©è¾“入框的值时触发 |
PipeLineLems/web/docs/components/Tab.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,146 @@
# Tab æ ‡ç­¾é¡µç»„ä»¶
## è¯¥ç»„件用于展示多个标签页,并支持切换标签页功能。
## ç¤ºä¾‹ 1
<Layout>
  <Tab :data="tabData" v-model:active="activeTab" size="small" @tab="handleTabChange">
    <TabPane v-for="item in tabData" :key="item.name" :name="item.name" :label="item.label">
      <component :is="item.component"/>
    </TabPane>
  </Tab>
</Layout>
<script setup>
import Tab from '@/components/Tab/Tab.tsx'
import TabPane from '@/components/Tab/TabPane.tsx'
import { ref, h } from 'vue'
const activeTab = ref('')
const tabData = ref([
  { name: 'tab1', label: '标签页1', component: h('div', null, '1') },
  { name: 'tab2', label: '标签页2', component: h('div', null, '2') },
  { name: 'tab3', label: '标签页3', component: h('div', null, '3') },
])
const handleTabChange = (tabName) => {
  activeTab.value = tabName
}
</script>
```vue
<template>
  <Tab
    :data="tabData"
    v-model:active="activeTab"
    size="small"
    @tab="handleTabChange"
  >
    <template #default>
      <TabPane
        v-for="item in tabData"
        :key="item.name"
        :name="item.name"
        :label="item.label"
      >
        <item.component />
      </TabPane>
    </template>
  </Tab>
</template>
<script setup>
import Tab from '@/components/Tab/Tab.tsx'
import TabPane from '@/components/Tab/TabPane.tsx'
import { ref, h } from 'vue'
const activeTab = ref('')
const tabData = ref([
  { name: 'tab1', label: '标签页1', component: h('div', null, '1') },
  { name: 'tab2', label: '标签页2', component: h('div', null, '2') },
  { name: 'tab3', label: '标签页3', component: h('div', null, '3') },
])
const handleTabChange = (tabName) => {
  activeTab.value = tabName
}
</script>
```
## ç¤ºä¾‹ 2
<Layout>
  <Tab :data="tabData" v-model:active="activeTab" size="small" @tab="handleTabChange">
  </Tab>
</Layout>
```vue
<template>
  <Tab
    :data="tabData"
    v-model:active="activeTab"
    size="small"
    @tab="handleTabChange"
  ></Tab>
</template>
<script setup>
import Tab from '@/components/Tab/Tab.tsx'
import TabPane from '@/components/Tab/TabPane.tsx'
import { ref, h } from 'vue'
const activeTab = ref('')
const tabData = ref([
  { name: 'tab1', label: '标签页1', component: h('div', null, '1') },
  { name: 'tab2', label: '标签页2', component: h('div', null, '2') },
  { name: 'tab3', label: '标签页3', component: h('div', null, '3') },
])
const handleTabChange = (tabName) => {
  activeTab.value = tabName
}
</script>
```
在该示例中,`Tab` ç»„件用于展示多个标签页,通过 `data` å±žæ€§ä¼ å…¥æ ‡ç­¾é¡µçš„æ•°æ®æ•°ç»„,通过 `v-model:active` å®žçŽ°å¯¹å½“å‰æ¿€æ´»æ ‡ç­¾é¡µçš„æŽ§åˆ¶ã€‚æ ‡ç­¾é¡µçš„å†…å®¹é€šè¿‡é»˜è®¤æ’æ§½ä¼ å…¥ï¼Œæ¯ä¸ªæ ‡ç­¾é¡µå¯¹åº”ä¸€ä¸ª `TabPane` ç»„件,并传入相应的组件作为内容。
## å±žæ€§
| å±žæ€§å | ç±»åž‹          | é»˜è®¤å€¼ | å¿…å¡« | æè¿°                       |
| ------ | ------------- | ------ | ---- | -------------------------- |
| data   | Array         | []     | å¦   | æ ‡ç­¾é¡µæ•°æ®æ•°ç»„             |
| size   | String        | ''     | å¦   | æ ‡ç­¾é¡µå°ºå¯¸                 |
| active | String/Number | ''     | å¦   | å½“前激活的标签页名称或索引 |
| type   | String        | 'list' | å¦   | æ ‡ç­¾é¡µç±»åž‹                 |
## äº‹ä»¶
| äº‹ä»¶å | å‚æ•°   | æè¿°                                 |
| ------ | ------ | ------------------------------------ |
| tab    | String | åˆ‡æ¢æ ‡ç­¾é¡µæ—¶è§¦å‘,参数为标签页的名称 |
## æ’æ§½
| åç§° | æè¿°       |
| ---- | ---------- |
| é»˜è®¤ | æ ‡ç­¾é¡µå†…容 |
## TabPane æ ‡ç­¾é¡µç»„ä»¶
该组件用于定义单个标签页。
## å±žæ€§
| å±žæ€§å | ç±»åž‹   | é»˜è®¤å€¼ | å¿…å¡« | æè¿°       |
| ------ | ------ | ------ | ---- | ---------- |
| label  | String | -      | æ˜¯   | æ ‡ç­¾é¡µæ ‡é¢˜ |
| name   | String | -      | æ˜¯   | æ ‡ç­¾é¡µåç§° |
| ...    | -      | -      | -    | å…¶ä»–属性   |
## æ’æ§½
| åç§° | æè¿°       |
| ---- | ---------- |
| é»˜è®¤ | æ ‡ç­¾é¡µå†…容 |
PipeLineLems/web/docs/components/Table.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,248 @@
抱歉,以下是您提供的组件代码的文档:
# Table ç»„ä»¶
## æè¿°
该组件用于展示数据表格,并提供了一系列功能,包括拖拽、分页、排序、右键菜单等。
## ä½¿ç”¨ç¤ºä¾‹
#### ç”±äºŽ vitepress çš„表格样式问题,导致组件表格样式冲突,这里就展示表格示例了,我截图放上去
![](../assets/image/table.png)
<!--
::: raw
<Layout>
<div class="vp-raw">
  <Table
    ref="tableRef"
    v-model:dataSource="dataSource"
    id='name'
    max-height="300"
    height="auto"
    :columns="columns"
    :contextMenu="contextMenu"
    :isSort="true"
    :isDrag="true"
    :isCheck="true">
  </Table>
</div>
</Layout>
::: -->
<script setup>
import { ref } from 'vue'
import Table from '@/components/Table/Table.tsx'
const tableRef = ref()
const dataSource = ref([
  {
    name: '张三',
    age: 18,
    sex: '男',
  },
  {
    name: '李四',
    age: 19,
    sex: '男',
  }
])
const columns = [{
  title: '序号',
  field: 'seq',
  type: 'seq'
},{
  title: '姓名',
  field: 'name',
},
{
  title: '年龄',
  field: 'age',
},
{
  title: '性别',
  field: 'sex',
}]
const selectedItems = ref([])
const contextMenu = [
  {
    label: '详情',
    fn: (c) => {},
    divided: true,
    icon: 'o',
  },
  {
    label: '删除',
    fn: async (c) => {},
    icon: 'close',
  },
]
</script>
```vue
<template>
  <template>
    <Table
      ref="tableRef"
      v-model:dataSource="dataSource"
      id="name"
      :columns="columns"
      :contextMenu="contextMenu"
      :isSort="true"
    >
    </Table>
  </template>
  <script setup>
    import { ref } from 'vue'
    import Table from '@/components/Table/Table.tsx'
    const tableRef = ref()
    const dataSource = ref([
      {
        name: '张三',
        age: 18,
        sex: '男',
      },
      {
        name: '李四',
        age: 19,
        sex: '男',
      },
    ])
    const columns = [
      {
        title: '序号',
        field: 'seq',
        type: 'seq',
      },
      {
        title: '姓名',
        field: 'name',
      },
      {
        title: '年龄',
        field: 'age',
      },
      {
        title: '性别',
        field: 'sex',
      },
    ]
    const selectedItems = ref([])
    const contextMenu = [
      {
        label: '详情',
        fn: (c) => {},
        divided: true,
        icon: 'o',
      },
      {
        label: '删除',
        fn: async (c) => {},
        icon: 'close',
      },
    ]
  </script>
</template>
```
以下是接口的文档:
## ParamsItem æŽ¥å£
用于描述表格请求参数的数据结构。
| å­—段名         | ç±»åž‹             | æè¿°             |
| -------------- | ---------------- | ---------------- |
| Sorting        | string           | æŽ’序字段         |
| SkipCount      | string \| number | è·³è¿‡çš„记录数     |
| MaxResultCount | number           | è¿”回的最大记录数 |
| å…¶ä»–字段       | any              | å…¶ä»–自定义参数   |
## ColumnType æŽ¥å£
用于描述表格列的数据结构。
| å­—段名   | ç±»åž‹             | æè¿°           |
| -------- | ---------------- | -------------- |
| title    | string           | åˆ—标题         |
| field    | string           | åˆ—字段名       |
| width    | string \| number | åˆ—宽度         |
| sortable | boolean          | æ˜¯å¦å¯æŽ’序     |
| required | boolean          | æ˜¯å¦å¿…å¡«       |
| å…¶ä»–字段 | any              | å…¶ä»–自定义参数 |
## TablePropsItemType æŽ¥å£
用于描述表格组件的属性。
| å­—段名            | ç±»åž‹              | é»˜è®¤å€¼ | æè¿°                 |
| ----------------- | ----------------- | ------ | -------------------- |
| selections        | string[]          | -      | é¢„选中项的 id æ•°ç»„   |
| autoFirstClickRow | boolean           | false  | æ˜¯å¦è‡ªåŠ¨é€‰ä¸­ç¬¬ä¸€è¡Œ   |
| params            | ParamsItem        | -      | è¯·æ±‚参数             |
| dataSource        | any[]             | []     | æ•°æ®æº               |
| columns           | ColumnType[]      | []     | åˆ—配置项             |
| pageSize          | number            | -      | æ¯é¡µæ¡æ•°             |
| total             | number            | -      | æ€»æ¡æ•°               |
| isHidePagination  | boolean           | false  | æ˜¯å¦éšè—åˆ†é¡µ         |
| isChecked         | boolean           | -      | æ˜¯å¦å¤šé€‰             |
| isSeq             | boolean           | -      | æ˜¯å¦æ˜¾ç¤ºåºå·         |
| isSort            | boolean           | -      | æ˜¯å¦æŽ’序             |
| id                | string            | -      | æ•°æ®é¡¹çš„唯一标识字段 |
| showDarg          | boolean \| string | false  | æ˜¯å¦å‡ºçŽ°æ‹–æ‹½         |
| isDrag            | boolean \| string | -      | æ˜¯å¦æ‹–拽             |
| disabledDrag      | boolean           | -      | æ˜¯å¦ç¦ç”¨æ‹–拽         |
| height            | string            | -      | è¡¨æ ¼é«˜åº¦             |
| maxHeight         | string            | -      | è¡¨æ ¼æœ€å¤§é«˜åº¦         |
| isVScroll         | boolean           | -      | æ˜¯å¦å¼€å¯è™šæ‹Ÿæ»šåЍ     |
| border            | string \| any     | -      | è¾¹æ¡†æ ·å¼             |
| url               | string            | -      | æ•°æ®è¯·æ±‚地址         |
| sortUrlTpl        | string            | -      | æŽ’序请求地址模板     |
| isFooter          | boolean           | -      | æ˜¯å¦æ˜¾ç¤ºåº•部         |
| gt                | number            | -      | -                    |
| contextMenu       | Object[]          | -      | å³é”®èœå•项数组       |
| rowConfig         | any               | -      | è¡Œé…ç½®é¡¹             |
| size              | string            | -      | è¡¨æ ¼å°ºå¯¸             |
| isStop            | boolean           | -      | æ˜¯å¦é˜»æ­¢å†’泡         |
| å…¶ä»–字段          | any               | -      | å…¶ä»–自定义参数       |
## MenuOptionType æŽ¥å£
用于描述右键菜单选项的数据结构。
| å­—段名   | ç±»åž‹   | æè¿°     |
| -------- | ------ | -------- |
| zIndex   | number | å±‚级     |
| minWidth | number | æœ€å°å®½åº¦ |
| x        | number | X è½´åæ ‡ |
| y        | number | Y è½´åæ ‡ |
## contextMenuItemType æŽ¥å£
用于描述右键菜单的状态数据结构。
| å­—段名  | ç±»åž‹                        | æè¿°               |
| ------- | --------------------------- | ------------------ |
| show    | boolean                     | æ˜¯å¦æ˜¾ç¤ºå³é”®èœå•   |
| current | Record<string, any> \| null | å½“前右键菜单项数据 |
| options | any                         | èœå•选项           |
## äº‹ä»¶
| äº‹ä»¶å            | å‚æ•°                    | æè¿°                 |
| ----------------- | ----------------------- | -------------------- |
| drag              | newIndex, oldIndex, row | æ‹–拽排序事件         |
| check             | records                 | é€‰æ‹©äº‹ä»¶             |
| sort              | row                     | æŽ’序改变事件         |
| page              | current                 | åˆ†é¡µæ”¹å˜äº‹ä»¶         |
| rowClick          | tableData               | è¡Œç‚¹å‡»äº‹ä»¶           |
| update:dataSource | dataSource              | æ›´æ–°æ•°æ®æºäº‹ä»¶       |
| clickFooter       | -                       | ç‚¹å‡»åº•部添加按钮事件 |
| update            | exposeMap               | æ›´æ–°æš´éœ²æ–¹æ³•事件     |
| load              | -                       | åŠ è½½å®Œæˆäº‹ä»¶         |
| beforeLoad        | -                       | åŠ è½½å‰äº‹ä»¶           |
| reload            | -                       | é‡æ–°åŠ è½½äº‹ä»¶         |
| update:total      | total                   | æ›´æ–°æ€»æ•°äº‹ä»¶         |
PipeLineLems/web/docs/components/TableFilter.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,98 @@
理解了,我会将属性、事件等信息用表格的形式展示出来,让文档更加清晰易读。下面是更新后的文档草稿:
---
# TableFilter è¡¨æ ¼ç­›é€‰ç»„件文档
## ç®€ä»‹
TableFilter æ˜¯ä¸€ä¸ªç”¨äºŽå®žçŽ°è¡¨æ ¼ç­›é€‰åŠŸèƒ½çš„ Vue ç»„件。它可以让用户轻松地添加、删除和修改表格的筛选条件,提升了用户对表格数据的控制和操作体验。
## ä½¿ç”¨æ–¹å¼
<Layout>
  <TableFilter icon='add-p' :columns="columns" :tableRef="tableRef" >
    <IconButton icon="f">点我筛选</IconButton>
  </TableFilter>
</Layout>
<script setup>
import TableFilter from '@/components/TableFilter/TableFilter.tsx'
import IconButton from '@/components/IconButton/IconButton.tsx'
import { ref } from 'vue'
const columns = [
  {
    prop: 'name',
    title: '姓名',
    el: 'input',
    placeholder: '请输入姓名',
  }
]
const tableRef = ref(null)
</script>
```vue
<template>
  <TableFilter icon="add-p" :columns="columns" :tableRef="tableRef">
    <IconButton icon="f">点我筛选</IconButton>
  </TableFilter>
</template>
<script setup>
import TableFilter from '@/components/TableFilter/TableFilter.tsx'
import IconButton from '@/components/IconButton/IconButton.tsx'
import { ref } from 'vue'
const columns = [
  {
    prop: 'name',
    title: '姓名',
    el: 'input',
    placeholder: '请输入姓名',
  },
]
const tableRef = ref(null)
</script>
```
## Props
| å±žæ€§å         | ç±»åž‹   | é»˜è®¤å€¼ | è¯´æ˜Ž         |
| -------------- | ------ | ------ | ------------ |
| columns        | Array  | []     | è¡¨æ ¼çš„列配置 |
| tableRef       | Object | null   | è¡¨æ ¼çš„引用   |
| modelValue     | Object | null   | å½“前组件的值 |
| text           | String | ''     | æŒ‰é’®æ–‡æœ¬     |
| fieldMap       | Object | {}     | å­—段映射     |
| options        | Array  | []     | é€‰é¡¹         |
| defaultOptions | Array  | []     | é»˜è®¤é€‰é¡¹     |
## Events
| äº‹ä»¶å            | è¯´æ˜Ž             |
| ----------------- | ---------------- |
| update:modelValue | æ›´æ–°ç»„件的值     |
| data              | å‘送数据事件     |
| change            | ç­›é€‰æ¡ä»¶å˜åŒ–事件 |
## æ’æ§½
| åç§°    | è¯´æ˜Ž                     |
| ------- | ------------------------ |
| default | ç”¨äºŽè‡ªå®šä¹‰è§¦å‘筛选的内容 |
## æ–¹æ³•
| æ–¹æ³•名        | è¯´æ˜Ž         |
| ------------- | ------------ |
| onAddFilter   | æ·»åŠ ç­›é€‰æ¡ä»¶ |
| onReset       | é‡ç½®ç­›é€‰æ¡ä»¶ |
| onSearchTable | æœç´¢è¡¨æ ¼æ•°æ® |
## æ³¨æ„äº‹é¡¹
- éœ€è¦æä¾›åˆé€‚çš„ `columns` é…ç½®å’Œ `tableRef` å¼•用才能正常使用组件。
- å¯ä»¥æ ¹æ®å…·ä½“业务需求,通过插槽自定义触发筛选的内容。
---
这样的表格形式更加直观,方便用户查阅组件的属性、事件和方法。
PipeLineLems/web/docs/components/Tag.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,154 @@
# Tag æ ‡ç­¾ç»„件文档
## ç®€ä»‹
Tag æ˜¯ä¸€ä¸ªç”¨äºŽæ˜¾ç¤ºæ ‡ç­¾æˆ–者下拉选择的 Vue ç»„件。它可以根据传入的数据和配置,展示不同样式和功能的标签,提供丰富的用户交互体验。
## ä½¿ç”¨æ–¹å¼
<Layout>
  å•标签
  <div v-for='v in [1,2,3,4]' style="width: 150px; margin-bottom: 5px;">
    <Tag>标签{{v}}</Tag>
  </div>
多个显示tip
<Tag :data="[
    {label: '标签一', value: 1},
    {label: '标签二', value: 2},
  ]">标签一</Tag>
单个显示 tip
<Tag :showTip="true" :data="[
  {label: '标签一', value: 1},
  {label: '标签二', value: 2},
]">标签一</Tag>
单个显示 tip,超出显示+
<Tag :max="3" :showTip="true" :data="[
  {label: '标签一', value: 1},
  {label: '标签二', value: 2},
  {label: '标签三', value: 3},
  {label: '标签四', value: 4},
]"></Tag>
选择 tag
<br/>
<Tag v-model="v" :options="[
  {label: '标签一', value: 1},
  {label: '标签二', value: 2},
  {label: '标签三', value: 3},
  {label: '标签四', value: 4},
]">标签一</Tag>
</Layout>
<script setup>
import Tag from '@/components/Tag/Tag.tsx'
import {ref } from 'vue'
const v = ref(1)
</script>
```vue
<template>
  å•标签
  <div v-for="v in [1, 2, 3, 4]" style="width: 150px; margin-bottom: 5px;">
    <Tag>标签{{ v }}</Tag>
  </div>
  å¤šä¸ªæ˜¾ç¤ºtip
  <Tag
    :data="[
      { label: '标签一', value: 1 },
      { label: '标签二', value: 2 },
    ]"
    >标签一</Tag
  >
  å•个显示 tip
  <Tag
    :showTip="true"
    :data="[
      { label: '标签一', value: 1 },
      { label: '标签二', value: 2 },
    ]"
    >标签一</Tag
  >
  å•个显示 tip,超出显示+
  <Tag
    :max="3"
    :showTip="true"
    :data="[
      { label: '标签一', value: 1 },
      { label: '标签二', value: 2 },
      { label: '标签三', value: 3 },
      { label: '标签四', value: 4 },
    ]"
  ></Tag>
  é€‰æ‹© tag
  <br />
  <Tag
    v-model="v"
    :options="[
      { label: '标签一', value: 1 },
      { label: '标签二', value: 2 },
      { label: '标签三', value: 3 },
      { label: '标签四', value: 4 },
    ]"
    >标签一</Tag
  >
</template>
<script setup>
import Tag from '@/components/Tag/Tag.tsx'
import { ref } from 'vue'
const v = ref(1)
</script>
```
## Props
| å±žæ€§å       | ç±»åž‹          | é»˜è®¤å€¼  | è¯´æ˜Ž                   |
| ------------ | ------------- | ------- | ---------------------- |
| data         | Array/Object  | null    | æ ‡ç­¾æ•°æ®               |
| options      | Array         | null    | ä¸‹æ‹‰é€‰é¡¹               |
| modelValue   | String/Number | ''      | ç»„件的值               |
| trigger      | String        | 'hover' | è§¦å‘方式               |
| showClose    | Boolean       | false   | æ˜¯å¦æ˜¾ç¤ºå…³é—­æŒ‰é’®       |
| showTip      | Boolean       | false   | æ˜¯å¦æ˜¾ç¤ºæç¤º           |
| valueKey     | String        | 'value' | æ•°æ®å€¼å¯¹åº”的键名       |
| labelKey     | String        | 'label' | æ ‡ç­¾æ˜¾ç¤ºæ–‡æœ¬å¯¹åº”的键名 |
| defaultValue | String        | ''      | é»˜è®¤å€¼                 |
| max          | Number        | 999     | æœ€å¤§å€¼                 |
## Events
| äº‹ä»¶å            | è¯´æ˜Ž         |
| ----------------- | ------------ |
| click             | ç‚¹å‡»äº‹ä»¶     |
| update:modelValue | æ›´æ–°ç»„件的值 |
| change            | å€¼å˜åŒ–事件   |
| mouseenter        | é¼ æ ‡ç§»å…¥äº‹ä»¶ |
| update:data       | æ›´æ–°æ•°æ®äº‹ä»¶ |
## æ–¹æ³•
| æ–¹æ³•名          | è¯´æ˜Ž                   |
| --------------- | ---------------------- |
| onCommand       | é€‰é¡¹å‘½ä»¤äº‹ä»¶           |
| onClose         | å…³é—­æ ‡ç­¾äº‹ä»¶           |
| onVisibleChange | ä¸‹æ‹‰é€‰æ‹©å¯è§æ€§å˜åŒ–事件 |
| onClick         | ç‚¹å‡»äº‹ä»¶               |
| onMouseenter    | é¼ æ ‡ç§»å…¥äº‹ä»¶           |
## æ’æ§½
| åç§°    | è¯´æ˜Ž         |
| ------- | ------------ |
| default | é»˜è®¤å†…容插槽 |
---
这样的表格形式能够更清晰地展示组件的各项属性、事件、方法和插槽,便于用户快速查阅和使用。
```
```
PipeLineLems/web/docs/components/TdButton.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,77 @@
# TdButton å•元格按钮组件文档
## ç®€ä»‹
TdButton æ˜¯ä¸€ä¸ªç”¨äºŽå•元格中显示按钮的 Vue ç»„件。它可以根据传入的文本、图标和配置,展示不同样式和功能的按钮,并支持禁用状态和鼠标悬停效果。
## ä½¿ç”¨æ–¹å¼
<Layout>
  <div style="width: 300px;">
    <TdButton
      :text="text"
      icon="add-p"
      tip="按钮文本"
      :disabled="disabled"
    >
      æŒ‰é’®æ–‡æœ¬
    </TdButton>
  </div>
</Layout>
<script setup>
import { ref } from 'vue'
import TdButton from '@/components/TdButton/TdButton.tsx'
const text = ref('测试') // æŒ‰é’®æ–‡æœ¬
const icon = ref('') // æŒ‰é’®å›¾æ ‡
const tip = ref('') // æŒ‰é’®æç¤ºä¿¡æ¯
const disabled = ref(false) // æ˜¯å¦ç¦ç”¨æŒ‰é’®
</script>
```vue
<template>
  <div style="width: 300px;">
    <TdButton :text="text" icon="add-p" tip="按钮文本" :disabled="disabled">
      æŒ‰é’®æ–‡æœ¬
    </TdButton>
  </div>
</template>
<script setup>
import { ref } from 'vue'
import TdButton from '@/components/TdButton/TdButton.tsx'
const text = ref('测试') // æŒ‰é’®æ–‡æœ¬
const icon = ref('') // æŒ‰é’®å›¾æ ‡
const tip = ref('') // æŒ‰é’®æç¤ºä¿¡æ¯
const disabled = ref(false) // æ˜¯å¦ç¦ç”¨æŒ‰é’®
</script>
```
## Props
| å±žæ€§å   | ç±»åž‹    | é»˜è®¤å€¼ | è¯´æ˜Ž                 |
| -------- | ------- | ------ | -------------------- |
| text     | String  | ''     | æŒ‰é’®æ–‡æœ¬             |
| icon     | String  | ''     | æŒ‰é’®å›¾æ ‡             |
| tip      | String  | ''     | æŒ‰é’®æç¤ºä¿¡æ¯         |
| disabled | Boolean | false  | æ˜¯å¦ç¦ç”¨æŒ‰é’®         |
| hover    | Boolean | false  | æ˜¯å¦æ˜¾ç¤ºé¼ æ ‡æ‚¬åœæ•ˆæžœ |
| style    | Object  | {}     | è‡ªå®šä¹‰æ ·å¼           |
## Events
| äº‹ä»¶å | è¯´æ˜Ž     |
| ------ | -------- |
| click  | ç‚¹å‡»äº‹ä»¶ |
## æ’æ§½
| åç§°    | è¯´æ˜Ž         |
| ------- | ------------ |
| default | é»˜è®¤å†…容插槽 |
---
这样的文档形式清晰地展示了组件的属性、事件和插槽,同时使用了 `setup` å‡½æ•°ç®¡ç†ç»„件的状态和逻辑,使代码更加简洁和可维护。
PipeLineLems/web/docs/components/Text.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,98 @@
# Text æ–‡æœ¬ç»„件文档
## ç®€ä»‹
Text æ˜¯ä¸€ä¸ªç”¨äºŽæ˜¾ç¤ºæ–‡æœ¬å†…容的 Vue ç»„件。它支持文本截断、自定义标签、行数限制、提示信息以及样式设置。
## ä½¿ç”¨æ–¹å¼
<Text>
  <Text
    :truncated="truncated"
    :tag="tag"
    :lineClamp="lineClamp"
    :tip="tip"
    :fontSize="fontSize"
    :color="color"
    @click="handleClick"
  >
    <!-- æ–‡æœ¬å†…容插槽 -->
    æˆ‘是文本内容
  </Text>
</Text>
<script setup>
import { ref } from 'vue'
import Text from '@/components/Text/Text.tsx'
const truncated = ref(false) // æ˜¯å¦æ–‡æœ¬æˆªæ–­
const tag = ref('span') // è‡ªå®šä¹‰æ ‡ç­¾ï¼Œé»˜è®¤ä¸º span
const lineClamp = ref(1) // è¡Œæ•°é™åˆ¶ï¼Œé»˜è®¤ä¸º 1
const tip = ref('1111') // æç¤ºä¿¡æ¯
const fontSize = ref('') // å­—体大小
const color = ref('') // æ–‡æœ¬é¢œè‰²
const handleClick = () => {
  // ç‚¹å‡»äº‹ä»¶å¤„理逻辑
}
</script>
```vue
<template>
  <Text
    :truncated="truncated"
    :tag="tag"
    :lineClamp="lineClamp"
    :tip="tip"
    :fontSize="fontSize"
    :color="color"
    @click="handleClick"
  >
    <!-- æ–‡æœ¬å†…容插槽 -->
    æˆ‘是文本内容
  </Text>
</template>
<script setup>
import { ref } from 'vue'
import Text from '@/components/Text/Text.tsx'
const truncated = ref(false) // æ˜¯å¦æ–‡æœ¬æˆªæ–­
const tag = ref('span') // è‡ªå®šä¹‰æ ‡ç­¾ï¼Œé»˜è®¤ä¸º span
const lineClamp = ref(1) // è¡Œæ•°é™åˆ¶ï¼Œé»˜è®¤ä¸º 1
const tip = ref('') // æç¤ºä¿¡æ¯
const fontSize = ref('') // å­—体大小
const color = ref('') // æ–‡æœ¬é¢œè‰²
const handleClick = () => {
  // ç‚¹å‡»äº‹ä»¶å¤„理逻辑
}
</script>
```
## Props
| å±žæ€§å    | ç±»åž‹    | é»˜è®¤å€¼ | è¯´æ˜Ž                    |
| --------- | ------- | ------ | ----------------------- |
| truncated | Boolean | false  | æ˜¯å¦æ–‡æœ¬æˆªæ–­            |
| tag       | String  | 'span' | è‡ªå®šä¹‰æ ‡ç­¾ï¼Œé»˜è®¤ä¸º span |
| lineClamp | Number  | 1      | è¡Œæ•°é™åˆ¶ï¼Œé»˜è®¤ä¸º 1      |
| tip       | String  | ''     | æç¤ºä¿¡æ¯                |
| fontSize  | String  | ''     | å­—体大小                |
| color     | String  | ''     | æ–‡æœ¬é¢œè‰²                |
## Events
| äº‹ä»¶å | è¯´æ˜Ž     |
| ------ | -------- |
| click  | ç‚¹å‡»äº‹ä»¶ |
## æ’æ§½
| åç§°    | è¯´æ˜Ž         |
| ------- | ------------ |
| default | æ–‡æœ¬å†…容插槽 |
---
这样的文档形式清晰地展示了组件的属性、事件和插槽,同时使用了 `setup` å‡½æ•°ç®¡ç†ç»„件的状态和逻辑,使代码更加简洁和可维护。
PipeLineLems/web/docs/components/Title.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,85 @@
# Title æ ‡é¢˜ç»„件文档
## ç®€ä»‹
Title ç»„件用于显示页面标题或带有描述的标题。它支持设置描述信息、上边距和下边距,并提供点击事件。
## ä½¿ç”¨æ–¹å¼
<Layout>
  <Title
    :desc="desc"
    :top="top"
    :bottom="bottom"
    @click="handleClick"
  >
    <!-- æ ‡é¢˜å†…容插槽 -->
    æ ‡é¢˜å†…容
    <!-- æè¿°å†…容插槽 -->
    <template #content>
      æè¿°å†…容
    </template>
  </Title>
</Layout>
<script setup>
import { ref } from 'vue'
import Title from '@/components/Title/Title.tsx'
const desc = ref('') // æè¿°ä¿¡æ¯
const top = ref(0) // ä¸Šè¾¹è·
const bottom = ref(0) // ä¸‹è¾¹è·
const handleClick = () => {
  // ç‚¹å‡»äº‹ä»¶å¤„理逻辑
}
</script>
```vue
<template>
  <Title :desc="desc" :top="top" :bottom="bottom" @click="handleClick">
    <!-- æ ‡é¢˜å†…容插槽 -->
    æ ‡é¢˜å†…容
    <!-- æè¿°å†…容插槽 -->
    <template #content> æè¿°å†…容 </template>
  </Title>
</template>
<script setup>
import { ref } from 'vue'
import Title from '@/components/Title/Title.tsx'
const desc = ref('') // æè¿°ä¿¡æ¯
const top = ref(0) // ä¸Šè¾¹è·
const bottom = ref(0) // ä¸‹è¾¹è·
const handleClick = () => {
  // ç‚¹å‡»äº‹ä»¶å¤„理逻辑
}
</script>
```
## Props
| å±žæ€§å | ç±»åž‹   | é»˜è®¤å€¼ | è¯´æ˜Ž     |
| ------ | ------ | ------ | -------- |
| desc   | String | ''     | æè¿°ä¿¡æ¯ |
| top    | Number | 0      | ä¸Šè¾¹è·   |
| bottom | Number | 0      | ä¸‹è¾¹è·   |
## Events
| äº‹ä»¶å | è¯´æ˜Ž     |
| ------ | -------- |
| click  | ç‚¹å‡»äº‹ä»¶ |
## æ’æ§½
| åç§°    | è¯´æ˜Ž         |
| ------- | ------------ |
| default | æ ‡é¢˜å†…容插槽 |
| content | æè¿°å†…容插槽 |
---
这样的文档形式清晰地展示了组件的属性、事件和插槽,同时使用了 `setup` å‡½æ•°ç®¡ç†ç»„件的状态和逻辑,使代码更加简洁和可维护。
PipeLineLems/web/docs/components/TouchScale.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,68 @@
# TouchScale è§¦æ‘¸ç¼©æ”¾ç»„件文档
## ç®€ä»‹
TouchScale ç»„件用于实现触摸屏上的图像缩放效果。通过触摸屏幕,用户可以对图像进行放大、缩小和移动操作。
## ä½¿ç”¨æ–¹å¼
<Layout>
  ç”¨æ‰‹è§¦æ‘¸å±å¹•,对图像进行放大、缩小和移动操作。
  <TouchScale>
    <!-- å›¾åƒå†…容插槽 -->
    <img src="https://images.pexels.com/photos/7945944/pexels-photo-7945944.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="..." />
  </TouchScale>
</Layout>
<script setup>
import TouchScale from '@/components/TouchScale/index.vue'
</script>
```vue
<template>
  <TouchScale>
    <!-- å›¾åƒå†…容插槽 -->
    <img
      src="https://images.pexels.com/photos/7945944/pexels-photo-7945944.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
      alt="..."
    />
  </TouchScale>
</template>
<script setup>
import TouchScale from '@/components/TouchScale/index.vue'
</script>
```
## æ’æ§½
| åç§°    | è¯´æ˜Ž         |
| ------- | ------------ |
| default | å›¾åƒå†…容插槽 |
## å±žæ€§
TouchScale ç»„件暂不支持任何属性。
## äº‹ä»¶
TouchScale ç»„件暂不支持任何事件。
## æ ·å¼
TouchScale ç»„件提供了基本的样式,可以通过以下方式进行定制:
```scss
// è‡ªå®šä¹‰æ ·å¼
._touchScale {
  /* Your styles here */
}
._touch-mask {
  /* Your styles here */
}
```
---
这样的文档形式清晰地介绍了组件的功能和用法,并提供了样式定制的示例。
PipeLineLems/web/docs/components/Upload.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,87 @@
# ä¸Šä¼ ç»„件文档
## ç®€ä»‹
上传组件提供了一个方便的方式来将文件上传到服务器。它利用了 Element Plus ä¸­çš„ `el-upload` ç»„件,并提供了自定义选项。
## ä½¿ç”¨æ–¹æ³•
<Layout>
  <Upload
    accept=".pdf,.doc,.docx"
    showFileList
    :msg="uploadMessage"
    @success="handleUploadSuccess"
  >
    <el-button type='primary'> ç‚¹å‡»ä¸Šä¼ æ–‡ä»¶ </el-button>
  </Upload>
</Layout>
<script setup>
import Upload from '@/components/Upload/index.vue'
const uploadMessage = '文件上传成功'
const handleUploadSuccess = (data) => {
  console.log('文件已上传:', data)
}
</script>
```vue
<template>
  <Upload
    accept=".pdf,.doc,.docx"
    showFileList
    :msg="uploadMessage"
    @success="handleUploadSuccess"
  >
    <el-button type="primary"> ç‚¹å‡»ä¸Šä¼ æ–‡ä»¶ </el-button>
  </Upload>
</template>
<script setup>
import Upload from '@/components/Upload/index.vue'
const uploadMessage = '文件上传成功'
const handleUploadSuccess = (data) => {
  console.log('文件已上传:', data)
}
</script>
```
## æ’æ§½
| åç§°    | æè¿°             |
| ------- | ---------------- |
| default | é»˜è®¤æ’槽内容     |
| text    | ä¸Šä¼ æŒ‰é’®æ–‡æœ¬æ’æ§½ |
## å±žæ€§
| åç§°         | ç±»åž‹   | é»˜è®¤å€¼ | æè¿°                               |
| ------------ | ------ | ------ | ---------------------------------- |
| accept       | å­—符串 |        | æŒ‡å®šæ–‡ä»¶ç±»åž‹ï¼Œå¤šä¸ªç±»åž‹ä½¿ç”¨é€—号分隔 |
| showFileList | å¸ƒå°”值 | false  | æ˜¯å¦åœ¨ä¸Šä¼ åŽæ˜¾ç¤ºæ–‡ä»¶åˆ—表           |
| msg          | å­—符串 |        | ä¸Šä¼ æˆåŠŸåŽæ˜¾ç¤ºçš„æç¤ºä¿¡æ¯           |
## äº‹ä»¶
| åç§°    | æè¿°                     |
| ------- | ------------------------ |
| success | æ–‡ä»¶ä¸Šä¼ æˆåŠŸæ—¶è§¦å‘çš„äº‹ä»¶ |
## æ–¹æ³•
上传组件不暴露任何自定义方法。
## æ ·å¼
上传组件提供了基本样式,可以使用 CSS è¿›è¡Œè‡ªå®šä¹‰ã€‚以下是一个示例:
```scss
.upload {
  font-size: 25px;
  color: #777;
}
```
PipeLineLems/web/docs/components/Variable.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,111 @@
# å˜é‡ç»„件文档
## ç®€ä»‹
变量组件用于选择或输入变量值。它提供了选择变量的功能,并支持多选和单选模式。
## ä½¿ç”¨æ–¹æ³•
输入框类型为 `默认`
<Layout>
<Variable
    v-model="variableValue"
    :isClose="true"
    :clearable="true"
    :isMultiple="false"
  />
</Layout>
输入框类型为 `input`
<Layout>
<Variable
    v-model="variableValue"
    :isClose="true"
    :clearable="true"
    :isMultiple="false"
    type="input"
  />
</Layout>
输入框类型为 `select`
<Layout>
<Variable
    v-model="variableValue"
    :isClose="true"
    :clearable="true"
    :isMultiple="false"
    type="select"/>
</Layout>
<script setup>
import Variable from '@/components/Variable/Variable.tsx'
import {ref} from 'vue'
const variableValue = ref('') // ç»‘定的变量值
</script>
```vue
<template>
  <Variable
    v-model="variableValue"
    :isClose="true"
    :clearable="true"
    :isMultiple="false"
    type="input"
  />
</template>
<script setup>
import Variable from '@/components/Variable/Variable.tsx'
import { ref } from 'vue'
const variableValue = ref('') // ç»‘定的变量值
</script>
```
## å±žæ€§
| å±žæ€§åç§°   | ç±»åž‹          | é»˜è®¤å€¼ | æè¿°                                         |
| ---------- | ------------- | ------ | -------------------------------------------- |
| modelValue | String/Number | -      | ç»‘定的变量值                                 |
| isClose    | Boolean       | false  | æ˜¯å¦æ˜¾ç¤ºå…³é—­å›¾æ ‡ï¼ˆä»…对 `input` ç±»åž‹æœ‰æ•ˆï¼‰    |
| clearable  | Boolean       | false  | æ˜¯å¦å¯æ¸…除输入内容(仅对 `input` ç±»åž‹æœ‰æ•ˆï¼‰  |
| dataSource | Array         | []     | æ•°æ®æºï¼ˆä»…对多选模式有效)                   |
| isMultiple | Boolean       | false  | æ˜¯å¦æ”¯æŒå¤šé€‰æ¨¡å¼                             |
| index      | Number        | 0      | æ•°æ®æºç´¢å¼•(仅对多选模式有效)               |
| field      | String        | ''     | æ•°æ®æºå­—段名称(仅对多选模式有效)           |
| type       | String        | ''     | è¾“入框类型,可选值为 `'input'` æˆ– `'select'` |
## äº‹ä»¶
| äº‹ä»¶åç§°          | æè¿°                   |
| ----------------- | ---------------------- |
| update:modelValue | å˜é‡å€¼æ›´æ–°æ—¶è§¦å‘的事件 |
| update:dataSource | æ•°æ®æºæ›´æ–°æ—¶è§¦å‘的事件 |
| change            | å˜é‡å€¼å˜åŒ–时触发的事件 |
## æ’æ§½
| æ’槽名称 | æè¿°             |
| -------- | ---------------- |
| é»˜è®¤æ’æ§½ | æ›¿æ¢é»˜è®¤å†…容     |
| text     | æ›¿æ¢ä¸Šä¼ æŒ‰é’®æ–‡æœ¬ |
## æ–¹æ³•
变量组件不暴露任何自定义方法。
## æ ·å¼
变量组件提供了基本样式,可以使用 CSS è¿›è¡Œè‡ªå®šä¹‰ã€‚以下是一个示例:
```scss
.variable {
  font-size: 16px;
  color: #333;
}
```
---
该文档提供了 "变量" ç»„件的详细介绍,包括属性、事件、插槽、方法和样式选项。
PipeLineLems/web/docs/components/index.md
PipeLineLems/web/docs/index.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
---
# https://vitepress.dev/reference/default-theme-home-page
layout: home
hero:
  name: 'information-ui'
  text: '前端信息化组件库'
  tagline: '基于Vue3.x开发的组件库'
  image:
    # æƒ³æ‰§è¡Œè¯¥å‘½ä»¤ï¼Œéœ€è¦å…ˆæŠŠvue版本升级到3.3+,或者把package.json中的vue删除,重新安装,编译好了后,再恢复
    src: /tt.png
    alt: lems
  actions:
    - theme: brand
      text: å¿«é€Ÿå¼€å§‹
      link: /quick-start
    - theme: alt
      text: æŸ¥çœ‹ç»„ä»¶
      link: /components/BaseContent
features:
  - title: é›†æˆ
    details: é›†æˆäºŽä¿¡æ¯åŒ–标准组件开发
  - title: å¤ç”¨
    details: ç»„ä»¶API复用,减少开发成本
  - title: æ ·å¼ç»Ÿä¸€
    details: æ ·å¼åŸºäºŽä¿¡æ¯åŒ–组件标准,省去样式走查步骤
---
PipeLineLems/web/docs/markdown-examples.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,85 @@
# Markdown Extension Examples
This page demonstrates some of the built-in markdown extensions provided by VitePress.
## Syntax Highlighting
VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting:
**Input**
````md
```js{4}
export default {
  data () {
    return {
      msg: 'Highlighted!'
    }
  }
}
```
````
**Output**
```js{4}
export default {
  data () {
    return {
      msg: 'Highlighted!'
    }
  }
}
```
## Custom Containers
**Input**
```md
::: info
This is an info box.
:::
::: tip
This is a tip.
:::
::: warning
This is a warning.
:::
::: danger
This is a dangerous warning.
:::
::: details
This is a details block.
:::
```
**Output**
::: info
This is an info box.
:::
::: tip
This is a tip.
:::
::: warning
This is a warning.
:::
::: danger
This is a dangerous warning.
:::
::: details
This is a details block.
:::
## More
Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown).
PipeLineLems/web/docs/postcss.confg.mts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
import { postcssIsolateStyles } from 'vitepress'
import tailwindcss from 'tailwindcss'
import autoprefixer from 'autoprefixer'
export default {
  plugins: [
    postcssIsolateStyles({
      includeFiles: [/vp-doc\.css/],
    }),
    tailwindcss(),
    autoprefixer(),
  ],
}
PipeLineLems/web/docs/public/lems.png
PipeLineLems/web/docs/public/tt.png
PipeLineLems/web/docs/quick-start.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
# å¿«é€Ÿå¼€å§‹
### æœ¬èŠ‚å°†ä»‹ç»å¦‚ä½•åœ¨é¡¹ç›®ä¸­ä½¿ç”¨ Information Ui。
## å®‰è£…
```js
npm install information-ui --save
or
yarn add information-ui
```
PipeLineLems/web/docs/vite.config.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,87 @@
import { defineConfig, loadEnv } from 'vite'
import vueJsx from '@vitejs/plugin-vue-jsx'
import path from 'path'
import VueTypeImports from 'vite-plugin-vue-type-imports'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import Components from 'unplugin-vue-components/vite'
const nodeEnv = process.env.NODE_ENV || 'production'
const env = loadEnv(nodeEnv, __dirname)
// import { postcssIsolateStyles } from 'vitepress'
// import tailwindcss from 'tailwindcss'
// import autoprefixer from 'autoprefixer'
const suffix = process.env.NODE_ENV === 'development' ? '' : '.ssr'
console.log(suffix, 'suffix')
export default defineConfig({
  ssr: {
    noExternal: ['element-plus'],
  },
  plugins: [
    vueJsx(),
    VueTypeImports(),
    Components({
      include: [/\.vue$/, /\.vue\?vue/, /\.md$/, /\.tsx/, /\.jsx/],
      resolvers: [
        ElementPlusResolver({
          importStyle: 'sass',
        }),
      ],
    }),
  ],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, '../src'),
      components: path.resolve(__dirname, '../src/components'),
      sdk: path.resolve(__dirname, `../src/cms/sdk.es${suffix}.js`),
    },
  },
  server: {
    host: '0.0.0.0',
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
    origin: `//localhost:${env.VITE_PORT}`,
    // open: true,
    port: env.VITE_PORT,
    cors: true,
    proxy: {
      '/api': {
        target: env.VITE_API_URL,
        changeOrigin: true,
      },
      '/hubs': {
        target: env.VITE_API_URL,
        changeOrigin: true,
        ws: true,
      },
    },
  },
  build: {
    outDir: 'dist',
    sourcemap: false,
    chunkSizeWarningLimit: 1500,
    target: 'ES2022',
    rollupOptions: {},
    //   output: {
    //     entryFileNames: `index.js`,
    //   },
  },
  css: {
    // postcss: {
    //   plugins: [
    //     postcssIsolateStyles({
    //       includeFiles: [/vp-doc\.css/],
    //     }),
    //     tailwindcss(),
    //     autoprefixer(),
    //   ],
    // },
    preprocessorOptions: {
      scss: {
        additionalData: `
          @use "@/assets/styles/element.scss" as *;
        `,
      },
    },
  },
})
PipeLineLems/web/env.d.ts
@@ -7,3 +7,4 @@
}
declare module 'koa-compose'
declare module 'd3'
PipeLineLems/web/index.html
@@ -4,13 +4,11 @@
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!-- <link rel="stylesheet" href="http://at.alicdn.com/t/font_3273823_ids2nmnjyss.css"> -->
    <title>information-standard</title>
    <title>信息化组件LMES</title>
    <script>
      window.cmstype = 'Web'
    </script>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
PipeLineLems/web/package-lock.json
ÎļþÌ«´ó
PipeLineLems/web/package.json
@@ -1,22 +1,39 @@
{
  "name": "information-base",
  "version": "1.0.0",
  "version": "2.3.0",
  "files": [
    "sdk",
    "packages.json"
  ],
  "scripts": {
    "menu": "node ./script/autoMenu.js",
    "menu": "node ./script/generateMenu.js",
    "pull": "bash pull.sh",
    "checkout": "bash checkout.sh",
    "dev": "npm run menu && vite --host",
    "docs:dev": "vitepress dev docs",
    "docs:build": "vitepress build docs",
    "docs:preview": "vitepress preview docs",
    "tag": "bash release.sh",
    "mac": "cd ./script/ && ./menu && cd ../ && vite --host",
    "open": "vite --host --open",
    "build": "node ./script/build.js",
    "build:go": "cd app && bash build.sh",
    "build:all": "npm run menu && vite build",
    "build-lib": "vite build --config=vite.lib.config.ts",
    "build-lib": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build --config=vite.lib.config.ts --mode",
    "build:custom": "cross-env NODE_OPTIONS=--max-old-space-size=8192 NODE_TYPE=custom vite build --config=vite.lib.config.ts",
    "build:widget": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build --config=vite.build.config.ts && node ./script/replace.ts",
    "widgets": "vite build --config=vite.lib.config.ts",
    "serve": "vite preview --host --port=8888",
    "build-serve": "npm run build && npm run serve",
    "vue-tsc": "vue-tsc --noEmit",
    "test": "jest"
    "test": "jest",
    "publish:prod:ui": "npm run build:widget && cd publish/information-ui && npm publish",
    "publish:beta": "node --experimental-json-modules publish.js beta",
    "publish:prod": "node --experimental-json-modules publish.js",
    "rust": "cd rust && cargo run"
  },
  "engines": {
    "node": ">=18.0.0"
  },
  "dependencies": {
    "@antv/g6": "4.8.24",
@@ -38,6 +55,7 @@
    "axios": "0.24.0",
    "chalk": "4.1.2",
    "cross-env": "7.0.3",
    "cross-spawn": "7.0.5",
    "d3": "7.9.0",
    "d3-hierarchy": "3.1.2",
    "dayjs": "1.11.10",
@@ -68,6 +86,7 @@
    "vue-i18n": "9.1.9",
    "vue-router": "4.0.11",
    "vue-virtual-scroller": "1.1.2",
    "vue3-cookies": "^1.0.6",
    "vuex": "4.0.2",
    "vxe-table": "4.6.20",
    "vxe-table-plugin-element": "3.1.0",
@@ -76,7 +95,6 @@
    "zip-a-folder": "3.1.7"
  },
  "devDependencies": {
    "cross-spawn": "7.0.5",
    "@arco-plugins/vite-vue": "1.4.5",
    "@babel/core": "7.26.0",
    "@babel/preset-typescript": "7.16.7",
@@ -106,7 +124,7 @@
    "jest": "27.5.1",
    "kill-port": "^2.0.1",
    "less": "^4.2.2",
    "lmes-create-widget": "1.0.6",
    "lmes-create-widget": "^1.0.2",
    "postcss": "8.4.38",
    "postcss-prefix-selector": "1.16.0",
    "rollup-plugin-copy": "3.5.0",
@@ -132,4 +150,4 @@
    "vitepress": "1.0.1",
    "vue-tsc": "^2.1.10"
  }
}
}
PipeLineLems/web/public/language/Common.en-US.json
@@ -1,3 +1,2146 @@
{
  "MesSuite": { "测试": "test" }
}
  "BarcodeManagement": {
    "添加条码段": "Add barcode segment",
    "请选择工序": "Please select a process",
    "请选择产品": "Please select a product",
    "适用产品": "Applicable product",
    "适用工序": "Applicable process",
    "选择": "Select",
    "解析规则": "Parsing rule",
    "校验规则": "Verification rule",
    "3、产品型号:取校验条码的该段与该工序生产型号进行比对": "3. Product model: Compare the section of the verification barcode with the production model of the process.",
    "4、固定字符:取实际的条码段与当前解析的条码段内容进行比对": "4. Fixed characters: Compare the actual barcode segment with the content of the currently parsed barcode segment.",
    "固定字符校验": "Fixed character check",
    "条码段类型": "Barcode Segment Type",
    "条码字段": "Barcode Field",
    "排序": "Sort",
    "格式": "Format",
    "自增数字类型时,按型号/日期自增": "For auto-incrementing number type, increment by model/date",
    "业务对象": "Business Object",
    "关联字段详情": "Details of Associated Fields",
    "父级ID": "Parent ID",
    "关联条码下拉框对应的下拉框key值": "Key value of the drop-down box corresponding to the associated barcode drop-down box",
    "变量名": "Variable Name",
    "状态": "Status",
    "条码段组成": "Barcode Segment Composition",
    "更新时间": "Update Time",
    "操作": "Operation",
    "具体规则": "Specific Rules",
    "起止符号": "Start and End Symbols",
    "校验条码段": "Verification Barcode Segment",
    "校验类型中文": "Verification Type (Chinese)",
    "校验条码": "Verification Barcode",
    "原始条码": "Original Barcode",
    "解析条码段": "Parsed Barcode Segment",
    "条码解析规则": "Barcode parsing rules",
    "条码生成规则": "Barcode generation rules",
    "条码校验规则": "Barcode verification rules",
    "新增": "Add",
    "编辑": "Edit",
    "删除": "Delete",
    "查看": "View",
    "请输入解析规则名称": "Please enter the name of the parsing rule",
    "使用中": "In use",
    "未使用": "Not in use",
    "请选择一个规则进行查看!": "Please select a rule to view!",
    "仅支持编辑单个查看!": "Only single viewing for editing is supported!",
    "请选择一个规则进行编辑!": "Please select a rule to edit!",
    "仅支持编辑单个规则!": "Only a single rule can be edited!",
    "请选择条码进行删除!": "Please select barcodes to delete!",
    "条码删除后不可恢复,是否确认删除?": "Once barcodes are deleted, they cannot be recovered. Are you sure you want to delete them?",
    "删除成功": "Delete successful",
    "查看规则": "View rules",
    "编辑规则": "Edit rules",
    "新增规则": "Add rules",
    "规则名称": "Rule name",
    "条码示例": "Barcode example",
    "解析类型": "Parsing type",
    "输入数字和英文逗号,以,号分割": "Enter numbers and English commas, separated by commas",
    "例如:2,12,1": "For example: 2, 12, 1",
    "分隔符号": "Separator symbol",
    "起始符号": "Starting symbol",
    "终止符号": "Ending symbol",
    "解析": "Parse",
    "固定长度": "Fixed length",
    "序号": "Serial number",
    "条码段名称": "Barcode segment name",
    "条码段内容": "Barcode segment content",
    "位数": "Number of digits",
    "存在重复的条码段名字": "There are duplicate barcode segment names",
    "保存成功": "Save successful",
    "请输入生成规则名称": "Please enter the name of the generation rule",
    "请输入校验规则名称": "Please enter the name of the verification rule",
    "请输入": "Please enter",
    "校验类型": "check type",
    "长度校验": "Length verification",
    "条码段": "Barcode segment",
    "根据条码解析规则解析出来条码段,选择需要检验的条码段": "Parse out barcode segments according to the barcode parsing rules and select the barcode segments that need to be verified",
    "1、长度:取校验条码该段进行长度比对": "1. Length: Take this segment of the verification barcode for length comparison",
    "2、物料编号:取校验条码的该段与该工序生产所需的物料编号进行比对": "2. Material number: Take this segment of the verification barcode and compare it with the material number required for production in this process",
    "长度": "Length",
    "物料编号": "Material number",
    "产品型号": "Product model",
    "备注": "Remarks",
    "至少有一个条码段选择了校验类型": "At least one barcode segment has selected a verification type",
    "条码规则名称": "Barcode rule name",
    "自定义条码段": "Custom barcode segment",
    "添加": "Add",
    "条码段来源": "Barcode source",
    "生成条码效果": "Generated barcode effect",
    "时间格式校验不正确,请重新输入": "The time format verification is incorrect. Please re-enter.",
    "条码段名称重复": "Barcode segment names are duplicated",
    "请输入必填项": "Please enter the required items",
    "条码编辑成功": "Barcode editing is successful",
    "条码段示例": "Barcode segment example",
    "自增数字规则": "Auto-incrementing number rule",
    "移动失败": "Move failed",
    "请选择": "Please select",
    "自增数字进制": "Auto-incrementing number system",
    "字段类型": "Field type",
    "根据条码解析规则解析出来条码段,选择需要用于生成规则的条码段": "Parse out barcode segments according to the barcode parsing rules and select the barcode segments that need to be used for the generation rule",
    "系统时间": "System time",
    "时间格式": "Time format",
    "关联规则": "Associated rule",
    "关联字段": "Associated field",
    "关联变量": "Associated variable",
    "按工单自增": "Auto-increment by work order",
    "按型号自增": "Auto-increment by model",
    "按日期自增": "Auto-increment by date",
    "十进制": "Decimal system",
    "二进制": "Binary system",
    "八进制": "Octal system",
    "十六进制": "Hexadecimal system",
    "系统字段": "System field",
    "业务字段": "Business field",
    "固定字符": "Fixed character",
    "自增数字": "Auto-incrementing number",
    "不能为空": "Cannot be empty",
    "工单": "Work order",
    "字段名称": "Field name",
    "总位数校验": "Total digit verification",
    "条码段位数校验": "Barcode segment digit verification",
    "条码段内容校验": "Barcode segment content verification",
    "提示": "Prompt",
    "取消": "Cancel",
    "确认": "Confirm",
    "规则解析字段": "Rule parsing field",
    "按型号/日期自增": "Auto-increment by model/date",
    "按工单/日期自增": "Auto-increment by work order/date",
    "添加/编辑条码段": "Add/edit barcode segment",
    "条码长度": "Barcode length"
  },
  "InspectionManagement": {
    "工序名称": "Process Name",
    "是否开始点检任务": "Do you want to start the inspection task",
    "产线段": "Production Line Segment",
    "开始时间": "Start Time",
    "结束时间": "End Time",
    "请输入模版名称": "Please input template name",
    "产品名称": "Product name",
    "模版名称": "Template name",
    "右侧为实际结果": "The actual results are on the right side",
    "左侧为预期结果": "The expected results are on the left side",
    "点检明细": "Details of spot inspection",
    "未激活": "Not activated",
    "点检任务管理": "Spot inspection task management",
    "点检记录": "Spot inspection records",
    "点检追溯报表": "Spot inspection traceability reports",
    "报表配置": "Report configuration",
    "序号": "Serial number",
    "产品型号": "Product model",
    "操作时间": "Operation time",
    "操作人": "Operator",
    "操作": "Operation",
    "添加": "Add",
    "点检任务模板": "Inspection task template",
    "详情": "Details",
    "已激活": "Activated",
    "开始": "Start",
    "暂停": "Pause",
    "结束": "End",
    "激活": "Activate",
    "编辑": "Edit",
    "查看": "View",
    "删除": "Delete",
    "时间范围": "Time range",
    "请输入关键字": "Please enter keywords",
    "点检任务名称": "Inspection task name",
    "创建时间": "Creation time",
    "操作用户": "Operating user",
    "点检件名称": "Inspection part name",
    "点检件ID": "Check item ID",
    "点检类型": "Check type",
    "第": "The",
    "次": "th time",
    "完成条件": "Completion condition",
    "点检次数": "Number of inspection times",
    "状态": "Status",
    "结果": "Result",
    "点检路线": "Inspection route",
    "备注": "Remarks",
    "创建副本": "Create a copy",
    "点检件编号": "Inspection part number",
    "配方": "Formula",
    "新增点检模板": "Add inspection template",
    "点检结果查看": "Check result viewing",
    "添加任务": "Add task",
    "请输入点检件名称": "Please enter the inspection part name",
    "点检件名称不允许为空": "The inspection part name is not allowed to be empty",
    "请输入点检件编号": "Please enter the inspection part number",
    "请输入配方": "Please enter the formula",
    "配方不允许为空": "The formula is not allowed to be empty",
    "请输入备注": "Please enter the remarks",
    "确认删除选中模版": "Confirm to delete the selected template",
    "请选择任务模版": "Please select the task template",
    "复制成功": "Copy successful",
    "不通过": "Fail",
    "通过": "Pass",
    "请输入": "Please enter",
    "修改": "Modify",
    "修改结果": "Modify result",
    "点检结果": "Inspection result",
    "预期结果": "Expected result",
    "详情参数": "Detail parameters",
    "点检详情参数查看": "Inspection detail parameter viewing",
    "请选择点检任务": "Please select the inspection task",
    "确认删除选择任务": "Confirm to delete the selected task",
    "删除成功": "Delete successful",
    "导入成功": "Import successful",
    "导入文件格式不正确,请导入.xlsx/.xls与.csv格式的文件": "The import file format is incorrect. Please import files in.xlsx/.xls or.csv format.",
    "导入失败": "Import failed",
    "点检任务模版": "template",
    "请选择任务": "Please select the task",
    "激活成功": "Activation successful",
    "是否暂停点检任务": "Whether to pause the inspection task",
    "暂停成功": "Pause successful",
    "开始成功": "Start successful",
    "是否结束点检任务": "Whether to end the inspection task",
    "已结束": "Ended",
    "是否删除点检任务": "Whether to delete the inspection task",
    "已删除": "Deleted",
    "添加点检件": "Add inspection item",
    "缺片信息记录": "Missing piece information record",
    "保存成功": "Save successful",
    "点检任务": "Inspection task",
    "请输入点检任务": "Please enter the inspection task",
    "请选择点检类型": "Please select the inspection type",
    "点检模版": "Inspection template",
    "请选择点检模版": "Please select the inspection template",
    "请选择产品型号": "Please select the product model",
    "达成次数:当该任务所有点检次数都完成则完成任务": "Number of completions: The task is completed when all inspection times of the task are completed.",
    "满足预期:当该任务点检结果与预期匹配后完成任务": "Meet expectations: The task is completed when the inspection result of the task matches the expectation.",
    "请输入完成条件": "Please enter the completion condition",
    "请选择完成条件": "Please select the completion condition",
    "请输入点检次数": "Please enter the number of inspection times"
  },
  "SopManagement": {
    "导入成功": "Import success",
    "导入失败": "Import Fail",
    "导入文件格式不正确,请导入.xlsx/.xls与.csv格式的文件": "Import file format is incorrect, please import .xlsx/.xls and .csv format files",
    "导入": "Import",
    "工序选择": "Process Selection",
    "SOP管理": "SOP Management",
    "SOP日志": "SOP Log",
    "新增": "Add",
    "复制": "Copy",
    "编辑": "Edit",
    "删除": "Delete",
    "请输入关键字": "Please enter keywords",
    "详情": "Details",
    "编辑SOP": "Edit SOP",
    "结束时间范围:": "End time range:",
    "导出": "Export",
    "向上添加一行": "Add a row upward",
    "向下添加一行": "Add a row downward",
    "查看Sop": "View Sop",
    "附件": "Attachment",
    "新增SOP": "Add SOP",
    "请选择工序": "Please select the process",
    "工序名称": "Process name",
    "请选择配方": "Please select the formula",
    "配方名称": "Formula name",
    "请输入配方名称": "Please enter the formula name",
    "SOP文件列表": "SOP file list",
    "取消": "Cancel",
    "确认": "Confirm",
    "添加": "Add",
    "请输入文件名": "Please enter the file name",
    "请输入工序名称": "Please enter the process name",
    "预览": "Preview",
    "添加本地文件": "Add local file",
    "拖拽至这里上传": "Drag and drop here to upload",
    "暂无附件": "No attachment",
    "下发成功": "Issuance successful",
    "工位名称": "Workstation name",
    "点检状态": "Inspection status",
    "操作": "Add",
    "点检SOP": "Spot check SOP",
    "保存成功": "Save successful",
    "序号": "Serial number",
    "SOP名称": "SOP name",
    "使用工序": "Used process",
    "已关联配方": "Related formula",
    "备注": "Remarks",
    "请选择一个SOP进行编辑!": "Please select an SOP to edit!",
    "仅支持编辑单个SOP!": "Only a single SOP can be edited!",
    "请选择一个SOP进行复制!": "Please select an SOP to copy!",
    "每次仅支持复制单个SOP!": "Only a single SOP can be copied at a time!",
    "是否确认复制选中SOP?": "Are you sure to copy the selected SOP?",
    "复制成功": "Copy successful",
    "请选择SOP进行删除!": "Please select SOPs to delete!",
    "是否确认删除选中SOP?": "Are you sure to delete the selected SOP?",
    "删除成功": "Delete successful",
    "用户名称": "User name",
    "时间": "Time",
    "产品ID": "Product ID",
    "生产参数": "Production parameters",
    "物料参数": "Material parameters",
    "sop执行日志": "SOP execution log",
    "导出成功": "Export successful",
    "请输入SOP名称": "Please enter the SOP name",
    "SOP名称不允许为空!": "The SOP name is not allowed to be empty!",
    "工序": "Process",
    "配方": "Formula",
    "请输入备注": "Please enter the remarks",
    "工步管理": "Work step management",
    "工步名称不允许为空!": "The work step name is not allowed to be empty!",
    "工步描述不允许为空!": "The work step description is not allowed to be empty!",
    "新增成功": "Add successful",
    "编辑成功": "Edit successful",
    "请选择需要删除的文件": "Please select the files to be deleted",
    "是否确认删除选中文件?": "Are you sure to delete the selected files?",
    "未开始": "Not started",
    "点检中": "In inspection",
    "合格": "Qualified",
    "不合格": "Unqualified",
    "产线段名称": "Production line segment name",
    "配方编号": "Formula number",
    "产品型号": "Product model",
    "工步名称": "Work step name",
    "操作描述": "Operation description",
    "文件名": "File name"
  },
  "ConsoleManagement": {
    "请选择工序": "Please select a process",
    "工位": "Workstation",
    "控制台": "Console",
    "请选择流程": "Please select the process",
    "操作成功": "Operation successful",
    "警告": "Warning",
    "是否重置所有流程?": "Whether to reset all processes?",
    "是否重置WCS流程?": "Whether to reset the WCS processes?",
    "请选择工位": "Please select the workstation",
    "时间": "Time",
    "至": "To",
    "开始时间": "Start time",
    "结束时间": "End time",
    "查询": "Search",
    "请输入查询内容": "Please enter the search content",
    "控制记录": "Control record",
    "导入成功": "Import successful",
    "导出成功": "Export successful",
    "开始时间不能大于结束时间": "The start time cannot be greater than the end time",
    "时间范围限制七天": "The time range is limited to seven days",
    "关键字长度限制2-200": "The keyword length is limited to 2-200",
    "请输入关键词进行检索!": "Please enter keywords for retrieval!",
    "流程控制确认": "Process control confirmation",
    "是否确认执行": "Are you sure to execute",
    "操作": "Operation",
    "备注": "Remarks",
    "流程功能设置": "Process function settings",
    "功能名称": "Function name",
    "功能描述": "Function description",
    "未启动": "Not started",
    "运行中": "Running",
    "部分运行": "Partially running",
    "请先选择工位": "Please select the workstation first",
    "查看流程": "View process",
    "一键重启所有流程": "Restart all processes with one click",
    "调试日志": "Debug log",
    "下发操作": "Issue operation",
    "功能字段操作": "Function field operation",
    "变量值": "Variable value",
    "请输入变量值": "Please enter the variable value",
    "请输入备注": "Please enter the remarks",
    "日志等级": "Log level",
    "全部": "All",
    "关键词": "Keyword",
    "请输入关键字": "Please enter the keyword",
    "节点名称": "Node name",
    "搜索": "Search",
    "导出": "Export",
    "历史": "History",
    "实时": "Real-time",
    "关键日志": "Key logs",
    "定位": "Locate",
    "序号": "Serial number",
    "参数": "Parameters",
    "快捷操作": "Quick operations",
    "参数信号操作": "Parameter signal operations",
    "功能字段": "Function field",
    "描述": "Description",
    "变量名": "Variable name",
    "类型": "Type",
    "下发": "Issue",
    "暂无数据": "No data available",
    "用户名": "Username",
    "工序": "Process",
    "工位名称": "Workstation name",
    "流程名称": "Process name",
    "操作名称": "Operation name",
    "取消流程将终止所有实例,直到下次启动!": "Canceling the process will terminate all instances until the next startup!",
    "是否确认取消": "Are you sure to cancel",
    "控制页面": "Control page"
  },
  "ControlPanel": {
    "请选择工序": "Please select a process",
    "工位": "Workstation",
    "控制台": "Console",
    "请选择流程": "Please select the process",
    "操作成功": "Operation successful",
    "警告": "Warning",
    "是否重置所有流程?": "Whether to reset all processes?",
    "是否重置WCS流程?": "Whether to reset the WCS processes?",
    "请选择工位": "Please select the workstation",
    "时间": "Time",
    "至": "To",
    "开始时间": "Start time",
    "结束时间": "End time",
    "查询": "Search",
    "请输入查询内容": "Please enter the search content",
    "控制记录": "Control record",
    "导入成功": "Import successful",
    "导出成功": "Export successful",
    "开始时间不能大于结束时间": "The start time cannot be greater than the end time",
    "时间范围限制七天": "The time range is limited to seven days",
    "关键字长度限制2-200": "The keyword length is limited to 2-200",
    "请输入关键词进行检索!": "Please enter keywords for retrieval!",
    "流程控制确认": "Process control confirmation",
    "是否确认执行": "Are you sure to execute",
    "操作": "Operation",
    "备注": "Remarks",
    "流程功能设置": "Process function settings",
    "功能名称": "Function name",
    "功能描述": "Function description",
    "未启动": "Not started",
    "运行中": "Running",
    "部分运行": "Partially running",
    "请先选择工位": "Please select the workstation first",
    "查看流程": "View process",
    "一键重启所有流程": "Restart all processes with one click",
    "调试日志": "Debug log",
    "下发操作": "Issue operation",
    "功能字段操作": "Function field operation",
    "变量值": "Variable value",
    "请输入变量值": "Please enter the variable value",
    "请输入备注": "Please enter the remarks",
    "日志等级": "Log level",
    "全部": "All",
    "关键词": "Keyword",
    "请输入关键字": "Please enter the keyword",
    "节点名称": "Node name",
    "搜索": "Search",
    "导出": "Export",
    "历史": "History",
    "实时": "Real-time",
    "关键日志": "Key logs",
    "定位": "Locate",
    "序号": "Serial number",
    "参数": "Parameters",
    "快捷操作": "Quick operations",
    "参数信号操作": "Parameter signal operations",
    "功能字段": "Function field",
    "描述": "Description",
    "变量名": "Variable name",
    "类型": "Type",
    "下发": "Issue",
    "暂无数据": "No data available",
    "用户名": "Username",
    "工序": "Process",
    "工位名称": "Workstation name",
    "流程名称": "Process name",
    "操作名称": "Operation name",
    "取消流程将终止所有实例,直到下次启动!": "Canceling the process will terminate all instances until the next startup!",
    "是否确认取消": "Are you sure to cancel",
    "控制页面": "Control page",
    "启动": "Start",
    "停止": "Stop"
  },
  "BOMManagement": {
    "全部": "All",
    "工序": "Process",
    "用量": "Dosage",
    "请选择工序": "Please select a process",
    "请选择物料类型": "Please select a material type",
    "新建物料": "New Material",
    "编辑物料": "Edit Material",
    "删除成功": "Deleted successfully",
    "产品名称": "Product Name",
    "产品型号": "Product Model",
    "BOM名称": "BOM Name",
    "操作成功": "Operation Successful",
    "BOM管理.xlsx": "BOM Management.xlsx",
    "BOM名称重复": "BOM Name Duplicated",
    "BOM列表": "Bom list",
    "序号": "Serial number",
    "物料编号": "Material code",
    "物料名称": "Material name",
    "物料类型": "Material type",
    "单位": "Unit",
    "条码规则": "Barcode rule",
    "BOM管理": "Bill of Material (BOM) management",
    "导入": "Import",
    "导出": "Export",
    "产品名称:": "Product name:",
    "产品型号:": "Product model:",
    "添加": "Add",
    "编辑": "Edit",
    "删除": "Delete",
    "筛选": "Filter",
    "用量不允许为空!": "Dosage cannot be empty!",
    "正数,可整型、浮点型": "Positive number, can be integer or floating-point type",
    "工序不允许为空!": "Process cannot be empty!",
    "物料编号不允许为空!": "Material code cannot be empty!",
    "请输入选择工序": "Please enter and select the process",
    "请输入物料编号": "Please enter the material code",
    "请输入用量": "Please enter the dosage",
    "创建成功": "Creation successful",
    "修改成功": "Modification successful",
    "请选择产品进行添加": "Please select a product to add",
    "请选择一个物料进行编辑!": "Please select a material to edit!",
    "仅支持编辑单个物料!": "Only single material editing is supported!",
    "请选择物料进行删除!": "Please select materials to delete!",
    "物料删除后,相关生产数据无法恢复,是否确认删除?": "After the materials are deleted, the related production data cannot be recovered. Are you sure to delete?"
  },
  "BusinessFieldSetting": {
    "导入成功": "Import success",
    "导入失败": "Import Fail",
    "导入文件格式不正确,请导入.xlsx/.xls与.csv格式的文件": "Import file format is incorrect, please import .xlsx/.xls and .csv format files",
    "导入": "Import",
    "导出": "Export",
    "保存成功": "Save successful",
    "保存": "Save",
    "序号": "Serial number",
    "字段名称": "Field name",
    "变量": "Variable",
    "备注": "Remarks",
    "业务字段设置": "Business field settings"
  },
  "FlowManagement": {
    "流程编号": "Flow number",
    "系统内置": "System built-in",
    "是": "Yes",
    "否": "No",
    "删除成功": "Delete success",
    "确认删除选中数据?": "Are you sure to delete the selected data?",
    "启用": "Enable",
    "禁用": "Disable",
    "克隆": "Clone",
    "请输入流程描述": "Please input flow desc",
    "流程描述": "FLow Description",
    "流程管理": "Flow Management",
    "全部": "All",
    "流程": "Flow",
    "参数信号配置": "Parameter Signal Configuration",
    "功能配置": "Parameter config",
    "字段配置": "Field Configuration",
    "功能名称": "Function Name",
    "功能描述": "Function Description",
    "功能标识": "Function Identifier",
    "功能选项": "Function Options",
    "分组名称": "Group Name",
    "字段名称": "Field Name",
    "字段描述": "Field Description",
    "字段类型": "Field Type",
    "字段标识": "Field Identifier",
    "显示": "Display",
    "默认值": "Default Value",
    "只读": "Read Only",
    "必填": "Required",
    "名称": "Name",
    "描述": "Description",
    "选项值": "Option Value",
    "操作": "Operation",
    "流程参数配置弹窗": "Flow Parameter Configuration Pop-up Window",
    "流程弹窗": "Flow Pop-up Window",
    "请输入流程名称": "Please enter the flow name",
    "请选择交互类型": "Please select the interaction type",
    "更新成功": "Update successful",
    "添加成功": "Add successful",
    "克隆成功": "Clone successful",
    "导入成功": "Import successful",
    "导入失败": "Import failed",
    "导入文件格式不正确,请导入.xlsx/.xls与.csv格式的文件": "The import file format is incorrect. Please import files in.xlsx/.xls and.csv formats.",
    "确认删除": "Confirm deletion",
    "启用成功": "Enable successful",
    "禁用成功": "Disable successful",
    "请选择流程": "Please select the flow",
    "序号": "Serial Number",
    "流程名称": "Flow Name",
    "交互类型": "Interaction Type",
    "功能项": "Function Items",
    "关联工序": "Associated Processes",
    "备注": "Remarks",
    "状态": "Status",
    "已禁用": "Disabled",
    "已启用": "Enabled",
    "查看": "View",
    "编辑": "Edit",
    "设计": "Design",
    "删除": "Delete",
    "批量配置": "Batch Configuration",
    "添加": "Add",
    "筛选": "Filter",
    "导入": "Import",
    "导出": "Export",
    "功能选项不能为空": "Function options cannot be empty",
    "功能选项名称不能重复": "Function option names cannot be duplicated",
    "选项值不能重复": "Option values cannot be duplicated",
    "请选择": "Please select",
    "功能选项对话框": "Function Option Dialog",
    "批量配置对话框": "Batch Configuration Dialog",
    "G6流程": "G6 Flow",
    "表头内容": "Header Content",
    "搜索": "Search",
    "选择工序": "Select Work Section",
    "确认工序": "Confirm Work Section",
    "确认流程": "Confirm Flow",
    "编辑基础流程": "Edit Base Flow",
    "删除流程": "Delete Flow",
    "复制流程": "Copy Flow",
    "确认流程表单": "Confirm Flow Form",
    "点击添加": "Click Add",
    "上传成功": "Upload Success",
    "上传失败": "Upload Failure",
    "上传前": "Before Upload",
    "上下文菜单": "Context Menu",
    "获取详情数据": "Get Detail Data",
    "参数配置": "Param config",
    "添加流程": "Add Flow",
    "关闭": "Close",
    "确认": "Confirm"
  },
  "MesSuite": {
    "请选择解析规则": "Please select the parse rule",
    "请输入总表名称": "Please enter the total table name",
    "控制页面": "Control Page",
    "控制记录": "Control Record",
    "点检任务管理": "Inspection Task Management",
    "点检记录": "Inspection Record",
    "点检追溯报表": "Inspection Traceability Report",
    "物料管理": "Material Management",
    "物料记录": "Material Record",
    "工序列表": "Process List",
    "工位列表": "Workstation List",
    "SOP管理": "SOP Management",
    "SOP日志": "SOP Log",
    "使用中": "In use",
    "未使用": "Not in use",
    "请输入解析规则名称": "Please enter the parsing rule name",
    "用户没有该权限!": "User does not have this permission!",
    "产品信息下发配置": "Product Information Distribution Configuration",
    "请选择一个配置!": "Please select a configuration!",
    "解析规则": "Parsing rule",
    "校验规则": "Verification rule",
    "取消导入成功": "Cancel import successful",
    "是否取消导入": "Are you sure to cancel import?",
    "导入文件格式不正确,请导入.xlsx/.xls与.csv格式的文件": "The import file format is incorrect. Please import files in.xlsx/.xls and.csv formats.",
    "导入进度": "Import Progress",
    "已用时间": "Used Time",
    "剩余时间": "Remaining Time",
    "计算中...": "Calculating...",
    "取消导入": "Cancel Import",
    "请选择一条解析规则": "Please select one parsing rule",
    "条码解析规则": "Barcode parsing rules",
    "请勿选择多个配置操作!": "Please don't select multiple configuration operations!",
    "配方过程值": "Formula Process Value",
    "请选择一个参数!": "Please select a parameter!",
    "采集参数": "Collection Parameters",
    "参数名": "Parameter Name",
    "参数描述": "Parameter description",
    "配方参数": "Formula Parameters",
    "物料参数": "Material parameter",
    "新增产品下发参数值配置": "Add Product Information Distribution Parameter Value Configuration",
    "产品下发信息": "Product Information Distribution",
    "数据源": "Data Source",
    "类型": "Type",
    "新增": "Add",
    "编辑": "Edit",
    "下发类型": "Issue Type",
    "下发数据": "Issue Data",
    "托盘管理": "Pallet Management",
    "托盘绑定记录": "Pallet Binding Record",
    "文件错误,下载失败": "File error, download failed",
    "是否取消上料": "Whether to cancel the material",
    "请选择数据": "Please select the data",
    "物料编号": "Material Code",
    "物料名称": "Material Name",
    "上料物料码": "Material Code",
    "实际物料码": "Actual Material Code",
    "请选择内容": "Please Select Content",
    "使用详情": "Use Detail",
    "物料码": "Material Code",
    "取消上料": "Cancel Material",
    "模板名称": "Template Name",
    "预览": "Preview",
    "名称": "Name",
    "关键字": "Keyword",
    "请输入关键字": "Please Enter Keywords",
    "仅支持对一条数据进行操作!": "Only supports operations on one piece of data!",
    "描述": "Description",
    "工位选择": "Station Selection",
    "工位名称": "Station name",
    "变量": "Variable",
    "字段": "Field",
    "条码生成规则": "Barcode generation rules",
    "流程上下文": "Flow Context",
    "标识": "FLag",
    "数据类型": "Data Type",
    "生成规则": "generation rules",
    "下发变量": "Issue Variable",
    "规则类型": "Rule type",
    "条码段组成": "Barcode Segment Composition",
    "条码示例": "Barcode example",
    "更新时间": "Update Time",
    "标签管理": "Label Management",
    "标签记录": "Label Record",
    "进站、出站的交互类型只能选择一个,请检查!": "Only one interaction type of entry and exit can be selected. Please check!",
    "关联条码生成规则": "Associated barcode generation rule",
    "条码名称": "Barcode name",
    "产品管理": "Product Management",
    "产品识别码": "Product identification code",
    "版本名称": "Version name",
    "备注": "Remarks",
    "产品名称": "Product Name",
    "产品型号": "Product Model",
    "产品": "Product",
    "选择产品型号": "Select Product Model",
    "打印机选择": "Printer selection",
    "请输入打印机名称": "Please enter the printer name",
    "打印机": "Printer",
    "产线段配置": "Product segment config",
    "配方选择": "Formula select",
    "工单管理": "Work Order Management",
    "工单记录": "Work Order Records",
    "请输入搜索": "Please input search",
    "该点检任务没有可使用的产线段": "There are no available production lines for this inspection task",
    "选择": "Select",
    "配方管理": "Formula management",
    "配方应用": "Formula application",
    "配方日志": "Formula log",
    "开始时间必须比结束时间小": "Start time must be to low end time",
    "请选择开始时间": "Please select start time",
    "请选择结束时间": "Please select end time",
    "时间范围": "Time Range",
    "产量统计": "Production Statistics",
    "工序节拍分析": "Process Beat Analysis",
    "不合格统计分析": "Unqualified Statistics Analysis",
    "待办不良品": "Pending Defective Products",
    "产品判定记录": "Product Judgment Record",
    "一码回溯": "One-Code Traceability",
    "产线段": "Production Line Segment",
    "该工单没有可使用的产线段": "There are no available production line segments for this work order.",
    "请选择产线段": "Please select production line segments",
    "向上添加一行": "Add a row above",
    "向下添加一行": "Add a row below",
    "删除": "Delete",
    "不能为空": "Cannot be empty",
    "确定": "Confirm",
    "查询": "search",
    "取消": "Cancel",
    "追溯报表": "Traceability report",
    "报表配置": "Report configuration",
    "共": "Total",
    "页": "Pages",
    "当前第": "Current",
    "每页": "Per Page",
    "条记录": "Records",
    "第": "th",
    "工序选择": "Process selection",
    "工艺路线": "Process route",
    "工序段": "Process section",
    "请选择工序段": "Please select a process segment",
    "全部": "All",
    "工序": "working procedure",
    "请输入工序名称": "Please enter the process name",
    "工序段名称": "Process segment name",
    "工序编号": "Process number",
    "工序名称": "Process name",
    "查看": "View",
    "序号": "Seq",
    "导入失败": "Import Fail",
    "保存成功": "Save success",
    "查看属性": "View Props",
    "复制": "Copy",
    "集合": "Set",
    "条件集": "Condition Set",
    "请输入标签内容": "Please enter label content",
    "NodeDrawer": "节点抽屉",
    "更新": "Update",
    "关闭": "Close",
    "确认": "Confirm",
    "复合条件": "Composite Condition",
    "条件": "Condition",
    "格式错误": "Format error",
    "暂无数据": "No data available",
    "请选择": "Please select",
    "请输入": "Please enter",
    "请输入步骤名称": "Please enter the step name",
    "节点名称重复,请检查后重试": "The node name is duplicated. Please check and try again.",
    "撤销": "Undo",
    "恢复": "Redo",
    "放大": "Enlarge",
    "缩小": "Reduce",
    "美化": "Beautify",
    "下载": "Download",
    "导入": "Import",
    "导出": "Export",
    "导出成功": "Export successful",
    "导入成功": "Import successful",
    "活动节点": "Active Node"
  },
  "FormulaManagement": {
    "配方值": "Formula value",
    "参数类型": "Parameter type",
    "SOP名称": "SOP name",
    "请选择状态": "Please select Status",
    "使用中": "In use",
    "未使用": "Not in use",
    "-配方版本管理": "-Formula verison Management",
    "当前版本": "Current version",
    "状态": "Status",
    "配方列表": "Formula list",
    "是否确认下发": "Are you sure to issue?",
    "下发成功": "Issuance successful",
    "请选择SOP": "Please select SOP",
    "新建配方": "New Formula",
    "编辑配方": "Edit Formula",
    "请选择一个配方进行编辑!": "Please select a formula to edit!",
    "仅支持编辑单个配方!": "Only single formula editing is supported!",
    "当前配方正在生产中,不允许编辑": "The current formula is in production and editing is not allowed.",
    "请选择配方进行删除!": "Please select formulas to delete!",
    "生产中的配方不支持删除!": "Formulas in production are not supported to be deleted!",
    "配方删除后,相关生产数据无法恢复,是否确认删除?": "After the formula is deleted, the related production data cannot be recovered. Are you sure to delete it?",
    "删除成功": "Delete successful",
    "请选择一个配方进行创建副本!": "Please select a formula to create a copy!",
    "只能选择一个配方创建副本!": "Only one formula can be selected to create a copy!",
    "请选择一个配方进行版本管理!": "Please select a formula for version management!",
    "仅支持单个配方进行版本管理!": "Only single formula version management is supported!",
    "配方版本管理": "Formula Version Management",
    "配方名称不允许为空!": "The formula name cannot be empty!",
    "配方编号不允许为空!": "The formula code cannot be empty!",
    "工艺路线不允许为空!": "The process route cannot be empty!",
    "请输入配方编号": "Please enter the formula code",
    "请输入工艺路线": "Please enter the process route",
    "创建副本成功": "Copy creation successful",
    "创建成功": "Creation successful",
    "修改成功": "Modification successful",
    "配方编号": "Formula number",
    "配方版本": "Formula version",
    "工位名称": "Workstation name",
    "记录时间": "Recording time",
    "参数名称": "Parameter name",
    "更新内容": "Updated content",
    "请选择配方名称": "Please select formula name",
    "请选择工序名称": "Please select process name",
    "配方日志.xlsx": "Formula log.xlsx",
    "操作成功": "Operation successful",
    "参数描述": "Parameter description",
    "存在未保存数据,是否需要保存": "There is unsaved data. Do you need to save it?",
    "参数名": "Parameter name",
    "标准值": "Standard value",
    "下限": "Lower limit",
    "上限": "Upper limit",
    "设定值": "Set value",
    "实时值": "Real-time value",
    "请选择版本": "Please select version",
    "工序类型": "Process type",
    "SOP": "SOP",
    "请选择工序类型": "Please select process type",
    "产品识别码": "Product identification code",
    "版本名称": "Version name",
    "备注": "Remarks",
    "版本名称不能为空!": "Version name cannot be empty!",
    "版本名称不能重复!": "Version names cannot be duplicated!",
    "是否删除选中的版本": "Whether to delete the selected version",
    "当前仅有一个配方版本,删除失败!": "There is only one formula version currently, and the deletion failed!",
    "版本正在应用,不可删除": "The version is currently in use and cannot be deleted",
    "请选择一个版本": "Please select a version",
    "请勿选择多个版本操作!": "Please do not select multiple versions for operation!",
    "当前版本不可创建副本": "The current version cannot create a copy",
    "是否确认将": "Whether to confirm to set",
    "版本设为该配方当前版本?": "as the current version of this formula?",
    "配方名称": "Formula name",
    "产品型号": "Product model",
    "产品名称": "Product name",
    "工序编号": "Process number",
    "工序段名称": "Process segment name",
    "序号": "Serial number",
    "编辑": "Edit",
    "导入": "Import",
    "工艺参数": "Process parameters",
    "请输入参数名": "Please enter parameter name",
    "版本": "Version",
    "实时更新": "Real-time update",
    "取消": "Cancel",
    "更新": "Update",
    "新建版本": "Create a new version",
    "请选择更新当前版本或新建版本?": "Please choose to update the current version or create a new version?",
    "工艺配方": "Process formula",
    "配方": "Formula",
    "请输入配方名称": "Please enter formula name",
    "请输入产品型号": "Please enter product model",
    "配方当前版本": "Current version of the formula",
    "工艺路线": "Process route",
    "工序名称": "Process name",
    "工序": "Process",
    "工序段": "Process segment",
    "请选择工序段": "Please select process segment",
    "全部": "All",
    "请输入工序名称": "Please enter process name",
    "配方下发": "Formula distribution",
    "SOP选择": "SOP selection",
    "配方弹窗": "Formula pop-up window",
    "工艺路线设置": "Process route setting",
    "参数选择": "Parameter selection",
    "采集参数": "Acquisition parameters",
    "配方参数": "Formula parameters",
    "请输入参数名或参数描述": "Please enter parameter name or parameter description",
    "选择产品型号": "Select product model",
    "产品": "Product",
    "版本管理": "Version management",
    "当前版本:": "Current version:",
    "添加": "Add",
    "删除": "Delete",
    "创建副本": "Create a copy",
    "设为当前": "Set as current",
    "请输入版本名称": "Please enter version name",
    "物料管理": "Material management",
    "添加条件": "Add conditions",
    "筛选": "Filter",
    "导出": "Export",
    "配方管理": "Formula management",
    "配方应用": "Formula application",
    "配方日志": "Formula log",
    "配方选择": "Formula select"
  },
  "MaterialManagement": {
    "是否取消上料": "Whether to cancel the material",
    "请选择物料编号进行删除!": "Please select the material code to delete!",
    "批次料使用总览": "Overall Usage Overview of Batch Materials",
    "工序段": "Process Section",
    "请选择工序段": "Please select the process section",
    "请输入工位": "Please enter the workstation",
    "搜索": "Search",
    "下发": "Issue",
    "工位列表": "Workstation List",
    "批次料列表": "Batch Material List",
    "工序名称": "Process Name",
    "工位名称": "Workstation Name",
    "上料物料码": "Loading Material Code",
    "实际物料码": "Actual Material Code",
    "请选择内容": "Please select the content",
    "请选择数据": "Please select the data",
    "是否重新下发物料码": "Whether to reissue the material code",
    "下发成功": "Issued successfully",
    "工位": "Workstation",
    "请选择工位": "Please Select Station",
    "请选择": "Please Select",
    "请输入": "Please Enter",
    "状态": "Status",
    "选择": "Select",
    "批次料上料": "Batch material feeding",
    "上料详情": "Loading details",
    "上料成功": "Loading successful",
    "不允许同时对多个物料编号上料!": "",
    "请输入物料编号": "Please enter the material code",
    "请输入单位": "Please input unit",
    "请输入搜索": "Please input search",
    "新建物料": "New Material",
    "编辑物料": "Edit Material",
    "操作成功": "Operation successful",
    "请选择一个物料进行编辑!": "Please select a material to edit!",
    "仅支持编辑单个物料!": "Only single material editing is supported!",
    "物料删除后,相关生产数据无法恢复,是否确认删除?": "After the material is deleted, the related production data cannot be recovered. Are you sure to delete it?",
    "新建物料编号": "New Material Code",
    "编辑物料编号": "Edit Material Code",
    "物料编号删除后,相关生产数据无法恢复,是否确认删除?": "After the material code is deleted, the related production data cannot be recovered. Are you sure to delete it?",
    "删除成功": "Delete successful",
    "请选择一个物料编号进行编辑!": "Please select a material code to edit!",
    "仅支持编辑单个物料编号!": "Only single material code editing is supported!",
    "请选择一个物料编号": "Please select a material code",
    "入库成功": "Warehousing successful",
    "校准成功": "Calibration successful",
    "物料编号不允许为空!": "The material code cannot be empty!",
    "请选择条码规则": "Please select the barcode rule",
    "请输入物料名称": "Please enter the material name",
    "请输入选择物料类型": "Please enter and select the material type",
    "物料名称不允许为空!": "The material name cannot be empty!",
    "物料类型不允许为空!": "The material type cannot be empty!",
    "单位不允许为空!": "The unit cannot be empty!",
    "请输入备注说明": "Please enter the remarks",
    "创建成功": "Creation successful",
    "修改成功": "Modification successful",
    "创建库位": "Create Location",
    "导入成功": "Import successful",
    "导入失败": "Import failed",
    "导入文件格式不正确,请导入.xlsx/.xls与.csv格式的文件": "The import file format is incorrect. Please import files in.xlsx/.xls and.csv formats.",
    "产品名称": "Product Name",
    "产品型号": "Product Model",
    "物料列表": "Material List",
    "导入": "Import",
    "导出": "Export",
    "备注": "Remarks",
    "物料编号列表": "Material Code List",
    "添加": "Add",
    "编辑": "Edit",
    "入库": "Warehousing",
    "校准": "Calibration",
    "删除": "Delete",
    "库存": "Inventory",
    "产品码": "Product Code",
    "使用时间": "Usage Time",
    "物料码": "Material Code",
    "物料使用量": "Material Usage Quantity",
    "时间范围": "Time Range",
    "开始时间": "Start Time",
    "结束时间": "End Time",
    "请输入关键字": "Please enter keywords",
    "选择产品型号": "Select Product Model",
    "序号": "Serial Number",
    "物料编号": "Material Code",
    "物料名称": "Material Name",
    "物料类型": "Material Type",
    "单位": "Unit",
    "条码规则": "Barcode Rule",
    "备注说明": "Remarks",
    "物料管理": "Material Management",
    "物料记录": "Material Record"
  },
  "OrderManagement": {
    "工单模版": "Order Template",
    "开始成功": "Start successfully",
    "停止成功": "Stop successfully",
    "生产状态": "Product Status",
    "未生产": "no Production",
    "开始": "Start",
    "停止": "Stop",
    "已更新": "Has been updated",
    "全部": "All",
    "导出": "Export",
    "工单暂停后,已上线产品会继续生产,是否确认暂停工单?": "After the work order is suspended, the products that have already been launched will continue to be produced. Are you sure you want to suspend the work order?",
    "删除成功": "Delete success",
    "撤销成功": "Revocation successful",
    "是否确认完成工单?": "Are you sure to complete the work order?",
    "编辑工单": "Edit Order",
    "请至少选择一个工单进行操作": "Please select at least one work order for operation",
    "工单删除后不可恢复,是否确认删除": "The work order cannot be restored after deletion. Are you sure to delete it",
    "工单撤销后,状态变为初始待激活状态;是否确认撤销工单?": "After the work order is cancelled, the status changes to the initial pending activation state; Are you sure to cancel the work order?",
    "不限": "No limit",
    "创建工单": "Create Order",
    "请输入": "Please Enter",
    "请选择一个工单进行操作": "Please select order only a data of action",
    "未激活": "Not activated",
    "已暂停": "Paused",
    "待生产": "To be produced",
    "已完成": "Completed",
    "已结束": "Ended",
    "排序": "Sort",
    "工单生产进度": "Work Order Production Progress",
    "工序序号": "Process Sequence Number",
    "工艺路线版本": "Process Route Version",
    "工艺路线": "Process Route",
    "工序标准文本码": "Process Standard Text Code",
    "工序名称": "Process Name",
    "前一工序文本码": "Previous Process Text Code",
    "后一工序文本码": "Next Process Text Code",
    "对应主工艺路线": "Corresponding Main Process Route",
    "BOM编号": "BOM Number",
    "物料描述": "Material Description",
    "数量": "Quantity",
    "重要追溯件": "Important Traceability Parts",
    "所属工序": "Belonging Process",
    "LOT号": "LOT Number",
    "LOT描述": "LOT Description",
    "LOT状态": "LOT Status",
    "LOT类型": "LOT Type",
    "序列号": "Serial Number",
    "导出模板": "Export Template",
    "导入": "Import",
    "下发": "Deliver",
    "激活": "Activate",
    "编辑": "Edit",
    "暂停": "Pause",
    "撤销": "Revoke",
    "完成": "Complete",
    "结束": "Finish",
    "删除": "Delete",
    "生产中": "In Production",
    "已经是第一位,无法再向上调整排序": "It's already in the first position and cannot be adjusted upward any more.",
    "已经是最后一位,无法再向下调整排序": "It's already in the last position and cannot be adjusted downward any more.",
    "获取工单": "Get Work Order",
    "输入订单号或产品LOT ID": "Enter the order number or product LOT ID",
    "结束工单": "Finish Work Order",
    "请填写结束原因": "Please fill in the reason for finishing.",
    "请填写工单结束原因": "Please fill in the reason for the work order to be finished.",
    "工单正在生产,是否强制结束?": "The work order is in production. Do you want to force it to finish?",
    "提示": "Tip",
    "确认": "Confirm",
    "操作成功": "Operation successful",
    "请选择": "Please Select",
    "重要": "Important",
    "非重要": "Unimportant",
    "搜索": "Search",
    "查询": "Query",
    "批次物料清单": "Batch Bill of Materials",
    "条码信息:": "Barcode Information:",
    "上料进度:": "Feeding Progress:",
    "计划开始:": "Planned Start:",
    "计划结束:": "Planned Finish:",
    "物料编号": "Material Code",
    "物料名称": "Material Name",
    "用料工序": "Material Usage Process",
    "用量": "Usage Quantity",
    "上料状态": "Feeding Status",
    "未上料": "Not Fed",
    "已上料": "Fed",
    "取消": "Cancel",
    "确定": "OK",
    "工单备料": "Work Order Material Preparation",
    "激活成功": "Activation Successful",
    "基础信息": "Basic Information",
    "工单号:": "Work Order Number:",
    "产品型号:": "Product Model:",
    "工艺配方:": "Process Formula:",
    "计划数量:": "Planned Quantity:",
    "计划开始时间:": "Planned Start Time:",
    "计划结束时间:": "Planned Finish Time:",
    "字段不能为空": "Field cannot be empty",
    "计划数量请输入正整数": "Please enter a positive integer for the planned quantity.",
    "更新": "Update",
    "新增": "Add",
    "成功": "Successful",
    "是否保存工单设置?": "Do you want to save the work order settings?",
    "新建工单": "Create Work Order",
    "添加条件": "Add Conditions",
    "筛选": "Filter",
    "时间范围:": "Time Range:",
    "产线段": "Production Line Segment",
    "开始时间": "Start Time",
    "结束时间": "End Time",
    "工单号": "Work Order Number",
    "工单来源": "Work Order Source",
    "计划开始时间": "Planned Start Time",
    "计划结束时间": "Planned Finish Time",
    "产品型号": "Product Model",
    "工艺配方": "Process Formula",
    "计划数量": "Planned Quantity",
    "投产数量": "Production Quantity",
    "合格数": "Qualified Quantity",
    "班次": "Shift",
    "实际开始时间": "Actual Start Time",
    "实际结束时间": "Actual End Time",
    "工单状态": "Work Order Status",
    "订��结束原因": "Order End Reason",
    "工单管理": "Work Order Management",
    "工单记录": "Work Order Records"
  },
  "ProcessConfiguration": {
    "产品信息下发配置": "Product Information Distribution Configuration",
    "产品信息下发": "Product Information Distribution",
    "产品下发信息": "Product Information Distribution",
    "产品信息下发设置": "Product Information Distribution Settings",
    "类型": "Type",
    "下发类型": "Issue Type",
    "下发数据": "Issue Data",
    "数据源": "Data Source",
    "工序段": "Process Section",
    "漏工序配置弹窗": "Missing Process Configuration Pop-up Window",
    "显示隐藏": "Visible or Hidden",
    "基础数据": "Base Data",
    "搜索值": "Search Value",
    "选择的值": "Selected Values",
    "关闭": "Close",
    "确认": "Confirm",
    "选择": "Select",
    "选择工序": "Select Process",
    "更新配置": "Update Configuration",
    "请选择部品": "Please Select Component",
    "请选择产线段": "Please Select Production Line Segment",
    "主产品": "Main Product",
    "请选择一个配置!": "Please select a configuration!",
    "请勿选择多个配置操作!": "Please don't select multiple configuration operations!",
    "请选择工序!": "Please select a process!",
    "请选择产线段!": "",
    "检测配置": "Check Config",
    "更新工序": "Update Process",
    "检测工序": "Check Process",
    "导入成功": "Import successful",
    "漏工序检测配置": "Missing Process Detection Configuration",
    "检测": "Detection",
    "产品码更新配置": "Product Code Update Configuration",
    "更新": "Update",
    "产品状态检测配置": "Product Status Detection Configuration",
    "匹配": "Matching ",
    "部品": "Parts",
    "匹配部品": "Matching Parts",
    "检测部品": "Detection Parts",
    "更新产线段": "Updated Production Line Segment",
    "检测产线段": "Detection Production Line Segment",
    "更新工序名称": "Updated Process Name",
    "检测工序名称": "Detection Process Name",
    "是否删除选中的配置": "Whether to delete the selected configuration",
    "保存成功!": "Saved successfully!",
    "配方名称": "Formula Name",
    "产品状态检测": "Product Status Detection",
    "成功": "Success",
    "序号": "Serial Number",
    "产品型号": "Product Model",
    "请选择所属产品型号": "Please select the affiliated product model",
    "产线段": "Production Line Segment",
    "请选择所属产线段": "Please select the affiliated production line segment",
    "工序编号": "Process Code",
    "工序名称": "Process Name",
    "漏工序检测": "Missing Process Detection",
    "产品码更新": "Product Code Update",
    "添加": "Add",
    "筛选": "Filter",
    "请输入关键字": "Please enter keywords",
    "选择工序配置": "Choose Process Configuration",
    "新增": "Add",
    "编辑": "Edit",
    "请选择一个设置!": "Please select one setting!",
    "请勿选择多个设置操作!": "Please don't select multiple setting operations!",
    "工艺路线首工序不支持配置!": "The first process of the process route does not support configuration!",
    "关联物料": "Related Materials",
    "漏工序检测设置": "Missing Process Detection Settings",
    "产品码更新设置": "Product Code Update Settings",
    "产品状态检测设置": "Product Status Detection Settings",
    "基本": "Basic",
    "信息": "Information",
    "产品型号:": "Product Model:",
    "当前工序:": "Current Process:",
    "配置": "Configuration",
    "删除": "Delete",
    "过程配置": "Process Configuration",
    "过程设置": "Process Settings"
  },
  "ProcessManagement": {
    "物料参数名": "Material Parameter Name",
    "参数传递": "Params Transfer",
    "参数名称": "Parameter Name",
    "触发信号": "Trigger Signal",
    "初始变量": "Initial Variable",
    "目标变量": "Target Variable",
    "传递结果": "Transfer Result",
    "物料检测": "Material inspection",
    "工位编号": "WorkStation code",
    "请输入工位编号": "Please Enter WorkStation code",
    "根据当前的产品配方关联的BOM表预设的用量进行扣减": "Deduct based on the preset usage of the BOM table associated with the current product formula",
    "进站、出站的交互类型只能选择一个,请检查!": "Only one interaction type of entry and exit can be selected. Please check!",
    "暂无数据": "No data",
    "请选择": "Please select",
    "工序列表": "WorkSection Report",
    "工位列表": "Workstations Report",
    "详情": "details",
    "筛选": "Filter",
    "添加条件": "Add Condition",
    "添加工序": "Add WorkSection",
    "工序设置": "WorkSection Settings",
    "分组条件": "Group Condition",
    "所属产线段": "Belonging production line segment",
    "请选择所属产线段": "Please select Belonging production line segment",
    "关联流程": "Associated Flow",
    "请选择关联流程": "Please select Associated Flow",
    "分组": "Group",
    "导入": "Import",
    "导出": "Export",
    "请输入": "Please enter",
    "请输入关键字": "Please enter keywords",
    "序号": "Number",
    "工序名称": "WorkSection Name",
    "工序编号": "WorkSection Number",
    "工位名称": "Workstation Name",
    "所属工序": "Belonging WorkSection",
    "请选择所属工序": "Please select Belonging WorkSection",
    "看板IP": "Dashboard IP",
    "SOP信号": "SOP Signal",
    "更新码变量": "Update code variable",
    "备注": "Remark",
    "批量配置": "Batch configuration",
    "参数配置": "Parameter configuration",
    "支持配置产线产品条码与唯一料的装配绑定关系": "Support configuration production line product barcode and unique material assembly binding relationship",
    "物料产品关联工序": "Material product assembly work section",
    "进站结果值配置": "Entry station result value configuration",
    "工序结果值配置": "WorkSection result value configuration",
    "补充说明映射": "Supplementary description mapping relationship",
    "关联条码生成规则": "Associated barcode generation rule",
    "条码名称": "Barcode name",
    "流程名称": "Flow name",
    "选择": "Select",
    "关联物料": "Associated material",
    "关键字": "Keyword",
    "参数信号配置": "Parameter signal configuration",
    "导入成功": "Import success",
    "导出成功": "Export success",
    "功能配置": "Function configuration",
    "采集参数": "Collection parameter",
    "配方参数": "Formula parameter",
    "不良原因": "Defect reason",
    "物料参数": "Material parameter",
    "展开工序详情": "Expand work section details",
    "向上添加工序": "Add work section upwards",
    "向下添加工序": "Add work section downwards",
    "创建工序副本": "Create work section copy",
    "创建副本成功": "Create copy success",
    "删除工序": "Delete work section",
    "是否删除": "Are you sure to delete",
    "删除成功": "Delete success",
    "工序": "WorkSection",
    "导入失败": "Import fail",
    "导入文件格式不正确,请导入.xlsx/.xls与.csv格式的文件": "Import file format is incorrect, please import .xlsx/.xls and .csv format files",
    "请输入工序名称": "Please enter workSection name",
    "请选择产线段": "Please select production line segment",
    "物料名称": "Material name",
    "物料类型": "Material type",
    "参数名": "Parameter name",
    "参数描述": "Parameter description",
    "当前工序数据未保存,是否确认关闭?": "Current work section data has not been saved, do you want to close?",
    "保存成功,注意重启流程服务!": "Save success, please restart the flow service!",
    "产线段": "Production line segment",
    "绑定唯一料": "Binding unique material",
    "进站结果": "Entry station result",
    "出站结果": "Exit station result",
    "下发值": "Issued value",
    "原始值": "Original value",
    "映射值": "Mapping value",
    "保存成功": "Save success",
    "展开工位详情": "Expand workstation details",
    "向上添加工位": "Add workstation upwards",
    "向下添加工位": "Add workstation downwards",
    "添加工位": "Add Workstation",
    "创建工位副本": "Create workstation copy",
    "创建工位副本成功": "Create workstation copy success",
    "删除工位": "Delete workstation",
    "工位": "Workstation",
    "当前工位数据未保存,是否确认关闭?": "Current workstation data has not been saved, do you want to close?",
    "请输入工位名称": "Please enter workstation name",
    "请输入工序编号": "Please enter workSection Number",
    "不能为空或空白字符!": "Cannot be empty or blank characters!",
    "看板IP地址": "Dashboard IP address",
    "请输入看板IP地址": "Please enter dashboard IP address",
    "请选择更新码": "Please select update code",
    "更新码": "Update code",
    "请选择SOP信号": "Please select SOP signal",
    "物料检验信号": "Material inspection signal",
    "物料条码变量": "Material barcode variable",
    "物料校验结果": "Material check result",
    "绑定物料": "Binding material",
    "物料条码信息缓存变量": "Material barcode information cache variable",
    "不良品原因名称": "Defect reason name",
    "判断值": "Judgment value",
    "不良品原因变量": "Defect reason variable",
    "功能名称": "Function name",
    "所属流程": "Belonging flow",
    "功能描述": "Function description",
    "功能选项": "Function option",
    "采集变量": "Collection variable",
    "下发关联变量": "Issued related variable",
    "监听关联变量": "Listen related variable",
    "功能字段": "Function field",
    "描述": "Description",
    "变量规则": "Variable rule",
    "物料编号": "Material number",
    "关联工序": "Related workSection",
    "单位": "Unit",
    "规则类型": "Rule type",
    "条码段组成": "Barcode segment composition",
    "条码示例": "Barcode example",
    "更新时间": "Update time",
    "校验类型": "Check type",
    "校验条码": "Check barcode",
    "交互类型": "Interaction type",
    "功能项": "Function item",
    "工序列表-列表": "WorkSection Report-List",
    "工位-列表": "Workstations Report-List",
    "工序列表-添加": "WorkSection Report-Add",
    "工序列表-设置": "WorkSection Report-Settings",
    "工序列表-过滤": "WorkSection Report-Filter",
    "工序列表-分组": "WorkSection Report-Group",
    "工序列表-导入": "WorkSection Report-Import",
    "工序列表-输出": "WorkSection Report-Export",
    "工位列表-添加": "Workstations Report-Add",
    "工位列表-批量配置": "Workstations Report-Batch Settings",
    "工位列表-过滤": "Workstations Report-Filter",
    "工位列表-分组": "Workstations Report-Group",
    "工位列表-导入": "Workstations Report-Import",
    "工位列表-输出": "Workstations Report-Export",
    "确定": "Confirm",
    "查询": "search",
    "确认": "Confirm",
    "取消": "Cancel",
    "追溯报表": "Traceability report",
    "报表配置": "Report configuration",
    "共": "Total",
    "页": "Pages",
    "当前第": "Current",
    "每页": "Per Page",
    "条记录": "Records",
    "第": "th",
    "操作": "Operation",
    "异常原因": "Abnormal reason",
    "结果说明": "Result Explanation",
    "物料扣减信号": "Material Deduction Signal",
    "物料条码": "Material Barcode",
    "物料用量提供方式": "Material Usage Provision Method",
    "设备提供": "Provided by Equipment",
    "BOM提供": "Provided by BOM",
    "物料使用量": "Material Usage Quantity",
    "扣减结果": "Deduction Result",
    "物料扣减": "Material Deduction",
    "工序类型": "Process Type",
    "请选择工序类型": "Please select the process type",
    "运行状态": "Running State",
    "请选择运行状态": "Please select the running state",
    "空闲状态,工位没有进行中的工单、任务": "Idle state, there are no ongoing work orders or tasks at the station",
    "生产状态,存在正常生产的工单": "Production state, there are normal production work orders",
    "点检状态,存在点检的任务": "Inspection state, there are inspection tasks"
  },
  "ProductionTracking": {
    "生产追踪数据": "Production Tracking Data",
    "全部": "All",
    "生产跟踪": "Production Tracking",
    "追溯": "Traceability",
    "产品型号": "Product Model",
    "请选择": "Please Select",
    "工序": "Process"
  },
  "ProductManagement": {
    "暂无数据": "No data",
    "产品管理": "Product management",
    "添加": "Add",
    "编辑": "Edit",
    "导入": "Import",
    "导出": "Export",
    "SOP管理": "SOP management",
    "SOP配置": "SOP configuration",
    "删除": "Delete",
    "请输入产品名称、产品型号、和产品简码": "Enter product name,model,code",
    "序号": "Serial number",
    "产品名称": "Product name",
    "产品型号": "Product model",
    "产品简号": "Product code",
    "配方名称": "Recipe name",
    "工艺路线": "Process route",
    "备注": "Remark",
    "预览": "Preview",
    "添加本地文件": "Add local file",
    "拖拽至这里上传": "Drag and drop here to upload",
    "工序段": "Process segment",
    "请选择工序段": "Please select a process segment",
    "全部": "All",
    "工序": "Process",
    "请输入工序名称": "Enter process name",
    "工步管理": "Workstep management",
    "新增": "Add",
    "附件": "Attachment",
    "工步和工步描述为必填": "Workstep and description are required",
    "工步名称不可以重复": "Workstep name cannot be repeated",
    "工步": "Workstep",
    "操作描述": "Operation description",
    "请选择工序": "Please select a process",
    "请选择需要删除的工步": "Please select the workstep to be deleted",
    "工序编号": "Process number",
    "工序名称": "Process name",
    "工序段名称": "Process segment name",
    "操作成功": "Operation successful",
    "产品名称不允许为空!": "Product name cannot be empty!",
    "请输入产品名称": "Please enter product name",
    "产品型号不允许为空!": "Product model cannot be empty!",
    "请输入产品型号": "Please enter product model",
    "请输入产品简号": "Please enter product code",
    "创建成功": "Creation successful",
    "修改成功": "Modification successful",
    "删除成功": "Deletion successful",
    "请选择产品进行删除!": "Please select a product to delete!",
    "产品删除后,相关生产数据无法恢复,是否确认删除?": "Product deletion cannot be recovered, are you sure to delete?",
    "新建产品": "New product",
    "请选择一个产品进行编辑": "Please select a product to edit",
    "请选择一个产品进行操作!": "Please select a product to operate!",
    "仅支持编辑单个产品": "Only support editing a single product",
    "该产品型号正在生产中,不可操作!": "The product model is in production, cannot be operated!",
    "仅支持对一个产品型号进行操作!": "Only support operating on a single product model!",
    "产品正在生产中,不允许编辑!": "Product is in production, cannot be edited!",
    "编辑产品": "Edit product",
    "确定": "Confirm",
    "查询": "search",
    "确认": "Confirm",
    "取消": "Cancel",
    "追溯报表": "Traceability report",
    "报表配置": "Report configuration",
    "共": "Total",
    "页": "Pages",
    "当前第": "Current",
    "每页": "Per Page",
    "条记录": "Records",
    "第": "th",
    "产品识别码": "Product identification code",
    "请输入产品识别码": "Please enter product identification code"
  },
  "SystemManagement": {
    "å¹´": "Year",
    "月": "Month",
    "周": "Week",
    "日": "Day",
    "追溯报表": "Traceability report",
    "生产数据查询时长": "Production data query duration",
    "导出设置": "Export settings",
    "百分比": "Percentage",
    "计划数量等于投产数量结束": "Planned quantity equals the end of the production quantity",
    "工单开启逻辑": "Work order start logic",
    "按列表顺序排产": "Arrange production according to the list order",
    "工单结束逻辑": "Work order end logic",
    "结束按钮": "End button",
    "开始按钮": "Start button",
    "自动控制": "Automatic control",
    "手动控制": "Manual control",
    "工单控制方式": "Work order control mode",
    "请输入必填项": "Please enter the required items",
    "连接成功": "Connection successful",
    "打印设置": "Print settings",
    "下载": "Download",
    "测试链接": "Test link",
    "复制": "Copy",
    "删除": "Delete",
    "序号": "Serial number",
    "请输入模板路径": "Please enter the template path",
    "模板路径": "Template path",
    "请输入 ip": "Please enter the IP",
    "请输入打印机名称": "Please enter the printer name",
    "打印机名称": "Printer name",
    "请输入": "Please Enter",
    "请输入自定义名称": "Please Enter Custom Name",
    "自定义名称": "Custom name",
    "文本名称": "Text name",
    "系统文本资源,用户可自定义其展示文本": "System text resources, users can customize their display text",
    "文本资源": "Text Resource",
    "工单设置": "Order Settings",
    "文本设置": "Text Settings",
    "更新配置": "Update configuration",
    "产线设置": "Line Settings",
    "产线代码": "Line Code",
    "自定义输入,长度为20字符": "Custom input, length is 20 characters",
    "请输入产线代码": "Please enter the line code",
    "设置产线代码,作为产线的唯一标识代码": "Set the line code as the unique identifier for the line",
    "产线结构": "Line Structure",
    "产线-工序-工位": "Line-Process-Station",
    "产线-产线段(工段)-工序-工位": "Line-Line Segment (Process Segment)-Process-Station",
    "设置产线的层次结构,用于工序的产线建模": "Set the hierarchy of the line for process line modeling",
    "调试模式": "Debug Mode",
    "启用调试模式": "Enable Debug Mode",
    "开": "On",
    "关": "Off",
    "启用后支持工单下发后修改工序配置、工艺配方要求": "Support modifying process configuration and process recipe requirements after the work order is issued",
    "产品管理": "Product Management",
    "SOP配置": "SOP Configuration",
    "禁用": "Disable",
    "启用": "Enable",
    "工艺配方": "Process Recipe",
    "下发方式": "Distribution Method",
    "下发工单联动配方下发": "Distribute recipe along with work order",
    "配方应用页面手动下发": "Manual distribution on recipe application page",
    "工序识别产品时下发配方": "Distribute recipe when the process gets the product",
    "配置工艺配方的下发方式": "Configure the distribution method of the process recipe",
    "功能模块": "Functional Module",
    "工单管理模块": "Order Management Module",
    "产线段定义": "Line Segment Definition",
    "系统设置启用后,支持对产线段定义": "Support line segment definition after system settings are enabled",
    "产线段删除后,引用关系一并删除": "When the line segment is deleted, the reference relationships are deleted together",
    "过程设置": "Process Settings",
    "检测时间范围": "Detection Time Range",
    "配置产品状态检测、漏工序、重码及产品码更新相关业务在追溯报表的数据查询范围": "Configure the data query range for product status detection, missing process, duplicate code, and product code update related business in traceability reports",
    "过程参数": "Process Parameters",
    "返修数据展示": "Rework Data Display",
    "展示所有数据": "Show All Data",
    "展示最新数据": "Show Latest Data",
    "选择返修产品的过程参数展示方式": "Choose the display method of process parameters for reworked products",
    "不良品管理": "Defective Product Management",
    "判定结果选择项": "Judgment Result Options",
    "设备返修": "Equipment Repair",
    "人工返修": "Manual Repair",
    "产品报废": "Product Scrap",
    "NG品流出": "NG Product Outflow",
    "选择不合格品处理的判定结果选项": "Choose judgment result options for defective product handling",
    "产线段不可重复,请检查": "",
    "保存成功": "Save Success",
    "近": "In the past ",
    "天": "days",
    "产线段名称": "Name of production line segment",
    "请输入产线段名称": "Please enter the name of the production line segment",
    "加工产品名称": "Processing product name",
    "请输入加工产品名称": "Please enter the name of the processed product",
    "操作": "operation",
    "通用设置": "General settings",
    "功能设置": "Function settings",
    "基本设置": "Basic settings",
    "不良品设置": "Defective product settings",
    "配方控制": "Formula control",
    "系统设置": "System settings",
    "开启后,产品管理模块,提供SOP管理按钮,可上传维护SOP文件": "After activation, the Product Management module provides an SOP management button for uploading and maintaining SOP files",
    "关闭后,工单模块不可用,所有生产流程不再涉及工单功能": "After deactivation, the Work Order module is unavailable and all production processes no longer involve the work order function",
    "高级设置": "Advanced settings"
  },
  "TraceManagement": {
    "上线时间为总表配置中末工序的记录时间": "The online time is the recording time of the final process in the summary table configuration",
    "部品": "Parts",
    "部品名称": "Parts Name",
    "部品来源工序": "Parts Source Process",
    "请选择解析规则": "Please select the parse rule",
    "导出中,请稍后...": "Exporting, please wait...",
    "时间范围不能超过": "The time range cannot exceed",
    "请输入总表名称": "Please enter the total table name",
    "å¹´": "Year",
    "月": "Month",
    "周": "Week",
    "天": "Day",
    "保存成功": "Save successful",
    "解析规则": "Parse Rule",
    "条码段": "Barcode Segment",
    "追溯": "Trace",
    "追溯报表.xlsx": "Traceability Report.xlsx",
    "工序/总表": "Process/General List",
    "最新加工时间为产品最近加工时间": "The latest processing time is the latest processing time of the product",
    "上线时间为工艺路线首工序的记录时间": "The online time is the recorded time of the first process of the process route",
    "返修判定": "Repair Flag",
    "点检ID": "Inspect ID",
    "点检任务名称": "Inspection task name",
    "物料参数": "Material parameter",
    "选择参数类型": "Select params type",
    "追溯报表": "Traceability Report",
    "报表配置": "Report Configuration",
    "请输入自然数!": "Please enter a natural number!",
    "输入数量大于参数数量,请重新输入": "The input quantity is greater than the parameter quantity. Please enter again.",
    "冻结列数不能包含多级表头,请重新输入": "The frozen column count cannot include multi-level headers. Please enter again.",
    "业务字段配置": "Business Field Configuration",
    "展示配置": "Display Configuration",
    "总表配置": "Overall Table Configuration",
    "保存成功!": "Saved successfully!",
    "保存失败!": "Failed to save!",
    "提示": "Tip",
    "是否删除": "Do you want to delete?",
    "是": "Yes",
    "否": "No",
    "序号": "Serial Number",
    "产品名称": "Product Name",
    "产品型号": "Product Model",
    "备注": "Remarks",
    "暂无数据": "No data available",
    "请勾选复选框!": "Please check the checkbox!",
    "请选择工序!": "Please select a process!",
    "请选择参数!": "Please select parameters!",
    "上限": "Upper Limit",
    "操作": "Operation",
    "修改": "Modify",
    "总表名称": "Overall Table Name",
    "组合工序": "Combined Process",
    "操作时间": "Operation Time",
    "操作人": "Operator",
    "查看": "View",
    "新建": "New",
    "工序段": "Process Segment",
    "工序名称": "Process Name",
    "选择": "Select",
    "删除": "Delete",
    "新建总表": "New Overall Table",
    "修改总表": "Modify Overall Table",
    "共": "Total",
    "页": "Pages",
    "当前第": "Current",
    "每页": "Per Page",
    "条记录": "Records",
    "导出时间范围": "Export Time Range",
    "工序": "Process",
    "参数": "Parameter",
    "上限值": "Upper Limit Value",
    "下限值": "Lower Limit Value",
    "还原": "Restore",
    "工序参数类型": "Process Parameter Type",
    "工序参数": "Process Parameter",
    "产线段名称": "Production Line Segment Name",
    "工序编号": "Process Number",
    "参数类型": "Parameter Type",
    "参数名称": "Parameter Name",
    "采集参数": "Collected Parameters",
    "下发参数": "Issued Parameters",
    "是否合格": "Qualified or Not",
    "时间范围": "Time Range",
    "物料识别码": "Material Identification Code",
    "工单号": "Work Order Number",
    "过程参数曲线": "Process Curve Parameters",
    "导出": "Export",
    "加工数": "Processing Quantity",
    "合格数": "Qualified Quantity",
    "合格率": "Pass Rate",
    "不合格数": "Unqualified Quantity",
    "不合格率": "Failure Rate",
    "冻结列数": "Frozen Column Count",
    "字段名称": "Field Name",
    "数据源": "Datasource",
    "适用型号": "Auto mode",
    "记录时间": "Recording time",
    "添加": "Add",
    "查询": "Search",
    "产品ID": "Product ID",
    "更新码": "Update Code",
    "模糊查询": "Fuzzy Query",
    "精确查询": "Accurate query",
    "全部": "All",
    "导出确认": "Export Confirmation",
    "不限": "No limit",
    "下限": "Lower limit",
    "请输入": "Please enter the content",
    "请选择": "Please Select",
    "请输入搜索": "Please input search",
    "创建成功!": "Create successfully",
    "请勾选两个或两个以上的工序!": "Please check two or more processes!",
    "结束时间不能小于开始时间": "The end time cannot be less than the start time",
    "实时": "Real time",
    "历史": "History",
    "工序来源切换": "Process source switching",
    "工序选择": "Process selection",
    "同步工艺路线": "Synchronous process route",
    "自定义选择": "Custom Selection",
    "过程参数": "Process Parameters",
    "配方参数": "Formula Parameters",
    "物料码": "Material Code",
    "请输入一码": "Please enter the code",
    "上传成功": "Upload successful",
    "请输入查询内容!": "Please enter the query content!",
    "向上插入一行": "Insert a row upward",
    "向下插入一行": "Insert a row downward",
    "导出成功": "Export successful",
    "操作成功": "Operation successful",
    "导入失败,请检查文件数据": "Import failed. Please check the file data",
    "一码": "One code",
    "曲线": "Curve",
    "位移(mm)": "Displacement (mm)",
    "压力(N)": "Pressure (N)",
    "下限:": "Lower limit:",
    "上限:": "Upper limit:",
    "实时:": "Real-time:",
    "结果:": "Result:",
    "实际值:": "Actual value:",
    "设定值:": "Set value:",
    "物料条码:": "Material barcode:",
    "批量导出": "Batch Export",
    "批量导出确认": "Batch Export Confirmation",
    "进行批量导出": "Perform Batch Export",
    "说明:": "Instructions:",
    "1、下述输入条码:需是正确的总成码、执行器码、阀体码、电机码,PCBA码等(产品码或物料码);": "1. The following input barcodes: They need to be correct assembly codes, actuator codes, valve body codes, motor codes, PCBA codes, etc. (product codes or material codes);",
    "2、导入文件格式要求为.xlsx/.xls;": "2. The format of the imported file is required to be.xlsx/.xls;",
    "3、系统会根据输入条码查询相关的加工信息进行导出。": "3. The system will query the relevant processing information according to the input barcodes for export.",
    "输入条码数据:": "Input barcode data:",
    "导入": "Import",
    "一码回溯": "One-Code Traceability",
    "上传MES": "Upload MES",
    "NG工序:": "NG Process:",
    "输入ID:": "Input ID:",
    "产品型号:": "Product Model:",
    "是否合格:": "Whether Qualified:",
    "OK": "Qualified",
    "NG": "Not Qualified",
    "产品ID:": "Product ID:",
    "更新码:": "Update Code:",
    "记录时间:": "Record Time:"
  },
  "QualityManagement": {
    "解绑": "Unbind",
    "不解绑": "Bind",
    "物料处理": "MATERIAL HANDLING",
    "批量操作": "bulk operation",
    "物料解绑": "Unbinding of materials",
    "请输入物料名称": "Please enter the material name",
    "根据所选的返修工序展示": "Display based on the selected repair process",
    "物料名称": "Material Name",
    "物料编号": "Material Code",
    "物料条码": "Material Barcode",
    "物料类型": "Material Type",
    "单位": "Unit",
    "用量": "Dosage",
    "操作": "Operation",
    "请选择需要操作的数据": "Please Need Data",
    "参数下限": "Parameter Down",
    "参数上限": "Parameter Up",
    "参数值": "Parameter Value",
    "参数名称": "Parameter Name",
    "全部": "All",
    "原因未知": "Reason unknown",
    "提示": "Prompt",
    "确定": "Confirm",
    "取消": "Cancel",
    "产品判定": "Product Judgment",
    "基础信息": "Basic Information",
    "暂无数据": "No Data",
    "生产数据": "Production Data",
    "展示该产品在加工过的工序,不良工序以红色标记": "Display the processes that the product has been processed through, and mark the defective processes in red",
    "工序": "Process",
    "记录时间": "Record Time",
    "以判定时间提交": "Submit with the judgment time",
    "不良品处理": "Defective Product Handling",
    "OK": "Qualified",
    "NG": "Not Qualified",
    "数据补充": "Data Supplement:",
    "关闭失败,请先删除已添加工序": "Closing failed. Please delete the added processes first.",
    "判定成功": "Judgment Successful",
    "不良原因配置": "Defective Reason Configuration",
    "请输入关键字": "Please Enter Keywords",
    "删除": "Delete",
    "不良原因不能为空": "Defective reason cannot be empty",
    "保存成功!": "Saved successfully!",
    "保存失败!": "Save failed!",
    "筛选": "Filter",
    "导出": "Export",
    "判定结果": "Judgment Result",
    "是否合格": "Whether Qualified",
    "返修工序": "Repair Process",
    "判定人员": "Judgment Personnel",
    "判定时间": "Judgment Time",
    "判定详情": "Judgment Details",
    "处理说明": "Processing Instructions",
    "时间范围最大跨度为60天": "The maximum time range span is 60 days",
    "产品判定记录.xlsx": "Product Judgment Record.xlsx",
    "添加": "Add",
    "产品型号": "Product Model",
    "判定": "Judge",
    "配置": "Configure",
    "时间范围:": "Time Range:",
    "产品码": "Product Code",
    "工单号": "Work Order Number",
    "请输入": "Please Enter",
    "序号": "Serial Number",
    "不良工序": "Defective Process",
    "不良原因": "Defective Reason",
    "最后记录时间": "Last Record Time",
    "开始时间不能大于结束时间": "The start time cannot be greater than the end time",
    "时间范围最大跨度为90天": "The maximum time range span is 90 days",
    "待办不良品": "Pending Defective Products",
    "产品判定记录": "Product Judgment Record"
  },
  "SopWidgetBox": {
    "请配置业务字段变量": "Please Configture bussiness field variable",
    "成功": "Success",
    "错误": "Error",
    "请输入物料码": "Please Enter Material Code",
    "物料名称": "Material Name",
    "物料编号": "Material Code",
    "物料类型": "Material Type",
    "物料码": "Material Code",
    "参数名称": "Parameter Name",
    "上限": "Upper Limit",
    "下限": "Lower Limit",
    "数值": "Value",
    "请输入数值": "Please Enter Value",
    "已更新": "Has been updated",
    "工序选择": "Process Selection",
    "请选择工序": "Please Select Process",
    "工位选择": "Station Selection",
    "请选择工位": "Please Select Station",
    "请输入": "Please Enter",
    "生产输入": "Production Input",
    "生产控制": "Production Control",
    "对不起,您的浏览器不支持该视频格式": "Sorry, your browser does not support this video format",
    "暂无文件": "No files available",
    "暂无图片": "No pictures available",
    "序号": "Serial Number",
    "工步": "Work Step",
    "操作描述": "Operation Description",
    "状态": "Status"
  },
  "StationBeatAnalysis": {
    "饼图": "Pie chart",
    "工序平均值": "Process average value",
    "分析工序": "Analyze the process",
    "工序加工周期(s)": "Process processing cycle (s)",
    "产品码": "Product Code",
    "班次合计": "Total number of shifts",
    "工序分组": "Process group",
    "工位理论加工周期": "Station theoretical processing cycle",
    "工位详情分析": "Station detail analysis",
    "分析工位": "Analysis station",
    "所有": "All",
    "工位在制品等待情况对比": "Station production waiting comparison",
    "星期一": "Monday",
    "星期二": "Tuesday",
    "星期三": "Wednesday",
    "星期四": "Thursday",
    "星期五": "Friday",
    "星期六": "Saturday",
    "星期日": "Sunday",
    "工位名称": "Station name",
    "工位产量分布及变化幅度": "Station output distribution and change amplitude",
    "工位加工能力对比": "Station processing capability comparison",
    "单位时间产量": "Unit time output",
    "工位平均值": "Station average value",
    "加工结果分组": "Processing result grouping",
    "产品加工详情": "Product processing details",
    "工位运行日志": "Station operation log",
    "单位产品等待时长": "Unit product waiting time",
    "待加工数量": "Number of waiting processing",
    "单位产品加工周期": "Unit product processing cycle",
    "移动极差MR": "Range control chart",
    "发生工位": "Occurrence station",
    "生产班次": "Production shift",
    "进站时间": "Arrival time",
    "出站时间": "Departure time",
    "加工结果": "Processing result",
    "工位加工周期(s)": "Station processing cycle (s)",
    "运行状态": "Running status",
    "开始时间": "Start time",
    "结束时间": "End time",
    "持续时长(s)": "Duration (s)",
    "确认": "Confirm",
    "取消": "Cancel",
    "暂无数据": "No data",
    "共": "Total",
    "页": "Pages",
    "当前第": "Current",
    "每页": "Per Page",
    "条记录": "Records",
    "第": "th",
    "操作": "Operation",
    "产量": "Production Volume",
    "时间分组": "Time Grouping",
    "产量统计": "Production Statistics",
    "工序名称": "Process Name",
    "保存成功": "Saved successfully",
    "星期": "Week",
    "工序产量分布及变化幅度": "Distribution and Magnitude of Changes in Process Output",
    "同比升降幅": "Year-on-year Increase/Decrease",
    "环比升降幅": "Sequential Increase/Decrease",
    "合格数量": "Qualified Quantity",
    "不良数量": "Unqualified Quantity",
    "产量:pcs": "Production: pcs",
    "幅度:": "Amplitude:",
    "班次分组": "Shift Grouping",
    "工序加工能力对比": "Comparison of Workstation Processing Capabilities",
    "单位时间产量:pcs/h": "Production per Unit Time: pcs/h",
    "理论加工周期": "Theoretical Processing Cycle",
    "实际加工周期": "Actual Processing Cycle",
    "单位产品加工周期:s": "Unit Product Processing Cycle: s",
    "工序在制品等待情况对比": "Comparison of Work-in-Progress Waiting Status in Processes",
    "单位产品等待时长:s": "Unit Product Waiting Time: s",
    "待加工数量:pcs": "Quantity to be Processed: pcs",
    "移动极差控制图": "Moving Range Control Chart",
    "UCL": "Upper Control Limit",
    "CL": "Center Line",
    "LCL": "Lower Control Limit",
    "频数": "Frequency",
    "节拍分析": "Beat Analysis",
    "产线设置": "Production Line Settings",
    "生产日期": "Production Date",
    "目标产量": "Target Production",
    "请输入": "Please Enter",
    "保存": "Save",
    "工序理论加工周期": "Theoretical Processing Cycle of Processes",
    "工序详情分析": "Process Detail Analysis",
    "产线整体概况": "Overall Production Line Overview",
    "当日产量(pcs)": "Daily Production (pcs)",
    "上周同比": "Year-on-year Comparison with Last Week",
    "昨日环比": "Sequential Comparison with Yesterday",
    "单位时间产量(pcs/h)": "Production per Unit Time (pcs/h)",
    "平均生产节拍(s)": "Average Production Rhythm (s)",
    "当日完成率": "Daily Completion Rate",
    "产品加工周期分析": "Product Processing Cycle Analysis",
    "样本数": "Sample Size",
    "平均值": "Average Value",
    "最大值": "Maximum Value",
    "最小值": "Minimum Value",
    "极差值": "Range Value",
    "频数分布直方图": "Frequency Distribution Histogram",
    "查询时间间隔不能超过30天": "The query time interval cannot exceed 30 days",
    "生成中": "Generating",
    "不合格品统计": "Unqualified Products Statistics",
    "帕累托图": "Pareto Chart",
    "产线段": "Production Line Segment",
    "产品型号": "Product Model",
    "查询日期": "Query Date",
    "实时模式(定时每分钟刷新当日数据)": "Real-time mode (refresh )",
    "导出分析报告": "Export Report",
    "产品不合格数": "Number of Unqualified Products",
    "产品不合格率": "Unqualified Rate of Products",
    "产品主要不合格原因": "Main Unqualified Reasons for Products",
    "工序": "Process",
    "工位": "Workstation",
    "班次": "Shift",
    "图表": "Chart",
    "柱形图": "Bar Chart",
    "折线图": "Line Chart",
    "柱形折线图": "Bar-Line Chart",
    "返回": "Back",
    "不合格原因分布": "Distribution of Unqualified Reasons",
    "全部": "All"
  },
  "LabelManagement": {
    "产品名称": "Product Name",
    "产品ID": "Product ID",
    "产品型号": "Product Model",
    "工序": "Process",
    "工位": "Workstation",
    "序号": "Serial Number",
    "标签名称": "Label Name",
    "适应产品名称": "Product Name",
    "操作用户": "Operating user",
    "备注": "Remark",
    "添加": "Add",
    "编辑": "Edit",
    "创建副本": "Create a copy",
    "删除": "Delete",
    "请输入关键字": "Please Enter Keywords",
    "使用产品": "Use Product",
    "重新打印": "Reload Print",
    "时间范围": "Time Range",
    "开始时间": "Start Time",
    "结束时间": "End Time",
    "打印时间": "Print Time",
    "打印模板": "Print Template",
    "打印结果": "Print Result",
    "添加标签": "Add Label",
    "数据映射": "Data Mapping",
    "动态字段": "Dynamic Field",
    "数据类型": "Data Type",
    "数据源": "Data Source",
    "工位名称": "Workstation Name",
    "标签模板": "Label Template",
    "打印次数": "Print Count",
    "选择": "Select",
    "适用产品": "Product Name",
    "适用工位": "Work Station",
    "请选择": "Please Select",
    "请选择适用产品": "Please Product",
    "请输入标签名称": "Please Enter Label Name",
    "工位选择": "Station Selection",
    "请选择标签模板": "Please Select Label Template",
    "变量": "Variable",
    "字段": "Field",
    "条码生成规则": "Barcode generation rules",
    "流程上下文": "Flow Context",
    "标识": "FLag",
    "生成规则": "generation rules",
    "下发变量": "Issue Variable",
    "规则类型": "Rule type",
    "条码段组成": "Barcode Segment Composition",
    "条码示例": "Barcode example",
    "更新时间": "Update Time",
    "适用产品名称": "Product Name",
    "请输入备注": "Please enter the remarks",
    "请选择打印机": "Please Select Print",
    "打印模版": "Print Template",
    "请选择打印记录": "Please Select Print Record",
    "适用工位不能为空": "The applicable workstation cannot be empty",
    "适用工位允许为空": "The applicable workstation cannot be empty",
    "数据映射不能为空": "Data mapping cannot be empty",
    "请选择适用工位": "Please select the applicable workstation",
    "复制": "Copy",
    "删除数据": "Delete Data",
    "请选择标签": "Please Select Label",
    "编辑标签": "Edit Label",
    "适用产品不允许为空": "The applicable product cannot be empty",
    "标签名称不允许为空": "The label name cannot be empty",
    "适用工位不允许为空": "The applicable workstation cannot be empty",
    "打印机": "Printer",
    "创建副本成功": "Copy creation successful",
    "确认删除选中标签?": "Confirm deletion of selected label?",
    "打印成功": "Print successful",
    "请输入打印次数": "Please enter the number of prints",
    "仅支持操作单条数据": "Only single data operations are supported",
    "保存成功": "Save successful",
    "删除成功": "Delete successful"
  },
  "WMS-WMSMissionManagement": {
    "任务管理": "Mission Management",
    "创建时间": "CreateTime",
    "筛选": "Filter",
    "导出": "Export",
    "请输入关键字": "Please Enter Keywords",
    "入库": "Inbound",
    "出库": "Outbound",
    "移位": "Transposing",
    "普通入库": "Ordinary storage",
    "普通出库": "Ordinary outbound",
    "空托入库": "Empty pallet storage",
    "空托出库": "Empty palletized outbound",
    "重分配": "redistribution",
    "普通移位": "Ordinary shift"
  },
  "PalletManagement": {
    "导入成功": "Import successful",
    "解绑成功": "Unbinding successful",
    "绑定成功": "Binding successful",
    "保存成功": "Save successful",
    "托盘": "Pallet",
    "导出托盘": "Export Pallet",
    "导出": "Export",
    "导入失败": "Import Fail",
    "导入文件格式不正确,请导入.xlsx/.xls与.csv格式的文件": "Import file format is incorrect, please import .xlsx/.xls and .csv format files",
    "请选择托盘": "Please Select Pallet",
    "确认删除选中托盘?": "Confirm deletion of selected pallet?",
    "删除成功": "Delete successful",
    "删除失败": "Delete failed",
    "删除中": "Deleting",
    "删除托盘": "Delete Pallet",
    "托盘管理": "Pallet Management",
    "托盘编号": "Pallet Number",
    "托盘类型": "Pallet Type",
    "操作": "Operation",
    "状态": "Status",
    "创建时间": "Creation Time",
    "更新时间": "Update Time",
    "仅支持操作单条数据": "Only single data operations are supported",
    "编辑托盘": "Edit Pallet",
    "托盘已使用,请重新选择": "Pallet has been used, please select again",
    "托盘绑定": "Pallet Binding",
    "托盘未使用,请重新选择": "Pallet has not been used, please select again",
    "是否确认解除绑定?": "Confirm pallet binding?",
    "解除绑定成功": "Pallet binding released successfully",
    "使用中": "In Use",
    "未使用": "Not Used",
    "添加": "Add",
    "绑定": "Bind",
    "解绑": "Unbind",
    "自动绑定": "Auto Bind",
    "自动解绑": "Auto Unbind",
    "手动绑定": "Manual Bind",
    "手动解绑": "Manual Unbind",
    "删除": "Delete",
    "托盘绑定记录": "Pallet Binding Record",
    "请输入关键字": "Please Enter Keywords",
    "序号": "Serial Number",
    "托盘码": "Pallet Code",
    "当前绑定产品码": "Current Binding Product Code",
    "备注": "Remark",
    "添加条件": "Add Condition",
    "时间范围": "Time Range",
    "开始时间": "Start Time",
    "结束时间": "End Time",
    "筛选": "Filter",
    "时间范围最大跨度为60天": "The maximum time range span is 60 days",
    "托盘绑定记录.xlsx": "Pallet Binding Record.xlsx",
    "产品码": "Product Code",
    "动作": "Action",
    "工序": "Process",
    "时间": "Time",
    "请选择动作": "Please Select Action",
    "请输入工序": "Please Enter Process",
    "请输入": "Please Enter",
    "请选择": "Please Select",
    "导入": "Import",
    "编辑": "Edit",
    "添加托盘": "Add Pallet",
    "请输入托盘码": "Please Enter Pallet Code",
    "请输入备注信息": "Please Enter Remark Information",
    "请输入产品码": "Please Enter Product Code",
    "产品码不允许为空": "Product code cannot be empty",
    "托盘码不允许为空": "Tray code cannot be empty",
    "请选择状态": "Please select Status"
  }
}
PipeLineLems/web/public/language/MesSuite.en-US.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
{
  "MesSuite": {
    "向上添加一行": "Add a row above",
    "向下添加一行": "Add a row below",
    "删除": "Delete",
    "不能为空": "Cannot be empty",
    "暂无数据": "No data",
    "确定": "Confirm",
    "查询": "search",
    "确认": "Confirm",
    "取消": "Cancel",
    "追溯报表": "Traceability report",
    "报表配置": "Report configuration",
    "共": "Total",
    "页": "Pages",
    "当前第": "Current",
    "每页": "Per Page",
    "条记录": "Records",
    "第": "th",
    "请选择": "Please select"
  }
}
PipeLineLems/web/public/language/ProcessManagement.en-US.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,154 @@
{
  "ProcessManagement": {
    "暂无数据": "No data",
    "请选择": "Please select",
    "工序列表": "WorkSection Report",
    "工位列表": "Workstations Report",
    "详情": "details",
    "筛选": "Filter",
    "添加条件": "Add Condition",
    "添加工序": "Add WorkSection",
    "工序设置": "WorkSection Settings",
    "分组条件": "Group Condition",
    "所属产线段": "Belonging production line segment",
    "请选择所属产线段": "Please select Belonging production line segment",
    "关联流程": "Associated Flow",
    "请选择关联流程": "Please select Associated Flow",
    "分组": "Group",
    "导入": "Import",
    "导出": "Export",
    "请输入": "Please enter",
    "请输入关键字": "Please enter keywords",
    "序号": "Number",
    "工序名称": "WorkSection Name",
    "工序编号": "WorkSection Number",
    "工位名称": "Workstation Name",
    "所属工序": "Belonging WorkSection",
    "请选择所属工序": "Please select Belonging WorkSection",
    "看板IP": "Dashboard IP",
    "SOP信号": "SOP Signal",
    "更新码变量": "Update code variable",
    "备注": "Remark",
    "批量配置": "Batch configuration",
    "参数配置": "Parameter configuration",
    "支持配置产线产品条码与唯一料的装配绑定关系": "Support configuration production line product barcode and unique material assembly binding relationship",
    "物料产品关联工序": "Material product assembly work section",
    "进站结果值配置": "Entry station result value configuration",
    "工序结果值配置": "WorkSection result value configuration",
    "补充说明映射": "Supplementary description mapping relationship",
    "关联条码生成规则": "Associated barcode generation rule",
    "条码名称": "Barcode name",
    "流程名称": "Flow name",
    "选择": "Select",
    "关联物料": "Associated material",
    "关键字": "Keyword",
    "参数信号配置": "Parameter signal configuration",
    "导入成功": "Import success",
    "导出成功": "Export success",
    "功能配置": "Function configuration",
    "采集参数": "Collection parameter",
    "配方参数": "Formula parameter",
    "不良原因": "Defect reason",
    "物料参数": "Material parameter",
    "展开工序详情": "Expand work section details",
    "向上添加工序": "Add work section upwards",
    "向下添加工序": "Add work section downwards",
    "创建工序副本": "Create work section copy",
    "创建副本成功": "Create copy success",
    "删除工序": "Delete work section",
    "是否删除": "Are you sure to delete",
    "删除成功": "Delete success",
    "工序": "WorkSection",
    "导入失败": "Import fail",
    "导入文件格式不正确,请导入.xlsx/.xls与.csv格式的文件": "Import file format is incorrect, please import .xlsx/.xls and .csv format files",
    "请输入工序名称": "Please enter workSection name",
    "请选择产线段": "Please select production line segment",
    "物料名称": "Material name",
    "物料类型": "Material type",
    "参数名": "Parameter name",
    "参数描述": "Parameter description",
    "当前工序数据未保存,是否确认关闭?": "Current work section data has not been saved, do you want to close?",
    "保存成功,注意重启流程服务!": "Save success, please restart the flow service!",
    "产线段": "Production line segment",
    "绑定唯一料": "Binding unique material",
    "进站结果": "Entry station result",
    "下发值": "Issued value",
    "原始值": "Original value",
    "映射值": "Mapping value",
    "保存成功": "Save success",
    "展开工位详情": "Expand workstation details",
    "向上添加工位": "Add workstation upwards",
    "向下添加工位": "Add workstation downwards",
    "添加工位": "Add Workstation",
    "创建工位副本": "Create workstation copy",
    "创建工位副本成功": "Create workstation copy success",
    "删除工位": "Delete workstation",
    "工位": "Workstation",
    "当前工位数据未保存,是否确认关闭?": "Current workstation data has not been saved, do you want to close?",
    "请输入工位名称": "Please enter workstation name",
    "请输入工序编号": "Please enter workSection Number",
    "不能为空或空白字符!": "Cannot be empty or blank characters!",
    "看板IP地址": "Dashboard IP address",
    "请输入看板IP地址": "Please enter dashboard IP address",
    "请选择更新码": "Please select update code",
    "更新码": "Update code",
    "请选择SOP信号": "Please select SOP signal",
    "物料检验信号": "Material inspection signal",
    "物料条码变量": "Material barcode variable",
    "物料校验结果": "Material check result",
    "绑定物料": "Binding material",
    "物料条码信息缓存变量": "Material barcode information cache variable",
    "不良品原因名称": "Defect reason name",
    "判断值": "Judgment value",
    "不良品原因变量": "Defect reason variable",
    "功能名称": "Function name",
    "所属流程": "Belonging flow",
    "功能描述": "Function description",
    "功能选项": "Function option",
    "采集变量": "Collection variable",
    "下发关联变量": "Issued related variable",
    "监听关联变量": "Listen related variable",
    "功能字段": "Function field",
    "描述": "Description",
    "变量规则": "Variable rule",
    "物料编号": "Material number",
    "关联工序": "Related workSection",
    "单位": "Unit",
    "规则类型": "Rule type",
    "条码段组成": "Barcode segment composition",
    "条码示例": "Barcode example",
    "更新时间": "Update time",
    "校验类型": "Check type",
    "校验条码": "Check barcode",
    "交互类型": "Interaction type",
    "功能项": "Function item",
    "工序列表-列表": "WorkSection Report-List",
    "工位-列表": "Workstations Report-List",
    "工序列表-添加": "WorkSection Report-Add",
    "工序列表-设置": "WorkSection Report-Settings",
    "工序列表-过滤": "WorkSection Report-Filter",
    "工序列表-分组": "WorkSection Report-Group",
    "工序列表-导入": "WorkSection Report-Import",
    "工序列表-输出": "WorkSection Report-Export",
    "工位列表-添加": "Workstations Report-Add",
    "工位列表-批量配置": "Workstations Report-Batch Settings",
    "工位列表-过滤": "Workstations Report-Filter",
    "工位列表-分组": "Workstations Report-Group",
    "工位列表-导入": "Workstations Report-Import",
    "工位列表-输出": "Workstations Report-Export",
    "确定": "Confirm",
    "查询": "search",
    "确认": "Confirm",
    "取消": "Cancel",
    "追溯报表": "Traceability report",
    "报表配置": "Report configuration",
    "共": "Total",
    "页": "Pages",
    "当前第": "Current",
    "每页": "Per Page",
    "条记录": "Records",
    "第": "th",
    "操作": "Operation",
    "异常原因": "Abnormal reason"
  }
}
PipeLineLems/web/public/language/ProductManagement.en-US.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
{
  "ProductManagement": {
    "暂无数据": "No data",
    "产品管理": "Product management",
    "添加": "Add",
    "编辑": "Edit",
    "导入": "Import",
    "导出": "Export",
    "SOP管理": "SOP management",
    "SOP配置": "SOP configuration",
    "删除": "Delete",
    "请输入产品名称、产品型号、和产品简码": "Enter product name,model,code",
    "序号": "Serial number",
    "产品名称": "Product name",
    "产品型号": "Product model",
    "产品简号": "Product code",
    "配方名称": "Recipe name",
    "工艺路线": "Process route",
    "备注": "Remark",
    "预览": "Preview",
    "添加本地文件": "Add local file",
    "拖拽至这里上传": "Drag and drop here to upload",
    "工序段": "Process segment",
    "请选择工序段": "Please select a process segment",
    "全部": "All",
    "工序": "Process",
    "请输入工序名称": "Enter process name",
    "工步管理": "Workstep management",
    "新增": "Add",
    "附件": "Attachment",
    "工步和工步描述为必填": "Workstep and description are required",
    "工步名称不可以重复": "Workstep name cannot be repeated",
    "工步": "Workstep",
    "操作描述": "Operation description",
    "请选择工序": "Please select a process",
    "请选择需要删除的工步": "Please select the workstep to be deleted",
    "工序编号": "Process number",
    "工序名称": "Process name",
    "工序段名称": "Process segment name",
    "操作成功": "Operation successful",
    "产品名称不允许为空!": "Product name cannot be empty!",
    "请输入产品名称": "Please enter product name",
    "产品型号不允许为空!": "Product model cannot be empty!",
    "请输入产品型号": "Please enter product model",
    "请输入产品简号": "Please enter product code",
    "创建成功": "Creation successful",
    "修改成功": "Modification successful",
    "删除成功": "Deletion successful",
    "请选择产品进行删除!": "Please select a product to delete!",
    "产品删除后,相关生产数据无法恢复,是否确认删除?": "Product deletion cannot be recovered, are you sure to delete?",
    "新建产品": "New product",
    "请选择一个产品进行编辑": "Please select a product to edit",
    "请选择一个产品进行操作!": "Please select a product to operate!",
    "仅支持编辑单个产品": "Only support editing a single product",
    "该产品型号正在生产中,不可操作!": "The product model is in production, cannot be operated!",
    "仅支持对一个产品型号进行操作!": "Only support operating on a single product model!",
    "产品正在生产中,不允许编辑!": "Product is in production, cannot be edited!",
    "编辑产品": "Edit product",
    "确定": "Confirm",
    "查询": "search",
    "确认": "Confirm",
    "取消": "Cancel",
    "追溯报表": "Traceability report",
    "报表配置": "Report configuration",
    "共": "Total",
    "页": "Pages",
    "当前第": "Current",
    "每页": "Per Page",
    "条记录": "Records",
    "第": "th"
  }
}
PipeLineLems/web/public/language/lmes.en-US.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
{
  "trace": {
    "追溯报表": "Traceability Report",
    "报表配置": "Report Configuration",
    "请输入自然数!": "Please enter a natural number!",
    "输入数量大于参数数量,请重新输入": "The input quantity is greater than the parameter quantity. Please enter again.",
    "冻结列数不能包含多级表头,请重新输入": "The frozen column count cannot include multi-level headers. Please enter again.",
    "业务字段配置": "Business Field Configuration",
    "展示配置": "Display Configuration",
    "总表配置": "Overall Table Configuration",
    "保存成功!": "Saved successfully!",
    "保存失败!": "Failed to save!",
    "提示": "Tip",
    "是否删除": "Do you want to delete?",
    "是": "Yes",
    "否": "No",
    "序号": "Serial Number",
    "产品名称": "Product Name",
    "产品型号": "Product Model",
    "备注": "Remarks",
    "暂无数据": "No data available",
    "请勾选复选框!": "Please check the checkbox!",
    "请选择工序!": "Please select a process!",
    "请选择参数!": "Please select parameters!",
    "上限": "Upper Limit",
    "操作": "Operation",
    "修改": "Modify",
    "总表名称": "Overall Table Name",
    "组合工序": "Combined Process",
    "操作时间": "Operation Time",
    "操作人": "Operator",
    "查看": "View",
    "新建": "New",
    "工序段": "Process Segment",
    "工序名称": "Process Name",
    "选择": "Select",
    "删除": "Delete",
    "新建总表": "New Overall Table",
    "修改总表": "Modify Overall Table",
    "共": "Total",
    "页": "Pages",
    "当前第": "Current",
    "每页": "Per Page",
    "条记录": "Records",
    "导出时间范围": "Export Time Range",
    "工序": "Process",
    "参数": "Parameter",
    "上限值": "Upper Limit Value",
    "下限值": "Lower Limit Value",
    "还原": "Restore",
    "工序参数类型": "Process Parameter Type",
    "工序参数": "Process Parameter",
    "产线段名称": "Production Line Segment Name",
    "工序编号": "Process Number",
    "参数类型": "Parameter Type",
    "参数名称": "Parameter Name",
    "采集参数": "Collected Parameters",
    "下发参数": "Issued Parameters"
  },
  "lmes": {
    "确定": "Confirm",
    "确认": "Confirm",
    "取消": "Cancel"
  }
}
PipeLineLems/web/public/mitm/mitm.html
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,167 @@
<!--
    mitm.html is the lite "man in the middle"
    This is only meant to signal the opener's messageChannel to
    the service worker - when that is done this mitm can be closed
    but it's better to keep it alive since this also stops the sw
    from restarting
    The service worker is capable of intercepting all request and fork their
    own "fake" response - wish we are going to craft
    when the worker then receives a stream then the worker will tell the opener
    to open up a link that will start the download
-->
<script>
// This will prevent the sw from restarting
let keepAlive = () => {
  keepAlive = () => {}
  var ping = location.href.substr(0, location.href.lastIndexOf('/')) + '/ping'
  var interval = setInterval(() => {
    if (sw) {
      sw.postMessage('ping')
    } else {
      fetch(ping).then(res => res.text(!res.ok && clearInterval(interval)))
    }
  }, 10000)
}
// message event is the first thing we need to setup a listner for
// don't want the opener to do a random timeout - instead they can listen for
// the ready event
// but since we need to wait for the Service Worker registration, we store the
// message for later
let messages = []
window.onmessage = evt => messages.push(evt)
let sw = null
let scope = ''
function registerWorker() {
  return navigator.serviceWorker.getRegistration('./').then(swReg => {
    return swReg || navigator.serviceWorker.register('sw.js', { scope: './' })
  }).then(swReg => {
    const swRegTmp = swReg.installing || swReg.waiting
    scope = swReg.scope
    return (sw = swReg.active) || new Promise(resolve => {
      swRegTmp.addEventListener('statechange', fn = () => {
        if (swRegTmp.state === 'activated') {
          swRegTmp.removeEventListener('statechange', fn)
          sw = swReg.active
          resolve()
        }
      })
    })
  })
}
// Now that we have the Service Worker registered we can process messages
function onMessage (event) {
  let { data, ports, origin } = event
  // It's important to have a messageChannel, don't want to interfere
  // with other simultaneous downloads
  if (!ports || !ports.length) {
    throw new TypeError("[StreamSaver] You didn't send a messageChannel")
  }
  if (typeof data !== 'object') {
    throw new TypeError("[StreamSaver] You didn't send a object")
  }
  // the default public service worker for StreamSaver is shared among others.
  // so all download links needs to be prefixed to avoid any other conflict
  data.origin = origin
  // if we ever (in some feature versoin of streamsaver) would like to
  // redirect back to the page of who initiated a http request
  data.referrer = data.referrer || document.referrer || origin
  // pass along version for possible backwards compatibility in sw.js
  data.streamSaverVersion = new URLSearchParams(location.search).get('version')
  if (data.streamSaverVersion === '1.2.0') {
    console.warn('[StreamSaver] please update streamsaver')
  }
  /** @since v2.0.0 */
  if (!data.headers) {
    console.warn("[StreamSaver] pass `data.headers` that you would like to pass along to the service worker\nit should be a 2D array or a key/val object that fetch's Headers api accepts")
  } else {
    // test if it's correct
    // should thorw a typeError if not
    new Headers(data.headers)
  }
  /** @since v2.0.0 */
  if (typeof data.filename === 'string') {
    console.warn("[StreamSaver] You shouldn't send `data.filename` anymore. It should be included in the Content-Disposition header option")
    // Do what File constructor do with fileNames
    data.filename = data.filename.replace(/\//g, ':')
  }
  /** @since v2.0.0 */
  if (data.size) {
    console.warn("[StreamSaver] You shouldn't send `data.size` anymore. It should be included in the content-length header option")
  }
  /** @since v2.0.0 */
  if (data.readableStream) {
    console.warn("[StreamSaver] You should send the readableStream in the messageChannel, not throught mitm")
  }
  /** @since v2.0.0 */
  if (!data.pathname) {
    console.warn("[StreamSaver] Please send `data.pathname` (eg: /pictures/summer.jpg)")
    data.pathname = Math.random().toString().slice(-6) + '/' + data.filename
  }
  // remove all leading slashes
  data.pathname = data.pathname.replace(/^\/+/g, '')
  // remove protocol
  let org = origin.replace(/(^\w+:|^)\/\//, '')
  // set the absolute pathname to the download url.
  data.url = new URL(`${scope + org}/${data.pathname}`).toString()
  if (!data.url.startsWith(`${scope + org}/`)) {
    throw new TypeError('[StreamSaver] bad `data.pathname`')
  }
  // This sends the message data as well as transferring
  // messageChannel.port2 to the service worker. The service worker can
  // then use the transferred port to reply via postMessage(), which
  // will in turn trigger the onmessage handler on messageChannel.port1.
  const transferable = data.readableStream
    ? [ ports[0], data.readableStream ]
    : [ ports[0] ]
  if (!(data.readableStream || data.transferringReadable)) {
    keepAlive()
  }
  return sw.postMessage(data, transferable)
}
if (window.opener) {
  // The opener can't listen to onload event, so we need to help em out!
  // (telling them that we are ready to accept postMessage's)
  window.opener.postMessage('StreamSaver::loadedPopup', '*')
}
if (navigator.serviceWorker) {
  registerWorker().then(() => {
    window.onmessage = onMessage
    messages.forEach(window.onmessage)
  })
}
// FF v102 just started to supports transferable streams, but still needs to ping sw.js
// even tough the service worker dose not have to do any kind of work and listen to any
// messages... #305
keepAlive()
</script>
PipeLineLems/web/public/mitm/sw.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,130 @@
/* global self ReadableStream Response */
self.addEventListener('install', () => {
  self.skipWaiting()
})
self.addEventListener('activate', event => {
  event.waitUntil(self.clients.claim())
})
const map = new Map()
// This should be called once per download
// Each event has a dataChannel that the data will be piped through
self.onmessage = event => {
  // We send a heartbeat every x second to keep the
  // service worker alive if a transferable stream is not sent
  if (event.data === 'ping') {
    return
  }
  const data = event.data
  const downloadUrl = data.url || self.registration.scope + Math.random() + '/' + (typeof data === 'string' ? data : data.filename)
  const port = event.ports[0]
  const metadata = new Array(3) // [stream, data, port]
  metadata[1] = data
  metadata[2] = port
  // Note to self:
  // old streamsaver v1.2.0 might still use `readableStream`...
  // but v2.0.0 will always transfer the stream through MessageChannel #94
  if (event.data.readableStream) {
    metadata[0] = event.data.readableStream
  } else if (event.data.transferringReadable) {
    port.onmessage = evt => {
      port.onmessage = null
      metadata[0] = evt.data.readableStream
    }
  } else {
    metadata[0] = createStream(port)
  }
  map.set(downloadUrl, metadata)
  port.postMessage({ download: downloadUrl })
}
function createStream (port) {
  // ReadableStream is only supported by chrome 52
  return new ReadableStream({
    start (controller) {
      // When we receive data on the messageChannel, we write
      port.onmessage = ({ data }) => {
        if (data === 'end') {
          return controller.close()
        }
        if (data === 'abort') {
          controller.error('Aborted the download')
          return
        }
        controller.enqueue(data)
      }
    },
    cancel (reason) {
      console.log('user aborted', reason)
      port.postMessage({ abort: true })
    }
  })
}
self.onfetch = event => {
  const url = event.request.url
  // this only works for Firefox
  if (url.endsWith('/ping')) {
    return event.respondWith(new Response('pong'))
  }
  const hijacke = map.get(url)
  if (!hijacke) return null
  const [ stream, data, port ] = hijacke
  map.delete(url)
  // Not comfortable letting any user control all headers
  // so we only copy over the length & disposition
  const responseHeaders = new Headers({
    'Content-Type': 'application/octet-stream; charset=utf-8',
    // To be on the safe side, The link can be opened in a iframe.
    // but octet-stream should stop it.
    'Content-Security-Policy': "default-src 'none'",
    'X-Content-Security-Policy': "default-src 'none'",
    'X-WebKit-CSP': "default-src 'none'",
    'X-XSS-Protection': '1; mode=block',
    'Cross-Origin-Embedder-Policy': 'require-corp'
  })
  let headers = new Headers(data.headers || {})
  if (headers.has('Content-Length')) {
    responseHeaders.set('Content-Length', headers.get('Content-Length'))
  }
  if (headers.has('Content-Disposition')) {
    responseHeaders.set('Content-Disposition', headers.get('Content-Disposition'))
  }
  // data, data.filename and size should not be used anymore
  if (data.size) {
    console.warn('Depricated')
    responseHeaders.set('Content-Length', data.size)
  }
  let fileName = typeof data === 'string' ? data : data.filename
  if (fileName) {
    console.warn('Depricated')
    // Make filename RFC5987 compatible
    fileName = encodeURIComponent(fileName).replace(/['()]/g, escape).replace(/\*/g, '%2A')
    responseHeaders.set('Content-Disposition', "attachment; filename*=UTF-8''" + fileName)
  }
  event.respondWith(new Response(stream, { headers: responseHeaders }))
  port.postMessage({ debug: 'Download started' })
}
PipeLineLems/web/pull.sh
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,95 @@
#!/bin/bash
# å®šä¹‰é»˜è®¤ç»„件数组
default_widget_Array=()
# è¯»å– .build.prod æ–‡ä»¶å†…容
if [[ -f ./.build.prod ]]; then
    while IFS= read -r line || [ -n "$line" ]; do
        default_widget_Array+=("$line")
    done < ./.build.prod
fi
# ä»Ž .build æ–‡ä»¶è¯»å–内容
buildFile="./.build"
build_widget_Array=()
if [ -f "$buildFile" ]; then
    while IFS= read -r line || [ -n "$line" ]; do
        build_widget_Array+=("$line")
    done < "$buildFile"
fi
if [ -f "$buildFile.local" ]; then
    while IFS= read -r line || [ -n "$line" ]; do
        build_widget_Array+=("$line")
    done < "$buildFile.local"
fi
# æ£€æŸ¥å¹¶åˆ›å»º src/widgets ç›®å½•
basePath="src/widgets"
if [ ! -d "$basePath" ]; then
    echo "目录 $basePath ä¸å­˜åœ¨ã€‚正在创建..."
    mkdir -p "$basePath"
fi
# å®šä¹‰ä¸€ä¸ªå‡½æ•°æ¥å¤„理组件
process_widget() {
    local widgetName=$1
    local gitUrl=$2
    local widgetPath="$basePath/$widgetName"
    if [ -d "$widgetPath" ]; then
        # å¦‚果文件夹存在,进入该文件夹并执行 git pull
        echo "目录 $widgetPath å·²å­˜åœ¨ã€‚正在拉取最新更改..."
        cd "$widgetPath" || exit
        # æ£€æŸ¥æ˜¯å¦è®¾ç½®äº†è·Ÿè¸ªåˆ†æ”¯
        branch_name=$(git rev-parse --abbrev-ref HEAD)
        upstream_branch=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null)
        if [ -z "$upstream_branch" ]; then
            echo "当前分支 $branch_name æ²¡æœ‰è®¾ç½®ä¸Šæ¸¸åˆ†æ”¯ã€‚正在设置上游分支为 origin/$branch_name..."
            git branch --set-upstream-to=origin/$branch_name
        fi
        # æ£€æŸ¥æ˜¯å¦æœ‰æœªæäº¤çš„æ›´æ”¹
        stash_result=1
        if [ -n "$(git status --porcelain)" ]; then
            echo "检测到未提交的更改。正在暂存更改..."
            git stash push -m "在拉取 $widgetName æ›´æ”¹ä¹‹å‰è‡ªåŠ¨æš‚å­˜"
            stash_result=$?
        fi
        # å°è¯•执行 git pull
        git pull --ff-only
        pull_result=$?
        # å¦‚果有 stash,则尝试恢复
        if [ "$stash_result" -eq 0 ]; then
            echo "正在恢复暂存的更改..."
            git stash pop
        fi
        cd - || exit
        # æ£€æŸ¥ pull çš„结果
        if [ "$pull_result" -ne 0 ]; then
            echo "$widgetName çš„ git pull å¤±è´¥ã€‚请手动解决冲突。"
        fi
    else
        # å¦‚果文件夹不存在,执行 git clone
        echo "目录 $widgetPath ä¸å­˜åœ¨ã€‚正在克隆仓库..."
        git clone "$gitUrl" "$widgetPath"
    fi
}
# å¤„理默认组件数组
for widgetName in "${default_widget_Array[@]}"; do
    gitUrl="https://gitlab.syc-cms.com/lmes-plugin/web/$widgetName.git"
    process_widget "$widgetName" "$gitUrl"
done
# å¤„理 .build æ–‡ä»¶ä¸­çš„组件数组
for widgetName in "${build_widget_Array[@]}"; do
    gitUrl="https://gitlab.syc-cms.com/lmes-plugin/web/business/$widgetName.git"
    process_widget "$widgetName" "$gitUrl"
done
PipeLineLems/web/release.sh
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,79 @@
#!/bin/bash
# é»˜è®¤ç»„件数组
widget_Array=()
# æŒ‡å®šåˆ†æ”¯åä½œä¸ºå‚æ•°ä¼ å…¥
target_branch=$1
if [[ -z "$target_branch" ]]; then
    echo "请指定目标分支,例如: bash $0 feature/wg/test"
    exit 1
fi
# è¯»å– .build.prod æ–‡ä»¶å†…容
if [[ -f ./.build.prod ]]; then
    while IFS= read -r line || [ -n "$line" ]; do
        widget_Array+=("$line")
    done < ./.build.prod
fi
# è¯»å– .build æ–‡ä»¶å†…容
if [[ -f ./.build ]]; then
    while IFS= read -r line || [ -n "$line" ]; do
        widget_Array+=("$line")
    done < ./.build
fi
# è¯»å– .build æ–‡ä»¶å†…容
if [[ -f ./.build.local ]]; then
    while IFS= read -r line || [ -n "$line" ]; do
        widget_Array+=("$line")
    done < ./.build.local
fi
# åŽ»é‡
widget_Array=($(echo "${widget_Array[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
# å­˜å‚¨åˆ‡æ¢ä¿¡æ¯çš„æ•°ç»„
declare -a switch_info
# å¾ªçŽ¯éåŽ†ç»„ä»¶æ•°ç»„
for widgetName in "${widget_Array[@]}"; do
    # æ£€æŸ¥ç»„件目录是否存在
    if [[ -d "src/widgets/$widgetName" ]]; then
        # è¿›å…¥ç»„件目录
        cd "src/widgets/$widgetName" || { echo "无法进入目录 src/widgets/$widgetName"; continue; }
        # èŽ·å–å½“å‰åˆ†æ”¯å
        current_branch=$(git symbolic-ref --short HEAD)
        # å¦‚果当前分支代码有变动,提交变更
        if [[ -n "$(git status --porcelain)" ]]; then
            git add .
            git commit -m 'merge'
        fi
       # åˆ›å»ºä¸€ä¸ªtag
        git tag  "$target_branch"
        git push origin "$target_branch"
        # èŽ·å–åˆ‡æ¢åŽçš„åˆ†æ”¯å
        new_branch=$(git symbolic-ref --short HEAD)
        # æŽ¨é€æŒ‡å®šåˆ†æ”¯åˆ°è¿œç¨‹
        # git push --set-upstream origin "$target_branch"
        # æ·»åŠ åˆ‡æ¢ä¿¡æ¯åˆ°æ•°ç»„
        switch_info+=("$widgetName: $current_branch -> $new_branch")
        # è¿”回到上一级目录
        cd - > /dev/null || exit
    else
        echo "目录 src/widgets/$widgetName ä¸å­˜åœ¨"
    fi
done
# ç»Ÿä¸€è¾“出切换信息
echo "切换信息:"
for info in "${switch_info[@]}"; do
    echo "$info"
done
PipeLineLems/web/script/ZipAFolder.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,174 @@
'use strict'
Object.defineProperty(exports, '__esModule', { value: true })
exports.tar =
  exports.zip =
  exports.ZipAFolder =
  exports.COMPRESSION_LEVEL =
    void 0
const tslib_1 = require('tslib')
const fs_1 = require('fs')
const path_1 = tslib_1.__importDefault(require('path'))
const archiver_1 = tslib_1.__importDefault(require('archiver'))
const promises_1 = tslib_1.__importDefault(require('fs/promises'))
const is_glob_1 = tslib_1.__importDefault(require('is-glob'))
const glob_1 = require('glob')
var COMPRESSION_LEVEL
;(function (COMPRESSION_LEVEL) {
  COMPRESSION_LEVEL[(COMPRESSION_LEVEL['uncompressed'] = 0)] = 'uncompressed'
  COMPRESSION_LEVEL[(COMPRESSION_LEVEL['medium'] = 5)] = 'medium'
  COMPRESSION_LEVEL[(COMPRESSION_LEVEL['high'] = 9)] = 'high'
})(COMPRESSION_LEVEL || (exports.COMPRESSION_LEVEL = COMPRESSION_LEVEL = {}))
class ZipAFolder {
  static tar(src, tarFilePath, zipAFolderOptions) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
      const o = zipAFolderOptions || {
        compression: COMPRESSION_LEVEL.high,
      }
      if (o.compression === COMPRESSION_LEVEL.uncompressed) {
        yield ZipAFolder.compress({
          src,
          targetFilePath: tarFilePath,
          format: 'tar',
          zipAFolderOptions,
        })
      } else {
        yield ZipAFolder.compress({
          src,
          targetFilePath: tarFilePath,
          format: 'tar',
          zipAFolderOptions,
          archiverOptions: {
            gzip: true,
            gzipOptions: {
              level: o.compression,
            },
          },
        })
      }
    })
  }
  static zip(src, zipFilePath, zipAFolderOptions) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
      const o = zipAFolderOptions || {
        compression: COMPRESSION_LEVEL.high,
      }
      if (o.compression === COMPRESSION_LEVEL.uncompressed) {
        yield ZipAFolder.compress({
          src,
          targetFilePath: zipFilePath,
          format: 'zip',
          zipAFolderOptions,
          archiverOptions: {
            store: true,
          },
        })
      } else {
        yield ZipAFolder.compress({
          src,
          targetFilePath: zipFilePath,
          format: 'zip',
          zipAFolderOptions,
          archiverOptions: {
            zlib: {
              level: o.compression,
            },
          },
        })
      }
    })
  }
  static compress(_a) {
    return tslib_1.__awaiter(
      this,
      arguments,
      void 0,
      function* ({
        src,
        targetFilePath,
        format,
        zipAFolderOptions,
        archiverOptions,
      }) {
        let output
        const globList = []
        if (
          !(zipAFolderOptions === null || zipAFolderOptions === void 0
            ? void 0
            : zipAFolderOptions.customWriteStream) &&
          targetFilePath
        ) {
          const targetBasePath = path_1.default.dirname(targetFilePath)
          if (targetBasePath === src) {
            throw new Error('Source and target folder must be different.')
          }
          try {
            if (!(0, is_glob_1.default)(src)) {
              yield promises_1.default.access(
                src,
                promises_1.default.constants.R_OK
              )
            }
            yield promises_1.default.access(
              targetBasePath,
              promises_1.default.constants.R_OK |
                promises_1.default.constants.W_OK
            )
          } catch (e) {
            throw new Error(`Permission error: ${e.message}`)
          }
          if ((0, is_glob_1.default)(src)) {
            for (const globPart of src.split(',')) {
              globList.push(...(yield (0, glob_1.glob)(globPart.trim())))
            }
            if (globList.length === 0) {
              throw new Error(`No glob match found for "${src}".`)
            }
          }
          output = (0, fs_1.createWriteStream)(targetFilePath)
        } else if (zipAFolderOptions && zipAFolderOptions.customWriteStream) {
          output = zipAFolderOptions.customWriteStream
        } else {
          throw new Error(
            'You must either provide a target file path or a custom write stream to write to.'
          )
        }
        const zipArchive = (0, archiver_1.default)(
          format,
          archiverOptions || {}
        )
        return new Promise((resolve, reject) =>
          tslib_1.__awaiter(this, void 0, void 0, function* () {
            output.on('close', resolve)
            output.on('error', reject)
            zipArchive.pipe(output)
            if ((0, is_glob_1.default)(src)) {
              for (const file of globList) {
                if ((yield promises_1.default.lstat(file)).isFile()) {
                  const content = yield promises_1.default.readFile(file)
                  zipArchive.append(content, {
                    name: file,
                  })
                }
              }
            } else {
              zipArchive.directory(
                src,
                (zipAFolderOptions === null || zipAFolderOptions === void 0
                  ? void 0
                  : zipAFolderOptions.destPath) || false,
                {
                  date: new Date(Date.now() + 8 * 60 * 60 * 1000),
                }
              )
            }
            yield zipArchive.finalize()
          })
        )
      }
    )
  }
}
exports.ZipAFolder = ZipAFolder
exports.zip = ZipAFolder.zip
exports.tar = ZipAFolder.tar
//# sourceMappingURL=ZipAFolder.js.map
PipeLineLems/web/script/build.js
@@ -1,33 +1,43 @@
const crossSpawn = require('cross-spawn')
const slash = require('slash')
const tag = require('./tag.js')
const { writeFileSync, rmSync, ensureFileSync } = require('fs-extra')
const { spawn } = require('node:child_process')
const { globSync } = require('glob')
const path = require('path')
const fs = require('fs-extra')
const slash = require('slash')
const crossSpawn = require('cross-spawn')
const { zip } = require('./ZipAFolder.js')
const os = require('os')
const chalk = require('chalk')
const assetsUrl =
  'https://lmes:gldt-ocw4NMSiDHD461ZoDvyK@gitlab.syc-cms.com:8443/lmes-plugin/web/assets.git'
const baseDir = './node_modules/.cache/wwwroot'
const baseBuildFile = './node_modules/.cache/widgets.json'
let isSingleBuild = false
const isWin = process.platform === 'win32'
const argvPath = './script/.argv'
const widgetName = process.argv[process.argv.length - 1]
const widgetsPath = globSync(`./src/widgets/*/index.ts`)
const getWidgetNames = widgetsPath.map((file) => {
  const parts = isWin
    ? path.resolve(file).split('\\')
    : path.resolve(file).split('/')
  return parts[parts.length - 2]
})
if (getWidgetNames.includes(widgetName)) {
  isSingleBuild = true
  writeFileSync(argvPath, widgetName)
}
const hostPath = slash(path.resolve(process.cwd(), baseDir))
const hostZipPath = slash(path.resolve(process.cwd(), './wwwroot.zip'))
let isSingleBuild = false
let isDebugMode = false
// ç¼–译结束数量
let buildCount = 0
// ç¼–译进程总数量
let buildSumCount = 0
let startTime = Date.now()
removeHostPackage()
buildWidgets()
/**
 * ç¼–译组件
 */
function buildWidgets() {
  const isWin = process.platform === 'win32'
  const argv = process.argv || []
  // å½“debug模式下,将本地205环境下的图片地址映射到代码中,可以将.env环境进行设置VITE_STATIC_URL
  if (argv.includes('debug')) {
    isDebugMode = true
    argv.splice(argv.indexOf('debug'), 1)
  }
  const widgetName = argv[argv.length - 1]
  const widgetsPath = globSync(`./src/widgets/*/index.ts`)
@@ -36,8 +46,15 @@
      ? path.resolve(file).split('\\')
      : path.resolve(file).split('/')
    return parts[parts.length - 2]
  }) // æ‰“包一个组件
  })
  try {
    fs.removeSync('./dist')
  } catch (error) {
    console.error('dist不存在,继续执行打包任务')
  }
  // æ‰“包一个组件
  if (widgetName && widgetNames.includes(widgetName)) {
    isSingleBuild = true
    writeFileSync(argvPath, widgetName)
@@ -68,7 +85,7 @@
 */
function divideArray(widgets) {
  // å½“打包时,操作电脑可能会卡
  const cpus = os.availableParallelism() > 1 ? os.availableParallelism() - 1 : 1
  const cpus = os.availableParallelism()
  let result = {}
  let dataPerKey = Math.floor(widgets.length / cpus)
  let remainingData = widgets.length
@@ -90,7 +107,8 @@
 */
function runBuild(nodeIndex) {
  const cmdParams = ['run', 'build-lib']
  const run = crossSpawn(
  cmdParams.push(isDebugMode ? 'development' : 'production')
  const run = spawn(
    process.platform === 'win32' ? 'npm.cmd' : 'npm',
    cmdParams,
    {
@@ -103,7 +121,101 @@
      },
    }
  )
  run.on('close', (code) => {
  run.on('close', async (code) => {
    if (code == 0 && isSingleBuild) rmSync(argvPath)
    // æ·»åŠ æ‰“åŒ…hash
    buildCount++
    await tag()
    if (!isSingleBuild) {
      if (buildCount >= buildSumCount) {
        console.log(chalk.green(`已经编译完所有组件,添加版本tag,请稍后...`))
        console.log(chalk.green(`开始打包wwwroot.zip包`))
        getHostPackage()
      }
    }
  })
}
/**
 * èŽ·å–host包 zip包
 * @param {*}
 */
function getHostPackage() {
  const resourcesPath = slash(
    path.resolve(process.cwd(), `${baseDir}/resources`)
  )
  const widgetsPath = slash(path.resolve(process.cwd(), `${baseDir}/widgets`))
  const currentDistPath = slash(path.resolve(process.cwd(), './dist'))
  const isResources = fs.existsSync(resourcesPath)
  const isWidgets = fs.existsSync(widgetsPath)
  if (!isResources) {
    fs.mkdirpSync(resourcesPath)
  }
  if (!isWidgets) {
    fs.mkdirpSync(widgetsPath)
  }
  const git = crossSpawn.sync('git', ['clone', assetsUrl, '-b', 'develop'], {
    stdio: 'inherit',
    cwd: resourcesPath,
    shell: true,
  })
  if (git.status === 0) {
    fs.rmSync(slash(path.resolve(resourcesPath, './assets/.git')), {
      recursive: true,
    })
    const dirs = globSync(slash(path.resolve(currentDistPath, './**/*.js')))
    dirs.forEach((dir) => {
      const widgetName = slash(dir).replace(currentDistPath, '.')
      const widgetPath = slash(path.resolve(widgetsPath, widgetName))
      fs.copySync(slash(dir), widgetPath)
    })
    zipDir(hostPath, hostZipPath)
      .then(() => {
        console.log(chalk.green(`${hostZipPath} åŽ‹ç¼©æˆåŠŸ`))
        fs.rmSync(hostPath, {
          recursive: true,
        })
        console.log(
          chalk.green(`编译总时间: ${(Date.now() - startTime) / 1000}秒`)
        )
      })
      .catch((error) => {
        console.log(error)
      })
  }
}
/**
 * åŽ‹ç¼©zip包
 * @param {*} dir
 * @param {*} zipPath
 * @returns
 */
function zipDir(dir, zipPath) {
  return new Promise(async (resolve, reject) => {
    try {
      await zip(slash(dir), slash(zipPath))
      resolve()
    } catch (error) {
      reject(error)
    }
  })
}
/**
 * åˆ é™¤host包
 * @param {*}
 */
function removeHostPackage() {
  if (fs.existsSync(hostPath)) {
    fs.rmSync(hostPath, {
      recursive: true,
    })
  }
  if (fs.existsSync(hostZipPath)) {
    fs.rmSync(hostZipPath, {
      recursive: true,
    })
  }
}
PipeLineLems/web/script/filterExternal.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,318 @@
export const external = [
  'vue',
  'sdk',
  'element-plus',
  'lodash',
  'sortablejs',
  '@vueuse/core',
  'dayjs',
  'vxe-table',
  '@element-plus/icons-vue',
  '@element-plus',
  /node_modules/,
  'koa-compose',
  'lodash',
  'lodash/add',
  'lodash/after',
  'lodash/ary',
  'lodash/assign',
  'lodash/assignIn',
  'lodash/assignInWith',
  'lodash/assignWith',
  'lodash/at',
  'lodash/attempt',
  'lodash/before',
  'lodash/bind',
  'lodash/bindAll',
  'lodash/bindKey',
  'lodash/camelCase',
  'lodash/capitalize',
  'lodash/castArray',
  'lodash/ceil',
  'lodash/chain',
  'lodash/chunk',
  'lodash/clamp',
  'lodash/clone',
  'lodash/cloneDeep',
  'lodash/cloneDeepWith',
  'lodash/cloneWith',
  'lodash/commit',
  'lodash/compact',
  'lodash/concat',
  'lodash/cond',
  'lodash/conforms',
  'lodash/conformsTo',
  'lodash/constant',
  'lodash/countBy',
  'lodash/create',
  'lodash/curry',
  'lodash/curryRight',
  'lodash/debounce',
  'lodash/deburr',
  'lodash/defaults',
  'lodash/defaultsDeep',
  'lodash/defaultTo',
  'lodash/defer',
  'lodash/delay',
  'lodash/difference',
  'lodash/differenceBy',
  'lodash/differenceWith',
  'lodash/divide',
  'lodash/drop',
  'lodash/dropRight',
  'lodash/dropRightWhile',
  'lodash/dropWhile',
  'lodash/each',
  'lodash/eachRight',
  'lodash/endsWith',
  'lodash/entries',
  'lodash/entriesIn',
  'lodash/eq',
  'lodash/escape',
  'lodash/escapeRegExp',
  'lodash/every',
  'lodash/extend',
  'lodash/extendWith',
  'lodash/fill',
  'lodash/filter',
  'lodash/find',
  'lodash/findIndex',
  'lodash/findKey',
  'lodash/findLast',
  'lodash/findLastIndex',
  'lodash/findLastKey',
  'lodash/first',
  'lodash/flatMap',
  'lodash/flatMapDeep',
  'lodash/flatMapDepth',
  'lodash/flatten',
  'lodash/flattenDeep',
  'lodash/flattenDepth',
  'lodash/flip',
  'lodash/floor',
  'lodash/flow',
  'lodash/flowRight',
  'lodash/forEach',
  'lodash/forEachRight',
  'lodash/forIn',
  'lodash/forInRight',
  'lodash/forOwn',
  'lodash/forOwnRight',
  'lodash/fromPairs',
  'lodash/functions',
  'lodash/functionsIn',
  'lodash/get',
  'lodash/groupBy',
  'lodash/gt',
  'lodash/gte',
  'lodash/has',
  'lodash/hasIn',
  'lodash/head',
  'lodash/identity',
  'lodash/includes',
  'lodash/indexOf',
  'lodash/initial',
  'lodash/inRange',
  'lodash/intersection',
  'lodash/intersectionBy',
  'lodash/intersectionWith',
  'lodash/invert',
  'lodash/invertBy',
  'lodash/invoke',
  'lodash/invokeMap',
  'lodash/isArguments',
  'lodash/isArray',
  'lodash/isArrayBuffer',
  'lodash/isArrayLike',
  'lodash/isArrayLikeObject',
  'lodash/isBoolean',
  'lodash/isBuffer',
  'lodash/isDate',
  'lodash/isElement',
  'lodash/isEmpty',
  'lodash/isEqual',
  'lodash/isEqualWith',
  'lodash/isError',
  'lodash/isFinite',
  'lodash/isFunction',
  'lodash/isInteger',
  'lodash/isLength',
  'lodash/isMap',
  'lodash/isMatch',
  'lodash/isMatchWith',
  'lodash/isNaN',
  'lodash/isNative',
  'lodash/isNil',
  'lodash/isNull',
  'lodash/isNumber',
  'lodash/isObject',
  'lodash/isObjectLike',
  'lodash/isPlainObject',
  'lodash/isRegExp',
  'lodash/isSafeInteger',
  'lodash/isSet',
  'lodash/isString',
  'lodash/isSymbol',
  'lodash/isTypedArray',
  'lodash/isUndefined',
  'lodash/isWeakMap',
  'lodash/isWeakSet',
  'lodash/join',
  'lodash/kebabCase',
  'lodash/keyBy',
  'lodash/keys',
  'lodash/keysIn',
  'lodash/last',
  'lodash/lastIndexOf',
  'lodash/lowerCase',
  'lodash/lowerFirst',
  'lodash/lt',
  'lodash/lte',
  'lodash/map',
  'lodash/mapKeys',
  'lodash/mapValues',
  'lodash/matches',
  'lodash/matchesProperty',
  'lodash/max',
  'lodash/maxBy',
  'lodash/mean',
  'lodash/meanBy',
  'lodash/memoize',
  'lodash/merge',
  'lodash/mergeWith',
  'lodash/method',
  'lodash/methodOf',
  'lodash/min',
  'lodash/minBy',
  'lodash/mixin',
  'lodash/multiply',
  'lodash/negate',
  'lodash/noConflict',
  'lodash/noop',
  'lodash/now',
  'lodash/nth',
  'lodash/nthArg',
  'lodash/once',
  'lodash/orderBy',
  'lodash/over',
  'lodash/overArgs',
  'lodash/overEvery',
  'lodash/overSome',
  'lodash/pad',
  'lodash/padEnd',
  'lodash/padStart',
  'lodash/parseInt',
  'lodash/partial',
  'lodash/partialRight',
  'lodash/partition',
  'lodash/pick',
  'lodash/pickBy',
  'lodash/property',
  'lodash/propertyOf',
  'lodash/pull',
  'lodash/pullAll',
  'lodash/pullAllBy',
  'lodash/pullAllWith',
  'lodash/pullAt',
  'lodash/random',
  'lodash/range',
  'lodash/rangeRight',
  'lodash/rearg',
  'lodash/reduce',
  'lodash/reduceRight',
  'lodash/reject',
  'lodash/remove',
  'lodash/repeat',
  'lodash/replace',
  'lodash/result',
  'lodash/reverse',
  'lodash/round',
  'lodash/sample',
  'lodash/sampleSize',
  'lodash/set',
  'lodash/setWith',
  'lodash/shuffle',
  'lodash/size',
  'lodash/slice',
  'lodash/snakeCase',
  'lodash/some',
  'lodash/sortBy',
  'lodash/sortedIndex',
  'lodash/sortedIndexBy',
  'lodash/sortedIndexOf',
  'lodash/sortedLastIndex',
  'lodash/sortedLastIndexBy',
  'lodash/sortedLastIndexOf',
  'lodash/sortedUniq',
  'lodash/sortedUniqBy',
  'lodash/split',
  'lodash/spread',
  'lodash/startCase',
  'lodash/startsWith',
  'lodash/stubArray',
  'lodash/stubFalse',
  'lodash/stubObject',
  'lodash/stubString',
  'lodash/stubTrue',
  'lodash/subtract',
  'lodash/sum',
  'lodash/sumBy',
  'lodash/tail',
  'lodash/take',
  'lodash/takeRight',
  'lodash/takeRightWhile',
  'lodash/takeWhile',
  'lodash/tap',
  'lodash/template',
  'lodash/throttle',
  'lodash/thru',
  'lodash/times',
  'lodash/toArray',
  'lodash/toFinite',
  'lodash/toInteger',
  'lodash/toLength',
  'lodash/toLower',
  'lodash/toNumber',
  'lodash/toPairs',
  'lodash/toPairsIn',
  'lodash/toPath',
  'lodash/toPlainObject',
  'lodash/toSafeInteger',
  'lodash/toString',
  'lodash/toUpper',
  'lodash/transform',
  'lodash/trim',
  'lodash/trimEnd',
  'lodash/trimStart',
  'lodash/truncate',
  'lodash/unary',
  'lodash/unescape',
  'lodash/union',
  'lodash/unionBy',
  'lodash/unionWith',
  'lodash/uniq',
  'lodash/uniqBy',
  'lodash/uniqWith',
  'lodash/uniqueId',
  'lodash/unset',
  'lodash/unzip',
  'lodash/unzipWith',
  'lodash/update',
  'lodash/updateWith',
  'lodash/upperCase',
  'lodash/upperFirst',
  'lodash/value',
  'lodash/valueOf',
  'lodash/values',
  'lodash/valuesIn',
  'lodash/without',
  'lodash/words',
  'lodash/wrap',
  'lodash/xor',
  'lodash/xorBy',
  'lodash/xorWith',
  'lodash/zip',
  'lodash/zipObject',
  'lodash/zipObjectDeep',
  'lodash/zipWith',
]
PipeLineLems/web/script/generateMenu.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,112 @@
const { glob } = require('glob')
const { readFileSync, writeFileSync, ensureDirSync } = require('fs-extra')
const { resolve } = require('path')
const regExp = /export default [\s\S]*?;/
const regExpObj = /\{[\s\S]*?;/
const babel = require('@babel/core')
const pkg = require('../package.json')
const isWin = process.platform === 'win32'
/**
 * æ ¹æ®widgets下的组件,自动生成菜单数据,用于对外引用
 */
async function start() {
  const tsFiles = await glob(resolve(process.cwd(), 'src/widgets/*/index.ts'), {
    ignore: 'node_modules/**',
    windowsPathsNoEscape: true,
  })
  const menu = []
  const menuMap = {}
  const errorKey = ' is not defined'
  tsFiles.forEach((filePath) => {
    const spl = !isWin ? filePath.split('/') : filePath.split('\\')
    const patchName = spl[spl.length - 2]
    const file = readFileSync(filePath, { encoding: 'utf8' })
    const { code } = babel.transformSync(file)
    const exportDefaultRegion = code.match(regExp)
    const exportDefaultContent = exportDefaultRegion[0]
    if (exportDefaultContent) {
      const v = exportDefaultContent.match(regExpObj)
      const canvasView = exportDefaultContent.match(/canvasView: ([^,]+),/)
      let canvasViewValue = canvasView ? canvasView[0] : ''
      canvasViewValue = !canvasViewValue.includes(')')
        ? canvasViewValue.replace(',', '),')
        : canvasViewValue
      const c = v[0].replace(canvasViewValue, '')
      let setViewMatch = c.match(/settingsView:\s*(.*?)(?=\s*[,}])/)
      let newCode = ''
      if (setViewMatch[0]) {
        newCode = c.replace(setViewMatch[0], '').replace(';', '')
      }
      if (newCode.includes('canvasView')) {
        newCode = newCode.replace(
          /canvasView\s*:\s*.*?(\{.*?\}|\(.*?\)|[^\s,]+)\s*,?\s*(?=\n|$)/gs,
          ''
        )
      }
      const codeRun = (code) => {
        const fn = new Function(`return ${code}`)
        const widgetInfo = fn()
        const row = {
          name: widgetInfo.name,
          path: `/${pkg.name}/` + patchName,
          patchName: patchName,
          icon: widgetInfo.icon,
          notPage: !!widgetInfo.notPage,
        }
        menu.push(row)
        menuMap[patchName] = row
      }
      try {
        codeRun(newCode)
      } catch (error) {
        if (error.message.includes(errorKey)) {
          const iconKey = error.message.split(errorKey)
          if (iconKey.length > 1) {
            const iconName = iconKey[0]
            const code = newCode.replaceAll(iconName, `"${iconName}"`)
            codeRun(code)
          }
        } else {
          console.error(error.message)
        }
      }
    }
  })
  const data = `export const menu: Record<string,any>[] = ${JSON.stringify(
    menu,
    null,
    2
  )};\nexport const menuMap: Record<string,any> = ${JSON.stringify(
    menuMap,
    null,
    2
  )};`
  // ç”Ÿæˆmenu JSON åˆ°build.prod
  // const buildInfo = readFileSync(resolve(process.cwd(), '.build'), {
  //   encoding: 'utf-8',
  // })
  // const recoveryWidget = buildInfo.split('\n')
  ensureDirSync(resolve(process.cwd(), './src/config/'))
  writeFileSync(resolve(process.cwd(), './src/config/menu.ts'), data, {
    encoding: 'utf-8',
  })
  // const widgets = menu
  //   .map((item) => item.patchName)
  //   .filter((name) => !recoveryWidget.includes(name))
  // writeFileSync(resolve(process.cwd(), '.build.prod'), widgets.join('\n'), {
  //   encoding: 'utf-8',
  // })
}
const startTime = performance.now()
start()
console.log('执行时间: ', Math.ceil(performance.now() - startTime), 'ms')
PipeLineLems/web/script/plugins/vite-plugin-development-filter.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
// @ts-nocheck
/**
 * å¢žåŠ debugger组件
 * @param option
 * @returns
 */
export default function VitePluginDevelopmentFilter(option: {
  tag: string
  prodTag: string
}): any {
  return {
    name: 'vite-plugin-development-filter',
    transform(code, id) {
      const { tag, prodTag } = option
      const regexWithCapture = new RegExp(
        `<${tag}>([\\s\\S]*?)<\\/${tag}>`,
        'g'
      )
      const regexWithCaptureProd = new RegExp(
        `<${prodTag}>([\\s\\S]*?)<\\/${prodTag}>`,
        'g'
      )
      if (regexWithCapture.test(code)) {
        if (process.env.NODE_ENV === 'production') {
          const newCode = code.replaceAll(regexWithCapture, '')
          return newCode
            .replaceAll(`<${prodTag}>`, '')
            .replaceAll(`</${prodTag}>`, '')
        }
      }
      if (regexWithCaptureProd.test(code)) {
        if (process.env.NODE_ENV === 'development') {
          const newCode = code.replaceAll(regexWithCaptureProd, '')
          return newCode
        }
      }
      return code
    },
  }
}
PipeLineLems/web/script/plugins/vite-plugin-image-filter.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
/**
 * å¢žåŠ debugger组件
 * @param option
 * @returns
 */
export default function VitePluginDevelopmentFilter(): any {
  return {
    name: 'vite-plugin-image-filter',
    apply: 'build',
    transform(code, id) {
      if (code.includes('createVNode(Icon')) {
        console.info('code', code)
      }
      // const tag = 'Icon'
      // const regexWithCapture = new RegExp(
      //   `<${tag}>([\\s\\S]*?)<\\/${tag}>`,
      //   'g'
      // )
      // if (regexWithCapture.test(code)) {
      //   if (process.env.NODE_ENV === 'production') {
      //     const newCode = code.replaceAll(regexWithCapture, '')
      //     return newCode
      //   }
      // }
      return code
    },
  }
}
PipeLineLems/web/script/plugins/vite-plugin-widget-provider.ts
@@ -5,6 +5,7 @@
// @ts-ignore
const filePath = isWin ? basePath.replaceAll('\\', '/') : basePath
const regex = new RegExp(`${filePath}/([^/]*)/index.ts`)
const NOT_PAGE = 'notPage:true'
/**
 * æå–关键字符
 * @param {*} code
@@ -58,8 +59,12 @@
        if (regex.test(id)) {
          const codeData = parseCode(code)
          const transformCode = mergeCodeString(codeData, code)
          // const emptyCode = code.replaceAll(' ', '')
          const newCode = transformCode
          // const newCode = emptyCode.includes(NOT_PAGE) ? code : transformCode
          return {
            code: transformCode,
            code: newCode,
            map: null, // å¦‚果可行将提供 source map
          }
        }
PipeLineLems/web/script/replace.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
/**
 * é€šè¿‡element plus自动的namespace进行样式分离
 * ç”±äºŽç›®å‰element plus自动的namespace,无法支持treeshaking
 * å› æ­¤æ‰“包之后,再遍历dist文件夹,替换打包之后的js、css值,实现namespace最终效果
 */
/* eslint-disable no-undef */
const fs = require('fs')
const path = require('path')
const distPath = path.resolve(__dirname, '../dist/information-ui/es')
fs.readdir(distPath, function (err, files) {
  if (err) return console.error('Error:(spec)', err)
  for (const filename of files) {
    if (!filename.endsWith('js') && !filename.endsWith('.css')) continue
    const filePath = distPath + '/' + filename
    fs.readFile(filePath, function (err, data) {
      if (err) {
        return err
      }
      let str = data.toString()
      // js仅替换namespace key值即可
      if (filename.endsWith('js')) {
        str = str.replace('"el"', '"cs"')
      }
      // css需要替换所有的el-
      if (filename.endsWith('.css')) {
        str = str.replace(/el-/g, 'cs-')
      }
      fs.writeFile(filePath, str, function (err) {
        if (err) return err
      })
    })
  }
})
PipeLineLems/web/script/tag.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
const { globSync } = require('glob')
const execa = require('execa')
const slash = require('slash')
const path = require('path')
const fs = require('fs-extra')
const getWidgetsPath = async () => {
  const widgetsPath = globSync(`./src/widgets/*/`)
  for (let index = 0; index < widgetsPath.length; index++) {
    const widgetPath = slash(widgetsPath[index])
    const slashCwd = slash(process.cwd())
    const cwd = slash(path.resolve(slashCwd, widgetPath))
    const gitInfo = await getGitInfo(cwd)
    const wPaths = widgetPath.split('/')
    const widgetName = wPaths[wPaths.length - 1]
    if (widgetName) {
      const tag = {
        commit: gitInfo.commit?.stdout,
        branch: gitInfo.branch?.stdout,
        userName: gitInfo.userName?.stdout,
      }
      const widgetTagFile = slash(
        path.resolve(slashCwd, `dist/${widgetName}/version.tag`)
      )
      const widgetFile = slash(
        path.resolve(slashCwd, `dist/${widgetName}/index.js`)
      )
      const isExist = fs.existsSync(widgetFile)
      if (isExist) {
        fs.writeJsonSync(widgetTagFile, tag)
      }
    }
  }
}
/**
 * èŽ·å–git信息
 * @returns
 */
const getGitInfo = async (cwd) => {
  const option = {
    cwd,
  }
  async function getGitHash() {
    return await execa('git', ['rev-parse', '--short', 'HEAD'], option)
  }
  async function getGitBranch() {
    return execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], option)
  }
  async function getGitUserName() {
    return execa('git', ['config', 'user.name'], option)
  }
  try {
    const commit = await getGitHash()
    const branch = await getGitBranch()
    const userName = await getGitUserName()
    return {
      commit,
      branch,
      userName,
    }
  } catch (error) {
    console.error(error)
    return {}
  }
}
const start = getWidgetsPath
module.exports = start
PipeLineLems/web/src/App.vue
@@ -122,7 +122,6 @@
  try {
    await fn('.tsx')
  } catch (error) {
    console.log(error, 'error')
    await fn('.vue')
  }
}
PipeLineLems/web/src/api/common-enum.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,137 @@
import sdk from 'sdk'
import { ConfigureCodeMap } from '@/libs/system-enum'
// import { getSettings } from '@/widgets/SystemManagement/Models/Service/Service'
const { utils } = sdk
const { request } = utils
/**
 * åˆ¤æ–­æ˜¯å¦ä¸ºå¯¹è±¡
 * @param str
 * @returns
 */
const isStringAnObject = (str: string) => {
  try {
    const obj = JSON.parse(str)
    return typeof obj === 'object' && obj !== null
  } catch (e) {
    return false
  }
}
/**
 * èŽ·å–è®¾ç½®
 * @returns
 */
export const getSettings = () => {
  return request.get(
    `/api/v1/settingmanagement/setting/G?namePrefix=SCMS.AppSettings`
  )
}
/**
 * æžšä¸¾èŽ·å–æŽ¥å£
 * @param type æžšä¸¾å€¼
 * @returns
 */
export const getEnumList = (type: string): Promise<any> => {
  return request({
    url: `/api/v1/messuite/query/enumeration/${type}`,
    method: 'get',
  })
}
/**
 * èŽ·å–å·¥åºæŽ¥å£
 * @param filter ç­›é€‰
 * @returns
 */
export const getWorkSectionApi = (
  filter?: any
): Promise<{ items: any[]; totalCount: number }> => {
  const str = new URLSearchParams(
    filter as unknown as URLSearchParams
  ).toString()
  return request.get(`/api/v1/messuite/query/worksection?` + str)
}
/**
 * èŽ·å–å·¥ä½
 * @param filter ç­›é€‰
 * @returns
 */
export const getWorkStationApi = (
  filter?: any
): Promise<{ items: any[]; totalCount: number }> => {
  const str = new URLSearchParams(
    filter as unknown as URLSearchParams
  ).toString()
  return request.get(`/api/v1/messuite/query/workstation?` + str)
}
export const getFormDataBySettings = async () => {
  const { settings } = (await getSettings()) || {
    settings: [],
  }
  const formData: any = {}
  settings.forEach((item: any) => {
    const keys = item.name.split('.')
    const key = keys[keys.length - 1]
    if (key) {
      if (typeof item.name === ConfigureCodeMap.ProductionLineSegment) {
        try {
          item.value = JSON.parse(item.value)
          formData[key] = item.value
        } catch (error) {
          item.value = []
        }
      } else {
        try {
          if (isStringAnObject(item.value)) {
            formData[key] = JSON.parse(item.value)
          } else {
            formData[key] = item.value
          }
        } catch (error) {
          formData[key] = []
          console.error(error)
        }
      }
    }
  })
  return formData
}
/**
 * èŽ·å–æµç¨‹åˆ—è¡¨
 * @returns
 */
export const getFlowData = () => {
  return request.get(
    `/api/v1/messuite/query/flowdefinition?MaxResultCount=9999&SkipCount=0`
  )
}
// èŽ·å–ç‰©æ–™ç±»åž‹
export const getMaterialTypes = () => {
  return request.get(`/api/v1/materialmanagement/material/types`)
}
/**
 * èŽ·å–ç‰©æ–™ç±»åž‹
 * @returns
 */
export const getMaterialTypesEnum = async () => {
  return request.get(`/api/v1/messuite/query/enumeration/MaterialType`)
}
// ç‰ˆæœ¬åŠŸèƒ½åˆ—è¡¨
export const getFeatureListData = (providerKey: string = 'Basic') => {
  if (providerKey) {
    return request.get(
      `/api/v1/featuremanagement/feature/E?providerKey=${providerKey}`
    )
  }
  return Promise.resolve({
    group: [],
  })
}
PipeLineLems/web/src/api/file.ts
@@ -35,3 +35,16 @@
    responseType: 'blob',
  })
}
/**
 * å¯¼å‡º post è¯·æ±‚
 * @param data
 * @returns
 */
export const postExportFileToClient = (
  url: string,
  params: Record<string, any>
) => {
  return request.post(url, params, {
    responseType: 'blob',
  })
}
PipeLineLems/web/src/api/index.ts
@@ -2,10 +2,12 @@
import { ElMessage } from 'element-plus'
const { request } = sdk.utils
// !非必要,不要在此加函数,请在对应的api文件中添加,组件api,请在组件中添加api.ts中文件添加
function getProject(tree: any[]) {
  // æ–¹ä¾¿è°ƒè¯•
  const projectId = import.meta.env.VITE_APP_PROJECT_ID
  const target = tree.find((e: any) => e.id === projectId)
  const target = tree.find((e: any) => e.id == projectId)
  if (target) return target
  for (let i = 0; i < tree.length; i++) {
    const item = tree[i]
@@ -18,10 +20,7 @@
    }
  }
}
export const getXProject = async () => {
  const tree: any = await request.get('/api/v1/project/node/tree')
  return tree
}
const projectInfo = async (id: string | number = 0) => {
  const info: Record<string, any> = await request.get(
    `/api/v1/project/${id}/info`
@@ -29,9 +28,15 @@
  sessionStorage.setItem('X-Project', info.identifier)
  sessionStorage.setItem('X-Project-Name', info.name)
}
export const getXProject = async () => {
  const tree: any = await request.get('/api/v1/project/node/tree')
  return tree
}
export const setXProject = async () => {
  try {
    const tree: any = await request.get('/api/v1/project/node/tree')
    const tree: any = await getXProject()
    if (tree.length) {
      const project = getProject(tree)
      await projectInfo(project.id)
@@ -45,6 +50,10 @@
  }
}
let provider = { ConfigName: '' }
export const setProvider = (computed: any) => {
  provider = computed
}
export const postImport = (file: FormData) => {
  return request({
    url: `/api/v1/zc/productsop/uploadsop`,
@@ -54,3 +63,221 @@
    data: file,
  })
}
//产品
function getproductList(data: any, hasFormula: boolean = true) {
  const url = `api/v1/messuite/query/product?filter=${data.filter}&hasFormula=${hasFormula}`
  const method = 'get'
  return import.meta.env.PROD
    ? sdk.request({ url, method })
    : request({ url, method })
}
// å·¥åºåˆ—表
function getWorksectionList(data: any) {
  const url = `api/v1/messuite/query/worksection?filter=${data.filter}&abilityType=${data.abilityType}&includeDetails=${data.includeDetails}`
  const method = 'get'
  return import.meta.env.PROD
    ? sdk.request({ url, method })
    : request({ url, method })
}
function getTableheader(data: any) {
  const url = `api/v1/tracemanagement/trace/tableheader?productId=${data.productId}`
  const method = 'get'
  return import.meta.env.PROD
    ? sdk.request({ url, method, params: { ...provider } })
    : request({ url, method, params: { ...provider } })
}
function getTablelayout(data: any) {
  const url = `api/v1/tracemanagement/trace/tablelayout?productId=${encodeURIComponent(
    data.productId
  )}&tableId=${data.tableId}&isSummary=${data.isSummary}&onlyIncludeParameter=${
    data.onlyIncludeParameter || false
  }`
  const method = 'get'
  return import.meta.env.PROD
    ? sdk.request({ url, method, params: { ...provider } })
    : request({ url, method, params: { ...provider } })
}
function getTraceList(data: any) {
  const url = `api/v1/tracemanagement/trace?SerialNumber=${encodeURIComponent(
    data.SerialNumber
  )}&SearchMode=${data.SearchMode}&From=${data.From}&To=${
    data.To
  }&IsQualified=${data.IsQualified}&TableId=${data.TableId}&IsSummary=${
    data.IsSummary
  }&SkipCount=${data.SkipCount}&MaxResultCount=${
    data.MaxResultCount
  }&ProductModel=${encodeURIComponent(
    data.ProductModel
  )}&Updatecode=${encodeURIComponent(
    data.Updatecode
  )}&MaterialCode=${encodeURIComponent(data.MaterialCode)}&OrderCode=${
    data.OrderCode
  }&IsAsc=${data.IsAsc || false}`
  const method = 'get'
  return import.meta.env.PROD
    ? sdk.request({ url, method, params: { ...provider } })
    : request({ url, method, params: { ...provider } })
}
function getCurve(data: any) {
  const url = `api/v1/tracemanagement/trace/curve?WorkSectionId=${data.WorkSectionId}&ParamId=${data.ParamId}&From=${data.From}&To=${data.To}&ProductModel=${data.ProductModel}`
  const method = 'get'
  return import.meta.env.PROD
    ? sdk.request({ url, method, params: { ...provider } })
    : request({ url, method, params: { ...provider } })
}
function getStatistics(data: any) {
  const url = `api/v1/tracemanagement/trace/statistics?SerialNumber=${encodeURIComponent(
    data.SerialNumber
  )}&SearchMode=${data.SearchMode}&From=${data.From}&To=${
    data.To
  }&IsQualified=${data.IsQualified}&TableId=${data.TableId}&IsSummary=${
    data.IsSummary
  }&ProductModel=${encodeURIComponent(data.ProductModel)}&Updatecode=${
    data.Updatecode
  }&MaterialCode=${data.MaterialCode}&OrderCode=${data.OrderCode}`
  const method = 'get'
  return import.meta.env.PROD
    ? sdk.request({ url, method, params: { ...provider } })
    : request({ url, method, params: { ...provider } })
}
function getSetting(data: any) {
  const providerName = provider.ConfigName ? 'U' : 'G'
  // const url = `api/v1/settingmanagement/setting?namePrefix=${data.namePrefix}`
  const url = `api/v1/settingmanagement/setting/${providerName}?namePrefix=${
    data.namePrefix
  }&providerKey=${provider.ConfigName || ''}`
  const method = 'get'
  return import.meta.env.PROD
    ? sdk.request({ url, method, params: provider })
    : request({ url, method, params: provider })
}
function saveSetting(data: any, providerKey?: string) {
  const providerName = provider.ConfigName ? 'U' : 'G'
  // const url = `api/v1/settingmanagement/setting`
  let _providerKey = providerKey
    ? `?providerKey=${provider.ConfigName || ''}`
    : ''
  const url = `api/v1/settingmanagement/setting/${providerName}${_providerKey}`
  const method = 'post'
  return import.meta.env.PROD
    ? sdk.request({ url, method, data, params: provider })
    : request({ url, method, data, params: provider })
}
function getTraceproductmodelconfig(data: any) {
  const url = `api/v1/tracemanagement/traceproductmodelconfig?ProductId=${
    data.ProductId
  }&GetIfEmppty=${data.GetIfEmppty || false}&Filter=${data.Filter || ''}`
  const method = 'get'
  return import.meta.env.PROD
    ? sdk.request({ url, method, params: { ...provider } })
    : request({ url, method, params: { ...provider } })
}
function getTracesummarytable(data: any) {
  const url = `api/v1/tracemanagement/tracesummarytable?Filter=${data.Filter}&ProductId=${data.ProductId}&includeAll=true`
  const method = 'get'
  return import.meta.env.PROD
    ? sdk.request({ url, method, params: { ...provider } })
    : request({ url, method, params: { ...provider } })
}
function creatTracesummarytable(data: any) {
  const url = `api/v1/tracemanagement/tracesummarytable`
  const method = 'post'
  return import.meta.env.PROD
    ? sdk.request({ url, method, data: { ...data, ...provider } })
    : request({ url, method, data: { ...data, ...provider } })
}
function updateTracesummarytable(data: any) {
  const url = `api/v1/tracemanagement/tracesummarytable/${data.id}`
  const method = 'put'
  return import.meta.env.PROD
    ? sdk.request({ url, method, data: { ...data, ...provider } })
    : request({ url, method, data: { ...data, ...provider } })
}
function delTracesummarytable(data: any) {
  const url = `api/v1/tracemanagement/tracesummarytable`
  const method = 'delete'
  return import.meta.env.PROD
    ? sdk.request({ url, method, data, params: { ...provider } })
    : request({ url, method, data, params: { ...provider } })
}
function getProcessroutebyproductmodel(data: any) {
  const url = `api/v1/messuite/query/processroutebyproductmodel?productModel=${
    data.productModel || ''
  }&includeDetails=${data.includeDetails || true}&includeFormulaDetails=${
    data.includeFormulaDetails || true
  }`
  const method = 'get'
  return import.meta.env.PROD
    ? sdk.request({ url, method })
    : request({ url, method })
}
function getProcessroute(data: any) {
  const url = `api/v1/messuite/query/processroutes?productId=${
    data.productId || ''
  }&includeDetails=${data.includeDetails || true}&includeFormulaDetails=${
    data.includeFormulaDetails || true
  }`
  const method = 'get'
  return import.meta.env.PROD
    ? sdk.request({ url, method })
    : request({ url, method })
}
function traceproductmodelconfig(data: any) {
  const url = `api/v1/tracemanagement/traceproductmodelconfig`
  const method = 'put'
  return import.meta.env.PROD
    ? sdk.request({ url, method, data: { ...data, ...provider } })
    : request({ url, method, data: { ...data, ...provider } })
}
function exportTrace(data: any) {
  const url = `api/v1/tracemanagement/trace/export?SerialNumber=${data.SerialNumber}&SearchMode=${data.SearchMode}&ProductModel=${data.ProductModel}&From=${data.From}&To=${data.To}&IsQualified=${data.IsQualified}&TableId=${data.TableId}&IsSummary=${data.IsSummary}&Updatecode=${data.Updatecode}&MaterialCode=${data.MaterialCode}&OrderCode=${data.OrderCode}`
  const method = 'get'
  return import.meta.env.PROD
    ? sdk.request({ url, method, data, params: provider, responseType: 'blob' })
    : request({ url, method, data, params: provider, responseType: 'blob' })
}
function saveTraceBackFieldWidth(data: any, tableName: string) {
  const url = `/api/v1/messuite/setting/SCMS.TraceManagement.TraceTableSettings/${tableName}`
  const method = 'post'
  return sdk.request({ url, method, data })
}
export {
  getproductList,
  getWorksectionList,
  getTraceList,
  getSetting,
  getTraceproductmodelconfig,
  saveSetting,
  getTracesummarytable,
  delTracesummarytable,
  creatTracesummarytable,
  getProcessroutebyproductmodel,
  getProcessroute,
  updateTracesummarytable,
  getTableheader,
  getTablelayout,
  getCurve,
  getStatistics,
  traceproductmodelconfig,
  exportTrace,
  saveTraceBackFieldWidth,
}
PipeLineLems/web/src/api/logic-flow.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
import sdk from 'sdk'
const { utils } = sdk
const { request } = utils
const isDev = process.env.NODE_ENV === 'development'
/**
 * èŽ·å–æµç¨‹å›¾xml
 * @returns
 */
export const getFlowXml = async (flowType: string | number) => {
  const res = await request.get(
    `/api/v1/flowmanagement/flowdesign/define?flowType=${flowType}`
  )
  return res
}
export const getFlowData = (category: string) => {
  return request.get(
    `/api/v1/flowmanagement/flowdesign/type?category=${category}`
  )
}
export const getFlowDetail = (type: string, flowType?: number) => {
  const flowTypeStr = flowType ? `&flowType=${flowType}` : ''
  return request.get(
    `/api/v1/flowmanagement/flowdesign/attribute?type=${type}${flowTypeStr}`
  )
}
export const saveFlowData = (data: any, isTemp: boolean) => {
  return request.post(
    `/api/v1/flowmanagement/flowdesign/define?isTemp=${isTemp}`,
    data
  )
}
PipeLineLems/web/src/api/period-setting.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
// @ts-ignore
import sdk from 'sdk'
const { utils } = sdk
const { request } = utils
import type {
  IVariables,
  IPeriod,
  IPeriodSetting,
} from '@/api/period-setting.type'
interface MyResponse<T> {
  data: {
    variables: IVariables[]
    periods: IPeriod[]
    [key: string]: any
  }
  status: number
  statusText: string
  headers: any
  config: any
}
// è®¾å¤‡å°è´¦Table
export default {
  getPeriodConfigs: (): Promise<MyResponse<IPeriodSetting>> => {
    return request({
      url: `/api/v1/periodmanagement/periodsetting`,
      method: 'get',
    })
  },
  postPeriodConfigs: (data: any) => {
    return request({
      url: `/api/v1/periodmanagement/periodsetting`,
      method: 'post',
      data,
    })
  },
}
PipeLineLems/web/src/api/period-setting.type.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
export interface IVariables {
  UUID?: string
  displayName: string
  name: string
  value: string
}
export interface IPeriod {
  name: string
  type: string
  startTime: string
  endTime: string
}
export interface IPeriodSetting {
  variables: IVariables[]
  periods: IPeriod[]
}
PipeLineLems/web/src/assets/images/add-p.png

PipeLineLems/web/src/assets/images/icon_process.png

PipeLineLems/web/src/components/.npmrc
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
registry=https://registry.npmjs.org/
PipeLineLems/web/src/components/BarcodeAnalysisDialog/BarcodeAnalysisDialog.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
.barContent {
  width: 100%;
  height: 600px;
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
  flex-direction: column;
  .barHeader {
    display: flex;
    /* justify-content: space-between; */
    align-items: center;
    width: 100%;
    margin-bottom: 10px;
    .search {
      margin-left: auto;
    }
  }
  .barSearch {
    width: 300px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    column-gap: 6px;
    .searchBtn {
      color: #fff;
      height: 26px !important;
    }
  }
  .tableContent {
    width: 100%;
    height: calc(100% - 90px);
    .withoutBtn {
      color: #000;
    }
  }
  .add {
    width: 22px;
    height: 22px;
    background-image: url(@/assets/svg/add.svg);
    background-size: 20px 20px;
    background-position: center;
    background-color: #8b9ca4;
    border-radius: 4px;
    margin-right: 2px;
    cursor: pointer;
  }
}
// .without-picker-popper_content.is-light {
//   background-color: var(--el-color-white) !important;
//   border: 1px solid var(--el-datepicker-border-color) !important;
//   box-shadow: var(--el-box-shadow-light) !important;
//   padding: 0 !important;
//   max-width: inherit !important;
//   .cs-popper__arrow:before {
//     background: var(--el-color-white) !important;
//   }
// }
PipeLineLems/web/src/components/BarcodeAnalysisDialog/BarcodeAnalysisDialog.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,141 @@
import { defineComponent, ref, computed } from 'vue'
import BaseTable from '@/components/Table/Table'
import {
  BarcodeAnalysisColumns,
  BarcodeAnalysisType,
  BarcodeAnalysisRow,
  BarcodeAnalysisCurrent,
} from '@/widgets/BarcodeManagement/state'
import dayjs from 'dayjs'
import Search from '@/components/Search/Search'
import { ElMessage, ElTooltip } from 'element-plus'
import { _t } from '@/libs/Language/Language'
import BaseDialog from '../BaseDialog/BaseDialog'
import { useVModel } from '@vueuse/core'
import styles from './BarcodeAnalysisDialog.module.scss'
export default defineComponent({
  name: 'BarcodeAnalysis',
  props: {
    visible: {
      type: Boolean,
      default: false,
    },
    selections: {
      type: Array,
      default: () => [],
    },
    radio: {
      type: Boolean,
      default: false,
    },
  },
  emits: ['confirm'],
  setup(props, { emit }) {
    const tableRef = ref()
    const visible = useVModel(props, 'visible', emit)
    const dataSource = ref<BarcodeAnalysisRow[]>([])
    const searchInner = ref('')
    const checkedList = ref<BarcodeAnalysisRow[]>([])
    const onSearch = () => {
      tableRef.value?.getList()
      tableRef.value?.clearSelectEvent()
    }
    const onCheck = (records: BarcodeAnalysisRow[]) => {
      checkedList.value = records
    }
    const onConfirm = () => {
      if (checkedList.value.length === 0) {
        ElMessage.warning(_t('请选择解析规则'))
        return
      }
      if (checkedList.value.length > 1) {
        ElMessage.warning(_t('请选择一条解析规则'))
        return
      }
      console.log(checkedList.value[0], 'checkedList.value[0]')
      emit('confirm', checkedList.value[0])
      visible.value = false
    }
    const onClose = () => {
      visible.value = false
      console.log('onClose')
    }
    return () =>
      visible.value ? (
        <BaseDialog
          title={_t('条码解析规则')}
          v-model={visible.value}
          width="1200"
          height="800"
          onClose={onClose}
          onConfirm={onConfirm}
        >
          <div class={styles.barContent}>
            <div class={styles.barHeader}>
              <Search
                class={styles.search}
                onConfirm={onSearch}
                placeholder={_t('请输入解析规则名称')}
                v-model={searchInner.value}
              />
            </div>
            <div class={styles.tableContent}>
              <BaseTable
                ref={tableRef}
                url="/api/v1/barcodemanagement/barcodeanalysis"
                params={{ Filter: searchInner.value }}
                v-model:dataSource={dataSource.value}
                selections={props.selections}
                columns={BarcodeAnalysisColumns.value}
                isChecked={true}
                isFooter={false}
                radio={props.radio}
                onCheck={onCheck}
                v-slots={{
                  type: ({ row }) => BarcodeAnalysisType[row.type],
                  barcodeSegmentComposition: ({ row }) => (
                    <ElTooltip
                      effect="dark"
                      content={row.barcodeAnalysisDetails
                        .map((e: any) => e.name)
                        .join('/')}
                      placement="top"
                    >
                      {row.barcodeAnalysisDetails
                        .map((e: any) => e.name)
                        .join('/')}
                    </ElTooltip>
                  ),
                  ruleByType: ({ row }) => {
                    switch (row.type) {
                      case 0:
                        return row.symbol
                      case 1:
                        return row.startSymbol + row.endSymbol
                      case 2:
                        return row.fixedLength
                      default:
                        return '-'
                    }
                  },
                  isUsed: ({ row }) =>
                    row.isUsed ? _t('使用中') : _t('未使用'),
                  lastModificationTime: ({ row }) =>
                    row.lastModificationTime
                      ? dayjs(row.lastModificationTime).format(
                          'YYYY-MM-DD HH:mm:ss'
                        )
                      : '-',
                }}
              />
            </div>
          </div>
        </BaseDialog>
      ) : null
  },
})
PipeLineLems/web/src/components/BarcodeGenerateDialog/BarcodeGenerateDialog.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
.relationDialog {
  width: 100%;
}
.select {
  font-size: 14px;
  font-family: PingFang SC, PingFang SC;
  font-weight: 400;
  color: #5a84ff;
  cursor: pointer;
}
.header {
  width: 100%;
  height: 35px;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  margin-bottom: 10px;
  .key {
    font-size: 12px;
    font-family: PingFang SC, PingFang SC;
    font-weight: 400;
    color: #35363b;
    margin-right: 10px;
  }
}
.table {
  width: 100%;
  height: calc(100% - 50px);
}
.selected {
  cursor: pointer;
  font-size: 14px;
  font-family: PingFang SC, PingFang SC;
  font-weight: 400;
  color: #5a84ff;
  &:hover {
    color: #5a84ff;
    opacity: 0.6;
  }
}
.selectVariable {
  :global(.cs-input__inner) {
    padding-right: 10px;
  }
}
PipeLineLems/web/src/components/BarcodeGenerateDialog/BarcodeGenerateDialog.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,190 @@
import Dialog from '@/components/BaseDialog/index.vue'
const BaseDialog: any = Dialog
import { defineComponent, SetupContext } from 'vue'
import styles from './BarcodeGenerateDialog.module.scss'
import { useRelationGenerate } from '@/hooks/Dialog'
import Text from '@/components/Text/Text'
import BaseTable from '@/components/Table/Table'
import Search from '@/components/Search/Search'
import DyForm from '@/components/DyForm/DyForm'
import { _t } from '@/libs/Language/Language'
import dayjs from 'dayjs'
export default defineComponent({
  name: '关联条码生成规则',
  props: {
    // æŽ§åˆ¶å¼¹çª—显示隐藏
    modelValue: {
      type: [Object, Number, String],
      default: () => ({}),
    },
    visible: {
      type: Boolean,
      default: null,
    },
    title: {
      type: String,
      default: _t('关联条码生成规则'),
    },
    mode: {
      type: String,
      default: 'select',
    },
    type: {
      type: String,
      default: 'select',
    },
    ruleType: {
      type: String,
      default: 'barcodegeneration',
    },
  },
  emits: ['update:modelValue', 'update:visible', 'close', 'confirm'],
  setup(props, ctx: SetupContext) {
    const {
      visible,
      data,
      modelValue,
      search,
      tableRef,
      columns,
      url,
      formData,
      barcodeVisible,
      formRef,
      onBarcodeConfirm,
      onClose,
      onConfirm,
      onSelect,
      onRowClick,
      onSearch,
    } = useRelationGenerate(props, ctx)
    return () => {
      const { description } = modelValue.value
      return (
        <div class={styles.relationDialog}>
          {props.type === 'select' ? (
            <el-input
              modelValue={formData.value.input}
              {...ctx.attrs}
              class={styles.selectVariable}
              placeholder={_t('请选择')}
              suffix-icon={
                <el-button
                  link
                  type="primary"
                  size="small"
                  style="margin-right: 10px;"
                  onClick={onSelect}
                >
                  {_t('选择')}
                </el-button>
              }
            ></el-input>
          ) : null}
          {props.mode === 'select' &&
            (description ? (
              <span class={styles.selected} onClick={onSelect}>
                {description}
              </span>
            ) : (
              <span onClick={onSelect} class={styles.select}>
                {_t('请选择')}
              </span>
            ))}
          <BaseDialog
            width="400px"
            destroy-on-close
            append-to-body={true}
            v-model={visible.value}
            title={_t(props.title)}
            onClose={onClose}
            onConfirm={onConfirm}
          >
            <div class={styles.table}>
              <DyForm
                v-model:formData={formData.value}
                ref={formRef}
                formItemProps={[
                  {
                    prop: 'barcode',
                    label: _t('生成规则'),
                    placeholder: _t('请选择'),
                    el: 'input',
                    suffixIcon: (
                      <el-button
                        link
                        type="primary"
                        size="small"
                        style="margin-right: 10px;"
                        onClick={() => (barcodeVisible.value = true)}
                      >
                        {_t('选择')}
                      </el-button>
                    ),
                    rules: [
                      {
                        message: '请选择生成规则',
                        required: true,
                        trigger: 'change',
                      },
                    ],
                  },
                  {
                    prop: 'dataVariable',
                    label: _t('下发变量'),
                    el: 'variable',
                    placeholder: _t('请选择'),
                    type: 'select',
                  },
                ]}
              />
            </div>
          </BaseDialog>
          <BaseDialog
            width="954px"
            height="536px"
            destroy-on-close
            append-to-body={true}
            v-model={barcodeVisible.value}
            title={_t(props.title)}
            onClose={() => (barcodeVisible.value = false)}
            onConfirm={onBarcodeConfirm}
          >
            <div class={styles.header}>
              <label class={styles.key}>{_t('条码名称')}</label>
              <Search v-model={search.value} onConfirm={onSearch} />
            </div>
            <div class={styles.table}>
              <BaseTable
                ref={tableRef}
                url={url.value}
                columns={columns.value}
                size="mini"
                v-model:dataSource={data.value}
                isHidePagination={true}
                pageSize={999}
                isVScroll
                onRowClick={onRowClick}
                v-slots={{
                  lastModificationTime: ({ row }: any) => (
                    <Text
                      tip={dayjs(row.lastModificationTime).format(
                        'YYYY-MM-DD HH:MM:ss'
                      )}
                    >
                      {dayjs(row.lastModificationTime).format(
                        'YYYY-MM-DD HH:MM:ss'
                      )}
                    </Text>
                  ),
                }}
              />
            </div>
          </BaseDialog>
        </div>
      )
    }
  },
})
PipeLineLems/web/src/components/BaseConfigProvider/BaseConfigProvider.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
import { defineComponent } from 'vue'
export default defineComponent({
  setup(props, { attrs, slots }) {
    const namespace = import.meta.env.VITE_APP_NAMESPACE
    return () => {
      return (
        <el-config-provider {...attrs} namespace={namespace}>
          {slots.default?.()}
        </el-config-provider>
      )
    }
  },
})
PipeLineLems/web/src/components/BaseContent/BaseContent.module.scss
@@ -5,8 +5,9 @@
  height: 100%;
  border: 1px solid #dbdbdb;
  font-family: PingFang SC, PingFang SC;
  padding: 0 12px;
  // padding: 0 12px;
  position: relative;
  overflow: hidden;
  .title {
    font-size: 16px;
@@ -34,16 +35,23 @@
    position: absolute;
    bottom: 0;
    left: 0;
    background-color: #fff;
    z-index: 0;
  }
}
.content {
  padding: 15px 20px 0;
  height: calc(100% - 46px);
.scrollContent {
  height: calc(100% - 94px);
  box-sizing: border-box;
  background: #fff;
  overflow: hidden;
  // padding: 0 12px;
  // padding-right: 4px;
}
.content {
  height: auto;
  background: #f7f8fa;
  border-radius: 5px 5px 5px 5px;
  padding: 14px 14px 20px 14px;
  border-radius: 5px 5px 5px 5px;
  .header {
    margin-bottom: 12px;
PipeLineLems/web/src/components/BaseContent/BaseContent.tsx
@@ -12,15 +12,23 @@
      type: String,
      default: '',
    },
    customClass: {
      type: String,
      default: '',
    },
  },
  setup(props, { slots }) {
  setup(props, { slots, attrs }) {
    return () => (
      <div class={styles.container}>
        <div class={styles.header}>
          <Icon width={22} height={22} icon={props.icon} />
          <div class={styles.title}>{props.title}</div>
        </div>
        <div class={styles.content}>{slots.default?.()}</div>
        <div class={styles.scrollContent}>
          <div class={[styles.content, props.customClass]}>
            {slots.default?.()}
          </div>
        </div>
        <footer class={styles.footer}>{slots.footer?.()}</footer>
      </div>
    )
PipeLineLems/web/src/components/BaseDialog/BaseDialog.scss
@@ -37,12 +37,9 @@
  .cs-dialog__footer {
    padding: 0;
    padding-top: 10px;
    padding-bottom: 10px;
  }
  .cs-dialog-footer {
    padding: 0 18px;
    padding-bottom: 10px;
    .cs-base-btn {
      width: 98px;
      height: 26px;
PipeLineLems/web/src/components/BaseDialog/index.vue
@@ -120,12 +120,9 @@
  .cs-dialog__footer {
    padding: 0;
    padding-top: 10px;
    padding-bottom: 10px;
  }
  .cs-dialog-footer {
    padding: 0 18px;
    padding-bottom: 10px;
    .cs-base-btn {
      width: 98px;
      height: 26px;
PipeLineLems/web/src/components/BaseDrawer/BaseDrawer.tsx
@@ -1,6 +1,8 @@
import { computed, defineComponent, onMounted, ref } from 'vue'
import { Component, computed, defineComponent, onMounted, ref } from 'vue'
import styles from './BaseDrawer.module.scss'
import Icon from '../Icon/Icon'
import { _t } from '@/libs/Language/Language'
//@ts-ignore
export default defineComponent<{
  [key: string]: any
@@ -21,6 +23,15 @@
      type: String,
      default: '',
    },
    submitDisabled: {
      type: Boolean,
      default: false,
    },
    appendToBody: {
      type: Boolean,
      default: true,
    },
    // modelValue: {
    //   type: Boolean,
    //   default: false,
@@ -41,6 +52,43 @@
    // })
    return () => {
      const vSlots: {
        footer: () => Component
        title?: () => Component
      } = {
        footer() {
          return (
            <div class={styles.csDialogFooter}>
              <el-button
                onClick={() => emit('close')}
                type="info"
                plain
                class={{
                  [styles.dialogBtn]: true,
                  [styles.csBaseBtn]: true,
                }}
              >
                {_t('取消')}
              </el-button>
              <el-button
                onClick={() => emit('confirm')}
                type="primary"
                disabled={props.submitDisabled}
                class={{
                  [styles.csBaseBtn]: true,
                }}
              >
                {_t('确认')}
              </el-button>
            </div>
          )
        },
      }
      if (slots.title) {
        vSlots.title = () => {
          return slots.title?.()
        }
      }
      return (
        <div
          class={styles.drawContent}
@@ -55,34 +103,8 @@
            }}
            onOpen={() => emit('open')}
            onClose={() => emit('close')}
            v-slots={{
              footer() {
                return (
                  <div class={styles.csDialogFooter}>
                    <el-button
                      onClick={() => emit('close')}
                      type="info"
                      plain
                      class={{
                        [styles.dialogBtn]: true,
                        [styles.csBaseBtn]: true,
                      }}
                    >
                      å–消
                    </el-button>
                    <el-button
                      onClick={() => emit('confirm')}
                      type="primary"
                      class={{
                        [styles.csBaseBtn]: true,
                      }}
                    >
                      ç¡®è®¤
                    </el-button>
                  </div>
                )
              },
            }}
            v-slots={vSlots}
            append-to-body={props.appendToBody}
            {...attrs}
            title={props.title}
            size={props.width || attrs.size}
PipeLineLems/web/src/components/BaseInput/BaseInput.module.scss
@@ -31,6 +31,51 @@
  display: flex;
  justify-content: flex-start;
  align-items: center;
  .input {
    border-radius: 4px;
    padding: 0 6px;
    height: calc(100% - 6px);
    border: 1px solid transparent;
    padding: 0 6px;
    transition: all 0.1s ease-in;
    &:focus {
      border: 1px solid #5a84ff;
    }
  }
}
:global(
    .arco-table-hover:not(.arco-table-dragging)
      .arco-table-tr:not(.arco-table-tr-empty):not(.arco-table-tr-summary):hover
      .arco-table-td:not(.arco-table-col-fixed-left):not(
        .arco-table-col-fixed-right
      )
  ) {
  .hover {
    display: block;
  }
  .hasHover {
    // display: none;
  }
  .baseInput {
    .input {
      border-radius: 4px;
      padding: 0 6px;
      background: #fff;
      border: 1px solid #d9d9d9;
      &:focus {
        border: 1px solid #5a84ff;
      }
    }
  }
}
:global(.arco-table) {
  .baseInput {
    .input {
      height: 30px;
    }
  }
}
:global(.information-table) {
@@ -49,4 +94,16 @@
  .hasHover {
    // display: none;
  }
  .baseInput {
    .input {
      border-radius: 4px;
      padding: 0 6px;
      background: #fff;
      height: calc(100% - 6px);
      border: 1px solid #d9d9d9;
      &:focus {
        border: 1px solid #5a84ff;
      }
    }
  }
}
PipeLineLems/web/src/components/BaseInput/BaseInput.tsx
@@ -1,9 +1,10 @@
import { defineComponent, SetupContext, ref, computed } from 'vue'
import styles from './BaseInput.module.scss'
import { getScopeT, Language } from '@/libs/Language/Language'
export default defineComponent({
  name: 'BaseInput',
  emits: ['update:modelValue', 'click'],
  emits: ['update:modelValue', 'click', 'change', 'enter'],
  props: {
    modelValue: {
      type: [String, Number],
@@ -13,8 +14,24 @@
      type: String,
      default: '请输入',
    },
    readOnly: {
      type: Boolean,
      default: false,
    },
    LanguageScopeKey: {
      type: String,
      default: '',
    },
    onChange: {
      type: Function,
    },
    onEnter: {
      type: Function,
    },
  },
  setup(props, { attrs, slots, emit }: SetupContext) {
    const _t = getScopeT(props.LanguageScopeKey)
    const input = computed({
      get() {
        return props.modelValue
@@ -27,16 +44,24 @@
      evt?.stopPropagation()
      emit('click', evt)
    }
    const onKeypress = (event: KeyboardEvent) => {
      if (event.keyCode === 13) {
        emit('enter')
      }
    }
    return () => {
      return (
        <div class={styles.baseInput} onClick={onClick}>
          <input
            placeholder={props.placeholder}
            {...attrs}
            placeholder={_t(props.placeholder)}
            class={{
              [styles.input]: true,
              [styles.hover]: true,
            }}
            v-model={input.value}
            onInput={() => emit('change')}
            onKeypress={onKeypress}
          />
          {/* <span class={styles.hasHover}>
            {input.value ? (
PipeLineLems/web/src/components/BaseTable/AutoTooltip.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
/**
 * @author: zhengbolin
 *
 * AutoTooltip ç»„ä»¶ - ç”¨äºŽè‡ªåŠ¨åˆ¤æ–­æ–‡æœ¬æ˜¯å¦æº¢å‡ºï¼Œå¹¶åœ¨æº¢å‡ºæ—¶å±•ç¤ºå·¥å…·æç¤ºã€‚
 *
 * åŽŸç†ï¼š
 * è¯¥ç»„件通过检测文本容器的 `scrollWidth` å’Œ `clientWidth` æ¥åˆ¤æ–­æ–‡æœ¬æ˜¯å¦æº¢å‡ºï¼Œ
 * å¦‚果溢出,则使用 Arco Design çš„ `Tooltip` ç»„件来展示工具提示。
 *
 * ä½¿ç”¨æ–‡æ¡£ï¼š
 * 1. ä¼ å…¥ `text` å±žæ€§ï¼Œç»„件会判断该文本是否溢出;
 * 2. è‹¥æ–‡æœ¬æº¢å‡ºï¼Œæ˜¾ç¤ºå·¥å…·æç¤ºï¼›è‹¥ä¸æº¢å‡ºï¼Œç›´æŽ¥å±•示文本;
 * 3. åœ¨çª—口大小变化时自动重新计算文本是否溢出。
 *
 */
import { defineComponent, ref, onMounted, onBeforeUnmount } from 'vue'
import { Tooltip } from '@arco-design/web-vue'
interface AutoTooltipProps {
  text: string // è¦å±•示的文本内容
}
const AutoTooltip = defineComponent({
  name: 'AutoTooltip',
  props: {
    text: {
      type: String,
      required: true
    }
  },
  setup(props: AutoTooltipProps) {
    const containerRef = ref<HTMLElement | null>(null) // å¼•用容器元素
    const isOverflow = ref(false)
    // æ£€æŸ¥æ–‡æœ¬æ˜¯å¦æº¢å‡º
    const checkOverflow = () => {
      if (containerRef.value) {
        isOverflow.value = containerRef.value.scrollWidth > containerRef.value.clientWidth
      }
    }
    // åœ¨ç»„件挂载后进行一次溢出检查,并监听窗口的 resize äº‹ä»¶
    onMounted(() => {
      checkOverflow()
      window.addEventListener('resize', checkOverflow)
    })
    // åœ¨ç»„件卸载前清除事件监听器
    onBeforeUnmount(() => {
      window.removeEventListener('resize', checkOverflow)
    })
    // æ¸²æŸ“函数
    return () => (
      <div
        ref={containerRef}
        style={{
          display: 'inline-block', // è®¾ç½®å®¹å™¨ä¸ºè¡Œå†…块级元素
          maxWidth: '100%', // æœ€å¤§å®½åº¦ä¸º 100%
          whiteSpace: 'nowrap', // é˜²æ­¢æ–‡æœ¬æ¢è¡Œ
          overflow: 'hidden', // è¶…出的文本隐藏
          textOverflow: 'ellipsis', // ä½¿ç”¨çœç•¥å·è¡¨ç¤ºæº¢å‡ºçš„æ–‡æœ¬
        }}
      >
        {isOverflow.value ? (
          // å¦‚果溢出,使用 Tooltip å±•示溢出的内容
          <Tooltip content={props.text}>
            <span>{props.text}</span>
          </Tooltip>
        ) : (
          // å¦åˆ™ç›´æŽ¥å±•示文本
          <span>{props.text}</span>
        )}
      </div>
    )
  },
})
export default AutoTooltip
PipeLineLems/web/src/components/BaseTable/BaseTable.d.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,104 @@
import { TableData, ClassName } from '@arco-design/web-vue'
import { CSSProperties } from 'vue'
export interface ParamsItem {
  Sorting?: string
  SkipCount?: string | number
  MaxResultCount?: number
  [key: string]: any
}
export interface ColumnType {
  title?: string
  field: string
  width?: string | number
  sortable?: boolean
  required?: boolean
  type?: string
  cellStyle?: ((record: TableData) => ClassName) | CSSProperties
  [key: string]: any
}
export interface TablePropsItemType {
  cellStyle?: Function
  rowStyle?: () => any
  rowClassName?: string | Function
  headBorder?: boolean
  emptyText?: string
  selections?: string[]
  autoFirstClickRow?: boolean
  // å‚æ•°
  params?: ParamsItem
  // æ•°æ®æº
  dataSource: any[]
  // åˆ—
  columns: ColumnType[]
  // å½“前页大小
  pageSize?: number
  // æ€»æ•°
  total?: number
  // æ˜¯å¦éšè—åˆ†é¡µ
  isHidePagination?: boolean
  // æ˜¯å¦å¤šé€‰
  isChecked?: boolean | Function
  // æ˜¯å¦æ˜¾ç¤ºåºå·
  isSeq?: boolean
  // æ˜¯å¦æŽ’序
  isSort?: boolean
  // id..
  id?: string
  // æ˜¯å¦å‡ºçŽ°æ‹–æ‹½ï¼ŒåºŸå¼ƒ
  showDarg?: boolean | string
  // æ˜¯å¦æ‹–拽
  isDrag?: boolean | string
  // æ˜¯å¦ç¦ç”¨æ‹–拽 ï¼ˆç­›é€‰çš„æ—¶å€™ä¸€èˆ¬ç¦ç”¨æ‹–拽)
  disabledDrag?: boolean
  // æ˜¯å¦è‡ªåŠ¨é«˜åº¦
  height?: string
  maxHeight?: string
  // æ˜¯å¦å¼€å¯è™šæ‹Ÿæ»šåЍ
  isVScroll?: boolean
  // è¾¹æ¡†
  border?: string | any
  // è¯·æ±‚地址
  url?: string
  // æŽ’序地址模版
  sortUrlTpl?: string
  //显示底部
  isFooter?: boolean
  gt?: number
  // å³é”®èœå•
  contextMenu?: Array<{
    label: string
    fn: (item: any) => void
    [key: string]: any
  }>
  rowConfig?: any
  size?: SizeType | undefined
  // é˜»æ­¢å†’泡
  isStop?: boolean
  LanguageScopeKey?: string
  style?: CSSProperties
  hightLightRow?: string
  columnResizable?: boolean
  columnResize?: Function
  // [key: string]: any
}
export interface MenuOptionType {
  zIndex?: number
  minWidth?: number
  x?: number
  y?: number
}
export interface contextMenuItemType {
  show: boolean
  current: Record<string, any> | null
  options: any
}
export interface ScopeType {
  rowIndex: number
  record: Record<string, TableData>
  column: ColumnType
}
PipeLineLems/web/src/components/BaseTable/BaseTable.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
.baseTable {
  width: 100%;
  height: 100%;
  min-height: 100px;
  --color-neutral-2: #dbdfe7;
  --base-row-background: #e6f7ff;
  // :global(.arco-table-th) {
  //   background-color: var(--color-neutral-2);
  // }
  // background-color: red;
  .baseTableRow {
    :global(.arco-table-td) {
      background-color: var(--base-row-background);
    }
  }
  :global(.arco-table .arco-table-body .arco-table-cell) {
    padding: 0;
    height: 42px;
  }
  .nullData {
    width: 100%;
    height: 23px;
    display: flex;
    justify-content: center;
    align-content: center;
    color: rgb(96, 98, 102);
    font-size: 13px;
  }
  .bodyCell {
    padding: 0 16px;
    height: 42px;
    display: flex;
    align-items: center;
  }
}
PipeLineLems/web/src/components/BaseTable/BaseTable.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
import { ConfigProvider } from '@arco-design/web-vue'
import {
  defineComponent,
  DefineComponent,
  PropType,
  reactive,
  defineProps,
  inject,
  SetupContext,
  computed,
  onMounted,
} from 'vue'
import { TablePropsItemType } from './BaseTable.d'
import { useVModels } from '@vueuse/core'
import { tableEmits, tableProps } from './Props'
import { _t, getScopeT, globalT } from '@/libs/Language/Language'
import { useUtils } from './useUtils'
import styles from './BaseTable.module.scss'
import { ColumnSlots } from './useColumns'
import { useEvent } from './useEvent'
import { createState } from './useState'
import { useHook } from './useHook'
import { debounce } from 'lodash'
const PropsDefineWrapper = {
  ...tableProps,
  ...tableEmits,
} as const
const emits = Object.keys(tableEmits).map((eventName: string) => {
  const name = eventName.split('on')?.[1]
  return name.replace(name[0], name[0].toLowerCase())
})
type BaseTablePropsDefine = DefineComponent<typeof PropsDefineWrapper>
const BaseTable: BaseTablePropsDefine = defineComponent({
  name: 'BaseTable',
  props: tableProps,
  emits,
  setup(props, ctx: SetupContext) {
    const { columns, columnSeq, dataSource, rowId, baseTableRef } = createState(
      props,
      ctx
    )
    const events = useEvent(props, ctx)
    const { getRowClassName, getTableStyle, autoHeight } = useHook(props, ctx)
    const slots = ColumnSlots(props, columns)
    const slotMap = {
      ...columnSeq.value,
      ...slots,
      empty: () => {
        return <div class={styles.nullData}>{_t('暂无数据')}</div>
      },
    }
    return () => {
      const style = getTableStyle()
      return (
        <div class={styles.baseTable} style={style} ref={baseTableRef}>
          <a-table
            {...props}
            columns={columns.value}
            data={dataSource.value}
            rowKey={rowId.value}
            bordered={{ wrapper: true, cell: true }}
            pagination={!props.isHidePagination}
            v-slots={slotMap}
            row-class={getRowClassName}
            columnResizable={props.columnResizable}
            headerCellClass="selector-ignore"
            scroll={{ x: props.width, y: autoHeight.value }}
            virtual-list-props={
              props.isVScroll ? { height: '100%' } : undefined
            }
            {...events}
          ></a-table>
        </div>
      )
    }
  },
})
export default BaseTable
PipeLineLems/web/src/components/BaseTable/Props.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
import { CSSProperties, PropType } from 'vue'
import { TablePropsItemType } from './BaseTable.d'
export const tableProps = {
  columns: {
    type: Array as PropType<TablePropsItemType['columns']>,
    default: () => [],
  },
  dataSource: {
    type: Array as PropType<TablePropsItemType['dataSource']>,
    default: () => [],
  },
  LanguageScopeKey: {
    type: String as PropType<TablePropsItemType['LanguageScopeKey']>,
    default: '',
  },
  id: {
    type: String as PropType<TablePropsItemType['id']>,
    default: 'id',
  },
  isSeq: {
    type: Boolean as PropType<TablePropsItemType['isSeq']>,
    default: true,
  },
  isHidePagination: {
    type: Boolean as PropType<TablePropsItemType['isHidePagination']>,
    default: false,
  },
  cellStyle: {
    type: [Function, Object] as [
      PropType<TablePropsItemType['cellStyle']>,
      any
    ],
  },
  hightLightRow: {
    type: String,
    default: '',
  },
  style: {
    type: Object,
    default: () => {},
  },
  columnResizable: {
    type: Boolean as PropType<TablePropsItemType['columnResizable']>,
    default: false,
  },
  /**
   * å®½åº¦
   */
  width: {
    type: Number,
  },
  isVScroll: {
    type: Boolean as PropType<TablePropsItemType['isVScroll']>,
    default: false,
  },
} as const
export const tableEmits = {
  onColumnResize: Function,
  onRowClick: Function,
}
PipeLineLems/web/src/components/BaseTable/useColumns.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
import { defineComponent, PropType, Ref } from 'vue'
import { ColumnType, ScopeType, TablePropsItemType } from './BaseTable.d'
import styles from './BaseTable.module.scss'
import { isFunction, isNil } from 'lodash'
import AutoTooltip from './AutoTooltip'
export const ColumnSlots: (
  props: TablePropsItemType,
  columns: Ref<ColumnType[]>
) => Record<string, Function> = (props, columns: Ref<ColumnType[]>) => {
  const slots: Record<string, Function> = {}
  const columnsData = columns.value.filter((column) => column.field !== 'seq')
  const getCellStyle = (data: ScopeType) =>
    isFunction(props.cellStyle)
      ? props.cellStyle({ ...data, row: data.record })
      : {}
  const RenderSlot = ({ column, record, rowIndex }: ScopeType) => {
    const cellStyle = getCellStyle({ column, record, rowIndex })
    const text = isNil(record[column.field]) ? '-' : record[column.field]
    const render = column.customRender ? column.customRender : () => <AutoTooltip text={text as string}/>
    return (
      <div class={styles.bodyCell} style={cellStyle}>
        {render({ column, record, rowIndex })}
      </div>
    )
  }
  /**
   * è‡ªå®šä¹‰åˆ—
   */
  columnsData.map((column) => {
    slots[column.field] = RenderSlot
    // if (isFunction(column.customRender)) {
    //   // slots[column.field] = ({ column, record, rowIndex }: ScopeType) => {
    //   //   const cellStyle = getCellStyle({ column, record, rowIndex })
    //   //   return (
    //   //     <div class={styles.bodyCell} style={cellStyle}>
    //   //       {render({ column, record, rowIndex })}
    //   //     </div>
    //   //   )
    //   // }
    //   delete column.customRender
    // } else {
    //   slots[column.field] = ({ column, record, rowIndex }: ScopeType) => {
    //     const cellStyle = getCellStyle({ column, record, rowIndex })
    //     return (
    //       <div class={styles.bodyCell} style={cellStyle}>
    //         {isNil(record[column.field]) ? '-' : record[column.field]}
    //       </div>
    //     )
    //   }
    // }
  })
  return slots
}
PipeLineLems/web/src/components/BaseTable/useEvent.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
import { TableData } from '@arco-design/web-vue'
import { debounce } from 'lodash'
import { onMounted, SetupContext } from 'vue'
import { TablePropsItemType } from './BaseTable.d'
import { useHook } from './useHook'
import { injectState } from './useState'
// import { currentRow } from './useState'
export const useEvent = (props: TablePropsItemType, ctx: SetupContext) => {
  const { emit } = ctx
  // const { boxRef, isResize } = injectState()
  const onRowClick = (record: TableData) => {
    // currentRow.value = record
  }
  const onColumnResize = debounce((dataIndex: string, width: number) => {
    emit('columnResize', dataIndex, width)
  }, 200)
  // onMounted(() => {
  //   boxRef.value?.addEventListener('mousedown', (event: MouseEvent) => {
  //     if (isResize.value) {
  //     }
  //   })
  //   boxRef.value?.addEventListener('mouseup', (event: MouseEvent) => {
  //     isResize.value = false
  //   })
  // })
  return {
    onRowClick,
    onColumnResize,
  }
}
PipeLineLems/web/src/components/BaseTable/useHook.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
import { TableData } from '@arco-design/web-vue'
import { computed, nextTick, onMounted, SetupContext } from 'vue'
import styles from './BaseTable.module.scss'
import { injectState } from './useState'
import { ColumnType, ScopeType, TablePropsItemType } from './BaseTable.d'
import { debounce, isFunction } from 'lodash'
export const useHook = (props: TablePropsItemType, ctx: SetupContext) => {
  const { currentRow, dataSource, rowId, currentHeight, baseTableRef } =
    injectState()
  /**
   * èŽ·å–è¡Œçš„æ ·å¼åç§°
   * @param record
   * @param rowIndex
   * @returns
   */
  const getRowClassName = (record: TableData, rowIndex: number) => {
    if (record === currentRow.value) {
      return styles.baseTableRow
    }
  }
  /**
   * èŽ·å–style
   */
  const getTableStyle = () => {
    return {
      ...props.style,
      '--base-row-background': props.hightLightRow,
    }
  }
  /**
   * è®¾ç½®å½“前行高亮
   * @param key id
   */
  const setCurrentRow = (key: string) => {
    const row = dataSource.value.find((item) => item[rowId.value] === key)
    if (row) {
      currentRow.value = row
    }
  }
  onMounted(() => {
    if (baseTableRef.value) {
      const fn = debounce((entries: any) => {
        const entry = entries[0]
        const borderBoxSize = entry.borderBoxSize[0] || {}
        currentHeight.value = borderBoxSize.blockSize
      }, 150)
      const resizeObserver = new ResizeObserver(fn)
      resizeObserver.observe(baseTableRef.value)
    }
  })
  const autoHeight = computed(() => {
    const height = currentHeight.value
    if (baseTableRef.value) {
      const rect = baseTableRef.value.getBoundingClientRect()
      const rectHeight = height || rect.height
      return rectHeight > 42 ? rectHeight - 42 : rectHeight
    }
  })
  ctx.expose({
    setCurrentRow,
  })
  return {
    getRowClassName,
    getTableStyle,
    autoHeight,
  }
}
PipeLineLems/web/src/components/BaseTable/useState.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
import { TableData } from '@arco-design/web-vue'
import { useVModels } from '@vueuse/core'
import { computed, inject, provide, ref, SetupContext } from 'vue'
import { useUtils } from './useUtils'
import { TablePropsItemType } from './BaseTable.d'
const KEY = Symbol('BaseTable')
const useStore = (props: TablePropsItemType, ctx: SetupContext) => {
  const currentRow = ref<TableData>()
  const { dataSource } = useVModels(props, ctx.emit)
  const { columns, columnSeq } = useUtils(props, ctx)
  const rowId = computed(() => props.id || 'id')
  const baseTableRef = ref()
  const currentHeight = ref<number>(0)
  const store = {
    currentRow,
    dataSource,
    columns,
    columnSeq,
    rowId,
    baseTableRef,
    currentHeight,
  }
  return store
}
export type StoreType = ReturnType<typeof useStore>
let store = {}
export const createState = (props: TablePropsItemType, ctx: SetupContext) => {
  store = useStore(props, ctx)
  provide(KEY, store)
  return store as StoreType
}
export const injectState = () => {
  const injectStore = inject(KEY, '')
  if (!injectStore) {
    return store as StoreType
  }
  return inject(KEY) as StoreType
}
PipeLineLems/web/src/components/BaseTable/useUtils.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
import { getScopeT, globalT } from '@/libs/Language/Language'
import { computed, h, inject, SetupContext } from 'vue'
import { ScopeType, TablePropsItemType } from './BaseTable.d'
import styles from './BaseTable.module.scss'
import { useVModels } from '@vueuse/core'
import { TableData } from '@arco-design/web-vue'
import { isFunction } from 'lodash'
export const useUtils = (props: TablePropsItemType, { emit }: SetupContext) => {
  const LanguageScopeKey =
    props.LanguageScopeKey || inject('LanguageScopeKey', '')
  const _t = LanguageScopeKey ? getScopeT(LanguageScopeKey) : globalT
  const getCellStyle = (data: ScopeType) =>
    isFunction(props.cellStyle)
      ? props.cellStyle({ ...data, row: data.record })
      : {}
  const columns = computed(() => {
    return props.columns.map((item) => {
      const render = item.render
      delete item.render
      const column = {
        tooltip: true,
        ellipsis: true,
        ...item,
        // è‡ªå®šä¹‰render
        customRender: render,
        title: _t(item.title),
        dataIndex: item.field,
        slotName: item.field || item.slotName,
        headerCellClass: 'selector-ignore',
        width: item.width ? parseInt(String(item.width)) : item.width,
      }
      return column
    })
  })
  /**
   * è®¾ç½®åºå·
   */
  const columnSeq = computed(() => {
    const seq = columns.value.find((column) => column.type === 'seq')
    if (seq && props.isSeq) {
      return {
        ...seq,
        seq: ({ column, record, rowIndex }: TableData) => {
          const cellStyle = getCellStyle({ column, record, rowIndex })
          return h(
            'div',
            {
              class: styles.bodyCell,
              style: cellStyle,
            },
            rowIndex + 1
          )
        },
      }
    }
    return {}
  })
  return {
    _t,
    LanguageScopeKey,
    columns,
    columnSeq,
    // slotsMap,
  }
}
PipeLineLems/web/src/components/Button/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
<!-- eslint-disable vue/no-mutating-props -->
<template>
  <button @click="emit('click')" class="custom-btn" v-bind="attrs">
    <slot></slot>
  </button>
</template>
<script setup lang="ts">
import { useAttrs, useSlots } from 'vue'
const attrs = useAttrs()
const emit = defineEmits(['click'])
</script>
<style scoped lang="scss">
.custom-btn {
  --el-button-text-color: var(--el-color-white);
  --el-button-bg-color: var(--el-color-primary);
  --el-button-border-color: var(--el-color-primary);
  --el-button-outline-color: var(--el-color-primary-light-5);
  --el-button-active-color: var(--el-color-primary-dark-2);
  --el-button-hover-text-color: var(--el-color-white);
  --el-button-hover-link-text-color: var(--el-color-primary-light-5);
  --el-button-hover-bg-color: var(--el-color-primary-light-3);
  --el-button-hover-border-color: var(--el-color-primary-light-3);
  --el-button-active-bg-color: var(--el-color-primary-dark-2);
  --el-button-active-border-color: var(--el-color-primary-dark-2);
  --el-button-disabled-text-color: var(--el-color-white);
  --el-button-disabled-bg-color: var(--el-color-primary-light-5);
  --el-button-disabled-border-color: var(--el-color-primary-light-5);
  display: inline-flex;
  justify-content: center;
  align-items: center;
  line-height: 1;
  min-height: 32px;
  white-space: nowrap;
  cursor: pointer;
  color: #fff;
  text-align: center;
  box-sizing: border-box;
  outline: none;
  transition: 0.1s;
  font-weight: var(--el-button-font-weight);
  user-select: none;
  vertical-align: middle;
  -webkit-appearance: none;
  background-color: var(--el-button-bg-color);
  border: var(--el-border);
  border-color: var(--el-button-border-color);
  padding: 8px 15px;
  font-size: var(--el-font-size-base);
  border-radius: var(--el-border-radius-base);
  opacity: 1;
  &:hover {
    border-color: var(--el-button-hover-border-color);
    background-color: var(--el-button-hover-bg-color);
    outline: none;
  }
  &:focus {
    border-color: var(--el-button-hover-border-color);
    background-color: var(--el-button-hover-bg-color);
    outline: none;
  }
  &:active {
    border-color: var(--el-button-active-border-color);
    background-color: var(--el-button-active-bg-color);
    outline: none;
  }
}
</style>
PipeLineLems/web/src/components/CanvasTableS2/CanvasTableS2.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
.extendedSummaryTable {
  width: calc(100% - 20px);
  height: calc(100% - 160px);
  min-height: 100px;
  margin: 10px;
  margin-bottom: 0;
  margin-top: 0;
  overflow: hidden;
  .exclamation {
    display: flex;
    align-items: center;
    justify-content: center;
    img {
      margin-left: 5px;
    }
  }
  :global(.vxe-table .vxe-cell--sort) {
    width: 1.5em;
    height: 9px;
    text-align: center;
    display: inline-block;
    position: relative;
  }
  :global(.vxe-table--render-default .vxe-table--body-wrapper) {
    transform: translate3d(0, 0, 0);
  }
}
PipeLineLems/web/src/components/CanvasTableS2/CanvasTableS2.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,272 @@
import { defineComponent, ref, reactive, computed, onMounted, watch } from 'vue'
import type { PropType } from 'vue'
import styles from './CanvasTableS2.module.scss'
import { PivotSheet, S2DataConfig, TableSheet, type S2Options } from '@antv/s2'
import { _t } from '@/libs/Language/Language'
export default defineComponent({
  name: 'CanvasTableS2',
  props: {
    tableHeader: {
      type: Array as PropType<any[]>,
      required: true,
    },
    dataList: {
      type: Array as PropType<any[]>,
      required: true,
    },
    fixedColumn: {
      type: Number,
      default: 0,
    },
    saveConfig: {
      type: Function,
      default: () => {},
    },
    isExtendedSummaryTable: {
      type: Boolean,
      default: false,
    },
    summaryDataStructure: {
      type: Array as PropType<any[]>,
      default: () => [],
    },
    tableColumns: {
      type: Array as PropType<any[]>,
      default: () => [],
    },
    onSortChange: {
      type: Function,
      default: () => {},
    },
  },
  setup(props, { emit, slots, expose }) {
    const tableRef = ref<HTMLDivElement>()
    const tableHeight = ref(0)
    const tableWidth = ref(0)
    /**
     * å¤„理扩展总表和总表和原数据
     */
    const data = computed(() => {
      return props.dataList
    })
    const findSeq = (structure: any[], seq: string) => {
      return structure.find((item) => {
        return Object.entries(item).find(([key, value]) => {
          if (key === 'Seq' && value[0].data) {
            return value[0].data == seq
          }
        })
      })
    }
    const summaryDataStructureComputed = computed(() => {
      const structure = props.summaryDataStructure
      const data = props.dataList
      const result = []
      data.forEach((item, index: number) => {
        if (!Object.keys(structure).length) return
        const findSeqItem = findSeq(structure, item.Seq)
        // éœ€è¦åˆ¤æ–­seq是否存在,如果存在,将item设置为{}
        if (findSeqItem) {
          if (result.find((resultItem) => resultItem.seq == item.Seq)) {
            result.push({
              item: {},
              seq: item.Seq,
            })
          } else {
            result.push({
              item: findSeqItem,
              seq: item.Seq,
            })
          }
        }
      })
      return result.map((item) => {
        if (item.item) {
          return item.item
        } else {
          return {}
        }
      })
    })
    /**
     * åˆå¹¶å•元格
     */
    const mergeCells = computed<any>(() => {
      const mergeCells = []
      // if (!props.isExtendedSummaryTable) {
      //   return mergeCells
      // }
      const mergeCellsMap = {}
      const mergeCellArr = []
      summaryDataStructureComputed.value.forEach((item, index: number) => {
        Object.entries(item).forEach(([key, arr]: [string, any[]]) => {
          const column = columnMap.value[key]
          arr.forEach((row, i) => {
            // mergeCellsMap[column?.index] = mergeCellsMap[column?.index] || []
            // mergeCellsMap[column?.index].push({
            //   colIndex: column?.index,
            //   rowIndex: index + row.rowSpan,
            //   showText: false,
            // })
            mergeCells.push({
              row: index,
              col: column?.index,
              rowspan: row.rowSpan,
              colspan: 1,
              key: key,
              name: column?.name,
            })
          })
        })
      })
      const filterMergeCells = mergeCells.filter(
        (item) =>
          (item.rowspan > 1 || item.colspan > 1) && item.col !== undefined
      )
      filterMergeCells.forEach((item) => {
        const cells = []
        if (item.rowspan > 1) {
          for (let index = 0; index < item.rowspan; index++) {
            cells.push({
              colIndex: item.col,
              rowIndex: item.row + index,
              showText: index == 0 ? true : false,
            })
          }
        }
        if (cells.length) {
          mergeCellArr.push(cells)
        }
      })
      // console.log(mergeCellArr, 'mergeCellArr')
      // Object.values(mergeCellsMap).forEach((arr: any[]) => {
      //   arr.forEach((item,index) => {
      //     if(index == 0) {
      //       item.showText = true
      //       item.
      //     } else {
      //     }
      //   })
      // })
      return mergeCellArr
    })
    const columnMap = computed(() => {
      const map = {}
      props.tableColumns.forEach((item, index) => {
        map[item.key] = {
          ...item,
          index: index,
        }
      })
      return map
    })
    /**
     * è¡¨å¤´æ•°æ®
     */
    const columns = computed(() => {
      return props.tableHeader.map((item) => {
        return {
          title: item.name,
          field: item.key,
          name: item.name,
          width: item.width,
          children: item.childs.map((child) => {
            return {
              title: child.name,
              name: child.name,
              field: child.key,
              width: child.width,
            }
          }),
        }
      })
    })
    watch(
      () => props.dataList,
      () => {
        if (Array.isArray(props.dataList) && data.value.length) {
          tableHeight.value = tableRef.value?.clientHeight
          tableWidth.value = tableRef.value?.clientWidth
          if (tableWidth.value && tableHeight.value) {
            render(tableWidth.value, tableHeight.value)
          }
        }
      }
    )
    const render = async (width, height) => {
      const filedWidthMap = {}
      props.tableColumns.forEach((column) => {
        filedWidthMap[column.key] = column.width || 200
      })
      const s2Options: S2Options = {
        width,
        height,
        hierarchyType: 'grid',
        mergedCellsInfo: mergeCells.value,
        style: {
          colCell: {
            widthByField: filedWidthMap,
          },
        },
        // æé«˜æ»šåŠ¨æ€§èƒ½
        transformCanvasConfig(renderer) {
          renderer.setConfig({
            enableCulling: true,
            enableRenderingOptimization: true,
          })
        },
      }
      const s2DataConfig: S2DataConfig = {
        fields: {
          columns: columns.value,
        },
        data: data.value,
      }
      const container = document.querySelector('#s2_table')
      const s2 = new TableSheet(container, s2DataConfig, s2Options)
      s2.setTheme({
        dataCell: {
          bolderText: {
            textAlign: 'center',
            textBaseline: 'middle',
          },
          text: {
            textAlign: 'center',
            textBaseline: 'middle',
          },
        },
      })
      await s2.render()
    }
    // onMounted(async () => {
    //   // èŽ·å–è¡¨æ ¼çš„é«˜åº¦
    //   const t = setTimeout(() => {
    //     tableHeight.value = tableRef.value?.clientHeight
    //     tableWidth.value = tableRef.value?.clientWidth
    //     render(tableWidth.value, tableHeight.value)
    //     clearTimeout(t)
    //   }, 0)
    // })
    return () => {
      return (
        <div
          class={styles.extendedSummaryTable}
          id="s2_table"
          ref={tableRef}
        ></div>
      )
    }
  },
})
PipeLineLems/web/src/components/CommonTable/CommonTable.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
.CommonTable {
  width: 100%;
  max-height: 490px;
  overflow: auto;
}
.table {
  :global(.custom-td-action) {
    margin-top: -3px !important;
  }
}
PipeLineLems/web/src/components/CommonTable/CommonTable.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,589 @@
import {
  computed,
  ref,
  defineComponent,
  SetupContext,
  onMounted,
  reactive,
  Fragment,
  DefineComponent,
  Component,
  nextTick,
} from 'vue'
import BaseTable from '@/components/Table/Table'
import styles from './CommonTable.module.scss'
import { Create } from '@/libs/Create/Create'
import BaseInput from '@/components/BaseInput/BaseInput'
import Variable from '@/components/Variable/Variable'
import Tag from '@/components/Tag/Tag'
import Icon from '@/components/Icon/Icon'
import { useVModels } from '@vueuse/core'
import RelationMaterielDialog from '@/widgets/ProcessManagement/Views/Pages/Dialog/RelationMaterielDialog/RelationMaterielDialog'
import RelationBarcodeGenerateDialog from '@/widgets/ProcessManagement/Views/Pages/Dialog/RelationBarcodeGenerateDialog/RelationBarcodeGenerateDialog'
import cloneDeep from 'lodash/cloneDeep'
import omit from 'lodash/omit'
import { ElMessage } from 'element-plus'
import { ConfirmBox } from '@/components/ConfirmBox/ConfirmBox'
// @ts-ignore
import { v4 as uuidv4 } from 'uuid'
import get from 'lodash/get'
import Text from '@/components/Text/Text'
import isNil from 'lodash/isNil'
import set from 'lodash/set'
import Select from '../Select/Select'
import { clone, has, isFunction } from 'lodash'
import { _t } from '@/libs/Language/Language'
import ElInputNumber from 'element-plus/es/components/input-number/index'
import ElSwitch from 'element-plus/es/components/switch/index'
import FlowContextDialog from '@/components/FlowContextDialog/FlowContextDialog'
import { emit } from 'process'
const WidgetMap: Record<string, Component> = {
  tag: Tag,
  input: BaseInput,
  variable: Variable,
  constant: BaseInput,
  generateBarcode: RelationBarcodeGenerateDialog,
  parsingBarcode: RelationBarcodeGenerateDialog,
  relationMateriel: RelationMaterielDialog,
  select: Select,
  inputNumber: ElInputNumber,
  switch: ElSwitch,
  flowItemKey: FlowContextDialog,
}
export interface CurrentType {
  row: any
  index: number
}
export default defineComponent({
  name: '参数表格',
  props: {
    dataSource: {
      type: Array,
      default: [],
    },
    columns: {
      type: Array,
      default: [],
    },
    isFooter: {
      type: Boolean,
      default: true,
    },
    isDrag: {
      type: Boolean,
      default: true,
    },
    isChecked: {
      type: Boolean,
      default: true,
    },
    isStop: {
      type: Boolean,
      default: false,
    },
    isContextMenu: {
      type: Boolean,
      default: false,
    },
    contextMenu: {
      type: Array,
      default: [],
    },
    mergeContextMenu: {
      type: Array,
      default: [],
    },
    height: {
      type: String,
      default: '',
    },
    style: {
      type: Object,
      default: () => ({}),
    },
    delConfig: {
      type: Object,
      default: () => ({
        tip: '',
      }),
    },
    uuid: {
      type: Boolean,
      default: false,
    },
    create: {
      type: Function,
      default: null,
    },
    isSeq: {
      type: Boolean,
      default: true,
    },
    autoHeight: {
      type: String,
      default: '',
    },
    maxHeight: {
      type: String,
      default: '',
    },
    LanguageScopeKey: {
      type: String,
      default: '',
    },
    customWidgetMap: {
      type: Object,
      default: () => ({}),
    },
    id: {
      type: String,
      default: 'id',
    },
    onRowClick: {
      type: Function,
    },
    createCheck: {
      type: Function,
    },
    onCheck: {
      type: Function,
    },
  },
  emits: ['update:dataSource', 'rowClick', 'check'],
  setup(props, ctx: SetupContext) {
    // const { dataSource } = useVModels(props, ctx.emit)
    const dataSource = computed({
      get() {
        return props.dataSource
      },
      set(v) {
        ctx.emit('update:dataSource', v)
      },
    })
    const fieldRequiredMap = reactive<Record<string, any>>({})
    const tableRef = ref<any>()
    const selections = ref<any>([])
    // èœå•
    const contextMenu = [
      {
        label: _t('向上添加一行'),
        fn: (current: CurrentType) => onUpAdd(current),
        icon: 'up',
      },
      {
        label: _t('向下添加一行'),
        fn: (current: CurrentType) => onDownAdd(current),
        icon: 'down',
      },
      ...props.mergeContextMenu,
      {
        label: _t('删除'),
        fn: ({ index }: CurrentType) => {
          if (selections.value.length) {
            dataSource.value = dataSource.value.filter((item: any) => {
              return !selections.value.includes(item.id)
            })
          } else {
            dataSource.value.splice(index, 1)
          }
        },
        icon: 'close',
      },
    ]
    const columns = computed(() => {
      if (props.isSeq) {
        return [
          {
            type: 'seq',
            width: 40,
            title: _t('序号'),
          },
          ...props.columns,
        ]
      }
      return props.columns
    })
    const onUpAdd = ({ row, index }: CurrentType) => {
      const fn = isFunction(props.createCheck) ? props.createCheck : () => true
      if (!fn()) return
      dataSource.value.splice(index, 0, genData())
    }
    const onDownAdd = ({ row, index }: CurrentType) => {
      const fn = isFunction(props.createCheck) ? props.createCheck : () => true
      if (!fn()) return
      dataSource.value.splice(index + 1, 0, genData())
    }
    const genData = () => {
      let create: Record<string, any> = {}
      if (props.create && typeof props.create === 'function') {
        const fn = props.create
        create = fn()
      } else {
        if (props.uuid) {
          const id = uuidv4()
          create.id = id
        }
      }
      return new Create(create)
    }
    const getFiled = computed(() => (row: any, field: string, column: any) => {
      if (isNil(row[field])) {
        row[field] = column.defaultValue
      }
      return get(row, field, column.defaultValue)
    })
    const customWidgetFnMap = (
      key: string,
      { Widget, column, config, field }: any
    ) => {
      const map: Record<string, any> = {
        tag: (
          <Widget
            key={column.field}
            {...config}
            LanguageScopeKey={props.LanguageScopeKey}
          >
            {field}
          </Widget>
        ),
      }
      return map[key]
    }
    /**生成slots配置 */
    const generationSlots = computed(() => {
      const slots: Record<string, ({ row, index }: any) => any> = {}
      const columns = props.columns || []
      const lowerCase = (v: string) => v.charAt(0).toLowerCase() + v.slice(1)
      Object.assign(WidgetMap, props.customWidgetMap)
      let i = 0
      columns.forEach((column: any) => {
        let el = column.el
        let WidgetEl = null
        if (isFunction(el)) {
          const elStr = el()
          el = typeof elStr === 'string' ? elStr : el
        } else {
          el = el ? lowerCase(el) : el
        }
        let Widget: Component | any = WidgetMap[el] || null
        if (column.field !== 'action') {
          const config = column.config || column.props || {}
          const onChange = config.onChange
          delete config.onChange
          slots[column.field] = (scope: any) => {
            const { row, index } = scope
            if (row.el && row.elField === column.field) {
              Widget = WidgetMap[lowerCase(row.el)] || null
            }
            const field = get(row, column.field)
            if (!Widget) {
              const msg = get(row, column.field)
              return typeof msg === 'string' ? (
                <Text tip={msg} LanguageScopeKey={props.LanguageScopeKey}>
                  {row.isRequired || column.required ? (
                    <Fragment>
                      {column.field !== 'description' ? (
                        <span style="color:#D9001B;">*</span>
                      ) : null}
                      {msg}
                    </Fragment>
                  ) : (
                    msg
                  )}
                </Text>
              ) : (
                '-'
              )
            }
            const options = column.options || row.options
            const ops = options?.value || options
            const slot = ctx.slots[column.field]
            // æ”¯æŒslots
            if (slot) {
              return slot(Widget, {
                row,
                options: ops,
                config,
                column,
                field: column.field,
                index,
                dataSource: dataSource.value,
              })
            }
            if (!ops) {
              const CurrentWidget = customWidgetFnMap(el, {
                Widget,
                column,
                config,
                field,
              })
              if (CurrentWidget) return CurrentWidget
            }
            return (
              <Widget
                key={column.field}
                {...config}
                // å¦‚果存在onChange,则调用onChange,传递row值
                onChange={(...args) => onChange?.(...args, row)}
                v-model:dataSource={dataSource.value}
                field={column.field}
                index={index}
                options={ops}
                modelValue={getFiled.value(row, column.field, column)}
                onUpdate:modelValue={(val: string | number) =>
                  onUpdateModelValue(val, row, column)
                }
                LanguageScopeKey={props.LanguageScopeKey}
              />
            )
          }
        } else {
          return '-'
          // return get(row, column.field)
        }
      })
      return slots
    })
    const onUpdateModelValue = (
      val: string | number,
      row: Record<string, any>,
      column: Record<string, any>
    ) => {
      set(row, column.field, val)
      column.format && column.format(val, row)
    }
    const onCheck = (records: any[]) => {
      selections.value = records.map((item) => item.id)
      ctx.emit('check', records)
    }
    const onAdd = () => {
      const fn = isFunction(props.createCheck) ? props.createCheck : () => true
      if (!fn()) return
      if (Array.isArray(dataSource.value)) {
        dataSource.value.push(genData())
      } else {
        dataSource.value = []
        dataSource.value.push(genData())
        tableRef.value?.scrollToRowLine()
      }
      tableRef.value?.scrollToRowLine()
    }
    const onDelRow = (row: any) => {
      const delFn = () =>
      (dataSource.value = dataSource.value.filter((item) => {
        return item !== row
      }))
      if (props.delConfig?.tip && !row?.id?.includes('row_')) {
        ConfirmBox(props.delConfig?.tip).then(() => {
          delFn()
        })
      } else {
        delFn()
      }
    }
    /**
     * èŽ·å–å­—æ®µRequired
     */
    const getFieldRequiredMap = () => {
      props.columns.forEach((item: any) => {
        // éšè—å­—段不需要校验
        if ((!item.hide && item.required) || item.customRequired) {
          fieldRequiredMap[item.field] = item
        }
      })
    }
    /**
     * æ ¡éªŒæ˜¯å¦æœ‰å¿…å¡«
     * @param data
     * @returns
     */
    const checkRequired = (data: any[]) => {
      const fields = Object.keys(fieldRequiredMap)
      let checked = false
      if (fields.length) {
        for (let index = 0; index < data.length; index++) {
          const item: any = data[index]
          if (checked) {
            break
          } else {
            for (let idx = 0; idx < fields.length; idx++) {
              const fieldKey = fields[idx]
              const v = get(item, fieldKey)
              const column = fieldRequiredMap[fieldKey]
              if (isNil(v) || v === '') {
                let msg = ''
                if (item.isRequired) {
                  msg = `${item.name} ${_t('不能为空')}`
                  checked = true
                } else if (column.required) {
                  msg = `${column.title} ${_t('不能为空')}`
                  checked = true
                }
                if (checked && msg) {
                  ElMessage.error(msg)
                }
                break
              } else {
                if (column.validator && column.validator instanceof Function) {
                  const rule = {
                    field: fieldKey,
                    fullField: column.field,
                    type: typeof v,
                    pattern: column.pattern,
                    message: column.ruleMessage,
                  }
                  const errorCallBack = (msg: string) => {
                    ElMessage.error(msg)
                    checked = true
                  }
                  column.validator(rule, v, errorCallBack)
                  if (checked) {
                    break
                  }
                }
              }
            }
          }
        }
      }
      return !checked
    }
    /**
     * èŽ·å–æ•°æ®ï¼Œå¹¶æä¾›æ ¡éªŒï¼Œåˆ é™¤å¤šä½™å­—æ®µ
     * @param noCheck
     * @returns
     */
    const getData = (noCheck = false) => {
      const data: any[] = []
      dataSource.value.forEach((item: any) => {
        const hasValue = has(item, 'value')
        // å‹¿åˆ ï¼Œitem.value中含有循环引用._ctx,目前未查到原因
        if (hasValue && has(item.value, '_ctx')) {
          delete item.value._ctx
        }
        const obj = cloneDeep({
          ...item,
        })
        data.push(obj)
      })
      if (noCheck || checkRequired(data)) {
        const newData = data.map((item: any) => {
          if (item.id && String(item.id).includes('row_')) {
            return omit(item, [props.id || 'id'])
          }
          return item
        })
        return newData
      }
    }
    const onRowClick = (record) => {
      ctx.emit('rowClick', record)
    }
    /**
     * é«˜äº®è¡Œ
     * @param row
     */
    const setCurrentRow = (key: string) => {
      nextTick(() => {
        tableRef.value?.setCurrentRow(key)
      })
    }
    const setSelectRow = (keys: string[]) => {
      clearSelectEvent()
      nextTick(() => {
        tableRef.value?.setSelectRow(keys)
      })
    }
    const clearSelectEvent = () => {
      tableRef.value?.clearSelectEvent?.()
    }
    onMounted(() => getFieldRequiredMap())
    ctx.expose({
      getData,
      setCurrentRow,
      clearSelectEvent,
      setSelectRow,
    })
    return () => {
      let contextMenuCustom: boolean | any = []
      if (props.isContextMenu || props.contextMenu?.length) {
        contextMenuCustom = props.contextMenu?.length
          ? props.contextMenu
          : contextMenu
      }
      return (
        <div
          class={styles.CommonTable}
          style={{ ...props.style, height: props.height }}
        >
          <BaseTable
            ref={tableRef}
            class={styles.table}
            columns={columns.value}
            id={props.id}
            contextMenu={contextMenuCustom}
            vSlots={{
              ...generationSlots.value,
              action: ({ row, column }: any) => {
                return (
                  <Icon
                    icon={column.icon || 'closeDark'}
                    style="cursor: pointer;"
                    onClick={() => onDelRow(row)}
                    width={16}
                    height={16}
                  />
                )
              },
            }}
            gt={40}
            size="mini"
            v-model:dataSource={dataSource.value}
            isHidePagination={true}
            isChecked={props.isChecked}
            isDrag={props.isDrag}
            isFooter={props.isFooter}
            isVScroll={true}
            isStop={props.isStop}
            onCheck={onCheck}
            height={props.height}
            maxHeight={props.maxHeight}
            autoHeight={props.autoHeight}
            onClickFooter={onAdd}
            LanguageScopeKey={props.LanguageScopeKey}
            onRowClick={onRowClick}
          />
        </div>
      )
    }
  },
})
PipeLineLems/web/src/components/ConfirmBox/ConfirmBox.module.scss
@@ -1,12 +1,25 @@
.deleteDialog {
  height: 125px;
  border: 1px solid #dde0e4;
  border-radius: 2px 2px 2px 2px;
.confirmDialog {
  min-height: 55px;
  // border: 1px solid #dde0e4;
  // border-radius: 2px 2px 2px 2px;
  word-break: break-all;
  font-size: 14px;
  font-size: 16px;
  color: #333;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  padding: 10 0px;
  overflow: auto;
  text-align: center;
}
.ConfirmBox {
  :global(.cs-dialog-content){
    height: auto !important;
    padding: 0 !important;
  }
  :global(.cs-dialog-footer){
    padding: 0;
  }
}
PipeLineLems/web/src/components/ConfirmBox/ConfirmBox.tsx
@@ -1,37 +1,57 @@
import { createApp, h, ref, nextTick } from 'vue'
import { createApp, h, ref, nextTick, Component } from 'vue'
import BaseDialog from '@/components/BaseDialog/index.vue'
import { ElConfigProvider } from 'element-plus'
import styles from './ConfirmBox.module.scss'
export const ConfirmBox = (text: string, title = '确认') => {
import { _t } from '@/libs/Language/Language'
export const ConfirmBox = (
  text: string | any,
  title = '确认',
  attrs: any = {}
) => {
  return new Promise((resolve, reject) => {
    const mountNode = document.createElement('div')
    document.body.appendChild(mountNode)
    const visible = ref(true)
    const RenderProvider = (Widget: any) => {
      return (
        <el-config-provider namespace="cs">
          <Widget />
        </el-config-provider>
      )
    }
    const app = createApp({
      render() {
        return h(
          BaseDialog,
          {
            modelValue: visible.value,
            'onUpdate:modelValue': (value: boolean) => {
              visible.value = value
        return RenderProvider(
          h(
            BaseDialog,
            {
              class: styles.ConfirmBox,
              modelValue: visible.value,
              'onUpdate:modelValue': (value: boolean) => {
                visible.value = value
              },
              title: _t(title),
              width: '379px',
              onConfirm: () => {
                resolve(true)
                nextTick(() => {
                  mountNode.remove()
                })
              },
              onClose: () => {
                reject(false)
                nextTick(() => {
                  mountNode.remove()
                })
              },
              ...attrs,
            },
            title: title,
            width: '379px',
            onConfirm: () => {
              resolve(true)
              nextTick(() => {
                mountNode.remove()
              })
            },
            onClose: () => {
              reject(false)
              nextTick(() => {
                mountNode.remove()
              })
            },
          },
          h('div', { class: styles.deleteDialog }, text)
            {
              default: h('div', { class: styles.confirmDialog }, text),
              footer: attrs.footer ? attrs.footer : null,
            }
          )
        )
      },
    })
PipeLineLems/web/src/components/Container/Container.module.scss
@@ -2,7 +2,7 @@
  position: relative;
  width: 100%;
  height: 100%;
  background: linear-gradient(93deg, #5a84ff 0%, #c5d4fe 100%);
  background: #5a84ff;
  border-radius: 12px 12px 12px 12px;
}
@@ -53,11 +53,12 @@
}
.content {
  // position: relative;
  padding:20px 10px;
  padding: 20px 10px;
  // z-index: 2;
  height: calc(100% - 40px);
  background: linear-gradient(180deg, #f6f9ff 0%, #ffffff 100%);
  box-shadow: 0px 3px 6px 1px rgba(0, 0, 0, 0.16);
  // box-shadow: 0px 3px 6px 1px rgba(0, 0, 0, 0.16);
  box-shadow: 0px 1px 1px 1px #dbdbdb;
  border-radius: 5px 5px 5px 5px;
  overflow: hidden;
}
PipeLineLems/web/src/components/Container/Container.tsx
@@ -3,6 +3,7 @@
import Icon from '@/components/Icon/Icon'
import { useVModel } from '@vueuse/core'
import { debounce } from 'lodash'
import { _t } from '@/libs/Language/Language'
export default defineComponent({
  name: '通用头部',
  props: {
@@ -49,7 +50,7 @@
                class={styles.innerInput}
                onBlur={() => onEventChange(false)}
                onFocus={() => onEventChange(true)}
                placeholder={!isBlur.value ? props.placeholder : ''}
                placeholder={!isBlur.value ? _t(props.placeholder) : ''}
                prefix-icon={
                  <Icon icon="white_search" width={12} height={12} />
                }
PipeLineLems/web/src/components/Content/Content.tsx
@@ -7,12 +7,17 @@
      type: String,
      default: '标题',
    },
    customStyle: {
      type: String,
    },
  },
  setup(props, { slots }) {
    return () => (
      <div class={styles.container}>
        <div class={styles.title}>{props.title}</div>
        <div class={styles.content}>{slots.default?.()}</div>
        <div class={styles.content} style={props.customStyle}>
          {slots.default?.()}
        </div>
      </div>
    )
  },
PipeLineLems/web/src/components/CsTree/CsTree.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,158 @@
.cs-setting-tree_custom_style {
  box-shadow: none;
  height: 28px;
  line-height: 28px;
  font-size: 12px;
  border-radius: 4px;
  color: var(--cms-text-el-input-color);
  border: 1px solid var(--cms-color-bg-4);
  background-color: var(--cms-color-bg-3);
  background-color: #202124;
  outline: none;
  box-sizing: border-box;
  cursor: pointer;
  overflow: hidden;
  ::deep(.cs-input__inner) {
    padding-right: 24px !important;
  }
  &:hover {
    border-bottom: 1px solid var(--cms-color-primary-1);
    box-shadow: none !important;
  }
  .cs-select__wrapper {
    outline: none;
    display: flex;
    align-items: center;
    position: relative;
    box-sizing: border-box;
    cursor: pointer;
    text-align: left;
    font-size: 12px;
    gap: 6px;
    min-height: 28px;
    line-height: 24px;
    border-radius: var(--el-border-radius-base);
    transition: var(--el-transition-duration);
    background-color: #141414;
    padding: 0 12px;
    box-shadow: none;
    &:hover {
      box-shadow: none !important;
    }
  }
  .cs-select__wrapper.is-focused {
    box-shadow: none;
  }
  .cs-select__selection {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    flex: 1;
    min-width: 0;
    gap: 6px;
  }
  .cs-select__input-wrapper.is-hidden {
    position: absolute;
    opacity: 0;
  }
  .cs-select__input-wrapper {
    max-width: 100%;
  }
  .cs-select__selected-item {
    display: flex;
    flex-wrap: wrap;
    user-select: none;
  }
  .cs-select__placeholder.is-transparent {
    user-select: none;
    color: var(--el-text-color-placeholder);
  }
  .cs-select__placeholder {
    position: absolute;
    display: block;
    top: 50%;
    transform: translateY(-50%);
    width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .cs-select__input {
    border: none;
    outline: none;
    padding: 0;
    color: var(--el-select-multiple-input-color);
    font-size: inherit;
    font-family: inherit;
    appearance: none;
    height: 24px;
    max-width: 100%;
    background-color: transparent;
  }
  .cs-select__prefix,
  .cs-select__suffix {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    gap: 6px;
    color: var(--el-input-icon-color, var(--el-text-color-placeholder));
    .cs-select__caret {
      color: var(--el-select-input-color);
      font-size: var(--el-select-input-font-size);
      transition: var(--el-transition-duration);
      transform: rotate(0);
      cursor: pointer;
    }
    .cs-icon {
      --color: inherit;
      height: 1em;
      width: 1em;
      line-height: 1em;
      display: inline-flex;
      justify-content: center;
      align-items: center;
      position: relative;
      fill: currentColor;
      color: var(--color);
      font-size: inherit;
    }
  }
}
.settings-cs-tree_check {
  box-shadow: 0 1px 6px 1px #0000008f !important;
  background: #202124 !important;
  border: 0 !important;
  .cs-select-dropdown__item:hover {
    background: #383737 !important;
    color: #ccc !important;
    border-radius: 4px;
  }
  .cs-select-dropdown__item.is-hovering,
  .cs-select-dropdown__item.is-selected {
    color: #ccc !important;
    border-radius: 4px;
  }
  .cs-popper__arrow::before {
    border-top: none !important;
    border-left: none !important;
    border-bottom-color: transparent !important;
    border-right-color: transparent !important;
    background: #383737 !important;
  }
}
.cs-tree_check-settings {
  .cs-tree {
    height: auto;
    background: #202124;
    .cs-tree-node__content:hover {
      background-color: #383737 !important;
    }
    .cs-tree-node:focus > .cs-tree-node__content {
      background-color: #383737;
    }
  }
}
PipeLineLems/web/src/components/CsTree/CsTree.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
/**
 * ç¦æ­¢åœ¨éžSetting配置下使用
 */
import { defineComponent, SetupContext } from 'vue'
import './CsTree.scss'
export default defineComponent<any>({
  name: 'CsTree',
  setup(props, { attrs }: SetupContext) {
    const namespace = import.meta.env.VITE_APP_NAMESPACE
    return () => (
      <el-config-provider namespace={namespace} z-index={300}>
        <el-tree-select
          class="cs-setting-tree_custom_style"
          popper-class="settings-cs-tree_check cs-tree_check-settings"
          {...attrs}
        />
      </el-config-provider>
    )
  },
})
PipeLineLems/web/src/components/DateTimePickRange/DateTimePickRange.tsx
@@ -2,6 +2,7 @@
import styles from './DateTimePickRange.module.scss'
import dayjs from 'dayjs'
import { ElMessage } from 'element-plus'
import { _t } from '@/libs/Language/Language'
export default defineComponent({
  name: '日期范围',
  props: {
@@ -25,8 +26,8 @@
    },
    clearable: {
      type: Boolean,
      default: true
    }
      default: true,
    },
  },
  emits: ['change'],
  setup(props, { attrs, slots, emit }) {
@@ -48,16 +49,16 @@
    const onChangeFrom = (val: Date) => {
      console.log(val)
      if (times.To && dayjs(val).isAfter(times.To)) {
        ElMessage.warning('开始时间必须比结束时间小')
        ElMessage.warning(_t('开始时间必须比结束时间小'))
        return
      }
      emit('change', times)
    }
    const onChangeTo = (val: Date) => {
      console.log(val);
      console.log(val)
      if (times.To && dayjs(val).isBefore(times.From)) {
        ElMessage.warning('开始时间必须比结束时间小')
        ElMessage.warning(_t('开始时间必须比结束时间小'))
        return
      }
      emit('change', times)
@@ -66,11 +67,11 @@
    return () => {
      return (
        <div class={styles.DateTimePickRange}>
          <span class={styles.label}>{props.label}</span>
          <span class={styles.label}>{_t(props.label)}</span>
          <el-date-picker
            v-model={times.From}
            type="datetime"
            placeholder="请选择开始时间"
            placeholder={_t('请选择开始时间')}
            onChange={onChangeFrom}
            valueFormat={props.valueFormat}
            clearable={props.clearable}
@@ -79,7 +80,7 @@
          <el-date-picker
            v-model={times.To}
            type="datetime"
            placeholder="请选择结束时间"
            placeholder={_t('请选择结束时间')}
            onChange={onChangeTo}
            valueFormat={props.valueFormat}
            clearable={props.clearable}
PipeLineLems/web/src/components/DialogPreView/Chart.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
import { SetupContext, defineComponent, nextTick, onMounted, ref, shallowRef } from 'vue'
import sdk from 'sdk'
import isEmpty from 'lodash/isEmpty'
const { packs } = sdk
const { echarts } = packs
export default defineComponent({
  name: '图表',
  props: {
    chartOptions: {
      type: Object,
      default: () => ({}),
    },
  },
  setup(props, { attrs, expose }: SetupContext) {
    const chartDomRef = ref()
    const myCharts = shallowRef()
    onMounted(async () => {
      await nextTick()
      if (isEmpty(props.chartOptions)) return
      myCharts.value = (await echarts).init(chartDomRef.value)
      myCharts.value.setOption(props.chartOptions)
    })
    const getChartInstance = () => {
      return myCharts.value
    }
    const setChartOptions = (options: any) => {
      if (myCharts.value) {
        myCharts.value.setOption(options)
      }
    }
    expose({
      getChartInstance,
      setChartOptions,
    })
    return () => <div style="height:100%;width:100%" ref={chartDomRef}></div>
  },
})
PipeLineLems/web/src/components/DialogPreView/DialogPreView.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,3 @@
.container {
  height: 100%;
}
PipeLineLems/web/src/components/DialogPreView/DialogPreView.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
import { PropType, SetupContext, computed, defineComponent, useSlots } from 'vue'
import BaseDialog from '@/components/BaseDialog/index.vue'
import styles from './DialogPreView.module.scss'
import { useVModel } from '@vueuse/core'
import Chart from './Chart'
import Picture from './Picture'
export default defineComponent({
  name: 'DialogPreView',
  props: {
    title: {
      type: String,
      default: '预览',
    },
    isChart: {
      type: Boolean,
      default: false,
    },
    modelValue: {
      type: Boolean,
      default: false,
    },
    chartOptions: {
      type: Object,
      default: () => ({}),
    },
    picList: {
      type: Object as PropType<string[]>,
      default: () => [],
    },
  },
  emits: ['update:modelValue', 'close', 'confirm'],
  setup(props, { emit, attrs }: SetupContext) {
    const visible = useVModel(props)
    const slots = useSlots()
    const onClose = () => {
      visible.value = false
      emit('close')
    }
    const onConfirm = () => {
      emit('confirm')
    }
    const height = computed(() => {
      return (attrs.height as string) || '578px'
    })
    return () => (
      <BaseDialog
        destroy-on-close
        class={styles.drawer}
        style="background: #fff"
        width="900px"
        height="578px"
        title={props.title}
        v-model={visible.value}
        onClose={onClose}
        onConfirm={onConfirm}
        v-slots={{
          footer: () => {
            slots.footer ? slots.footer : null
          },
        }}
      >
        <div class={styles.container}>
          {props.isChart ? (
            <Chart chartOptions={props.chartOptions} />
          ) : (
            <Picture height={height.value} picList={props.picList} />
          )}
        </div>
      </BaseDialog>
    )
  },
})
PipeLineLems/web/src/components/DialogPreView/Picture.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
import {
  PropType,
  SetupContext,
  computed,
  defineComponent,
  nextTick,
  onMounted,
  ref,
} from 'vue'
export default defineComponent({
  name: '图片',
  props: {
    picList: {
      type: Object as PropType<string[]>,
      default: () => [],
    },
    height: {
      type: String,
      default: '578px',
    },
  },
  setup(props, { attrs }: SetupContext) {
    onMounted(async () => {})
    const arrow = computed(() => {
      return props.picList.length > 2 ? 'always' : 'never'
    })
    return () => (
      <el-carousel height={props.height} arrow={arrow.value}>
        {props.picList.map((item) => {
          return (
            <el-carousel-item>
              <el-image
                style="width: 100%; height: 100%"
                src={item}
                fit="contain"
              />
            </el-carousel-item>
          )
        })}
      </el-carousel>
    )
  },
})
PipeLineLems/web/src/components/DyDatePicker/DyDatePicker.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
import { defineComponent } from 'vue'
export default defineComponent({
  name: 'DyDatePicker',
  setup(props: any, { attrs }) {
    return () => (
      <el-date-picker
        {...props}
        {...attrs}
        value-format="YYYY-MM-DD HH:mm:ss"
      />
    )
  },
})
PipeLineLems/web/src/components/DyForm/DyForm.d.ts
@@ -5,6 +5,7 @@
  description?: string
  name?: string
  value: string | number
  tip?: string
}
export interface FormItemPropType {
@@ -18,7 +19,7 @@
  el?: string | Component | DefineComponent | Ref<string>
  options?: OptionItemType[] | any[] | Ref<any>
  isTitle?: boolean
  title?:string | Component
  title?: string | Component
  [key: string]: any | Ref<string>
}
PipeLineLems/web/src/components/DyForm/DyForm.module.scss
@@ -14,13 +14,30 @@
      margin-left: 10px;
    }
  }
  :global(.cs-select){
  :global(.cs-select) {
    width: 100%;
  }
  :global(.cs-form--inline .cs-form-item){
      width: 46%;
  :global(.cs-form--inline .cs-form-item) {
    width: 46%;
  }
  :global(.cs-form--inline .cs-form-item:nth-last-of-type(2n)){
      margin-right: 0;
  :global(.cs-form--inline .cs-form-item:nth-last-of-type(2n)) {
    margin-right: 0;
  }
  :global(.cs-date-editor) {
    width: 100%;
  }
}
.optionLabel {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.formitemPropsLabel {
  display: flex;
  justify-content: flex-start;
  align-items: center;
}
.itemDistance {
  margin-bottom: 20px !important;
}
PipeLineLems/web/src/components/DyForm/DyForm.tsx
@@ -3,20 +3,26 @@
  defineComponent,
  PropType,
  ref,
  Ref,
  onMounted,
  SetupContext,
  computed,
  unref,
  markRaw,
  DefineComponent,
  Component,
  watch,
  Fragment,
  useSlots,
} from 'vue'
import styles from './DyForm.module.scss'
import ElInput from 'element-plus/es/components/input/index'
import DyInput from '../Input/Input'
import Option from '@/components/Select/Option'
import Select from '@/components/Select/Select'
import SelectInput from '@/components/SelectInput/SelectInput'
import SearchSelect from '@/components/SearchSelect/SearchSelect'
import type { FormInstance } from 'element-plus'
import Icon from '../Icon/Icon'
import { Warning } from '@element-plus/icons-vue'
import RelationFlowDialog from '@/components/RelationFlowDialog/RelationFlowDialog'
import {
  FormPropsType,
  FormItemPropType,
@@ -28,19 +34,44 @@
import TextareaFlow from '../Flow/Flow'
import get from 'lodash/get'
import set from 'lodash/set'
import { has } from 'lodash'
import Tab from '@/components/Tab/Tab'
import TabPane from '@/components/Tab/TabPane'
import ElInputNumber from 'element-plus/es/components/input-number/index'
import { getCurrentLang, Language } from '@/libs/Language/Language'
const formItemElementMap = markRaw<Record<string, any>>({
  input: ElInput,
const formItemElementMap: Record<string, any> = markRaw({
  input: DyInput,
  inputNumber: ElInputNumber,
  select: Select,
  selectInput: SelectInput,
  flow: RelationFlowDialog,
  variable: Variable,
  textareaFlow: TextareaFlow,
  filterSelect: SearchSelect,
  switch: (props: PropType<any>, { attrs }: SetupContext) => {
    return <el-switch {...attrs} />
  },
  dateTime: (props: PropType<any>, { attrs }: SetupContext) => {
    return <el-date-picker
      type="datetime"
      format="YYYY-MM-DD HH:mm:ss"
      {...attrs}
    ></el-date-picker>
  },
  date: (props: PropType<any>, { attrs }: SetupContext) => {
    return <el-date-picker
      type="date"
      format="YYYY-MM-DD"
      {...attrs}
    ></el-date-picker>
  },
})
const Type: Record<string, any> = {
  select: 'select',
}
export default defineComponent<FormPropsType>({
export default defineComponent({
  //@ts-ignore
  name: '动态表单',
  props: {
@@ -57,16 +88,31 @@
      default: () => ({}),
    },
    formItemProps: {
      type: Array,
      type: Array as PropType<FormItemPropType[]>,
      default: () => [],
    },
    inLine: {
      type: Boolean,
      default: false,
    },
    customWidgetMap: {
      type: Object,
      default: () => ({}),
    },
    isCategory: {
      type: Boolean,
      default: false,
    },
    LanguageScopeKey: {
      type: String,
      default: '',
    },
  },
  setup(props: PropsType, { attrs, emit, expose }: SetupContext) {
    const formRef = ref<FormInstance>()
    const active = ref('')
    const isZh = ref(true)
    const slots = useSlots()
    const form: any = computed({
      get() {
        return props.formData
@@ -75,9 +121,14 @@
        emit('update:formData', v)
      },
    })
    const formPropsRef = ref<any>({})
    const currentWidgetModel = computed(() => {
      return (path: string) => {
        if (path.includes('.')) {
          const args = path.split('.')
          return get(form.value, args)
        }
        return get(form.value, path)
      }
    })
@@ -94,6 +145,14 @@
        })
      })
    }
    /**
     * èŽ·å–refs
     * @returns
     */
    const getRefByKey = (key: string) => {
      if (key) return formPropsRef.value[key]
      return formPropsRef.value
    }
    const resetForm = () => {
      if (!formRef.value) return false
@@ -101,10 +160,26 @@
    }
    const formItemProps = computed(() => {
      if (props.isCategory) {
        const tabMap: Record<string, FormItemPropType> = {}
        const tabs: FormItemPropType[] = []
        if (Array.isArray(props.formItemProps)) {
          props.formItemProps.forEach((item: any) => {
            tabMap[item.category] = tabMap[item.category] || []
            tabMap[item.category].push(item)
          })
          Object.keys(tabMap).forEach((key: string) => {
            tabs.push({
              name: key,
              content: tabMap[key],
            })
          })
        }
        return tabs
      }
      return props.formItemProps || []
    })
    expose({ validate, resetForm })
    const FormRender: any = ($props: any) => {
      const item: FormItemPropType = $props.item
@@ -112,17 +187,158 @@
      if (item.el && Type[item.el as string]) {
        return options.map((el: OptionItemType) => (
          <Option
            label={el.label || el.description || el.name}
            value={el.value}
          ></Option>
            label={el.label || el.description || el.name}
            class={styles.optionLabel}
            key={el.value}
          >
            {el.label || el.description || el.name}
            {el.tip ? (
              <el-tooltip
                class="box-item"
                effect="dark"
                content={el.tip}
                placement="top"
                key={el.value}
                persistent={false}
              >
                <el-icon>
                  <Warning />
                </el-icon>
              </el-tooltip>
            ) : null}
          </Option>
        ))
      }
      return null
    }
    const onUpdateModelValue = (v: string | number, prop: string) => {
      set(form.value, prop, v)
    const onUpdateModelValue = (v: string | number, path: string) => {
      if (path.includes('.')) {
        const args = path.split('.')
        return set(form.value, args, v)
      }
      set(form.value, path, v)
    }
    const initFormData = () => {
      formItemProps.value.forEach((item: FormItemPropType) => {
        form.value[item.prop] = form.value[item.prop] || item.defaultValue
      })
      active.value = formItemProps.value[0].name
    }
    const checkZh = (lang: string) => {
      const languageStr = lang.toLowerCase()
      if (languageStr.includes('zh')) return true
      if (languageStr.includes('original')) return true
      return false
    }
    const onLanguageChange = () => {
      Language.useChange((language: any) => {
        isZh.value = checkZh(language.lang)
      })
    }
    onMounted(() => {
      isZh.value = checkZh(getCurrentLang())
      onLanguageChange()
    })
    const RenderItemProps = ($props: any) => {
      const WidgetMap = {
        ...formItemElementMap,
        ...props.customWidgetMap,
      }
      const formItemProps = $props.formItemProps
      return (
        <Fragment>
          {formItemProps.map((item: FormItemPropType, index: number) => {
            if (item.isTitle) {
              if (typeof item.title === 'string') {
                return <Title style="margin-bottom: 10px">{item.title}</Title>
              }
              return item.title
            }
            const itemProps: FormItemPropType = {}
            Object.entries(item).forEach(([key, value]) => {
              itemProps[key] = unref(value)
            })
            const el =
              typeof itemProps.el === 'string'
                ? WidgetMap[itemProps.el]
                : itemProps.el || null
            const hasSlot = itemProps.slot
            const Component = hasSlot ? slots[itemProps.el] : el
            const isHide = has(item.isHide, 'value')
              ? item.isHide?.value
              : item.isHide
            return Component && !isHide ? (
              <el-form-item
                label={itemProps.label}
                prop={itemProps.prop}
                rules={itemProps.rules}
                key={itemProps.prop + index}
                class={styles.itemDistance}
                labelPosition={itemProps.labelPosition}
                labelWidth={isZh.value ? props.labelWidth : 'auto'}
                vSlots={
                  itemProps.icon
                    ? {
                      label: () => (
                        <label
                          key={itemProps.prop}
                          class={styles.formitemPropsLabel}
                        >
                          {itemProps.label}
                          {itemProps.icon ? (
                            <el-tooltip
                              class="box-item"
                              effect="dark"
                              content={itemProps.tip}
                              placement="top"
                              raw-content={itemProps.rawContent}
                              persistent={false}
                            >
                              <Icon
                                style="margin-left: 5px"
                                icon={itemProps.icon}
                              />
                            </el-tooltip>
                          ) : null}
                        </label>
                      ),
                    }
                    : null
                }
              >
                <Component
                  style={{
                    width: itemProps.width,
                    height: itemProps.height,
                  }}
                  {...itemProps}
                  placeholder={itemProps.placeholder}
                  ref={(ref) => (formPropsRef.value[itemProps.prop] = ref)}
                  modelValue={currentWidgetModel.value(itemProps.prop)}
                  onUpdate:modelValue={(val: string | number) =>
                    onUpdateModelValue(val, itemProps.prop)
                  }
                >
                  <FormRender item={itemProps} />
                </Component>
              </el-form-item>
            ) : null
          })}
        </Fragment>
      )
    }
    expose({ validate, resetForm, initFormData, getRefByKey })
    return () => {
      return (
@@ -130,67 +346,28 @@
          <el-form
            labelPosition={props.labelPosition}
            labelWidth={props.labelWidth}
            // labelWidth={isZh.value ? props.labelWidth : 'auto'}
            model={form.value}
            ref={formRef}
            inline={props.inLine}
          >
            {formItemProps.value.map(
              (item: FormItemPropType, index: number) => {
                if (item.isTitle) {
                  if (typeof item.title === 'string') {
                    return (
                      <Title style="margin-bottom: 10px">{item.title}</Title>
                    )
                  }
                  return item.title
                }
                const itemProps: FormItemPropType = {}
                Object.entries(item).forEach(([key, value]) => {
                  itemProps[key] = unref(value)
                })
                const el =
                  typeof itemProps.el === 'string'
                    ? formItemElementMap[itemProps.el]
                    : itemProps.el || null
                const Component = el
                return Component && !item.isHide ? (
                  <el-form-item
                    label={itemProps.label}
                    prop={itemProps.prop}
                    rules={itemProps.rules}
                    key={itemProps.prop}
                    vSlots={
                      itemProps.labelIcon
                        ? {
                            label: () => (
                              <label class={styles.formitemPropsLabel}>
                                {itemProps.label}
                                <Icon icon={itemProps.labelIcon} />
                              </label>
                            ),
                          }
                        : null
                    }
                  >
                    <Component
                      style={{
                        width: itemProps.width,
                        height: itemProps.height,
                      }}
                      {...itemProps}
                      // v-model={form.value[itemProps.prop as keyof any]}
                      modelValue={currentWidgetModel.value(itemProps.prop)}
                      onUpdate:modelValue={(val: string | number) =>
                        onUpdateModelValue(val, itemProps.prop)
                      }
                    >
                      <FormRender item={itemProps} />
                    </Component>
                  </el-form-item>
                ) : null
              }
            {props.isCategory ? (
              <Tab
                active={active.value}
                class={styles.tabContent}
                size="small"
                type="params"
              >
                {formItemProps.value.map((item: any) => {
                  return (
                    <TabPane key={item.name} label={item.name} name={item.name}>
                      <RenderItemProps formItemProps={item.content} />
                    </TabPane>
                  )
                })}
              </Tab>
            ) : (
              <RenderItemProps formItemProps={formItemProps.value} />
            )}
          </el-form>
        </div>
PipeLineLems/web/src/components/EllipsisTooltip/EllipsisTooltip.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
<!--
<EllipsisTooltipLabel width="100%" :content="content" />
-->
<template>
  <div
    class="content"
    :style="{ width: props.width }"
    @mouseover="mouseover"
    @mouseleave="mouseleave"
    :title="props.tooltipContent ? props.tooltipContent : props.content"
    :data-tooltip-disabled="!visible"
    :data-tooltip-effect="props.effect"
    :data-tooltip-placement="props.placement"
    :data-tooltip-popper-class="props.popperClass"
    :data-tooltip-raw-content="props.rawContent"
  >
    <span ref="contentRef">
      <slot name="content">{{ props.content }}</slot>
    </span>
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
interface Props {
  width: string // è¶…出宽度时用省略号表示
  content: string // æ˜¾ç¤ºå†…容
  tooltipContent?: string // æç¤ºå†…容(设置‘提示内容’与‘显示内容’不同)
  effect?: string
  placement?: string
  popperClass?: string
  disabled?: boolean
  rawContent?: boolean
}
const props = withDefaults(defineProps<Props>(), {
  effect: 'dark',
  placement: 'top-start',
  content: '',
  popperClass: '',
  width: '',
  tooltipContent: '',
  disabled: false,
  rawContent: false,
})
let visible = ref(false)
const contentRef = ref()
const mouseover = function (): void {
  // è®¡ç®—span标签的offsetWidth与父盒子元素的offsetWidth,来判断tooltip是否显示
  visible.value =
    contentRef.value.offsetWidth > contentRef.value.parentNode.offsetWidth ? true : false
}
const mouseleave = function (): void {
  visible.value = false
}
</script>
<style lang="scss" scoped>
.content {
  text-overflow: ellipsis;
  word-break: keep-all;
  white-space: nowrap;
  overflow: hidden;
}
</style>
PipeLineLems/web/src/components/Flow/Flow.module.scss
@@ -1,6 +1,6 @@
.flows_pick {
  width: 100%;
  height: 108px;
  height: 66px;
  box-shadow: 0 0 0 1px var(--cs-input-border-color, var(--cs-border-color))
    inset;
  border-radius: var(--cs-input-border-radius, var(--cs-border-radius-base));
@@ -8,25 +8,24 @@
  background-color: #fff;
  display: flex;
  justify-content: flex-start;
  padding: 10px 10px;
  padding: 7px;
  overflow: auto;
  flex-wrap: wrap;
  padding-right: 0;
  align-items: flex-start;
  align-content: flex-start;
  padding-bottom: 0px;
  cursor: no-drop;
  > div {
    margin-bottom: 5px;
  }
  &:hover {
    box-shadow: 0 0 0 1px #c0c4cc inset;
  }
}
.disabled {
  background-color: #f6f7fa;
  background-color: var(--cs-disabled-bg-color);
}
.flowTag {
  display: inline;
  color: #b7bac0;
  margin-top: -10px;
  margin-top: -7px;
  margin-left: 4px;
  color: #5c5c5c;
}
PipeLineLems/web/src/components/Flow/Flow.tsx
@@ -1,6 +1,8 @@
import { defineComponent } from 'vue'
import styles from './Flow.module.scss'
import Tag from '../Tag/Tag'
import { scope } from '@/libs/Language/Language'
export default defineComponent({
  name: '流程显示tag',
  props: {
@@ -12,9 +14,18 @@
      type: Boolean,
      default: false,
    },
    LanguageScopeKey: {
      type: String,
      default: '',
    },
    placeholder: {
      type: String,
      default: '',
    },
  },
  emits: ['click'],
  setup(props, { attrs, slots, emit }) {
    const _t = scope(props.LanguageScopeKey)
    return () => {
      return (
        <div
@@ -24,7 +35,7 @@
          }}
        >
          {!props.modelValue?.length ? (
            <span class={styles.flowTag}>关联流程</span>
            <span class={styles.flowTag}>{props.placeholder}</span>
          ) : (
            props.modelValue.map((item: any) => {
              return (
PipeLineLems/web/src/components/FlowContextDialog/FlowContextDialog.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
.relationDialog {
  width: 100%;
}
.select {
  font-size: 14px;
  font-family: PingFang SC, PingFang SC;
  font-weight: 400;
  color: #5a84ff;
  cursor: pointer;
}
.header {
  width: 100%;
  height: 35px;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  margin-bottom: 10px;
  .key {
    font-size: 12px;
    font-family: PingFang SC, PingFang SC;
    font-weight: 400;
    color: #35363b;
    margin-right: 10px;
  }
}
.table {
  width: 100%;
  height: calc(100% - 50px);
  :global(
      .information-table
        .vxe-table--body
        tbody
        tr
        td:nth-of-type(2)
        .over-ellipsis
    ) {
    white-space: pre;
  }
}
.selected {
  cursor: pointer;
  font-size: 14px;
  font-family: PingFang SC, PingFang SC;
  font-weight: 400;
  color: #5a84ff;
  &:hover {
    color: #5a84ff;
    opacity: 0.6;
  }
}
.selectVariable {
  :global(.cs-input__inner) {
    padding-right: 10px;
  }
}
PipeLineLems/web/src/components/FlowContextDialog/FlowContextDialog.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,199 @@
import Dialog from '@/components/BaseDialog/index.vue'
const BaseDialog: any = Dialog
import { computed, defineComponent, inject, ref, SetupContext } from 'vue'
import styles from './FlowContextDialog.module.scss'
import { useRelationMaterial } from '@/hooks//Dialog'
import BaseTable from '@/components/Table/Table'
import { columns } from './config'
import Search from '@/components/Search/Search'
import { _t } from '@/libs/Language/Language'
import { ElMessage } from 'element-plus'
import { get } from 'lodash'
export default defineComponent({
  name: '流程上下文',
  props: {
    modelValue: {
      type: String,
      default: '',
    },
    title: {
      type: String,
      default: '',
    },
    readonly: {
      type: Boolean,
      default: false,
    },
    type: {
      type: String,
      default: 'input',
    },
    clearable: {
      type: Boolean,
      default: false,
    },
    flowType: {
      type: String,
    },
    // ç”¨æ¥å®šä¹‰æ•°æ®ç»“æž„
    dyStruct: {
      type: Boolean,
      default: false,
    },
    radio: {
      type: Boolean,
      default: false,
    },
  },
  emits: ['update:modelValue', 'close', 'confirm'],
  setup(props: any, ctx: SetupContext) {
    const modelValue = computed({
      get() {
        return props.modelValue
      },
      set(v) {
        ctx.emit('update:modelValue', v)
      },
    })
    const flowType = props.flowType || inject('flowType')
    const data = ref([])
    const visible = ref(false)
    const tableRef = ref()
    const search = ref('')
    const selected = ref('')
    const originData = ref([])
    const current = ref()
    const dyModel = computed(() => {
      if (props.dyStruct) {
        return modelValue.value
      }
      return modelValue
    })
    const onSelect = () => {
      visible.value = true
    }
    const onClose = () => {
      visible.value = false
    }
    const onConfirm = () => {
      visible.value = false
      if (props.dyStruct) {
        modelValue.value = {
          name: current.value?.description,
          value: selected.value,
        }
      } else {
        modelValue.value = selected.value
      }
    }
    const onSearch = (val: string) => {
      if (val) {
        data.value = originData.value.filter((item: any) => {
          return (
            item.name.includes(val) ||
            item.value.includes(val) ||
            item.description.includes(val)
          )
        })
      } else {
        data.value = originData.value
      }
    }
    const onCheck = (row: any[]) => {
      if (row.length > 1) {
        return ElMessage.warning('仅支持对一条数据进行操作!')
      }
      if (row[0]) {
        selected.value = row[0].value
        current.value = row[0]
      }
    }
    const onOpen = () => {
      if (dyModel.value) {
        tableRef.value?.setSelectRow([dyModel.value?.value])
      }
    }
    const onLoad = () => {
      originData.value = data.value
    }
    const Input = () => {
      const readonly =
        typeof props.readonly?.value === 'boolean'
          ? (props.readonly?.value as boolean)
          : (props.readonly as boolean)
      return (
        <el-input
          v-model={dyModel.value.value}
          clearable={props.clearable}
          class={styles.selectVariable}
          placeholder={_t('请选择')}
          readonly={readonly}
          title={props.title}
          suffix-icon={
            <el-button
              link
              type="primary"
              size="small"
              style="margin-right: 10px;"
              onClick={onSelect}
            >
              {_t('选择')}
            </el-button>
          }
        ></el-input>
      )
    }
    return () => {
      return (
        <div class={styles.relationDialog}>
          <Input />
          <BaseDialog
            width="1200px"
            height="536px"
            v-model={visible.value}
            title={_t('流程上下文')}
            onClose={onClose}
            onConfirm={onConfirm}
            onOpen={onOpen}
            destroy-on-close
            append-to-body
          >
            <div class={styles.header}>
              <label class={styles.key}>{_t('关键字')}</label>
              <Search
                v-model={search.value}
                field="filter"
                onConfirm={onSearch}
                placeholder={_t('请输入关键字')}
              />
            </div>
            <div class={styles.table}>
              <BaseTable
                url={`/api/v1/flowmanagement/flowdesign/flowitemkey`}
                ref={tableRef}
                params={{
                  flowType: flowType,
                }}
                columns={columns}
                size="mini"
                v-model:dataSource={data.value}
                isChecked={true}
                isVScroll
                onCheck={onCheck}
                isStop={true}
                id="value"
                isHidePagination={true}
                onLoad={onLoad}
                radio={props.radio}
              />
            </div>
          </BaseDialog>
        </div>
      )
    }
  },
})
PipeLineLems/web/src/components/FlowContextDialog/config.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
export const columns = [
  {
    title: '序号',
    field: 'seq',
    type: 'seq',
  },
  {
    title: '名称',
    field: 'name',
  },
  {
    title: '标识',
    field: 'value',
  },
  {
    title: '描述',
    field: 'description',
  },
  {
    title: '数据类型',
    field: 'type',
  },
]
PipeLineLems/web/src/components/G6Flow/G6Flow.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
.logicFlow {
  width: 100%;
  height: 100%;
  position: relative;
  .beautify {
    position: absolute;
    right: 250px;
    top: 30px;
    z-index: 1;
  }
  .xmlbeautify {
    position: absolute;
    right: 150px;
    top: 30px;
    z-index: 1;
  }
}
PipeLineLems/web/src/components/G6Flow/G6Flow.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,295 @@
import {
  defineComponent,
  SetupContext,
  onMounted,
  reactive,
  ref,
  computed,
  provide,
} from 'vue'
import styles from './G6Flow.module.scss'
import {
  fittingString,
  getFlowDataToXml,
  getJsonByXml,
  getLogicFLowStruct,
  generateFlowXml,
} from './core/transformHelp'
import { getFlowXml, getFlowData, saveFlowData } from '@/api/logic-flow'
import G6Renderer from './components/Renderer/Renderer'
import Canvas from './components/Canvas/Canvas'
import { createStore, resetStore } from './core/store'
import { FlowType } from './type'
import Tools from './components/Tools/Tools'
import { cloneDeep, uniqBy } from 'lodash'
import { v4 as uuidv4 } from 'uuid'
import NodeDrawer from './components/NodeDrawer/NodeDrawer'
import EdgeDrawer from './components/EdgeDrawer/EdgeDrawer'
import { nodeFontSize } from './components/Nodes'
import { ConditionType } from './core/enum'
import { ElMessage } from 'element-plus'
import { baseStore } from './core/store'
import { _t } from '@/libs/Language/Language'
interface NodeDrawerConfig {
  visible: boolean
  title: string
  model: Record<string, any>
}
export default defineComponent({
  name: 'G6Flow',
  props: {
    flowType: {
      type: [String, Number],
      default: '',
    },
    height: {
      type: [String, Number],
      default: 750,
    },
    isEdit: {
      type: Boolean,
      default: false,
    },
    flowName: {
      type: String,
    },
  },
  setup(props, { slots, attrs, expose }: SetupContext) {
    const {
      graphEvent,
      selected,
      flowBaseConfig,
      edgeMap,
      nodeMap,
      flowConfig,
    } = createStore()
    const graphData = ref<FlowType | null>(null)
    const g6RenderRef = ref<any>()
    const selectedEdgeToolType = ref<string>('')
    const isEdit = computed(() => {
      return props.isEdit
    })
    const nodeData = ref<Record<string, any>[]>([])
    const edgeData = ref<Record<string, any>[]>([])
    const nodeItem = ref<Record<string, any> | null>(null)
    const nodeDrawerConfig = reactive<NodeDrawerConfig>({
      visible: false,
      title: _t('节点配置'),
      model: {},
    })
    provide('flowType', props.flowType)
    const sortNodeDataUniq = (Activities: any[]) => {
      const data = Activities.sort(
        (a: { sort: number }, b: { sort: number }) => {
          return a.sort - b.sort
        }
      )
      return uniqBy(data, 'type')
    }
    const initData = async () => {
      selected.value = null
      graphData.value = null
      resetStore()
      const res = await getFlowXml(props.flowType)
      const json = getJsonByXml(res.content, props.flowName)
      flowConfig.type = res.type
      flowConfig.version = res.version
      flowBaseConfig.value = {
        type: res.type,
        version: flowConfig.version,
        name: props.flowName,
      }
      graphData.value = getLogicFLowStruct(json)
      const data = await Promise.all([
        getFlowData('Activities'),
        getFlowData('Transitions'),
      ])
      const Activities = sortNodeDataUniq(data[0].Activities)
      const Transitions = sortNodeDataUniq(data[1].Transitions)
      nodeData.value = Activities
      edgeData.value = Transitions
      // edgeMap
      Transitions.forEach((transition) => {
        edgeMap.set(transition.type, transition)
      })
      // nodeMap
      Activities.forEach((activity) => {
        nodeMap.set(activity.type, activity)
      })
    }
    const onAutoLayout = () => {
      g6RenderRef.value?.autoLayout()
    }
    const onDrop = (event: DragEvent) => {
      if (nodeItem.value) {
        g6RenderRef.value?.addNode(nodeItem.value, {
          x: event.x,
          y: event.y,
        })
      }
    }
    const onDragstart = (node: Event) => {
      nodeItem.value = node
    }
    const onToolsEdgeClick = (type: string) => {
      selectedEdgeToolType.value = type
    }
    const onCopy = (model: any, isShow: any) => {
      if (g6RenderRef.value) {
        const { flowMap, flowNodeMap } = baseStore
        const graph = g6RenderRef.value.getGraph()
        const newModel = cloneDeep(model)
        const label =
          newModel.properties.Name +
          '_' +
          parseInt(String(Math.random() * 10000))
        newModel.id = uuidv4()
        newModel.x = model.x + 10
        newModel.y = model.y + 10
        newModel.name = label
        newModel.properties = {
          ...newModel.properties,
          Name: label,
          label: label,
          name: label,
          oldId: label,
        }
        newModel.label = fittingString(label, 195, nodeFontSize + 2)
        flowMap.set(label, newModel)
        flowNodeMap.set(newModel.id, newModel)
        graph?.addItem('node', newModel)
      }
      isShow.value = false
    }
    const onDelete = (model: any, isShow: any) => {
      if (g6RenderRef.value) {
        const graph = g6RenderRef.value.getGraph()
        const item = graph?.findById(model.id)
        graph?.removeItem(item)
      }
      isShow.value = false
    }
    const onViewDialog = (model: any, isShow: any) => {
      nodeDrawerConfig.visible = true
      nodeDrawerConfig.model = model
      isShow.value = false
    }
    const clear = () => {
      nodeDrawerConfig.model = {}
      graphEvent.clearAllState()
      graphSave(true)
    }
    const graphSave = async (isTemp: boolean = false) => {
      const graph = g6RenderRef.value.getGraph()
      const xml = generateFlowXml(graph)
      const data = {
        ...flowConfig,
        content: xml,
        version: flowConfig.version || 1,
      }
      await saveFlowData(data, isTemp)
      !isTemp && ElMessage.success(_t('保存成功'))
    }
    expose({ graphSave })
    onMounted(initData)
    return () => {
      if (!graphData.value) return <el-empty description={_t('暂无数据')} />
      const Drawer = graphEvent.type.value === 'edge' ? EdgeDrawer : NodeDrawer
      const contextMenu = computed<any[]>(() => {
        const menu: any[] = [
          {
            type: 'view',
            label: _t('查看属性'),
            fn: onViewDialog,
            divided: true,
            disabled: false,
            icon: 'viewProps',
          },
        ]
        if (isEdit.value) {
          if (graphEvent.type.value === 'node') {
            menu.push({
              label: _t('复制'),
              fn: onCopy,
              divided: true,
              icon: 'copy',
              type: 'copy',
            })
          }
          menu.push({
            label: _t('删除'),
            fn: onDelete,
            type: 'del',
            divided: true,
            disabled: false,
            icon: 'delete-menu',
          })
        }
        return menu
      })
      return (
        <div class={styles.logicFlow}>
          {isEdit.value ? (
            <Tools
              nodeData={nodeData.value}
              edgeData={edgeData.value}
              onDragstart={onDragstart}
              onEdgeClick={onToolsEdgeClick}
            />
          ) : null}
          <Drawer
            title={nodeDrawerConfig.title}
            v-model={nodeDrawerConfig.visible}
            isEdit={isEdit.value}
            model={
              nodeDrawerConfig.visible ? nodeDrawerConfig.model : undefined
            }
            onConfirm={clear}
            onClose={clear}
            graph={g6RenderRef.value?.getGraph()}
            type={graphEvent.type.value}
            edgeData={edgeData.value}
          ></Drawer>
          <Canvas
            onDrop={onDrop}
            grid={{ visible: true }}
            minimap={true}
            height={props.height}
          >
            <G6Renderer
              ref={g6RenderRef}
              graphData={graphData.value}
              drag-node={isEdit.value}
              click-select={isEdit.value}
              editing={isEdit.value}
              zoom-canvas
              drag-canvas
              edgeType={!!selectedEdgeToolType.value}
              create-edge={!!selectedEdgeToolType.value}
              edgeContextMenu={contextMenu.value}
              flowName={props.flowName}
              contextMenu={[...contextMenu.value]}
            ></G6Renderer>
          </Canvas>
        </div>
      )
    }
  },
})
PipeLineLems/web/src/components/G6Flow/components/Canvas/Canvas.module.scss
PipeLineLems/web/src/components/G6Flow/components/Canvas/Canvas.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
import {
  defineComponent,
  Fragment,
  computed,
  Component,
  DefineComponent,
  SetupContext,
} from 'vue'
export default defineComponent<{
  grid: Record<string, any>
  minimap: Record<string, any> | boolean
  height?: number
  width?: number
  [key: string]: any
}>({
  name: 'Canvas',
  emits: ['drop'],
  setup(props: any, { slots, attrs, emit }: SetupContext) {
    const config = {
      layout: {
        type: 'dagre',
        nodesep: 100,
        ranksep: 40,
        controlPoints: true,
      },
      fitView: true,
      autoFit: false,
    }
    const baseConfig = computed(() => {
      return {
        ...config,
        ...props,
        ...attrs,
      }
    })
    return () => {
      const Widgets = (slots.default && slots.default()) || []
      return (
        <span onDrop={(...arg) => emit('drop', ...arg)}>
          {Widgets.map((Widget: any) => {
            return <Widget {...baseConfig.value} />
          })}
        </span>
      )
    }
  },
})
PipeLineLems/web/src/components/G6Flow/components/ConditionDialog/ConditionDialog.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
.conditionDialog {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.dialog {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  .leftBar {
    width: 208px;
    display: flex;
    justify-content: flex-start;
    align-items: flex-start;
    height: 470px;
    flex-direction: column;
    border: 1px solid #ccc;
    border-radius: 4px;
    overflow-wrap: auto;
    padding: 4px;
    .nodeStyle {
      width: 100%;
      display: flex;
      justify-content: space-between;
      align-items: center;
      font-size: 12px;
    }
    .box {
      margin-left: 0px;
    }
    // .label {
    // }
    .tree {
      width: 100%;
      border-radius: 4px;
    }
  }
  .content {
    width: calc(100% - 215px);
    height: 470px;
    background-color: #f8f8f8;
    padding: 20px;
    border-radius: 5px;
    position: relative;
    .condition {
      position: absolute;
      left: 0;
      bottom: 0;
      border-top: 1px solid #ddd;
      width: 100%;
      font-size: 13px;
      display: flex;
      align-items: center;
      justify-content: flex-start;
      flex-wrap: wrap;
      padding-left: 10px;
    }
  }
}
PipeLineLems/web/src/components/G6Flow/components/ConditionDialog/ConditionDialog.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,253 @@
import { defineComponent, computed, ref, nextTick } from 'vue'
import styles from './ConditionDialog.module.scss'
import BaseDialog from '@/components/BaseDialog/index.vue'
import DyForm from '@/components/DyForm/DyForm'
import { CirclePlus, Delete } from '@element-plus/icons-vue'
import NodeDialog from '../NodeDialog/NodeDialog'
import {
  ConditionType,
  CompositeCondition,
  ConditionItemType,
} from '../../core/enum'
import { Condition } from '../../type'
import { v4 as uuidv4 } from 'uuid'
import { cloneDeep } from 'lodash'
import { _t } from '@/libs/Language/Language'
interface CompositeCondition {
  Expression: string
  Label: string
  NOT: boolean
  Operator: string
  Property: string
  Value: string
  // Value: {
  //   '@_xsi:type': string
  //   '#text': string | number // Assuming "#text" can be either string or number
  // }
}
export default defineComponent({
  name: '条件集',
  props: {
    formItemProps: {
      type: Array,
      default: [],
    },
    conditionOptions: {
      type: Array,
      default: [],
    },
    formData: {
      type: Object,
      default: () => ({}),
    },
    onConditionChange: {
      type: Function,
      default: () => null,
    },
    getCondition: {
      type: Function,
      default: () => null,
    },
  },
  emits: ['update:modelValue', 'update:formData', 'updateFormData'],
  setup(props, ctx) {
    const edgeType = ref('')
    const visible = ref(false)
    const formRef = ref(null)
    const dataSource = ref([])
    const treeRef = ref()
    const nodeVisible = ref(false)
    const currentFormData = ref({})
    const formItemProps = ref<any[]>([])
    const currentNode = ref<{
      nodeData: {
        children?: any[]
        [key: string]: any
      } | null
    }>({
      nodeData: null,
    })
    const formData = ref<Condition | Record<string, any>>({})
    const currentCondition = computed(() => {
      const type = edgeType.value
      return props.getCondition(type, currentFormData.value)
    })
    /**
     * æ˜¯å¦æ˜¯å¤åˆç±»åž‹
     */
    const onOpenCondition = () => {
      const nodeId = uuidv4()
      formData.value = cloneDeep(props.formData)
      formData.value.nodeId = nodeId
      formData.value.root = true
      visible.value = true
      nextTick(async () => {
        await handleNodeClick(formData.value as Condition)
        treeRef.value.setCurrentKey(nodeId)
      })
    }
    const onConfirmBtn = () => {
      ctx.emit('updateFormData', formData.value)
      visible.value = false
    }
    const onAddNode = (data: Condition) => {
      currentNode.value.nodeData = data
      nodeVisible.value = true
    }
    const onDeleteNode = (node: any, data: Condition) => {
      const nodeId = data.nodeId
      if (node.parent) {
        node.parent.data.children = node.parent.data.children.filter(
          (data: Condition) => {
            data.nodeId !== nodeId
          }
        )
        nextTick(async () => {
          await handleNodeClick(formData.value as Condition)
        })
      }
    }
    const handleNodeClick = async (data: Condition | any) => {
      const formItems = await props.onConditionChange(
        data[ConditionType],
        false
      )
      formItemProps.value = [
        {
          label: _t('条件'),
          prop: ConditionType,
          clearable: true,
          el: 'select',
          placeholder: _t('请选择'),
          options: props.conditionOptions,
          disabled: true,
        },
        ...formItems,
      ]
      currentFormData.value = data
      edgeType.value = data[ConditionType]
      setCurrentKey(data.nodeId)
    }
    // èŠ‚ç‚¹ç±»åž‹å¼¹çª—
    const onConditionDialog = async (data: Condition) => {
      const nodeData = currentNode.value.nodeData
      if (nodeData) {
        data.nodeId = uuidv4()
        nodeData.children = nodeData.children || []
        nodeData.children.push(data)
        await handleNodeClick(data)
        setCurrentKey(data.nodeId)
      }
    }
    const setCurrentKey = (nodeId: string) => {
      nextTick(() => {
        treeRef.value.setCurrentKey(nodeId)
      })
    }
    return () => {
      return (
        <div class={styles.conditionDialog}>
          {/* <el-input
            readonly
            placeholder="请选择条件集"
            v-model={condition.value}
            onClick={onOpenCondition}
          /> */}
          <el-button
            onClick={onOpenCondition}
            style="width: 200px;"
            size="small"
          >
            {_t('集合')}
          </el-button>
          <NodeDialog
            options={props.conditionOptions}
            v-model:visible={nodeVisible.value}
            onConfirm={onConditionDialog}
          ></NodeDialog>
          <BaseDialog
            width="800px"
            title={_t('条件集')}
            v-model={visible.value}
            onClose={() => (visible.value = false)}
            onConfirm={onConfirmBtn}
            destroy-on-close
          >
            {formData.value.nodeId ? (
              <div class={styles.dialog}>
                <div class={styles.leftBar}>
                  <el-tree
                    style="max-width: 200px"
                    ref={treeRef}
                    class={styles.tree}
                    data={[formData.value]}
                    node-key="nodeId"
                    onNodeClick={handleNodeClick}
                    default-expand-all
                    highlight-current
                    expand-on-click-node={false}
                    v-slots={{
                      default: ({ node, data }: any) => {
                        return (
                          <div class={styles.nodeStyle}>
                            <span class={styles.label}>
                              {data.Label || data.label || _t('请输入标签内容')}
                            </span>
                            <div class={styles.tools}>
                              {data[ConditionType] === CompositeCondition ? (
                                <el-button
                                  onClick={() => onAddNode(data)}
                                  type="primary"
                                  link
                                >
                                  <el-icon>
                                    <CirclePlus />
                                  </el-icon>
                                </el-button>
                              ) : null}
                              <el-button
                                onClick={() => onDeleteNode(node, data)}
                                class={styles.box}
                                type="primary"
                                link
                                disabled={data.root}
                              >
                                <el-icon>
                                  <Delete />
                                </el-icon>
                              </el-button>
                            </div>
                          </div>
                        )
                      },
                    }}
                  ></el-tree>
                </div>
                <div class={styles.content}>
                  <DyForm
                    // ref={formConditionRef}
                    isLine={true}
                    formItemProps={formItemProps.value}
                    v-model:formData={currentFormData.value}
                    labelWidth="140px"
                  ></DyForm>
                  <div class={styles.condition}>{currentCondition.value}</div>
                </div>
              </div>
            ) : null}
          </BaseDialog>
        </div>
      )
    }
  },
})
PipeLineLems/web/src/components/G6Flow/components/EdgeDrawer/EdgeDrawer.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
.formDrawer {
  margin-left: 40px;
}
.drawContent {
  position: relative;
  width: 100%;
  height: 100%;
  .condition {
    position: absolute;
    left: 0;
    bottom: 0;
    border-top: 1px solid #ddd;
    width: 100%;
    height: 40px;
    padding: 10px;
    color: #333;
    font-size: 13px;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    flex-wrap: wrap;
  }
}
.edgeDialogTitle {
  font-size: 18px;
  font-family: Source Han Sans CN, Source Han Sans CN;
  font-weight: bold;
  color: #464e54;
  line-height: 0px;
  > img {
    margin-left: 10px;
  }
}
PipeLineLems/web/src/components/G6Flow/components/EdgeDrawer/EdgeDrawer.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,492 @@
import {
  computed,
  defineComponent,
  ref,
  onMounted,
  watch,
  SetupContext,
  PropType,
  nextTick,
} from 'vue'
import BaseDrawer from '@/components/BaseDrawer/BaseDrawer'
import DyForm from '@/components/DyForm/DyForm'
import { getFlowDetail, getFlowData } from '@/api/logic-flow'
import { FormItemPropType } from '@/components/DyForm/DyForm.d'
import ElInputNumber from 'element-plus/es/components/input-number/index'
import { cloneDeep, debounce, isFunction, sortBy, throttle } from 'lodash'
import styles from './EdgeDrawer.module.scss'
import { fittingString } from '../../core/transformHelp'
import { width, fontSize } from '../Nodes'
import ConditionDialog from '../ConditionDialog/ConditionDialog'
import { ConditionType, ValueText, CompositeCondition } from '../../core/enum'
import { Condition } from '../../type'
import { injectStore } from '../../core/store'
import Icon from '@/components/Icon/Icon'
import FlowContextDialog from '@/components/FlowContextDialog/FlowContextDialog'
import { WidgetNameType } from '../Models/WidgetTypeByEnum'
import { _t } from '@/libs/Language/Language'
export default defineComponent({
  name: 'NodeDrawer',
  props: {
    title: {
      type: String,
    },
    model: {
      type: Object,
      default: () => ({}),
    },
    modelValue: {
      type: Boolean,
    },
    graph: {
      type: Object,
      default: null,
    },
    type: {
      type: String,
      default: '',
    },
    edgeData: {
      type: Array,
      default: [],
    },
    isEdit: {
      type: Boolean,
      default: false,
    },
  },
  emits: ['update:modelValue', 'close', 'confirm', 'update:title'],
  setup(props, { slots, emit }) {
    const { flowMap, flowNodeMap, edgeMap } = injectStore()
    const isOpen = ref(false)
    const edgeType = ref('')
    const title = ref(props.title)
    const formRef = ref<any>(null)
    const formConditionRef = ref<any>(null)
    const conditionFormItemProps = ref<FormItemPropType>([])
    const formDataCondition = ref({})
    const conditionConfig = ref<Record<string, any>>({
      has: false,
      item: {},
    })
    const conditionOptions = ref<
      {
        label: string
        value: string
      }[]
    >([])
    const visible = computed({
      get: () => props.modelValue,
      set: (value) => {
        emit('update:modelValue', value)
      },
    })
    const model = computed(() => props.model)
    const currentCondition = computed(() => {
      const data = formData.value?.Condition || {}
      return getCondition(edgeType.value, data)
    })
    const currentTip = computed(() => {
      const type = model.value?.properties?.[ConditionType]
      return edgeMap.get(type)?.description
    })
    const formData: Record<string, any> = ref()
    const formItemProps = ref<FormItemPropType>([])
    const customWidgetMap = {
      inputNumber: ElInputNumber,
      flowItemKey: FlowContextDialog,
      condition: (props: any, { attrs }: SetupContext) => (
        <ConditionDialog
          formItemProps={conditionFormItemProps.value}
          conditionOptions={conditionOptions.value}
          onConditionChange={onConditionChange}
          onUpdateFormData={onUpdateFormData}
          getCondition={getCondition}
          v-model:formData={formData.value.Condition}
          {...props}
          {...attrs}
        />
      ),
    }
    const onClose = () => {
      visible.value = false
      isOpen.value = false
      emit('close')
    }
    const onUpdateFormData = (data: Condition) => {
      formData.value.Condition = data
    }
    const onConfirm = () => {
      const graph = props.graph
      const item = graph.findById(model.value.id)
      const modelData = cloneDeep(model.value)
      modelData.properties = formData.value
      modelData.label = formData.value?.Condition?.Label || formData.value.Label
      item.update(modelData)
      visible.value = false
      emit('confirm')
    }
    const formItemSort = (formItems: FormItemPropType[]) => {
      return sortBy(formItems, ['sort'])
    }
    const getConditionsItems = async (type: string) => {
      const data = await getFlowDetail(type)
      return generateFormItems(data.attributes || [])
    }
    const getConditions = async () => {
      const data = await getFlowData('Conditions')
      const conditions: Record<string, any> = data.Conditions || []
      conditionOptions.value = conditions.map((item: any) => {
        let name = item.type === CompositeCondition ? _t('复合条件') : item.name
        return {
          ...item,
          label: name,
          value: item.type,
          tip: item.description,
        }
      })
    }
    const onClearCondition = (val: string) => {
      if (!val) {
        formData.value.Condition = {}
      }
    }
    const onConditionChange = async (
      val: string,
      isNative: boolean = true,
      isSelectChange?: boolean
    ) => {
      const data = await getConditionsItems(val)
      if (isSelectChange) {
        formData.value.Condition.Operator = ''
      }
      if (isNative) {
        edgeType.value = val
        conditionFormItemProps.value = data
      }
      return data
    }
    const getRules = (item: any) => {
      return item.required
        ? [
            {
              required: true,
              message: item.name,
              trigger: 'blur',
            },
          ]
        : []
    }
    const generateFormItems = (attributes: any[]) => {
      const formItems: any[] = []
      attributes?.forEach((item: FormItemPropType) => {
        const placeholder = WidgetNameType[item.propertyType]?.placeholder
        const el =
          WidgetNameType[item.propertyType]?.el ||
          WidgetNameType[item.propertyType]?.type
        const type = WidgetNameType[item.propertyType]?.el
          ? WidgetNameType[item.propertyType]?.type
          : undefined
        if (item.propertyType !== 'Condition') {
          const formItem: FormItemPropType = {
            label: item.name,
            // prop:
            //   item.propertyType === 'Object'
            //     ? `${item.propertyKey}.${ValueText}`
            //     : item.propertyKey,
            prop: item.propertyKey,
            readonly: item.readonly,
            el,
            rules: getRules(item),
            tip: item.description || item.name,
            icon: 'wen',
            options: item.propertyData?.map((item: any) => {
              return {
                label: item.name,
                tip: item.description,
                value: item.value,
              }
            }),
            disabled: ['Source', 'Sink'].includes(item.propertyKey),
            defaultValue: item.propertyValue,
            clearable: !item.nullable ? false : true,
            controlsPosition: 'right',
            step: 1,
            min: WidgetNameType[item.propertyType]?.type?.min ?? undefined,
            nullable: item.nullable ? undefined : item.propertyValue,
            valueOnClear: item.nullable ? undefined : item.propertyValue,
            type,
            rows: WidgetNameType[item.propertyType]?.rows || 10,
          }
          if (item.pattern) {
            formItem.rules.push({
              pattern: new RegExp(item.pattern),
              message: item.ruleMessage || _t('格式错误'),
              trigger: 'blur',
            })
          }
          if (placeholder) {
            formItem.placeholder = placeholder + item.name
          }
          if (item.visible) {
            formItems.push(formItem)
          }
        } else {
          conditionConfig.value.has = true
          conditionConfig.value.item = item
        }
      })
      return formItems
    }
    const onOpen = async () => {
      const type =
        model.value?.properties?.[ConditionType] || 'BusinessTransition'
      if (!props.model && type) return
      isOpen.value = true
      getConditions()
      const data = await getFlowDetail(type)
      const attributes = data.attributes || []
      const formItems: FormItemPropType[] = generateFormItems(attributes)
      if (conditionConfig.value.has) {
        formItems.push({
          label: conditionConfig.value.item.name,
          clearable: conditionConfig.value.item.nullable ? true : false,
          prop: `Condition.${ConditionType}`,
          el: 'select',
          placeholder: _t('请选择'),
          icon: 'wen',
          defaultValue: '',
          rules: getRules(conditionConfig.value.item),
          options: conditionOptions.value,
          tip: conditionConfig.value.item.description,
          onClear: onClearCondition,
          onChange: (val: string, isNative: boolean) =>
            onConditionChange(val, isNative, true),
        })
      }
      title.value = data.name
      formItemProps.value = formItemSort(formItems)
      nextTick(() => {
        formRef.value?.initFormData()
        initEdgeFormData()
      })
    }
    const initEdgeFormData = () => {
      formData.value = {}
      nextTick(() => {
        const properties = cloneDeep(model.value?.properties)
        const Condition = properties?.Condition || {}
        formData.value = {
          ...formData.value,
          ...properties,
        }
        onConditionChange(Condition[ConditionType])
        const { oldSourceId, oldTargetId } = formData.value
        if (oldSourceId && oldTargetId) {
          const sourceName = flowMap.get(oldSourceId).name
          const sinkName = flowMap.get(oldTargetId).name
          formData.value.Source = sourceName
          formData.value.Sink = sinkName
        } else {
          const sourceName = flowNodeMap.get(model.value?.source)?.name
          const sinkName = flowNodeMap.get(model.value?.target)?.name
          formData.value.Source = sourceName
          formData.value.Sink = sinkName
        }
      })
    }
    const OperatorType: Record<string, string> = {
      RelOpEqual: '=',
      RelOpLess: '<',
      RelOpLarge: '>',
      RelOpLessEq: '<=',
      RelOpLargeEq: '>=',
      RelOpNotEqual: '!=',
      RelOpContain: 'Contains',
    }
    /**
     * èŽ·å–æ¡ä»¶
     * @returns
     */
    const getCondition = (type: string, data: Record<string, any>) => {
      const not = data.NOT ? '!' : ''
      const typeMap: Record<string, Function> = {
        PropertyCondition() {
          // PropertyToCompare需要比较的值
          // Property å±žæ€§
          // Operator æ¯”较符
          // Value å¸¸é‡
          // Parameter å‚数名
          const result = data.PropertyToCompare || data.Value
          const property =
            data.Parameter && data.Property
              ? `${data.Parameter}.${data.Property}`
              : data.Property || data.Parameter
          if (property && OperatorType[data.Operator] && result) {
            return `${not} ${property} ${OperatorType[data.Operator]} ${result}`
          }
        },
        CompositeCondition() {
          const treeToString: any = (tree: any[]) => {
            if (!Array.isArray(tree) || tree.length === 0) {
              return ''
            }
            return tree
              .map((node) => {
                if (node.children && node.children.length > 0) {
                  // é€’归处理子节点
                  return `(${treeToString(node.children)})`
                } else {
                  // å¤„理当前节点
                  return node.condition?.trim()
                }
              })
              .join(` ${tree[0].operator?.trim()} `)
          }
          const fn = (data: any[], root: boolean = false) => {
            let sumArray: any[] = []
            data.forEach((item) => {
              const operator = item.Operator
              if (Array.isArray(item.children)) {
                item.children.forEach((composition: any) => {
                  const type = composition[ConditionType]
                  if (type === CompositeCondition) {
                    if (composition.children) {
                      sumArray.push({
                        children: fn([composition]),
                        operator,
                      })
                    }
                  } else {
                    const condition = getCondition(type, composition)
                    sumArray.push({
                      condition,
                      operator,
                    })
                  }
                })
              }
            })
            return sumArray
          }
          const condition = treeToString(fn([data]))
          return `${not} ${condition}`
        },
        ChoiceCondition() {
          const name = data.ActivityRowName
          const choice = data.Choice
          if (name && choice) {
            return `${not} ${name}.Choice = ${choice}`
          }
        },
        TableCondition() {
          const name = data.PropertyName
          const operator = OperatorType[data.Operator]
          const value = data.Value
          if (name && operator && value) {
            return `${not} ${name} ${operator} ${value}`
          }
        },
        XMLCondition() {
          const parameter = data.Parameter
          const prefix = data.Prefix
          const xPath = data.XPath
          if (parameter && prefix && xPath) {
            return `${not} ${parameter}[${prefix}:${xPath}].Result>0`
          }
        },
      }
      const fn = typeMap[type]
      return isFunction(fn) && fn()
    }
    onMounted(() => {
      if (!isOpen.value) {
        onOpen()
      }
    })
    return () => {
      return (
        <BaseDrawer
          onClose={onClose}
          onConfirm={onConfirm}
          onOpen={onOpen}
          width="700px"
          v-model={visible.value}
          submitDisabled={!formItemProps.value.length || !props.isEdit}
          destroy-on-close
          v-slots={{
            title: () => {
              return (
                <div class={styles.edgeDialogTitle}>
                  {title.value || model.value.label}
                  <el-tooltip
                    effect="dark"
                    content={currentTip.value}
                    placement="top"
                  >
                    <Icon icon="wen" />
                  </el-tooltip>
                </div>
              )
            },
          }}
        >
          <div class={styles.drawContent}>
            {formItemProps.value.length ? (
              <DyForm
                ref={formRef}
                customWidgetMap={customWidgetMap}
                formItemProps={formItemProps.value}
                v-model:formData={formData.value}
                labelWidth="140px"
              ></DyForm>
            ) : (
              <el-empty image-size={200} description={_t('暂无数据')} />
            )}
            {conditionConfig.value.has ? (
              <div class={styles.formDrawer}>
                <DyForm
                  ref={formConditionRef}
                  customWidgetMap={customWidgetMap}
                  formItemProps={conditionFormItemProps.value}
                  v-model:formData={formData.value.Condition}
                  labelWidth="140px"
                ></DyForm>
              </div>
            ) : null}
            <div class={styles.condition}>{currentCondition.value}</div>
          </div>
        </BaseDrawer>
      )
    }
  },
})
PipeLineLems/web/src/components/G6Flow/components/Menu/Menu.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
.contextMenuItemC {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  padding: 6px 0 6px 11px;
  .iconBox {
    width: auto;
  }
  .labelC {
    font-size: 14px;
    font-family: PingFang SC, PingFang SC;
    font-weight: 400;
    color: #333333;
  }
}
PipeLineLems/web/src/components/G6Flow/components/Menu/Menu.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,181 @@
import { defineComponent, ref, computed } from 'vue'
import Icon from '@/components/Icon/Icon'
import styles from './Menu.module.scss'
import './index.scss'
import { isNil } from 'lodash'
interface contextMenuItemType {
  current: Record<string, any> | null
  options: any
}
interface CurrentType {
  row: any
  index: number
}
interface ItemType {
  label: string
  icon: string
  fn: (c: CurrentType) => void
  disabled?: boolean
  divided?: boolean
}
export default defineComponent({
  name: '右键菜单',
  props: {
    contextMenu: {
      type: Array,
    },
    graph: {
      type: Object,
    },
    visible: {
      type: Boolean,
      default: false,
    },
    options: {
      type: Object,
      default: () => {},
    },
    model: {
      type: Object,
      default: () => {},
    },
    isShow: {
      type: Boolean,
      default: false,
    },
  },
  setup(props, { emit }) {
    const menuRef = ref<any>()
    // const contextMenu: ItemType[] | unknown[] = props.contextMenu || []
    const contextMenu = computed(() => {
      return props.contextMenu || []
    })
    //  [
    //   {
    //     label: '复制',
    //     fn: (c: CurrentType) => {},
    //     divided: true,
    //     icon: 'o',
    //   },
    //   {
    //     label: '删除',
    //     fn: (c: CurrentType) => {},
    //     divided: true,
    //     disabled: false,
    //     icon: 'up',
    //   },
    //   {
    //     label: '查看属性',
    //     fn: (c: CurrentType) => {},
    //     divided: true,
    //     disabled: false,
    //     icon: 'up',
    //   },
    // ]
    const visible = computed({
      get: () => {
        return props.visible
      },
      set(value) {
        emit('update:visible', value)
      },
    })
    const isShow = computed({
      get: () => {
        return props.isShow
      },
      set(value) {
        emit('update:isShow', value)
      },
    })
    const contextMenuConfig = computed(() => {
      return {
        options: {
          zIndex: 2000,
          minWidth: 132,
          x: 0,
          y: 0,
          ...props.options,
        },
      }
    })
    const contextDisabled: any = computed(() => {
      return (item: any) => {
        if (item.disabled !== undefined) {
          if (!isNil(item.disabled?.value)) {
            return item.disabled?.value
          } else {
            return item.disabled
          }
        }
        return false
      }
    })
    /**
     * èœå•
     * @param item
     */
    const onHandleMenuItem = (event: TouchEvent | MouseEvent, item: any) => {
      event?.stopPropagation()
      item.fn && item.fn(props.model, isShow)
    }
    return () => {
      return isShow.value ? (
        <context-menu
          // v-if="contextMenu?.length > 0"
          ref={menuRef}
          v-model:show={visible.value}
          options={contextMenuConfig.value.options}
        >
          {
            // @ts-ignore
            contextMenu.value.map((item: ItemType, index: number) => {
              return (
                <span
                  onTouchstart={(event) => onHandleMenuItem(event, item)}
                  onClick={(event) => onHandleMenuItem(event, item)}
                >
                  <context-menu-item
                    key={index}
                    label={item.label}
                    disabled={!!contextDisabled.value(item)}
                    style={{
                      filter: contextDisabled.value(item)
                        ? 'opacity(0.4)'
                        : 'none',
                    }}
                  >
                    <div
                      style={{
                        cursor: !contextDisabled.value(item)
                          ? 'pointer'
                          : 'not-allowed',
                      }}
                      class={styles.contextMenuItemC}
                    >
                      <div style="width: 16px; margin-right: 7px">
                        <Icon
                          height={16}
                          class={styles.iconBox}
                          icon={item.icon}
                        />
                      </div>
                      <div class={styles.labelC}>{item.label}</div>
                    </div>
                  </context-menu-item>
                </span>
              )
            })
          }
        </context-menu>
      ) : null
    }
  },
})
PipeLineLems/web/src/components/G6Flow/components/Menu/index.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
// å³ä¾§èœå•
.mx-context-menu {
  width: 132px;
  background: #ffffff;
  box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.16);
  padding: 0px;
  .mx-context-menu-item-wrapper {
    padding: 4px;
    height: 40px;
    border-bottom: 1px solid #e3e6ed;
    .mx-context-menu-item {
      cursor: pointer;
      border-radius: 6px;
      width: 100%;
      padding: 0;
      height: 100%;
    }
  }
  .mx-context-menu-item-wrapper:last-child {
    border-bottom: none;
  }
  .table-context-menu-item-c {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: flex-start;
    align-items: center;
    padding: 6px 0 6px 11px;
    .icon-box {
      width: auto;
    }
    .label-c {
      font-size: 14px;
      font-family: PingFang SC, PingFang SC;
      font-weight: 400;
      color: #333333;
    }
  }
}
PipeLineLems/web/src/components/G6Flow/components/Models/CreateFormItem.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
import { _t } from '@/libs/Language/Language'
import { injectStore } from '../../core/store'
import { WidgetNameType } from './WidgetTypeByEnum'
const { flowMap } = injectStore()
export class CreateFormItem {
  category: string = ''
  label: string = ''
  prop: string = ''
  el: any
  rules: any
  readonly: boolean = false
  tip: string = ''
  icon: string = ''
  options: any
  defaultValue: any
  clearable: boolean = true
  controlsPosition: string = ''
  step: number = 1
  min: number = 0
  propertyType: string = ''
  elementAttributes: any
  elementType: any
  type: string = ''
  placeholder: string = ''
  valueOnClear: number | any
  nullable: boolean = false
  pattern: string = ''
  rows: string | number = 10
  constructor(item: any, currentNode: string) {
    this.init(item, currentNode)
  }
  init(item: any, currentNode: string) {
    const el =
      WidgetNameType[item.propertyType]?.el ||
      WidgetNameType[item.propertyType]?.type
    this.category = item.category
    this.label = item.name
    this.prop = item.propertyKey
    this.el = el
    this.readonly = item.readonly
    this.tip = item.description
    this.icon = 'wen'
    this.options = item.options
    this.defaultValue = item.propertyValue
    this.clearable = item.nullable ? true : false
    this.controlsPosition = 'right'
    this.valueOnClear = item.nullable ? undefined : item.propertyValue
    this.step = 1
    this.nullable = item.nullable
    this.min = WidgetNameType[item.propertyType]?.type?.min ?? undefined
    this.propertyType = item.propertyType
    this.elementAttributes = item?.elementAttributes
    this.elementType = item.elementType
    this.placeholder = item.placeholder
    this.type = WidgetNameType[item.propertyType]?.el
      ? WidgetNameType[item.propertyType]?.type
      : 'select'
    this.rules = this.getRules(item.propertyKey, currentNode)
    this.rows = item.rows || WidgetNameType[item.propertyType]?.rows
    const requiredRule = {
      required: true,
      message: this.options.length ? _t('请选择') : _t('请输入') + item.name,
      trigger: 'change',
    }
    if (item.required && !this.rules) {
      this.rules = [requiredRule]
    }
    if (item.pattern) {
      this.rules.push({
        pattern: new RegExp(item.pattern),
        message: item.ruleMessage || _t('格式错误'),
        trigger: 'change',
      })
    }
  }
  getRules(key: string, currentNode: string) {
    // æ ¹æ®å®žé™…情况实现获取规则的逻辑
    const ruleMap: Record<string, any> = {
      Name: [
        {
          required: true,
          message: _t('请输入步骤名称'),
          trigger: 'change',
        },
        {
          validator: (rule: any, value: string, callback: any) => {
            let count = 0
            let isSameValue = false
            flowMap.forEach((item: any) => {
              if (item?.properties?.name === value) {
                if (currentNode !== item.id) {
                  count++
                  if (count >= 1) {
                    return (isSameValue = true)
                  }
                }
              }
            })
            if (isSameValue) {
              callback(_t('节点名称重复,请检查后重试'))
            } else {
              callback()
            }
          },
          trigger: 'blur',
        },
      ],
    }
    return ruleMap[key]
  }
}
PipeLineLems/web/src/components/G6Flow/components/Models/WidgetTypeByEnum.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
import { _t } from '@/libs/Language/Language'
export const WidgetNameType: Record<string, any> = {
  String: {
    type: 'input',
    placeholder: _t('请输入'),
  },
  Textarea: {
    el: 'input',
    type: 'textarea',
    placeholder: _t('请输入'),
    rows: 10,
  },
  Boolean: {
    type: 'switch',
  },
  Enum: {
    type: 'select',
    placeholder: _t('请选择'),
  },
  Int32: {
    type: 'inputNumber',
    placeholder: _t('请输入'),
  },
  Int64: {
    type: 'inputNumber',
    placeholder: _t('请输入'),
  },
  UInt32: {
    type: 'inputNumber',
    placeholder: _t('请输入'),
    min: 0,
  },
  ArrayList: {
    type: 'tableArray',
    placeholder: '',
  },
  Object: {
    type: 'tableArray',
    placeholder: '',
  },
  Variable: {
    type: 'variable',
    placeholder: _t('请选择'),
  },
  FlowItemKey: {
    type: 'flowItemKey',
    placeholder: _t('请选择'),
  },
  ConditionCollection: {
    type: 'condition',
  },
  Decimal: {
    type: 'inputNumber',
    placeholder: _t('请输入'),
    precision: 6,
  },
  Double: {
    type: 'inputNumber',
    placeholder: _t('请输入'),
    precision: 6,
  },
  Single: {
    type: 'inputNumber',
    placeholder: _t('请输入'),
    precision: 6,
  },
}
PipeLineLems/web/src/components/G6Flow/components/NodeDialog/NodeDialog.module.scss
PipeLineLems/web/src/components/G6Flow/components/NodeDialog/NodeDialog.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
import { computed, defineComponent, ref } from 'vue'
import BaseDialog from '@/components/BaseDialog/index.vue'
import DyForm from '@/components/DyForm/DyForm'
import { ConditionType } from '../../core/enum'
import { _t } from '@/libs/Language/Language'
export default defineComponent({
  props: ['visible', 'options'],
  emits: ['confirm', 'update:visible'],
  setup(props, { emit }) {
    const formData = ref({})
    const formRef = ref()
    const visible = computed({
      get() {
        return props.visible
      },
      set(v) {
        emit('update:visible', v)
      },
    })
    const onConfirm = async () => {
      await formRef.value.validate()
      visible.value = false
      emit('confirm', { ...formData.value })
    }
    return () => {
      return (
        <BaseDialog
          width="400px"
          title={_t('条件类型')}
          v-model={visible.value}
          onClose={() => (visible.value = false)}
          onConfirm={onConfirm}
        >
          <DyForm
            ref={formRef}
            isLine={true}
            formItemProps={[
              {
                label: '条件类型',
                prop: ConditionType,
                el: 'select',
                options: props.options || [],
                rules: [
                  {
                    required: true,
                    message: _t('请选择条件类型'),
                    trigger: 'blur',
                  },
                ],
              },
            ]}
            v-model:formData={formData.value}
            labelWidth="80px"
          ></DyForm>
        </BaseDialog>
      )
    }
  },
})
PipeLineLems/web/src/components/G6Flow/components/NodeDrawer/NodeDrawer.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
.formDrawer {
  margin-left: 40px;
}
.drawContent {
  position: relative;
  width: 100%;
  height: 100%;
  .condition {
    position: absolute;
    left: 0;
    bottom: 0;
    border-top: 1px solid #ddd;
    width: 100%;
    height: 40px;
    padding: 10px;
    color: #333;
    font-size: 13px;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    flex-wrap: wrap;
  }
}
.edgeDialogTitle {
  font-size: 18px;
  font-family: Source Han Sans CN, Source Han Sans CN;
  font-weight: bold;
  color: #464e54;
  line-height: 0px;
  > img {
    margin-left: 10px;
  }
}
.tip {
  font-size: 12px;
  color: #8a8a8a;
  position: absolute;
  left: 20px;
  top: 60px;
}
PipeLineLems/web/src/components/G6Flow/components/NodeDrawer/NodeDrawer.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,283 @@
import {
  computed,
  defineComponent,
  ref,
  onMounted,
  watch,
  SetupContext,
  PropType,
  nextTick,
  inject,
} from 'vue'
import BaseDrawer from '@/components/BaseDrawer/BaseDrawer'
import DyForm from '@/components/DyForm/DyForm'
import { injectStore } from '../../core/store'
import { StoreKey } from '../../type/index.d'
import { getFlowDetail } from '@/api/logic-flow'
import { FormItemPropType } from '@/components/DyForm/DyForm.d'
import ElInputNumber from 'element-plus/es/components/input-number/index'
import { cloneDeep, get, isNil, isNull, omitBy, remove, sortBy } from 'lodash'
import { fittingString } from '../../core/transformHelp'
import { width, nodeFontSize } from '../Nodes'
import TableArray from '@/components/TableArray/TableArray'
import { CreateFormItem } from '../Models/CreateFormItem'
import { WidgetNameType } from '../Models/WidgetTypeByEnum'
import { ConditionType } from '../../core/enum'
import FlowContextDialog from '@/components/FlowContextDialog/FlowContextDialog'
import styles from './NodeDrawer.module.scss'
import Icon from '@/components/Icon/Icon'
import { _t } from '@/libs/Language/Language'
export default defineComponent({
  name: 'NodeDrawer',
  props: {
    title: {
      type: String,
    },
    model: {
      type: Object,
      default: () => ({}),
    },
    modelValue: {
      type: Boolean,
    },
    graph: {
      type: Object,
      default: null,
    },
    type: {
      type: String,
      default: '',
    },
    isEdit: {
      type: Boolean,
      default: false,
    },
  },
  emits: ['update:modelValue', 'close', 'confirm', 'update:title'],
  setup(props, { slots, emit }) {
    const isOpen = ref(false)
    const title = ref(props.title)
    const formRef = ref<any>(null)
    const flowType = inject('flowType', '')
    const { nodeMap } = injectStore()
    const customWidgetMap = {
      inputNumber: ElInputNumber,
      switch: (props: PropType<any>, { attrs }: SetupContext) => {
        return <el-switch {...attrs} />
      },
      tableArray: TableArray,
      flowItemKey: FlowContextDialog,
    }
    const visible = computed({
      get: () => props.modelValue,
      set: (value) => {
        emit('update:modelValue', value)
      },
    })
    const model = computed(() => props.model)
    const formData: Record<string, any> = ref({})
    const formItemProps = ref<FormItemPropType>([])
    const formItemPropMap = ref<Record<string, FormItemPropType>>({})
    const currentNode = computed(() => {
      return model.value?.properties
    })
    const onClose = () => {
      visible.value = false
      isOpen.value = false
    }
    /**
     * è¿‡æ»¤id
     */
    const filterDataId = () => {
      formItemProps.value.forEach((formItem: FormItemPropType) => {
        if (formItem.propertyType === 'ArrayList') {
          const data = get(
            formData.value,
            `[${formItem.prop}][${formItem.elementType}]`,
            []
          )
          data.forEach((item: any) => {
            delete item.id
          })
        }
      })
    }
    const onConfirm = async () => {
      const refs: Record<string, any> = {}
      const valid = await formRef.value?.validate()
      let validField = true
      formItemProps.value.forEach((formItem: FormItemPropType) => {
        if (formItem.propertyType === 'ArrayList') {
          refs[formItem.prop] = formRef.value?.getRefByKey(formItem.prop)
        }
      })
      Object.keys(refs).forEach((key: string) => {
        const ref = refs[key]
        const valid = ref?.valid()
        if (!valid) {
          return (validField = false)
        }
      })
      if (!validField) return
      if (!valid) return
      const graph = props.graph
      const item = graph.findById(model.value.id)
      const modelData = cloneDeep(model.value)
      const label = formData.value.Name
      filterDataId()
      modelData.properties = formData.value
      modelData.name = label
      modelData.label = fittingString(label, width - 5, nodeFontSize + 2)
      item.update(modelData)
      visible.value = false
      emit('confirm', formData.value)
    }
    const formItemSort = (formItems: FormItemPropType[]) => {
      return sortBy(formItems, ['sort'])
    }
    const updateFormData = () => {
      const properties = model.value?.properties
      formData.value = {
        ...formData.value,
        ...properties,
      }
      const type = currentNode.value?.type
      if (!formData.value[ConditionType] && type) {
        formData.value[ConditionType] = type
      }
      formItemProps.value.forEach((formItem: FormItemPropType) => {
        const data = formData.value[formItem.prop]
        if (formItem.propertyType === 'ArrayList') {
          if (Array.isArray(data) || !data) {
            formData.value[formItem.prop] = {}
          }
          if (!data?.[formItem.elementType]) {
            formData.value[formItem.prop][formItem.elementType] = []
          }
          const childData = formData.value[formItem.prop][formItem.elementType]
          if (!Array.isArray(childData) && childData) {
            formData.value[formItem.prop][formItem.elementType] = [childData]
          }
        } else {
          if (isNil(data)) {
            formData.value[formItem.prop] = formItem.defaultValue
          }
          if (formItem.propertyType === 'Enum') {
            const v = formData.value[formItem.prop]
            formData.value[formItem.prop] =
              v !== null ? String(formData.value[formItem.prop]) : v
          }
        }
      })
    }
    const onOpen = async () => {
      formItemProps.value = {}
      formItemPropMap.value = {}
      formData.value = {}
      if (!currentNode.value?.type) return
      isOpen.value = true
      const data = await getFlowDetail(
        model.value?.properties?.type,
        flowType !== '' ? Number(flowType) : undefined
      )
      const attributes = data.attributes || []
      const formItems: FormItemPropType[] = []
      attributes?.forEach((item: FormItemPropType) => {
        const placeholder = WidgetNameType[item.propertyType]?.placeholder
        const options =
          item.propertyData?.map((item: any) => {
            return {
              label: item.name,
              tip: item.description,
              value: item.value,
            }
          }) || []
        const option = {
          ...item,
          options,
          placeholder: placeholder && placeholder + item.name,
        }
        const formItem: FormItemPropType = new CreateFormItem(
          option,
          model.value.id
        )
        if (item.visible) {
          formItems.push(formItem)
        }
      })
      formItemProps.value = formItemSort(formItems)
      formItemProps.value.forEach((formItem: FormItemPropType) => {
        formItemPropMap.value[formItem.prop] = formItem
      })
      title.value = data.name
      nextTick(() => {
        formRef.value?.initFormData()
        updateFormData()
      })
    }
    onMounted(() => {
      if (!isOpen.value) {
        onOpen()
      }
    })
    return () => {
      const type = model.value?.properties?.type
      const nodeInfo = type ? nodeMap.get(type) : {}
      return (
        <BaseDrawer
          onClose={onClose}
          onConfirm={onConfirm}
          onOpen={onOpen}
          // title={title.value || model.value.label}
          width="60%"
          v-model={visible.value}
          submitDisabled={!formItemProps.value.length || !props.isEdit}
          destroy-on-close
          v-slots={{
            title: () => {
              return (
                <div class={styles.edgeDialogTitle}>
                  {title.value || model.value.label}
                  <div class={styles.tip}>{nodeInfo?.description}</div>
                </div>
              )
            },
          }}
        >
          {formItemProps.value.length ? (
            <DyForm
              isCategory={true}
              ref={formRef}
              customWidgetMap={customWidgetMap}
              formItemProps={formItemProps.value}
              v-model:formData={formData.value}
              labelWidth="140px"
            ></DyForm>
          ) : (
            <el-empty image-size={200} description={_t('暂无数据')} />
          )}
        </BaseDrawer>
      )
    }
  },
})
PipeLineLems/web/src/components/G6Flow/components/Nodes/EndNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
import { NODES } from '../../core/enum'
import { width, height, x, y } from './index'
const EndNode: {
  type: string
  options: Record<string, any>
} = {
  type: NODES.END_ACTIVITY,
  options: {
    drawShape(cfg: Record<string, any>, group: any) {
      const rect = group.addShape('rect', {
        attrs: {
          x: -75 - x,
          y: -25 - y,
          width,
          height,
          radius: 10,
          stroke: '#5B8FF9',
          fill: '#C6E5FF',
          lineWidth: 3,
        },
        name: 'rect-shape',
      })
      return rect
    },
  },
}
export default EndNode
PipeLineLems/web/src/components/G6Flow/components/Nodes/Node.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
import { defineComponent } from 'vue'
import styles from './index.module.scss'
export default defineComponent({
  name: 'Node',
  props: {
    cfg: {
      type: Object,
      required: true,
    },
  },
  setup() {
    return () => {
      return <div class={styles.nodeContent}>Node</div>
    }
  },
})
PipeLineLems/web/src/components/G6Flow/components/Nodes/OrdinaryNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
import { NODES } from '../../core/enum'
import { width, height, x, y } from './index'
// const pngMap = import.meta.glob('../../../../assets/images/*.png', {
//   eager: true,
// })
// const iconMap: Record<string, any> = Object.entries(pngMap).reduce(
//   (acc, [key, value]: any[]) => {
//     const name = key
//       .replace('../../../../assets/images/', '')
//       .replace('.png', '')
//     return {
//       ...acc,
//       [name]: value.default,
//     }
//   },
//   {}
// )
const OrdinaryNode: {
  type: string
  options: Record<string, any>
} = {
  type: NODES.ACTIVITIES,
  options: {
    drawShape(cfg: Record<string, any>, group: any) {
      const type = cfg?.properties?.type
      const isRoot = cfg?.isRoot
      const rect = group.addShape('rect', {
        zIndex: 1,
        attrs: {
          x: -width / 2,
          y: -height / 2,
          width,
          height,
          radius: 1,
          stroke: '#5B8FF9',
          fill: '#C6E5FF',
          lineWidth: 2,
        },
        name: 'rect-shape',
      })
      group.addShape('image', {
        zIndex: 100,
        draggable: false,
        attrs: {
          x: -width / 2 + 1,
          y: -height / 2 + 1,
          radius: 1,
          width: 20,
          height: 20,
          // img: isRoot ? iconMap[NODES.ACTIVITY] : iconMap[type],
          img: `/resources/assets/images/${isRoot ? NODES.ACTIVITY : type}.png`,
        },
        name: 'node-icon',
      })
      return rect
    },
  },
}
export default OrdinaryNode
PipeLineLems/web/src/components/G6Flow/components/Nodes/StartNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
import { NODES } from '../../core/enum'
import { width, height, x, y } from './index'
const StartNode: {
  type: string
  options: Record<string, any>
} = {
  type: NODES.ACTIVITY,
  options: {
    drawShape(cfg: Record<string, any>, group: any) {
      const rect = group.addShape('rect', {
        attrs: {
          x: -75 - x,
          y: -25 - y,
          width,
          height,
          radius: 10,
          stroke: '#5B8FF9',
          fill: '#C6E5FF',
          lineWidth: 3,
        },
        name: 'rect-shape',
      })
      return rect
    },
  },
}
export default StartNode
PipeLineLems/web/src/components/G6Flow/components/Nodes/index.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,5 @@
.nodeContent {
  width: 100%;
  height: 100%;
  background-color: red;
}
PipeLineLems/web/src/components/G6Flow/components/Nodes/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
const w = 50
const h = 10
export const x = w / 2
export const y = h / 2
export const width = 150 + w
export const height = 50 + h
export const fontSize = 16
export const nodeFontSize = fontSize + 2
PipeLineLems/web/src/components/G6Flow/components/Renderer/Renderer.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
.renderer {
  position: relative;
  // overflow: hidden;
  padding: 10px;
}
.miniMap {
  width: 200px;
  height: 120px;
  position: absolute;
  left: calc(100% - 215px);
  bottom: calc(100% - 130px);
  border: 1px solid #ccc;
  z-index: 99;
  background-color: #fff;
}
.toolBar {
  width: 46px;
  > div {
    width: 46px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-direction: column;
    border: none !important;
    padding: 0;
  }
}
PipeLineLems/web/src/components/G6Flow/components/Renderer/Renderer.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,332 @@
import {
  defineComponent,
  onMounted,
  ref,
  nextTick,
  watch,
  computed,
  SetupContext,
  Fragment,
  onUnmounted,
} from 'vue'
import styles from './Renderer.module.scss'
import { ConditionType, NODES } from '../../core/enum'
import G6, { Graph } from '@antv/g6'
import { behaviorMap } from '../../core/behavior'
import StartNode from '../Nodes/StartNode'
import EndNode from '../Nodes/EndNode'
import OrdinaryNode from '../Nodes/OrdinaryNode'
import Core from '../../core/Core'
import { injectStore } from '../../core/store'
import { fontSize, nodeFontSize } from '../Nodes'
import Menu from '../Menu/Menu'
import { cloneDeep } from 'lodash'
import ToolBar from '../ToolBar/ToolBarDefine'
import Tooltip from '../Tooltip/Tooltip'
interface PropsType {
  graphData: Record<string, any>
  [key: string]: any
  style: Record<string, any>
}
export default defineComponent<PropsType>({
  // @ts-ignore
  name: 'G6FlowRenderer',
  props: {
    graphData: {
      type: Object,
      required: true,
    },
    style: { type: Object, default: () => ({}) },
    minimap: { type: [Boolean, Object], default: false },
    isEdgeAnimation: { type: Boolean, default: false },
    dragCanvas: { type: Boolean, default: false },
    dragNode: { type: Boolean, default: false },
    activateRelations: { type: Boolean, default: false },
    zoomCanvas: { type: Boolean, default: false },
    clickSelect: { type: Boolean, default: false },
    createEdge: { type: Boolean, default: false },
    flowName: { type: String, default: '流程图' },
    editing: {
      type: Boolean,
      default: false,
    },
    contextMenu: {
      type: Array,
    },
    edgeContextMenu: {
      type: Array,
    },
  },
  // emits: Object.keys(behaviorMap),
  emits: [],
  setup(props: PropsType, { expose, attrs, slots, emit }: SetupContext) {
    const { graphEvent, flowMap, flowNodeMap } = injectStore()
    const core = new Core()
    const canvasConfig = computed(() => {
      return {
        ...attrs,
      }
    })
    let graph: Graph | null = null
    const graphConfig = {
      width: 0,
      height: 0,
    }
    /**
     * æ³¨å†Œè¾¹ä¸ŽèŠ‚ç‚¹
     */
    const batchRegister = () => {
      G6.registerNode(NODES.END_ACTIVITY, EndNode.options, 'single-node')
      G6.registerNode(NODES.ACTIVITIES, OrdinaryNode.options, 'single-node')
    }
    /**
     * è‡ªåŠ¨å¸ƒå±€
     */
    const autoLayout = () => {
      if (graph) {
        const layout = canvasConfig.value?.layout || {}
        graph.updateLayout({ ...layout })
      }
    }
    /**
     * è‡ªåŠ¨å¸ƒå±€
     */
    const render = () => {
      if (graph) {
        if (!props.graphData?.nodes?.length) return
        const isEdit = props.graphData?.nodes?.[0]?.isEdit
        if (isEdit) {
          graph.destroyLayout()
          graph.changeData(props.graphData)
        }
        graph.render()
        setTimeout(zoomCanvas)
      }
    }
    /**
     * ç¼©æ”¾åˆ°ä¸­é—´
     */
    const zoomCanvas = (x?: number, y?: number) => {
      if (graph) {
        const { width, height } = graphConfig
        graph?.zoomTo(0.7, {
          x: x || width / 2 - 33,
          y: y || 0,
          // duration: 1000,
        })
      }
    }
    const getTools = () => {
      const toolBarInstance = new ToolBar({
        className: styles.toolBar,
        format: autoLayout,
        downName: props.flowName,
      })
      const g6ToolInstance = toolBarInstance.instanceToolBar()
      return g6ToolInstance
    }
    const getMiniMap = () => {
      const container = document.querySelector(
        `.${styles.miniMap}`
      ) as HTMLDivElement
      if (!container) return
      const minimap = new G6.Minimap({
        container,
      })
      return minimap
    }
    /**
     * æ¸²æŸ“逻辑流
     * @param graphData
     */
    const renderG6Graph = () => {
      if (!Object.keys(props.graphData).length) return
      if (graph) {
        graph.data(props.graphData)
      }
      render()
    }
    /**
     * èŽ·å–åŠŸèƒ½äº‹ä»¶é…ç½®
     * @returns
     */
    const getBaseBehaviorConfig = () => {
      const events: string[] = []
      Object.entries(props || {}).forEach(([key, value]: any[]) => {
        if (behaviorMap[key] && value) {
          events.push(behaviorMap[key])
        }
      })
      return events
    }
    /**
     * åˆå§‹åŒ–渲染
     */
    const initializeRenderer = async () => {
      batchRegister()
      await renderG6Graph()
    }
    /**
     * å®žä¾‹åŒ–LogicFlow
     */
    const instanceG6Graph = () => {
      const container = document.querySelector(
        `.${styles.renderer}`
      ) as HTMLElement
      if (!container) return
      const width = container.scrollWidth
      const height = container.scrollHeight || 500
      graphConfig.width = width
      graphConfig.height = height
      const defaultProps = core.setDefaultProps()
      const behavior = getBaseBehaviorConfig()
      const minimap = getMiniMap()
      const toolBar = props.editing ? getTools() : null
      const toolTip = Tooltip()
      const plugins = [minimap, toolBar, toolTip].filter((v) => v)
      graph = new G6.Graph({
        container,
        width,
        height,
        modes: {
          default: [...behavior],
        },
        // è®¾ç½®ä¸ºtrue,启用 redo & undo æ ˆåŠŸèƒ½
        enabledStack: true,
        plugins,
        ...defaultProps,
        ...canvasConfig.value,
      })
      graphEvent.init(graph, canvasConfig.value.layout)
      initializeRenderer()
    }
    /**
     * èŽ·å–å½“å‰LogicFlow实例
     * @returns
     */
    const getCurrentInstance = () => {
      return graph
    }
    const currentHeight = computed(() => {
      const height = canvasConfig.value?.height
      if (height) {
        return height + 'px'
      }
      return window.innerHeight + 'px'
    })
    const addNode = (node: any, position: any) => {
      if (!graph) return
      const point = graph?.getPointByClient(position.x, position.y)
      const config = {
        label: node.name,
        type: node.category,
      }
      const model = core.createNode(config, {
        x: point.x,
        y: point.y,
        properties: {
          Name: node.name,
          type: node.type,
          [ConditionType]: node.type,
        },
      })
      if (flowMap.get(model.Name)) {
        const v = parseInt(String(Math.random() * 10000))
        const newName = model.Name + '_' + v
        model.Name = newName
        model.label = newName
        model.name = newName
        model.properties.Name = newName
      }
      flowNodeMap.set(model.id, model)
      flowMap.set(model.name, model)
      graph?.addItem('node', model)
    }
    const getGraph = () => {
      return graph
    }
    watch(
      () => props.graphData,
      (v, oldV) => {
        if (v !== oldV && v) {
          instanceG6Graph()
        }
      }
    )
    watch(
      () => props.createEdge,
      () => {
        if (!props.createEdge) {
          graph?.removeBehaviors('create-edge', 'default')
        } else {
          graph?.addBehaviors('create-edge', 'default')
        }
      }
    )
    onMounted(() => {
      instanceG6Graph()
    })
    onUnmounted(() => {
      graph?.destroy()
      graphEvent?.onUnmounted()
    })
    expose({
      getGraph,
      render,
      autoLayout,
      getCurrentInstance,
      zoomCanvas,
      addNode,
    })
    return () => {
      let contextMenu =
        graphEvent.type.value === 'edge'
          ? props.edgeContextMenu
          : props.contextMenu
      if (graphEvent.model.value?.isRoot) {
        contextMenu = contextMenu.filter(
          (item: any) => !['copy', 'del'].includes(item.type)
        )
      }
      return (
        <div
          class={styles.renderer}
          // onClick={(event: Event) => onCancelSelect(event)}
          style={{
            width: '100%',
            height: currentHeight.value,
            ...props.style,
          }}
        >
          <div class={styles.miniMap}></div>
          <div class={styles.toolBar}></div>
          {contextMenu && contextMenu.length > 0 && (
            <Menu
              contextMenu={contextMenu}
              visible={true}
              v-model:isShow={graphEvent.isShow.value}
              options={graphEvent.position.value}
              model={graphEvent.model.value}
            />
          )}
        </div>
      )
    }
  },
})
PipeLineLems/web/src/components/G6Flow/components/ToolBar/ToolBar.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
.toolBarContent {
  width: auto;
  height: fit-content;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  flex-direction: column;
  width: 46px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-direction: column;
  border: none !important;
  box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
    0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
  padding-left: 0;
  margin: 0;
  user-select: none;
}
.toolBarItem {
  width: 20px;
  height: 40px !important;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
  &:hover {
    filter: invert(50%) sepia(45%) saturate(3890%) hue-rotate(208deg)
      brightness(102%) contrast(106%);
  }
}
PipeLineLems/web/src/components/G6Flow/components/ToolBar/ToolBar.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,94 @@
import { defineComponent } from 'vue'
import styles from './ToolBar.module.scss'
import BaseConfigProvider from '@/components/BaseConfigProvider/BaseConfigProvider'
import { _t } from '@/libs/Language/Language'
export default defineComponent({
  name: 'ToolBar',
  components: {
    BaseConfigProvider,
  },
  setup() {
    const namespace = import.meta.env.VITE_APP_NAMESPACE
    const toolBar = [
      {
        code: 'undo',
        name: _t('撤销'),
        icon: 'undo.svg',
      },
      {
        code: 'redo',
        name: _t('恢复'),
        icon: 'redo.svg',
      },
      {
        code: 'enlarge',
        name: _t('放大'),
        icon: 'enlarge.svg',
      },
      {
        code: 'reduce',
        name: _t('缩小'),
        icon: 'reduce.svg',
      },
      {
        code: 'format',
        name: _t('美化'),
        icon: 'format1.svg',
      },
      {
        code: 'download',
        name: _t('下载'),
        icon: 'download.svg',
      },
      {
        code: 'import',
        name: _t('导入'),
        icon: 'import.svg',
      },
      {
        code: 'export',
        name: _t('导出'),
        icon: 'export.png',
      },
    ]
    return () => {
      return (
        <ul class={styles.toolBarContent}>
          {toolBar.map((item: any) => {
            const imgUrl = new URL(
              `../../../../assets/images/${item.icon}`,
              import.meta.url
            ).href
            return (
              <el-config-provider namespace={namespace}>
                <el-tooltip
                  class="box-item"
                  effect="dark"
                  content={item.name}
                  placement="right"
                >
                  <li
                    class={styles.toolBarItem}
                    key={item.code}
                    // @ts-ignore
                    code={item.code}
                  >
                    <img
                      src={imgUrl}
                      width={20}
                      height={20}
                      // @ts-ignore
                      code={item.code}
                    />
                  </li>
                </el-tooltip>
              </el-config-provider>
            )
          })}
        </ul>
      )
    }
  },
})
PipeLineLems/web/src/components/G6Flow/components/ToolBar/ToolBarDefine.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
import G6, { Graph } from '@antv/g6'
import styles from './ToolBar.module.scss'
import { h, createApp } from 'vue'
import ToolBar from './ToolBar'
import { generateFlowXml, importXmlInit } from '../../core/transformHelp'
import { ElMessage } from 'element-plus'
import { injectStore } from '../../core/store'
import { _t } from '@/libs/Language/Language'
export default class ToolBarDefine {
  #className: string
  #toolBarInstance: any
  #downName: string | undefined
  #format?: () => void
  #animateCfg = { duration: 200, easing: 'easeCubic' }
  constructor({ className, format, downName }: any) {
    this.#className = className
    this.#format = format
    this.#downName = downName
  }
  get container() {
    return document.querySelector(`.${this.#className}`)
  }
  get getContent() {
    return () => {
      const div = document.createElement('div')
      const app = createApp(() => {
        return <ToolBar />
      })
      app.mount(div)
      return div
    }
  }
  removeNode(dom: HTMLElement | HTMLInputElement) {
    const t = setTimeout(() => {
      dom.remove()
      clearTimeout(t)
    }, 2000)
  }
  handleClick(code: string, graph: Graph) {
    const { flowBaseConfig } = injectStore()
    if (this.#toolBarInstance) {
      const toolBar: typeof G6.ToolBar | any = this.#toolBarInstance
      const fnMap: Record<string, () => void> = {
        redo: () => {
          toolBar.redo()
        },
        undo: () => {
          toolBar.undo()
        },
        enlarge: () => {
          graph.zoom(1.1, { x: 0, y: 0 }, true, this.#animateCfg)
        },
        reduce: () => {
          graph.zoom(0.9, { x: 0, y: 0 }, true, this.#animateCfg)
        },
        format: () => {
          this.#format && this.#format()
        },
        export: () => {
          const xml = generateFlowXml(graph)
          const blob = new Blob([xml], { type: 'text/xml' })
          const url = URL.createObjectURL(blob)
          const a = document.createElement('a')
          a.href = url
          const { name, type, version } = flowBaseConfig.value
          const flowName = `${name}_${type}_${version}`
          a.download = flowName + '.pfd'
          a.click()
          ElMessage.success(_t('导出成功'))
          URL.revokeObjectURL(url)
          this.removeNode(a)
        },
        import: () => {
          const fileInput = document.createElement('input')
          fileInput.type = 'file'
          fileInput.accept = 'application/pfd'
          fileInput.click()
          fileInput.onchange = (e: any) => {
            const file = e.target.files[0]
            const reader = new FileReader()
            reader.readAsText(file)
            reader.onload = (e: any) => {
              const xml = e.target.result
              importXmlInit(graph, xml)
              ElMessage.success(_t('导入成功'))
              this.removeNode(fileInput)
            }
          }
        },
        download: () => {
          const oldRatio = window.devicePixelRatio
          window.devicePixelRatio = 3
          graph.downloadFullImage(this.#downName, 'image/png', {
            backgroundColor: '#fff',
            padding: 10,
          })
          setTimeout(() => {
            window.devicePixelRatio = oldRatio
          }, 0)
        },
      }
      const fn = fnMap[code]
      fn && fn()
    }
  }
  instanceToolBar() {
    this.#toolBarInstance = new G6.ToolBar({
      container: this.container as any,
      getContent: this.getContent,
      handleClick: this.handleClick.bind(this) as () => void,
    })
    return this.#toolBarInstance
  }
}
PipeLineLems/web/src/components/G6Flow/components/Tools/Tools.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,159 @@
.tools {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  flex-direction: column;
  width: 46px;
  position: absolute;
  left: 0px;
  top: 340px;
  z-index: 99;
  border-radius: 5px;
  height: auto;
  background-color: #fff;
  user-select: none;
  box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
    0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
  // border: 1px solid #e2e2e2;
  // box-shadow: 0px 1px 5px #aaa;
  .box {
    width: 39px;
    height: 39px;
    flex-shrink: 0;
    display: flex;
    flex-shrink: 1;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    &:hover {
      color: #5a84ff;
    }
    &:active {
      opacity: 0.7;
    }
    > span {
      font-size: 10px;
    }
    .icon {
      width: 26px;
      height: 26px;
      padding: 4px;
      border-radius: 4px;
      display: flex;
      justify-content: center;
      align-items: center;
      &:hover {
        .img {
          filter: invert(50%) sepia(45%) saturate(3890%) hue-rotate(208deg)
            brightness(102%) contrast(106%);
        }
      }
    }
    .selectNode {
      background-color: rgba(90, 132, 255, 0.9);
      .img {
        filter: invert(100%) sepia(6%) saturate(7478%) hue-rotate(180deg)
          brightness(101%) contrast(106%);
      }
      &:hover {
        background-color: rgba(90, 132, 255, 0.9);
        .img {
          filter: invert(100%) sepia(6%) saturate(7478%) hue-rotate(180deg)
            brightness(101%) contrast(106%);
        }
      }
    }
  }
  .nodeBox {
    width: 100%;
    height: 45px;
    flex-direction: column;
  }
  .boxLi {
    width: 100%;
    height: 45px;
    font-size: 10px;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    > span {
      margin-top: 2px;
    }
  }
}
.popover {
  padding-right: 0 !important;
}
.nodes {
  width: 100%;
  max-height: 350px;
  min-height: 170px;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-direction: column;
  // border-radius: 5px;
  // position: absolute;
  // border: 1px solid #e2e2e2;
  // left: 85px;
  // top: 56px;
  z-index: 9;
  overflow: auto;
  background-color: #fff;
  .ul {
    width: 100%;
    display: flex;
    align-items: flex-start;
    justify-content: flex-start;
    padding: 0;
    margin: 0;
    flex-wrap: wrap;
    flex: 1;
    .li {
      width: 154px;
      height: 30px;
      list-style-type: none;
      font-size: 12px;
      color: #333;
      display: flex;
      align-items: center;
      padding: 5px 10px;
      border: 1px solid #ddd;
      border-radius: 5px;
      // box-shadow: 0px 1px 3px #ccc;
      cursor: pointer;
      margin: 0 5px;
      margin-top: 5px;
      cursor: move;
      .icon {
        margin-right: 10px;
      }
      &:hover {
        color: #5a84ff;
        border: 1px solid #5b8ff9;
      }
    }
  }
}
.hoverEdge {
  filter: brightness(2.5);
}
.hide {
  display: none;
}
.boxShow {
  color: rgba(90, 132, 255);
  .icon {
    background-color: rgba(90, 132, 255);
  }
  .img {
    filter: brightness(2.5);
  }
}
PipeLineLems/web/src/components/G6Flow/components/Tools/Tools.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,247 @@
import {
  computed,
  ref,
  defineComponent,
  onUnmounted,
  watch,
  onMounted,
} from 'vue'
import styles from './Tools.module.scss'
import Icon from '@/components/Icon/Icon'
import { injectStore } from '../../core/store'
import { _t } from '@/libs/Language/Language'
export default defineComponent({
  props: {
    nodeData: {
      type: Array,
      default: () => [],
    },
    edgeData: {
      type: Array,
      default: () => [],
    },
  },
  emits: ['dragstart', 'edgeClick'],
  setup(props, ctx) {
    const visible = ref(false)
    const { selected } = injectStore()
    let flag = false
    const activeNames = ref<string[]>([])
    const onClickBox = (event: MouseEvent | TouchEvent) => {
      nodeData.value.forEach((item: any) => {
        if (!activeNames.value.includes(item.group)) {
          activeNames.value.push(item.group)
        }
      })
      if (!flag) {
        event.stopPropagation()
        visible.value = !visible.value
      }
    }
    const onTouchstart = (event: TouchEvent) => {
      flag = true
      visible.value = !visible.value
      event.stopPropagation()
    }
    const onTouchstartBox = (event: TouchEvent) => {
      event.stopPropagation()
    }
    const onClickEdge = (item: any) => {
      selected.value = item.type === selected.value ? null : item.type
      ctx.emit('edgeClick', selected.value)
    }
    const nodeData = computed(() => {
      return props.nodeData || []
    })
    const nodeMap = computed(() => {
      const nodeData = props.nodeData || []
      const map: Record<string, any[]> = {}
      nodeData.forEach((item: any) => {
        map[item.group] = map[item.group] || []
        map[item.group].push(item)
      })
      return map
    })
    const edgeData = computed(() => {
      return props.edgeData || []
    })
    const onDragstart = (item: Record<string, any>, event: DragEvent) => {
      ctx.emit('dragstart', item)
    }
    const onCollapseChange = (v: string) => {}
    document.addEventListener('click', () => {
      if (visible.value && !flag) {
        visible.value = false
      }
    })
    document.addEventListener('touchstart', () => {
      if (visible.value) {
        visible.value = false
      }
    })
    onUnmounted(() => {
      selected.value = null
    })
    // watch(nodeData, (data: any[]) => {
    // })
    return () => {
      return (
        <div>
          {/* å·¥å…·ç®± */}
          <div class={[styles.tools]}>
            <el-popover
              placement="right-end"
              width={365}
              trigger="click"
              popper-class={styles.popover}
              visible={visible.value}
              v-slots={{
                reference: () => {
                  return (
                    <div
                      onClick={onClickBox}
                      onTouchstart={onTouchstart}
                      class={{
                        [styles.box]: true,
                        [styles.nodeBox]: true,
                        [styles.boxShow]: visible.value,
                      }}
                    >
                      <el-tooltip
                        class="box-item"
                        effect="dark"
                        content={_t('活动节点')}
                        placement="right"
                      >
                        <div
                          class={{
                            [styles.icon]: true,
                            [styles.selectNode]: visible.value,
                          }}
                        >
                          <Icon
                            class={styles.img}
                            icon="node1"
                            width={20}
                            height={20}
                            draggable={false}
                          ></Icon>
                        </div>
                      </el-tooltip>
                    </div>
                  )
                },
              }}
            >
              <div
                class={{
                  [styles.nodes]: true,
                }}
                onTouchstart={onTouchstartBox}
              >
                <el-collapse
                  v-model={activeNames.value}
                  onChange={onCollapseChange}
                  onClick={(event: Event) => event?.stopPropagation()}
                  style={{ width: 'calc(100% - 10px)', marginRight: '10px' }}
                >
                  {Object.keys(nodeMap.value).map((key) => {
                    const nodes = nodeMap.value[key]
                    return (
                      <el-collapse-item title={key} name={key}>
                        <ul class={styles.ul}>
                          {nodes.map((item: any) => {
                            return (
                              <el-tooltip
                                class="box-item"
                                effect="dark"
                                content={item.description}
                                key={item.type}
                                placement="top"
                              >
                                <li
                                  onDragstart={(event) =>
                                    onDragstart(item, event)
                                  }
                                  draggable
                                  class={styles.li}
                                >
                                  {item.imageRef ? (
                                    <Icon
                                      class={styles.icon}
                                      icon={item.imageRef}
                                      width={15}
                                      height={15}
                                    ></Icon>
                                  ) : null}
                                  {item.name}
                                </li>
                              </el-tooltip>
                            )
                          })}
                        </ul>
                      </el-collapse-item>
                    )
                  })}
                </el-collapse>
              </div>
            </el-popover>
            {edgeData.value.map((item: any, index: number) => {
              return (
                <div
                  key={item.type}
                  onClick={() => onClickEdge(item)}
                  class={{
                    [styles.box]: true,
                    [styles.boxLi]: true,
                    [styles.boxShow]: selected.value === item.type,
                  }}
                >
                  <el-tooltip
                    class="box-item"
                    effect="dark"
                    content={item.name}
                    placement="right"
                  >
                    <div
                      class={{
                        [styles.icon]: true,
                        [styles.selectNode]: selected.value === item.type,
                      }}
                    >
                      <Icon
                        // @ts-ignore
                        class={{
                          [styles.img]: true,
                        }}
                        draggable={false}
                        icon={item.imageRef}
                        width={20}
                        height={20}
                      ></Icon>
                    </div>
                  </el-tooltip>
                  {/* <span>{item.name}</span> */}
                </div>
              )
            })}
          </div>
          {/* èŠ‚ç‚¹ */}
        </div>
      )
    }
  },
})
PipeLineLems/web/src/components/G6Flow/components/Tooltip/Tooltip.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
.g6-tooltip {
  border-radius: 6px;
  font-size: 12px;
  color: #fff;
  background-color: #000;
  padding: 2px 8px;
  text-align: center;
}
PipeLineLems/web/src/components/G6Flow/components/Tooltip/Tooltip.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
import G6 from '@antv/g6'
import './Tooltip.scss'
export default () =>
  new G6.Tooltip({
    offsetX: 35,
    offsetY: 30,
    itemTypes: ['node'],
    getContent: (e: any) => {
      return `<span>${e?.item.getModel().name}</span>`
    },
  })
PipeLineLems/web/src/components/G6Flow/core/Core.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,172 @@
import { NODES, TYPE, ConditionType } from './enum'
import { Create } from '@/libs/Create/Create'
import { v4 as uuidv4 } from 'uuid'
import { fontSize, nodeFontSize } from '../components/Nodes'
interface NodeItem {
  type: TYPE.NODE | TYPE.EDGE
  label: string
}
interface PointType {
  x: number
  y: number
  properties: any
}
export default class Core {
  createNode({ label, type }: NodeItem, { x, y, properties }: PointType) {
    const id = uuidv4()
    return new Create({
      id,
      type,
      label,
      x,
      y,
      properties,
      Name: label,
      name: label,
    })
  }
  setDefaultProps() {
    return {
      fitCenter: true,
      animate: true,
      minZoom: 0.2,
      maxZoom: 1.4,
      defaultNode: {
        type: NODES.ACTIVITIES,
        labelCfg: {
          style: {
            fill: '#333',
            fontSize: nodeFontSize,
            textAlign: 'center',
            textBaseline: 'middle',
            fontWeight: 'bold',
          },
        },
      },
      defaultEdge: {
        type: 'polyline',
        labelCfg: {
          // refX: 10,
          // refY: -5,
          style: {
            fill: '#333',
            stroke: '#aaa',
            fontSize: fontSize - 2,
          },
        },
        style: {
          radius: 20,
          offset: 45,
          endArrow: true,
          lineWidth: 3,
          stroke: '#aaa',
          // router: true,
        },
      },
      edgeStateStyles: {
        active: {
          stroke: '#5a84ff',
          lineWidth: 6,
          'text-shape': {
            fontSize: fontSize,
          },
        },
        hover: {
          stroke: '#5a84ff',
          lineWidth: 6,
          'text-shape': {
            fontSize: fontSize,
          },
        },
      },
      nodeStateStyles: {
        hover: {
          stroke: '#d9d9d9',
          fill: '#8ca9ff',
          'text-shape': {
            fill: '#fff',
          },
        },
        active: {
          stroke: 'rgb(104 53 255 / 50%)',
          shadowColor: '#5b8ff9',
          shadowBlur: 7,
          'text-shape': {
            fill: '#333',
          },
        },
        selected: {
          stroke: 'rgb(104 53 255 / 50%)',
          fill: '#8ca9ff',
          'text-shape': {
            fill: '#fff',
          },
        },
      },
    }
  }
  static createRoot() {
    return new Create({
      id: '启动',
      type: NODES.ACTIVITIES,
      isRoot: true,
      text: {
        x: 0,
        y: 0,
      },
      label: '启动',
      properties: {
        Alias: '启动',
        Name: '启动',
        Description: '',
        EnterMode: '',
        ExitMode: '',
        JoinType: '',
        SplitType: '',
        ExtendedProperty: '',
        [ConditionType]: '',
        type: NODES.ACTIVITIES,
        id: '启动',
        name: '启动',
        label: '启动',
      },
      isEdit: true,
    })
  }
  static createBaseXml(name?: string) {
    name = name || ''
    return `<ProcessflowDefine Version="1">
      <Define>
        <ProcessflowBuilder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
          <Parameters></Parameters>
          <Name>${name}</Name>
          <Description></Description>
          <Root xsi:type="">
            <Alias>启动</Alias>
            <Name>启动</Name>
            <Description></Description>
            <EnterMode></EnterMode>
            <ExitMode></ExitMode>
            <JoinType></JoinType>
            <SplitType></SplitType>
            <ExtendedProperty></ExtendedProperty>
          </Root>
          <Activities></Activities>
          <Transitions></Transitions>
        </ProcessflowBuilder>
      </Define>
      <Appearance>
        <ProcessflowAppearance>
          <启动>
            <x>74.55000000000001</x>
            <y>56.25</y>
          </启动>
        </ProcessflowAppearance>
      </Appearance>
    </ProcessflowDefine>`
  }
}
PipeLineLems/web/src/components/G6Flow/core/GraphEvent.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,235 @@
import G6, { Edge, Graph } from '@antv/g6'
import {} from './store'
import { onUnmounted, ref } from 'vue'
import { cloneDeep } from 'lodash'
import { fontSize } from '../components/Nodes'
import type { Store, StoreKey } from '../type/index'
import { ConditionType } from './enum'
export default class GraphEvent {
  store: Store
  graph: Graph | null = null
  position = ref<{ x: number; y: number }>({ x: 0, y: 0 })
  visible = ref<boolean>(false)
  isShow = ref<boolean>(false)
  model = ref<any>(null)
  item = ref<any>(null)
  type = ref<string>('')
  isContextMenu = ref<boolean>(false)
  layout = {}
  isChange = false
  touchTime = 0
  touchMoveTime = 0
  currentTouchTime = 0
  touchEvent = {} as any
  currentPosition: null | {
    x: number
    y: number
    controlPointsMap?: Record<
      string | number,
      Array<{
        x: number
        y: number
      }>
    >
  } = null
  constructor(store: Store) {
    this.store = store
    this.nodeContextMenu = this.nodeContextMenu.bind(this)
    this.edgeContextMenu = this.edgeContextMenu.bind(this)
    this.edgeMouseOver = this.edgeMouseOver.bind(this)
    this.edgeClick = this.edgeClick.bind(this)
    this.onNodeMouseMove = this.onNodeMouseMove.bind(this)
    this.onNodeTouchStart = this.onNodeTouchStart.bind(this)
    this.onNodeTouchEnd = this.onNodeTouchEnd.bind(this)
    this.onNodeTouchmove = this.onNodeTouchmove.bind(this)
    this.cancelEvent = this.cancelEvent.bind(this)
    this.onAfterCreateEdge = this.onAfterCreateEdge.bind(this)
    this.onCanvasContextMenu = this.onCanvasContextMenu.bind(this)
    document.addEventListener('touchstart', this.cancelEvent, true)
    document.addEventListener(
      'click',
      (...arg) => this.cancelEvent(...arg, 'click'),
      true
    )
  }
  cancelEvent(e?: Event, type?: string) {
    this.touchMoveTime = 0
    if (!type) {
      const t = setTimeout(() => {
        this.isShow.value = false
        clearTimeout(t)
      }, 200)
    } else {
      this.isShow.value = false
    }
  }
  onUnmounted() {
    document.removeEventListener('touchstart', this.cancelEvent, true)
    document.removeEventListener('click', this.cancelEvent, true)
  }
  init(graph: Graph, layout: Record<string, any>) {
    this.touchMoveTime = 0
    this.touchTime = 0
    this.currentTouchTime = 0
    this.graph = graph
    this.layout = layout
    this.graph.on('canvas:contextmenu', this.onCanvasContextMenu)
    this.graph.on('canvas:touchmove', this.onNodeTouchmove)
    this.graph.on('node:touchstart', this.onNodeTouchStart)
    this.graph.on('edge:touchstart', this.onNodeTouchStart)
    this.graph.on('touchend', this.onNodeTouchEnd)
    this.graph.on('node:contextmenu', this.nodeContextMenu)
    this.graph.on('edge:contextmenu', this.edgeContextMenu)
    this.graph.on('node:mouseenter', (...arg) => {
      this.currentPosition = null
      this.nodeMouseOver(...arg, true)
    })
    this.graph.on('node:mouseleave', (...arg) =>
      this.nodeMouseOver(...arg, false)
    )
    this.graph.on('edge:mouseenter', (...arg) =>
      this.edgeMouseOver(...arg, true)
    )
    this.graph.on('edge:mouseleave', (...arg) =>
      this.edgeMouseOver(...arg, false)
    )
    this.graph.on('edge:click', (...arg) => this.edgeClick(...arg))
    this.graph.on('aftercreateedge', this.onAfterCreateEdge)
  }
  onCanvasContextMenu(event: Event) {
    event?.preventDefault()
  }
  onAfterCreateEdge({ edge }: { edge: Edge }) {
    const model: any = edge.getModel()
    if (!model.properties) {
      model.properties = {}
    }
    if (!model.properties[ConditionType]) {
      model.properties[ConditionType] = this.store.selected.value
    }
    edge.update(model)
  }
  onNodeTouchmove(event: TouchEvent) {
    this.touchTime = 0
    this.touchMoveTime++
    if (this.touchMoveTime < 20) {
      this.touchTime = this.currentTouchTime
    }
  }
  onNodeTouchEnd(event: Event | any) {
    event.stopPropagation()
    if (this.touchTime !== 0) {
      const now = Date.now()
      const time = now - this.touchTime
      if (time > 300) {
        this.nodeContextMenu(this.touchEvent)
      }
    }
    this.touchTime = 0
    this.currentTouchTime = 0
  }
  onNodeTouchStart(event: Event | any) {
    this.touchEvent = event
    this.touchTime = Date.now()
    this.currentTouchTime = Date.now()
    event.stopPropagation()
  }
  contextMenuSetting(event: any | Event) {
    event?.preventDefault()
    this.model.value = event.item?.getModel()
    this.position.value = {
      x: event.clientX,
      y: event.clientY,
    }
    this.visible.value = true
    this.isShow.value = true
    this.item.value = event.item
    this.type.value = event.item?._cfg.type
  }
  nodeContextMenu(event: any | Event) {
    this.item.value = event.item
    this.contextMenuSetting(event)
  }
  edgeContextMenu(event: any | Event) {
    this.item.value = event.item
    this.isContextMenu.value = true
    this.graph?.setItemState(event.item, 'hover', true)
    this.contextMenuSetting(event)
  }
  edgeMouseOver(event: any | Event, type: boolean) {
    const { item } = event
    this.item.value = event.item
    if (!this.isContextMenu.value) {
      const model = item.getModel() || {}
      const style = model.style || {}
      if (!style['text-shape']) {
        style['text-shape'] = {
          fontSize: fontSize,
        }
      }
      this.graph?.setItemState(item, 'hover', type)
    }
  }
  nodeMouseOver(event: any | Event, type: boolean) {
    this.graph?.setItemState(event.item, 'active', type)
  }
  edgeClick(event: any | Event) {
    const { item } = event
    this.item.value = event.item
    this.graph?.setItemState(item, 'selected', true)
  }
  clearAllState() {
    // æ¸…除 'active' ä¸Ž 'hover' çŠ¶æ€
    this.isContextMenu.value = false
    this.item.value?.clearStates(['actived', 'hover', 'selected'])
    this.item.value = null
  }
  onNodeMouseMove(nodeData: any) {
    if (nodeData.name !== 'drag' && !this.currentPosition) {
      const model = nodeData.item.getModel() || {}
      return (this.currentPosition = {
        x: model.x,
        y: model.y,
      })
    }
    if (this.currentPosition && nodeData.name === 'drag') {
      const edges = nodeData.item.getEdges()
      const model = nodeData.item.getModel() || {}
      const { x, y } = model
      const diffX = x - this.currentPosition.x
      const diffY = y - this.currentPosition.y
      edges.forEach((edge: any, index: number) => {
        const edgeModel = cloneDeep(edge.getModel() || {})
        if (
          this.currentPosition &&
          !this.currentPosition?.controlPointsMap?.[index]
        ) {
          const controlPointsMap = this.currentPosition.controlPointsMap || {}
          controlPointsMap[index] = edgeModel.controlPoints || []
          this.currentPosition.controlPointsMap = controlPointsMap
        }
        const controlPoints = cloneDeep(
          this.currentPosition?.controlPointsMap?.[index] || []
        )
        controlPoints.forEach((point: { x: number; y: number }) => {
          point.x = point.x + diffX
          point.y = point.y + diffY
        })
        edgeModel.controlPoints = controlPoints
        this.graph?.updateItem(edge, edgeModel)
        // edgeModel.controlPoints = []
      })
    }
    // this.graph?.refreshPositions()
    // console.log(edges, this.currentPosition, 'node:mousedown1-2-2-2-11-2210210')
  }
}
PipeLineLems/web/src/components/G6Flow/core/behavior.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
export const behaviorMap: {
  [key: string]: string
} = {
  dragCanvas: 'drag-canvas',
  dragNode: 'drag-node',
  zoomCanvas: 'zoom-canvas',
  clickSelect: 'click-select',
  createEdge: 'create-edge',
}
PipeLineLems/web/src/components/G6Flow/core/enum.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,77 @@
export const CURVE = 'curve'
export const NODES = {
  ACTIVITIES: 'Activities',
  ACTIVITY: 'StartActivity',
  VARIABLE_MONITOR_ACTIVITY: 'VariableMonitorActivity',
  VARIABLE_READ_ACTIVITY: 'VariableReadActivity',
  MATERIAL_ASSOCIATION_ACTIVITY: 'MaterialAssociationActivity',
  TRAY_ASSOCIATION_ACTIVITY: '"TrayAssociationActivity"',
  VARIABLE_WRITE_ACTIVITY: 'VariableWriteActivity',
  INBOUND_INITIALIZE_ACTIVITY: 'InboundInitializeActivity',
  DETERMINE_PROCESS_ACTIVITY: 'DetermineProcessActivity',
  BUSINESS_ACTIVITY: 'BusinessActivity',
  PRODUCT_STATEDETECTION_ACTIVITY: 'ProductStateDetectionActivity',
  DUPLICATE_CODE_DETECTION_ACTIVITY: 'DuplicateCodeDetectionActivity',
  MISSING_PROCESS_DETECTION_ACTIVITY: 'MissingProcessDetectionActivity',
  OUTBOUND_INITIALIZE_ACTIVITY: 'OutboundInitializeActivity',
  PARAMETER_COLLECT_ACTIVITY: 'ParameterCollectActivity',
  PLC_QUALIFICATION_JUDGMENT_ACTIVITY: 'PLCQualificationJudgmentActivity',
  LOCAL_QUALIFICATION_JUDGMENT_ACTIVITY: 'LocalQualificationJudgmentActivity',
  PARAMETER_SAVE_ACTIVITY: 'ParameterSaveActivity',
  END_ACTIVITY: 'EndActivity',
  ORDINARY_NODE: 'OrdinaryNode', //普通节点
}
export const ActivityKey = [
  'Alias',
  'Name',
  'EnterMode',
  'ExitMode',
  'JoinType',
  'SplitType',
  'NeedTagChange',
  'TaskDelay',
  'MonitorVariableName',
  'CompareVariableValue',
  'ExtendedProperty',
  '@_xsi:type',
]
export const TransitionKey = ['Label', 'Condition', 'Sink', 'Source']
export const ConditionKey = [
  'Label',
  'Expression',
  'NOT',
  'Operator',
  'Property',
  'Value',
  '@_xsi:type',
]
export const ConditionType = '@_xsi:type'
export const ValueText = '#text'
export enum TYPE {
  NODE = 'node',
  EDGE = 'edge',
}
/**
 * å¤åˆç±»åž‹
 */
export const CompositeCondition = 'CompositeCondition'
export interface ConditionItemType {
  [ConditionType]: string
}
export enum OpType {
  RelOpEqual = '=',
  RelOpLess = '<',
  RelOpLarge = '>',
  RelOpLessEq = '<=',
  RelOpLargeEq = '>=',
  RelOpNotEqual = '!=',
  RelOpContain = 'Contains',
}
PipeLineLems/web/src/components/G6Flow/core/store.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
import { provide, inject, ref, isRef, Ref, reactive, toRaw } from 'vue'
import mitt from 'mitt'
import type { Store, StoreKey } from '../type/index'
import GraphEvent from './GraphEvent'
const selected = ref(null)
const lf = ref()
const theme = ref({})
const lastLines = ref<any[]>([])
/**
 * Name,value
 */
const flowMap: Map<string, any> = new Map()
/**
 * id,value
 */
const flowNodeMap: Map<string, any> = new Map()
const xmlData = ref(null)
/**
 * ä¿å­˜æ—¶é…ç½®
 */
const flowBaseConfig = ref<{
  type?: number
  version?: number
  content?: string
}>({})
const edgeMap = new Map()
const nodeMap = new Map()
const flowConfig: {
  type?: number
  version?: number
} = {}
export const baseStore: any = {
  lf,
  theme,
  xmlData,
  flowNodeMap,
  edgeMap,
  flowMap,
  selected,
  lastLines,
  flowBaseConfig,
  nodeMap,
  flowConfig,
}
const graphEvent = new GraphEvent(baseStore)
const store = {
  ...baseStore,
  graphEvent,
} as Store
export const resetStore = () => {
  flowMap.clear()
  flowNodeMap.clear()
  flowBaseConfig.value = {}
  xmlData.value = null
  lastLines.value = []
  edgeMap.clear()
  nodeMap.clear()
}
export const createStore = () => {
  return store
}
export const injectStore = (key?: StoreKey) => {
  if (key) {
    return store[key]
  }
  return store as Store
}
export const emitter = mitt()
PipeLineLems/web/src/components/G6Flow/core/transformHelp.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,527 @@
import { XMLParser, XMLBuilder } from 'fast-xml-parser'
import {
  CURVE,
  NODES,
  ActivityKey,
  TransitionKey,
  ConditionKey,
  ConditionType,
  ValueText,
} from './enum'
import { v4 as uuidv4 } from 'uuid'
import { injectStore, resetStore } from './store'
import { Condition, FlowType } from '../type'
import { cloneDeep, isNil, set, get, isNull, omitBy } from 'lodash'
import { nodeFontSize } from '../components/Nodes'
import { _t } from '@/libs/Language/Language'
const { flowMap, xmlData, flowNodeMap } = injectStore()
import G6 from '@antv/g6'
import Core from './Core'
import { ElMessage } from 'element-plus'
export const isFirstLetterUpperCase = (str: string) => {
  // ä½¿ç”¨æ­£åˆ™è¡¨è¾¾å¼æ£€æŸ¥é¦–字母是否为大写
  return /^[A-Z]/.test(str) || str === ConditionType
}
export const fittingString = (
  str: string,
  maxWidth: number,
  fontSize: number
) => {
  const ellipsis = '...'
  const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0]
  let currentWidth = 0
  let res = str
  const pattern = new RegExp('[\u4E00-\u9FA5]+') // distinguish the Chinese charactors and letters
  str.split('').forEach((letter, i) => {
    if (currentWidth > maxWidth - ellipsisLength) return
    if (pattern.test(letter)) {
      // Chinese charactors
      currentWidth += fontSize
    } else {
      // get the width of single letter according to the fontSize
      currentWidth += G6.Util.getLetterWidth(letter, fontSize)
    }
    if (currentWidth > maxWidth - ellipsisLength) {
      res = `${str.substr(0, i)}${ellipsis}`
    }
  })
  return res
}
/**
 * èŽ·å–xml字符串的json对象
 * @param xml
 * @returns
 */
export const getJsonByXml = (xml: string, name?: string) => {
  const options = {
    ignoreAttributes: false,
    attributeNamePrefix: '@_',
  }
  const parser = new XMLParser(options)
  try {
    const json = parser.parse(xml)
    xmlData.value = json
    return json
  } catch (error) {
    const newXml = Core.createBaseXml(name)
    const json = parser.parse(newXml)
    xmlData.value = json
    return json
  }
}
/**
 * è½¬æ¢æµç¨‹å›¾æ•°æ®ä¸ºxml
 * @param flowData
 */
export const getFlowDataToXml = ({ nodes, edges }: FlowType) => {
  const Activity: any[] = []
  const Transition: any[] = []
  const nodeMap: Record<string, any> = {}
  nodes.forEach((item) => {
    const obj: Record<string, any> = {}
    Object.keys(item.properties || {}).forEach((key) => {
      if (isFirstLetterUpperCase(key)) {
        obj[key] = item.properties[key]
      }
    })
    const oldId = item?.properties?.oldId
    const flow = flowMap.get(oldId)
    obj.Name = flow?.properties?.Name || item.properties.Name || ''
    nodeMap[obj.Name] = {
      x: item.x,
      y: item.y,
    }
    Activity.push(obj)
  })
  edges.forEach((item) => {
    const obj: Record<string, any> = {}
    Object.keys(item?.properties || {}).forEach((key) => {
      if (isFirstLetterUpperCase(key)) {
        obj[key] = item.properties[key]
      }
      const condition = item.properties?.Condition || {}
      Object.keys(condition || {}).forEach((key) => {
        obj.Condition = obj.Condition || {}
        if (isFirstLetterUpperCase(key)) {
          obj.Condition[key] = condition[key]
        }
      })
    })
    obj.Condition = handleConditionChildren(obj.Condition)
    if (!Object.keys(obj.Condition).length) {
      delete obj.Condition
    }
    const oldSourceId = item.properties?.oldSourceId
    const oldTargetId = item.properties?.oldTargetId
    const flowSource = flowMap.get(oldSourceId)
    const flowTarget = flowMap.get(oldTargetId)
    if (oldSourceId && oldTargetId) {
      obj.Source = flowSource?.properties?.Name || ''
      obj.Sink = flowTarget?.properties?.Name || ''
    } else {
      const sourceName = flowNodeMap.get(item?.source)?.name
      const sinkName = flowNodeMap.get(item?.target)?.name
      obj.Source = sourceName
      obj.Sink = sinkName
    }
    nodeMap[`${obj.Source}_${obj.Sink}`] = {
      controlPoints: item.controlPoints,
      endPoint: item.endPoint,
      startPoint: item.startPoint,
    }
    Transition.push(obj)
  })
  // æµç¨‹å›¾ç»“æž„
  return getXmlByJson(Activity, Transition, nodeMap)
}
/**
 * è½¬æ¢å¯¹è±¡ï¼Œå°å†™å’Œå¤§å†™çš„分流
 */
export const translationObj = (obj: Record<string, any>) => {
  const up: Record<string, any> = {}
  const low: Record<string, any> = {}
  if (isNil(obj)) return null
  Object.entries(obj).forEach(([key, value]) => {
    if (isFirstLetterUpperCase(key)) {
      up[key] = value
    } else {
      low[key] = value
    }
  })
  return {
    up,
    low,
  }
}
/**
 * å¤„理复杂条件的子集
 */
const handleConditionChildren = (condition: Condition) => {
  const newCondition = cloneDeep(condition)
  const deepConditionFn = (condition: Condition) => {
    let conditionObj: any = {}
    const obj = translationObj(condition)
    if (Array.isArray(condition?.children) && condition?.children?.length > 0) {
      const Condition: any[] = []
      condition?.children.forEach((item) => {
        Condition.push(deepConditionFn(item))
      })
      conditionObj = {
        Conditions: {
          Condition,
        },
      }
    }
    if (obj) {
      Object.assign(conditionObj, obj.up)
    }
    return conditionObj
  }
  return deepConditionFn(newCondition)
}
/**
 * å¤„理Name为Id的情况,为可改动
 * æ•°æ®è½¬æ¢ï¼Œå°†Id换成UUID
 */
export const transformFlowStructByNodeId = ({
  nodes,
  edges,
}: {
  nodes: any[]
  edges: any[]
}) => {
  const newNodes = nodes.map((item) => {
    const { id } = item.properties
    const uuid = uuidv4()
    const node = {
      ...item,
      id: uuid,
      name: id,
      type: NODES.ACTIVITIES,
      properties: {
        ...item.properties,
        name: id,
        id: uuid,
        oldId: id,
      },
    }
    flowMap.set(id, node)
    flowNodeMap.set(uuid, node)
    return node
  })
  const newEdges = edges.map((item) => {
    const { sourceNodeId, targetNodeId } = item
    const id = uuidv4()
    return {
      ...item,
      id,
      label: item.label || item.properties?.Label,
      type: 'polyline',
      targetNodeId: flowMap.get(targetNodeId)?.id || '',
      target: flowMap.get(targetNodeId)?.id || '',
      source: flowMap.get(sourceNodeId)?.id || '',
      sourceNodeId: flowMap.get(sourceNodeId)?.id || '',
      properties: {
        ...item.properties,
      },
      controlPoints:
        item.controlPoints && !Array.isArray(item.controlPoints)
          ? [item.controlPoints]
          : item.controlPoints,
    }
  })
  return {
    nodes: newNodes,
    edges: newEdges,
  }
}
/**
 * èŽ·å–å¹¶è§£æžæµç¨‹å›¾ç»“æž„
 * @param data
 * @returns
 */
export const getLogicFLowStruct = (data: Record<string, any>) => {
  try {
    if (Object.keys(data).length === 0) return null
    // è¾¹
    let Transition =
      data?.ProcessflowDefine?.Define?.ProcessflowBuilder?.Transitions
        .Transition || []
    // èŠ‚ç‚¹
    let Activities =
      data?.ProcessflowDefine?.Define?.ProcessflowBuilder?.Activities
        .Activity || []
    Activities = Array.isArray(Activities) ? Activities : [Activities]
    Transition = Array.isArray(Transition) ? Transition : [Transition]
    const nodeMap =
      data?.ProcessflowDefine?.Appearance.ProcessflowAppearance || {}
    const Root = data?.ProcessflowDefine.Define.ProcessflowBuilder.Root
    const nodes = Activities.map((item: Record<string, any>) => {
      const type = item[ConditionType]
      const isEnd = type === NODES.END_ACTIVITY
      const { x, y } = nodeMap[item.Name] || {}
      return {
        id: item.Name,
        type: isEnd ? NODES.END_ACTIVITY : NODES.ACTIVITIES,
        x: x || item.x || 0,
        y: y || item.y || 0,
        text: {
          x: item.x || 0,
          y: item.y || 0,
        },
        label: fittingString(item.Name, 195, nodeFontSize + 2),
        properties: {
          ...item,
          name: item.Alias,
          type: item[ConditionType],
          id: item.Name,
          label: item.Name,
        },
      }
    })
    if (Root) {
      const { x, y } = nodeMap[Root.Name] || {}
      nodes.unshift({
        id: Root.Name,
        type: NODES.ACTIVITIES,
        x: x || 0,
        y: y || 0,
        isRoot: true,
        text: {
          x: 0,
          y: 0,
        },
        label: fittingString(Root.Name, 195, nodeFontSize + 2),
        properties: {
          ...Root,
          type: Root[ConditionType],
          id: Root.Name,
          name: Root.Alias,
          label: Root.Name,
        },
      })
      nodes[0].isEdit = !!(x + y)
    }
    // åˆ¤æ–­æ˜¯å¦éœ€è¦è‡ªå®šä¹‰å¸ƒå±€
    const edges = Transition.map((item: Record<string, any>, index: number) => {
      const Condition = item?.Condition
        ? handlerConditionToChildren(item?.Condition)
        : {}
      const edgeItem = nodeMap[`${item.Source}_${item.Sink}`] || {}
      return {
        id: index + 1,
        type: CURVE,
        targetNodeId: item.Sink,
        sourceNodeId: item.Source,
        // text: {
        //   x: 0,
        //   y: 0,
        //   value: item?.Condition?.Label,
        // },
        label: item?.Condition?.Label || '',
        properties: {
          ...item,
          Condition,
          oldSourceId: item.Source,
          oldTargetId: item.Sink,
          name: item?.Label,
        },
        ...edgeItem,
      }
    })
    const flows = { nodes, edges }
    return transformFlowStructByNodeId(flows)
  } catch (error) {
    const data = {
      nodes: [Core.createRoot()],
      edges: [],
    }
    return transformFlowStructByNodeId(data)
  }
}
/**
 * å°†Condition处理成tree结构
 */
const handlerConditionToChildren = (c: any) => {
  const condition = cloneDeep(c)
  const getConditionTree = (condition: any) => {
    let newCondition: any = {}
    // å¤åˆæ¡ä»¶-> Condition
    if (condition?.Conditions) {
      const Condition = condition.Conditions?.Condition
      const children = []
      if (!Array.isArray(Condition) && Condition[ConditionType]) {
        children.push(Condition)
      } else {
        children.push(...Condition)
      }
      if (Array.isArray(children) && children?.length > 0) {
        const newChildren: any[] = []
        children.forEach((child) => {
          newChildren.push(getConditionTree(child))
        })
        newCondition.children = newChildren
      }
      delete condition.Conditions
    }
    // const Value =
    //   typeof condition.Value === 'object'
    //     ? condition.Value
    //     : {
    //         [ValueText]: condition.Value,
    //       }
    const currentCondition = {
      ...condition,
    }
    Object.assign(newCondition, {
      ...currentCondition,
    })
    return newCondition
  }
  return getConditionTree(condition)
}
/**
 * èŽ·å–json的xml字符串
 * @param json
 * @returns
 */
export const getXmlByJson = (
  Activity: any[],
  Transition: any[],
  ProcessflowAppearance?: any
) => {
  const json = cloneDeep(xmlData.value)
  const Root = Activity.shift()
  const flowAppearance = get(
    json,
    'ProcessflowDefine.Appearance.ProcessflowAppearance',
    {
      å¯åЍ: {},
    }
  )
  set(
    json,
    'ProcessflowDefine.Define.ProcessflowBuilder.Transitions.Transition',
    Transition
  )
  set(
    json,
    'ProcessflowDefine.Define.ProcessflowBuilder.Activities.Activity',
    Activity
  )
  set(json, 'ProcessflowDefine.Define.ProcessflowBuilder.Root', Root)
  set(json, 'ProcessflowDefine.Appearance.ProcessflowAppearance', {
    '@_xmlns:xsi': flowAppearance['@_xmlns:xsi'],
    '@_xmlns:xsd': flowAppearance['@_xmlns:xsd'],
    ...ProcessflowAppearance,
  })
  const builder = new XMLBuilder({
    ignoreAttributes: false,
    attributeNamePrefix: '@_',
    format: true,
  })
  const xml = builder.build(json)
  return xml
}
/**
 * é¦–字母小写
 * @param str
 * @returns
 */
export const toLowerCaseFirstLetter = (str: string) => {
  return str.charAt(0).toLowerCase() + str.slice(1)
}
/**
 * ç­›é€‰èŠ‚ç‚¹çš„target线条有哪些,用来开启动画
 * @param nodeId
 * @param edges
 * @returns
 */
export const getNodeTargetLines = (
  nodeId: string,
  edges: Record<string, any>[]
) => {
  const lines = edges.filter((item: Record<string, any>) => {
    return item.targetNodeId === nodeId
  })
  return lines
}
const removeNullFields: any = (data: any) => {
  if (Array.isArray(data)) {
    return data
      .map((item) => removeNullFields(item))
      .filter(
        (item) =>
          !(
            item === null ||
            (Array.isArray(item) &&
              item.length === 0 &&
              !removeNullFields(item).length) ||
            item === ''
          )
      )
  } else if (typeof data === 'object' && data !== null) {
    const newObj: any = {}
    for (const key in data) {
      const value = data[key]
      if (
        (value !== null && value !== '') ||
        (Array.isArray(value) && value.length > 0)
      ) {
        newObj[key] = removeNullFields(value)
      }
    }
    return newObj
  } else {
    return data
  }
}
/**
 * è¾“出xml
 * @param graph
 * @returns
 */
export const generateFlowXml = (graph: any) => {
  const graphData = graph.save()
  const data: any = removeNullFields(graphData)
  return getFlowDataToXml(data)
}
/**
 * è¾“出xml
 * @param graph
 * @returns
 */
export const importXmlInit = (graph: any, xml: string) => {
  try {
    resetStore()
    const json = getJsonByXml(xml)
    const graphData = getLogicFLowStruct(json)
    // graph.destroy()
    graph.data(graphData)
    graph.render()
  } catch (error) {
    ElMessage.error(_t('导入失败'))
  }
}
PipeLineLems/web/src/components/G6Flow/type/index.d.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
// export interface
import GraphEvent from '../core/GraphEvent'
export interface Store {
  lf: any
  xmlData: Ref<any>
  theme: Ref<any>
  flowMap: Map<string, any>
  selected: Ref<any>
  lastLines: Ref<any[]>
  graphEvent: GraphEvent
  flowBaseConfig: Ref<any>
  edgeMap: Map<string, any>
  nodeMap: Map<string, any>
  flowConfig: Ref<any>
}
export enum StoreKey {
  LF = 'lf',
  THEME = 'theme',
  FLOW_MAP = 'flowMap',
  SELECTED = 'selected',
  LAST_LINES = 'lastLines',
  XML_DATA = 'xmlData',
}
export interface FlowType {
  edges: any[]
  nodes: any[]
}
interface Condition {
  '@_xsi:type': string
  _xsiType: string // å¯¹åº” "_xsi:type"
  Expression: string
  Label: string
  Not: boolean
  Operator: string
  Property: string
  root: boolean
  Value: string
  // Value: {
  //   _xsiType: string // å¯¹åº” "@_xsi:type"
  //   Text: string | number // å¯¹åº” "#text"
  // }
  nodeId: string
  children: Condition[]
}
PipeLineLems/web/src/components/Icon/Icon.tsx
@@ -1,4 +1,5 @@
import { computed, defineComponent } from 'vue'
import { computed, defineComponent, ref } from 'vue'
import parse from 'style-to-object'
export default defineComponent({
  name: '图标',
  props: {
@@ -14,21 +15,54 @@
      type: Number,
      default: 12,
    },
    draggable: {
      type: Boolean,
      default: false,
    },
    cursor: {
      type: String,
      default: '',
    },
    class: {
      type: String,
      default: '',
    },
    style: {
      type: [Object, String],
      default: () => ({}),
    },
  },
  emits: ['click'],
  setup(props, { attrs, slots, emit }) {
    const VITE_STATIC_URL = process.env.VITE_STATIC_URL || ''
    const imgUrl = computed(() => {
      const imgName = props.icon
      return new URL(`../../assets/images/${imgName}.png`, import.meta.url).href
      let imgName = props.icon
      if (!imgName) return ''
      let baseDir = 'images'
      if (imgName.includes('files/')) {
        baseDir = 'files'
        imgName = imgName.split('/')[1]
      }
      return `${VITE_STATIC_URL}/resources/assets/${baseDir}/${imgName}.png`
    })
    const style = computed(() => {
      if (typeof props.style === 'string') {
        return parse(props.style)
      }
      return props.style
    })
    return () => {
      if (imgUrl.value.includes('undefined')) return null
      return (
        <img
          onClick={(evt: Event) => emit('click', evt)}
          width={props.width}
          height={props.height}
          src={imgUrl.value}
          style={{ cursor: props.cursor, ...style.value }}
          class={props.class}
          {...attrs}
        />
      )
PipeLineLems/web/src/components/IconButton/IconButton.module.scss
@@ -3,10 +3,10 @@
  border-radius: 6px 6px 6px 6px;
  padding: 5px;
  padding: 5px 10px;
  color: #464E54!important;
  color: #464e54 !important;
  margin-left: 0 !important;
  >span {
  > span {
    font-size: 14px;
    font-family: PingFang SC, PingFang SC;
    font-weight: 400;
@@ -15,17 +15,46 @@
  :global(.cs-button) {
    color: #464e54;
  }
}
.btn:hover {
  background-color: #ECECEC!important;
  background-color: transparent !important;
  opacity: 1 !important;
}
.btn:active {
  background-color: #dbdbdb!important;
  background-color: transparent !important;
}
.img {
  width: 16px;
  margin-right: 4px;
}
.status {
  &:hover {
    .img {
      filter: invert(49%) sepia(100%) saturate(6281%) hue-rotate(222deg)
        brightness(99%) contrast(99%);
      opacity: 1;
    }
    > span {
      color: #265cfb !important;
    }
  }
  color: #464e54;
}
.add {
  .iconFont {
    font-size: 19px;
    color: #5a84ff;
  }
  &:hover {
    .iconFont {
      font-size: 19px;
      color: #265cfb;
    }
    > span {
      color: #265cfb !important;
    }
  }
  color: #464e54;
}
PipeLineLems/web/src/components/IconButton/IconButton.tsx
@@ -16,20 +16,37 @@
  emits: ['click'],
  setup(props: IconButtonProps, { attrs, slots, emit }: SetupContext) {
    const imgName = computed(() => props.icon)
    const imgUrl = () =>
      new URL(`../../assets/images/${imgName.value}.png`, import.meta.url).href
    const VITE_STATIC_URL = process.env.VITE_STATIC_URL || ''
    let status = attrs.status === undefined ? true : attrs.status
    let isAdd = attrs.status === 'add'
    const imgUrl = () => {
      let baseDir = 'images'
      let name = imgName.value
      if (imgName.value) {
        if (imgName.value.includes('files/')) {
          baseDir = 'files'
          name = imgName.value.split('/')[1]
        }
      }
      return `${VITE_STATIC_URL}/resources/assets/${baseDir}/${name}.png`
    }
    const BtnRender = () => {
      return (
        <el-button
          {...attrs}
          type={props.type}
          text
          class={styles.btn}
          class={{
            [styles.btn]: true,
            [styles.status]: attrs.disabled ? false : status,
          }}
          onClick={(evt: Event) => emit('click', evt)}
        >
          {imgName.value ? <img src={imgUrl()} class={styles.img} /> : null}
          <span style={props.type === 'primary' ? { color: '#5a84ff' } : {}}>
            {slots.default && slots.default()}
            {slots.default?.()}
          </span>
        </el-button>
      )
@@ -44,14 +61,14 @@
          persistent={false}
          popper-style={{
            marginTop: '-7px',
            padding: '8px',
            padding: '10px',
          }}
          trigger="click"
          vSlots={{
            reference: BtnRender,
          }}
        >
          {slots.default && slots.default()}
          {slots.default?.()}
        </el-popover>
      )
    }
@@ -63,7 +80,7 @@
          </span>
        )
      }
      return <BtnRender />
      return BtnRender()
    }
  },
})
PipeLineLems/web/src/components/Image/Image.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
import { computed, defineComponent } from 'vue'
import parse from 'style-to-object'
export default defineComponent({
  name: '图片',
  props: {
    src: {
      type: String,
      default: '',
    },
    draggable: {
      type: Boolean,
      default: false,
    },
    cursor: {
      type: String,
      default: '',
    },
    class: {
      type: String,
      default: '',
    },
    style: {
      type: [Object, String],
      default: () => ({}),
    },
    alt: {
      type: String,
    },
  },
  emits: ['click'],
  setup(props, { attrs, slots, emit }) {
    const VITE_STATIC_URL = process.env.VITE_STATIC_URL || ''
    const imgUrl = computed(() => {
      if (props.src.includes('/')) {
        return props.src
      }
      const imgName = props.src
      if (!imgName) return ''
      return `${VITE_STATIC_URL}/resources/assets/files/${imgName}.png`
    })
    const style = computed(() => {
      if (typeof props.style === 'string') {
        return parse(props.style)
      }
      return props.style
    })
    return () => {
      if (imgUrl.value.includes('undefined')) return null
      return (
        <img
          onClick={(evt: Event) => emit('click', evt)}
          src={imgUrl.value}
          style={{ cursor: props.cursor, ...style.value }}
          class={props.class}
          alt={props.alt}
          {...attrs}
        />
      )
    }
  },
})
PipeLineLems/web/src/components/ImportProcessDialog/ImportProcessDialog.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,88 @@
.content {
  padding: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.timeItem {
  margin-bottom: 16px;
  display: flex;
  align-items: center;
  &:last-child {
    margin-bottom: 0;
  }
}
.label {
  color: #666;
  margin-right: 8px;
}
.time {
  font-size: 16px;
  color: #333;
}
.processContent {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  height: 10px;
  margin: 10px 0;
  margin-top: 20px;
}
.processItem {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  height: 20px;
  border-radius: 15px;
  background-color: #f0f0f0;
  margin: 10px 0;
  width: calc(100% - 70px);
}
.process {
  height: 20px;
  width: 0%;
  border-radius: 5px;
  background-color: #5a97ff;
  transition: width 0.3s ease-in-out;
  position: relative;
  border-radius: 15px;
  overflow: hidden;
  &::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: linear-gradient(
      90deg,
      rgba(255, 255, 255, 0) 0%,
      rgba(255, 255, 255, 0.3) 50%,
      rgba(255, 255, 255, 0) 100%
    );
    animation: shimmer 1.5s infinite;
  }
}
@keyframes shimmer {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(100%);
  }
}
.processTitle {
  font-size: 14px;
  color: #333;
}
PipeLineLems/web/src/components/ImportProcessDialog/ImportProcessDialog.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,229 @@
import { computed, defineComponent, Fragment, ref } from 'vue'
import styles from './ImportProcessDialog.module.scss'
import BaseDialog from '@/components/BaseDialog/BaseDialog'
import { ElMessage } from 'element-plus'
import { _t, Language } from '@/libs/Language/Language'
import IconButton from '../IconButton/IconButton'
import { Socket } from '@/libs/Socket/index'
import dayjs from 'dayjs'
import { ConfirmBox } from '@/components/ConfirmBox/ConfirmBox'
const ReceiveProgress = 'ReceiveProgress'
const Cancel = 'Cancel'
export default defineComponent({
  name: 'ImportProcessDialog',
  props: {
    visible: {
      type: Boolean,
      default: false,
    },
    elapsedTime: {
      type: String,
      default: '00:00:00',
    },
    remainingTime: {
      type: String,
      default: '00:00:00',
    },
    IsInspectionPointTask: {
      type: Boolean,
      default: false,
    },
  },
  setup(props, { emit }) {
    const timeText = computed(() => _t('计算中...'))
    const visible = ref(false)
    const connectionId = ref('')
    const dtoJson = ref('')
    const progress = ref(0)
    const remainingTime = ref('00:00:00')
    const elapsedTime = ref(timeText.value)
    const clearTime = ref(null)
    const startTime = ref<dayjs.Dayjs>()
    const isCancel = ref(false)
    let socket: Socket | null = null
    const handleClose = () => {
      visible.value = false
      clearTime.value && clearTime.value()
    }
    /**
     * å¤´éƒ¨é…ç½®
     */
    const headers = computed(() => {
      return {
        Authorization: `Bearer ${sessionStorage.getItem('Token')}`,
        'X-Project': sessionStorage.getItem('X-Project'),
        'Accept-Language': Language.getLangReqHeader(),
      }
    })
    /**
     * ä¸Šä¼ æˆåŠŸ
     */
    const onSuccess = () => {
      if (!isCancel.value) {
        ElMessage.success(_t('导入成功'))
        handleClose()
      }
    }
    /**
     * å¤±è´¥
     * @param err
     */
    const onError = (err: any) => {
      try {
        const message = JSON.parse(err.message)
        ElMessage.error(message.msg)
      } catch (error) {
        ElMessage.error(_t('导入失败'))
      }
      handleClose()
    }
    const initSocket = () => {
      return new Promise((resolve, reject) => {
        socket = new Socket({
          url: '/hubs/v1/traceImportProgress',
          name: '追溯导入进度',
        })
        socket?.connection?.start().then((data: any) => {
          if (socket) {
            connectionId.value = socket!.connection!.connectionId
            dtoJson.value = JSON.stringify({
              key: connectionId.value,
              IsInspectionPointTask: props.IsInspectionPointTask,
            })
            socket.on(ReceiveProgress, onReceiveProgress)
            resolve(true)
          } else {
            reject(false)
          }
        })
      })
    }
    const onReceiveProgress = (data: any) => {
      progress.value = data
      if (progress.value === 0) {
        remainingTime.value = timeText.value
      } else if (progress.value > 0 && progress.value < 100) {
        const elapsed = dayjs().diff(startTime.value, 'second')
        const totalEstimatedSeconds = Math.ceil(
          (elapsed * 100) / progress.value
        )
        const remainingSeconds = Math.max(totalEstimatedSeconds - elapsed, 0)
        remainingTime.value = dayjs()
          .startOf('day')
          .add(remainingSeconds, 'second')
          .format('HH:mm:ss')
      } else if (progress.value >= 100) {
        remainingTime.value = '00:00:00'
        clearTime.value && clearTime.value()
      }
    }
    /**
     * ä¸Šä¼ é’©å­
     */
    const onBeforeUpload = async (file: File) => {
      const result = await initSocket()
      if (!result) {
        return false
      }
      const format = ['xlsx', 'xls', 'csv']
      if (!format.includes(file.name.split('.')[1])) {
        ElMessage.error(
          _t('导入文件格式不正确,请导入.xlsx/.xls与.csv格式的文件')
        )
        return false
      }
      visible.value = true
      clearTime.value = updateTime()
      return true
    }
    const updateTime = () => {
      startTime.value = dayjs()
      const t = setInterval(() => {
        elapsedTime.value = dayjs()
          .startOf('day')
          .add(dayjs().diff(startTime.value, 'second'), 'second')
          .format('HH:mm:ss')
      }, 1000)
      return () => clearInterval(t)
    }
    const onOpen = () => {
      elapsedTime.value = '00:00:00'
      remainingTime.value = timeText.value
      progress.value = 0
    }
    const onCancelImport = async () => {
      try {
        await ConfirmBox(_t('是否取消导入'))
        isCancel.value = true
        socket?.call(Cancel, connectionId.value).then((res: any) => {
          visible.value = false
          clearTime.value && clearTime.value()
          elapsedTime.value = '00:00:00'
          remainingTime.value = timeText.value
          ElMessage.success(_t('取消导入成功'))
        })
      } catch (error) {
        isCancel.value = false
      }
    }
    return () => (
      <Fragment>
        <el-upload
          name="file"
          accept=".xlsx,.xls,.csv"
          show-file-list={false}
          data={{ dtoJson: dtoJson.value }}
          onError={onError}
          onSuccess={onSuccess}
          before-upload={onBeforeUpload}
          headers={headers.value}
          action="/api/v1/tracemanagement/trace/import"
        >
          <IconButton icon="in"></IconButton>
        </el-upload>
        <BaseDialog
          v-model={visible.value}
          title={_t('导入进度')}
          onOpen={onOpen}
          onClose={handleClose}
          hideClose={true}
          showFooter={false}
          v-slots={{
            footer: () => (
              <el-button onClick={onCancelImport}>{_t('取消导入')}</el-button>
            ),
          }}
        >
          <div class={styles.processContent}>
            <div class={styles.processTitle}>{_t('导入进度')}</div>
            <div class={styles.processItem}>
              <div
                class={styles.process}
                style={{ width: `${progress.value}%` }}
              ></div>
            </div>
          </div>
          <div class={styles.content}>
            <div class={styles.timeItem}>
              <span class={styles.label}>{_t('已用时间')}</span>
              <span class={styles.time}>{elapsedTime.value}</span>
            </div>
            <div class={styles.timeItem}>
              <span class={styles.label}>{_t('剩余时间')}</span>
              <span class={styles.time}>{remainingTime.value}</span>
            </div>
          </div>
        </BaseDialog>
      </Fragment>
    )
  },
})
PipeLineLems/web/src/components/Input/Input.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
import { defineComponent } from 'vue'
export default defineComponent({
  name: 'DyFormInput',
  setup(props, { attrs, slots }) {
    return () => <el-input {...props} {...attrs} />
  },
})
PipeLineLems/web/src/components/LabelDialog/LabelDialog.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
.container {
  height: 560px;
}
.mainTable {
  height: calc(100% - 90px);
}
.tools {
  margin-top: 15px;
  display: flex;
  width: 270px;
  align-items: center;
  .name {
    margin-right: 10px;
  }
}
.box {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
PipeLineLems/web/src/components/LabelDialog/LabelDialog.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,129 @@
import { defineComponent } from 'vue'
import BaseDialog from '@/components/BaseDialog/index.vue'
import { useSelectDialog } from './hook'
import styles from './LabelDialog.module.scss'
import Search from '@/components/Search/Search'
import BaseTable from '@/components/Table/Table'
import { _t } from '@/libs/Language/Language'
import Text from '../Text/Text'
export default defineComponent({
  name: 'LabelDialog',
  props: {
    modelValue: {
      type: Boolean,
      default: false,
    },
    title: {
      type: String,
      default: '',
    },
    data: {
      type: Array,
      default: () => [],
    },
    printerName: {
      type: String,
    },
    radio: {
      type: Boolean,
      default: false,
    },
  },
  emits: ['update:modelValue', 'close', 'confirm'],
  setup(props, ctx) {
    const {
      onClose,
      onConfirm,
      onCheck,
      onOpen,
      onSearch,
      printName,
      printVisible,
      url,
      visible,
      innerValue,
      tableRef,
      dataSource,
      columns,
      selections,
      onPreview,
    } = useSelectDialog(props, ctx)
    return () => (
      <BaseDialog
        destroy-on-close
        class={styles.drawer}
        style="background: #fff"
        width="664px"
        height="578px"
        title={_t('标签管理')}
        v-model={visible.value}
        onClose={onClose}
        onConfirm={onConfirm}
        onOpen={onOpen}
      >
        <div class={styles.container}>
          <BaseDialog
            title={printName.value}
            v-model={printVisible.value}
            width="664px"
            height="500px"
            onClose={() => (printVisible.value = false)}
            onConfirm={() => (printVisible.value = false)}
          >
            <el-image
              style="width: 100%;"
              src={url.value}
              zoom-rate={1.2}
              max-scale={7}
              min-scale={0.2}
              preview-src-list={[url.value]}
              fit="cover"
            />
          </BaseDialog>
          <div class={styles.tools}>
            <span class={styles.name}>{_t('查询')}</span>
            <Search v-model={innerValue.value} onConfirm={onSearch} />
          </div>
          <div class={styles.mainTable}>
            <BaseTable
              params={{
                // Filter: innerValue.value,
                printerName: props.printerName,
              }}
              selections={selections.value}
              pageSize={50}
              ref={tableRef}
              url="/api/v1/labelmanagement/label/template"
              style="margin-top:10px"
              v-model:dataSource={dataSource.value}
              columns={columns.value}
              isChecked={true}
              isHidePagination={true}
              id="templateName"
              onCheck={onCheck}
              radio={props.radio}
              v-slots={{
                templateName: ({ row }) => {
                  return (
                    <div class={styles.box}>
                      <Text tip={row.templateName} truncated={true}>
                        {row.templateName}
                      </Text>
                      <el-button
                        type="primary"
                        link
                        onClick={() => onPreview(row)}
                      >
                        {_t('预览')}
                      </el-button>
                    </div>
                  )
                },
              }}
            />
          </div>
        </div>
      </BaseDialog>
    )
  },
})
PipeLineLems/web/src/components/LabelDialog/hook.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,97 @@
import { ref, onMounted, reactive, computed, watch, nextTick } from 'vue'
import { _t } from '@/libs/Language/Language'
export const useSelectDialog = (props: any, ctx: any) => {
  const visible = computed({
    get() {
      return props.modelValue
    },
    set(val) {
      ctx.emit('update:modelValue', val)
    },
  })
  const url = ref('')
  const dataSource = ref<any[]>([])
  const innerValue = ref('')
  const tableRef = ref()
  const checkedList = ref<any[]>([])
  const imageRef = ref()
  const printName = ref('')
  const printVisible = ref(false)
  const columns = computed(() => [
    {
      title: _t('序号'),
      type: 'seq',
      width: '60',
    },
    {
      field: 'templateName',
      title: _t('模板名称'),
    },
    {
      field: 'remark',
      title: _t('备注'),
    },
  ])
  const onClose = () => {
    visible.value = false
  }
  const onConfirm = async () => {
    ctx.emit('confirm', checkedList.value)
  }
  const onSearch = async () => {
    const data: any[] = await tableRef.value?.getList()
    dataSource.value = data.filter((item: any) =>
      item.templateName.includes(innerValue.value)
    )
  }
  const onCheck = (list: any) => {
    checkedList.value = list
  }
  const selections = computed(() => {
    return props.data?.map((item: any) => item.templateName) ?? []
  })
  const onOpen = () => {
    // console.log(dataSource.value, selections, props.data)
  }
  const onPreview = (row: any) => {
    url.value = `/api/v1/labelmanagement/label/preview?printerName=${row.printerName}&templateName=${row.templateName}`
    printName.value = row.printerName
    printVisible.value = true
  }
  return {
    selections,
    columns,
    dataSource,
    tableRef,
    innerValue,
    visible,
    url,
    imageRef,
    printName,
    printVisible,
    onPreview,
    onSearch,
    onCheck,
    onOpen,
    onClose,
    onConfirm,
  }
}
PipeLineLems/web/src/components/LogicFlow/LogicFlow.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
.logicFlow {
  width: 100%;
  height: 100%;
  position: relative;
  .beautify {
    position: absolute;
    right: 30px;
    top: 30px;
    z-index: 1;
  }
  .xmlbeautify {
    position: absolute;
    right: 150px;
    top: 30px;
    z-index: 1;
  }
}
PipeLineLems/web/src/components/LogicFlow/LogicFlow.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,145 @@
import { defineComponent, SetupContext, onMounted, reactive, ref } from 'vue'
import styles from './LogicFlow.module.scss'
import {
  getFlowDataToXml,
  getJsonByXml,
  getLogicFLowStruct,
} from './core/transformHelp'
import { getFlowXml } from '@/api/logic-flow'
import LogicRenderer from './components/Renderer/Renderer'
import Canvas from './components/Canvas/Canvas'
import Theme from './components/Theme/Theme'
import NodeDrawer from './components/NodeDrawer/NodeDrawer'
import EdgeDrawer from './components/EdgeDrawer/EdgeDrawer'
import { injectStore } from './core/store'
import Empty from '../Empty/Empty'
import { FlowType } from './type'
interface NodeDrawerConfig {
  visible: boolean
  title: string
  node: Record<string, any>
}
export default defineComponent({
  name: 'LogicFlow',
  props: {
    flowType: {
      type: [String, Number],
      default: '',
    },
  },
  setup(props, { slots, attrs }: SetupContext) {
    const { lf } = injectStore()
    const graphData = ref<FlowType | null>(null)
    const logicRenderRef = ref<any>()
    const nodeDrawerConfig = reactive<NodeDrawerConfig>({
      visible: false,
      title: '节点配置',
      node: {},
    })
    const edgeDrawerConfig = reactive<NodeDrawerConfig>({
      visible: false,
      title: '条件配置',
      node: {},
    })
    const initData = async () => {
      const res = await getFlowXml(props.flowType)
      const json = getJsonByXml(res)
      graphData.value = getLogicFLowStruct(json)
    }
    const onAutoLayout = () => {
      logicRenderRef.value?.autoLayout()
    }
    /**
     * èŠ‚ç‚¹åŒå‡»
     * @param data
     */
    const onNodeDbClick = ({ data }: Record<string, any>) => {
      nodeDrawerConfig.visible = true
      nodeDrawerConfig.node = data
      nodeDrawerConfig.title = data.properties?.name || data.id || '节点配置'
    }
    const onEdgeDbClick = ({ data }: Record<string, any>) => {
      edgeDrawerConfig.visible = true
      edgeDrawerConfig.node = data
      edgeDrawerConfig.title = data.properties?.name || data.id || '条件配置'
    }
    const onView = (properties: Record<string, any>) => {
      onNodeDbClick({
        data: {
          id: properties.id,
          properties,
        },
      })
    }
    const onTransformXml = () => {
      if (graphData.value !== null) {
        getFlowDataToXml(graphData.value)
      }
    }
    onMounted(initData)
    return () => {
      if (!graphData.value) return <el-empty description="暂无数据" />
      return (
        <div class={styles.logicFlow}>
          <NodeDrawer
            lf={lf}
            title={nodeDrawerConfig.title}
            v-model={nodeDrawerConfig.visible}
            node={nodeDrawerConfig.node}
          ></NodeDrawer>
          <EdgeDrawer
            title={edgeDrawerConfig.title}
            v-model={edgeDrawerConfig.visible}
            node={edgeDrawerConfig.node}
          ></EdgeDrawer>
          <el-button
            onClick={onTransformXml}
            class={styles.xmlbeautify}
            size="small"
            type="primary"
          >
            xml
          </el-button>
          <el-button
            onClick={onAutoLayout}
            class={styles.beautify}
            size="small"
            type="primary"
          >
            ä¸€é”®ç¾ŽåŒ–
          </el-button>
          <Canvas grid={{ visible: true }} minimap={true}>
            <Theme
              snapline={{
                stroke: '#ff0000', // å¯¹é½çº¿é¢œè‰²
                strokeWidth: 0.5, // å¯¹é½çº¿å®½åº¦
              }}
            />
            <LogicRenderer
              ref={logicRenderRef}
              graphData={graphData.value}
              onNodeDbClick={onNodeDbClick}
              onEdgeDbClick={onEdgeDbClick}
              onView={onView}
              // adjustEdge={true}
              isEdgeAnimation={true}
              // isSilentMode={true}
              // adjustNodePosition={true}
              // hideAnchors={true}
              // edgeTextEdit={false}
              // nodeTextEdit={false}
            ></LogicRenderer>
          </Canvas>
        </div>
      )
    }
  },
})
PipeLineLems/web/src/components/LogicFlow/components/Canvas/Canvas.module.scss
PipeLineLems/web/src/components/LogicFlow/components/Canvas/Canvas.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
import {
  defineComponent,
  Fragment,
  computed,
  Component,
  DefineComponent,
  SetupContext,
} from 'vue'
export default defineComponent<{
  grid: Record<string, any>
  minimap: Record<string, any> | boolean
}>({
  name: 'LogicFlowCanvas',
  setup(props: any, { slots, attrs }: SetupContext) {
    const logicFlowConfig = {
      grid: {
        size: 20,
        visible: true,
        type: 'dot',
        config: {
          color: '#ababab',
          thickness: 1,
        },
      },
    }
    const baseConfig = computed(() => {
      return {
        ...logicFlowConfig,
        ...props,
        ...attrs,
      }
    })
    return () => {
      const Widgets = (slots.default && slots.default()) || []
      return (
        <span>
          {Widgets.map((Widget: any) => {
            return <Widget {...baseConfig.value} />
          })}
        </span>
      )
    }
  },
})
PipeLineLems/web/src/components/LogicFlow/components/EdgeDrawer/EdgeDrawer.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
PipeLineLems/web/src/components/LogicFlow/components/EdgeDrawer/EdgeDrawer.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,185 @@
import { computed, defineComponent, ref, onMounted, watch } from 'vue'
import BaseDrawer from '@/components/BaseDrawer/BaseDrawer'
import DyForm from '@/components/DyForm/DyForm'
import { injectStore } from '../../core/store'
import { StoreKey } from '../../type/index.d'
import { BaseEdgeModel } from '@logicflow/core'
export default defineComponent({
  name: 'NodeDrawer',
  props: {
    modelValue: {
      type: Boolean,
      default: false,
    },
    title: {
      type: String,
      default: '',
    },
    node: {
      type: Object,
      default: () => ({}),
    },
  },
  emits: ['update:modelValue', 'close', 'confirm'],
  setup(props, { slots, emit }) {
    const lf = injectStore(StoreKey.LF)
    const visible = computed({
      get: () => props.modelValue,
      set: (value) => {
        emit('update:modelValue', value)
      },
    })
    const node = computed(() => props.node)
    const formData = ref<any>({})
    const formItemProps = [
      {
        label: '名称',
        prop: 'Name',
        el: 'input',
        placeholder: '请输入名称',
        rules: [{ required: true, message: '请输入名称', trigger: 'blur' }],
      },
      {
        label: '目标环节',
        prop: 'Sink',
        el: 'input',
        placeholder: '请输入目标环节',
        disabled: true,
      },
      {
        label: '源环节',
        prop: 'Source',
        el: 'input',
        placeholder: '请输入源环节',
        disabled: true,
      },
      {
        label: '比较符',
        prop: 'Operator',
        el: 'select',
        placeholder: '请输入比较符',
        options: [
          {
            label: '相等',
            value: 'RelOpEqual',
          },
          {
            label: '或者',
            value: 'Or',
          },
          {
            label: '并且',
            value: 'And',
          },
        ],
      },
      {
        label: '标签',
        prop: 'Label',
        el: 'input',
        placeholder: '请输入标签',
      },
      {
        label: '常量值',
        prop: 'Value',
        el: 'input',
        placeholder: '请输入常量值',
      },
      {
        label: '求反',
        prop: 'NOT',
        el: 'select',
        placeholder: '请输入求反',
        options: [
          {
            label: '否',
            value: 'false',
          },
          {
            label: '是',
            value: 'true',
          },
        ],
      },
      {
        label: '属性名',
        prop: 'Property',
        el: 'input',
        placeholder: '请输入属性名',
      },
      {
        label: '参数名',
        prop: 'Parameter',
        el: 'input',
        placeholder: '请输入参数名',
      },
    ]
    const onClose = () => {
      visible.value = false
    }
    const onConfirm = () => {
      const edgeModel: BaseEdgeModel = lf.value.getEdgeModelById(node.value.id)
      edgeModel.setProperties({
        Label: formData.value.Name,
        Sink: formData.value.Sink,
        Source: formData.value.Source,
        Condition: {
          Expression: formData.value.Label,
          Operator: formData.value.Operator,
          Property: formData.value.Property,
          Value: formData.value.Value,
          NOT: formData.value.NOT,
          Parameter: formData.value.Parameter,
        },
      })
      edgeModel.updateText(formData.value.Name)
      visible.value = false
    }
    const initData = () => {
      const { properties } = node.value
      const Value =
        typeof properties.Condition?.Value === 'string'
          ? String(properties.Condition?.Value)
          : ''
      formData.value = {
        Name: properties?.Label,
        Sink: properties.Sink,
        Source: properties.Source,
        Label: properties.Condition?.Expression, //标签
        Operator: properties.Condition?.Operator, //比较符号
        Property: properties.Condition?.Property, //属性名
        Value, //常量值
        NOT: String(properties.Condition?.NOT), //求反
        Parameter: properties.Condition?.Parameter, //参数名
      }
    }
    watch(node, (v, oldV) => {
      if (v !== oldV) {
        initData()
      }
    })
    return () => {
      return (
        <BaseDrawer
          onClose={onClose}
          onConfirm={onConfirm}
          title={props.title}
          width="600px"
          v-model={visible.value}
          destroy-on-close
        >
          <DyForm
            formItemProps={formItemProps}
            v-model:formData={formData.value}
          ></DyForm>
        </BaseDrawer>
      )
    }
  },
})
PipeLineLems/web/src/components/LogicFlow/components/Edges/Curve.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,67 @@
// è´å¡žå°”曲线
import { BezierEdge, BezierEdgeModel } from '@logicflow/core'
import { CURVE } from '../../core/enum' // æŠ˜çº¿
import { PolylineEdge, PolylineEdgeModel } from '@logicflow/core'
import { CurvedEdge, CurvedEdgeModel } from '@logicflow/extension'
class CurveModel extends BezierEdgeModel {
  initEdgeData(data: any) {
    super.initEdgeData(data)
    this.radius = 500
  }
  setAttributes() {
    this.isAnimation = true
    this.text.editable = false
    this.offset = 20
    const t = setTimeout(() => {
      this.isAnimation = false
      clearTimeout(t)
    }, 3000)
  }
  getEdgeAnimationStyle() {
    const style = super.getEdgeAnimationStyle()
    style.strokeDasharray = '15 2'
    style.animationDuration = '30s'
    style.stroke = '#5a84ff'
    // #9265f3
    return style
  }
  getEdgeStyle() {
    const style = super.getEdgeStyle()
    const { properties } = this
    if (properties.isActived) {
      style.strokeDasharray = '2 2'
    }
    if (this.isSelected || this.isHovered) {
      style.stroke = '#5a84ff'
    } else {
      style.stroke = '#c4c8d5'
    }
    return style
  }
  getTextStyle() {
    const style: Record<string, any> = super.getTextStyle()
    style.color = '#444'
    style.fontSize = 13
    if (this.isSelected || this.isHovered) {
      style.color = '#5a84ff'
    } else {
      style.color = '#444'
    }
    return style
  }
  getOutlineStyle() {
    const style: Record<string, any> = super.getOutlineStyle()
    style.stroke = '#5a84ff'
    style.hover.stroke = '#5a84ff'
    return style
  }
  getAdjustStart() {}
}
export default {
  type: CURVE,
  view: BezierEdge,
  model: CurveModel,
}
PipeLineLems/web/src/components/LogicFlow/components/NodeDrawer/NodeDrawer.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
PipeLineLems/web/src/components/LogicFlow/components/NodeDrawer/NodeDrawer.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,124 @@
import { computed, defineComponent, ref, onMounted, watch } from 'vue'
import BaseDrawer from '@/components/BaseDrawer/BaseDrawer'
import DyForm from '@/components/DyForm/DyForm'
import { injectStore } from '../../core/store'
import { StoreKey } from '../../type/index.d'
export default defineComponent({
  name: 'NodeDrawer',
  props: {
    modelValue: {
      type: Boolean,
      default: false,
    },
    title: {
      type: String,
      default: '',
    },
    node: {
      type: Object,
      default: () => ({}),
    },
    lf: {
      type: Object,
      default: () => ({}),
    },
  },
  emits: ['update:modelValue', 'close', 'confirm'],
  setup(props, { slots, emit }) {
    const lf = injectStore(StoreKey.LF)
    const visible = computed({
      get: () => props.modelValue,
      set: (value) => {
        emit('update:modelValue', value)
      },
    })
    const node = computed(() => props.node)
    const formData = ref({})
    const formItemProps = [
      {
        label: '名称',
        prop: 'Name',
        el: 'input',
        placeholder: '请输入名称',
        rules: [{ required: true, message: '节点名称', trigger: 'blur' }],
      },
      {
        label: '迁出模式',
        prop: 'SplitType',
        el: 'input',
        placeholder: '请输入迁出模式',
        rules: [{ required: true, message: '迁出模式', trigger: 'blur' }],
      },
      {
        label: '迁入模式',
        prop: 'JoinType',
        el: 'input',
        placeholder: '请输入迁入模式',
        rules: [{ required: true, message: '迁入模式', trigger: 'blur' }],
      },
      {
        label: '退出模式',
        prop: 'ExitMode',
        el: 'input',
        placeholder: '请输入退出模式',
        rules: [{ required: true, message: '退出模式', trigger: 'blur' }],
      },
      {
        label: '进入模式',
        prop: 'EnterMode',
        el: 'input',
        placeholder: '请输入进入模式',
        rules: [{ required: true, message: '进入模式', trigger: 'blur' }],
      },
    ]
    const onClose = () => {
      visible.value = false
    }
    const onConfirm = () => {
      lf.value.getNodeModelById(node.value.id).setProperties({
        ...formData.value,
      })
      visible.value = false
    }
    const initData = () => {
      const { properties } = node.value
      formData.value = {
        Name: properties.Name,
        SplitType: properties.SplitType,
        JoinType: properties.JoinType,
        ExitMode: properties.ExitMode,
        EnterMode: properties.EnterMode,
      }
    }
    watch(node, (v, oldV) => {
      if (v !== oldV) {
        initData()
      }
    })
    return () => {
      return (
        <BaseDrawer
          onClose={onClose}
          onConfirm={onConfirm}
          title={props.title}
          width="600px"
          v-model={visible.value}
          destroy-on-close
        >
          <DyForm
            formItemProps={formItemProps}
            v-model:formData={formData.value}
          ></DyForm>
        </BaseDrawer>
      )
    }
  },
})
PipeLineLems/web/src/components/LogicFlow/components/Nodes/BaseNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,85 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp, defineComponent } from 'vue'
import styles from './index.module.scss'
import Icon from '@/components/Icon/Icon'
import { injectStore, emitter } from '../../core/store'
export default defineComponent({
  name: 'BaseNode',
  props: {
    node: {
      type: Object,
      default: () => ({}),
    },
    color: {
      type: String,
      default: '#000',
    },
    background: {
      type: String,
      default: '#fff',
    },
    icon: {
      type: String,
    },
    width: {
      type: [String, Number],
      default: '101px',
    },
    height: {
      type: [String, Number],
      default: '33px',
    },
    borderColor: {
      type: String,
      default: '#ccc',
    },
    type: {
      type: String,
      default: 'base',
    },
  },
  emits: ['view'],
  setup(props, { emit }) {
    const { selected, onSelectNode } = injectStore()
    const onClickDetail = () => {
      emitter.emit('view', props.node)
    }
    return () => {
      const style = {
        background: props.background,
        color: props.color,
        width: props.width,
        height: props.height,
        borderColor: props.borderColor,
      }
      const node = props.node
      return (
        <div
          onClick={(event: Event) => onSelectNode(node, event)}
          class={styles.baseNodeContent}
        >
          <div
            class={{
              [styles.baseNode]: true,
              [styles.baseNodeSelected]: node.id === selected.value,
            }}
            style={style}
          >
            {props.type === 'node' ? (
              <Icon
                class={styles.detail}
                icon="detail"
                width={15}
                height={15}
                onClick={onClickDetail}
              />
            ) : null}
            <Icon icon={props.icon} width={25} height={25} />
            <div class={styles.nodeText}>{node?.Name}</div>
          </div>
        </div>
      )
    }
  },
})
PipeLineLems/web/src/components/LogicFlow/components/Nodes/EndNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import BaseNode from './BaseNode'
import { NODES } from '../../core/enum'
class StartModel extends HtmlNodeModel {
  setAttributes() {
    this.text.editable = false
    const width = 120
    const height = 36
    this.width = width
    this.height = height
    this.anchorsOffset = [
      // [width / 2, 0],//右边
      // [0, height / 2], //下边
      // [-width / 2, 0],
      [0, -height / 2], //上边
    ]
  }
}
class StartNode extends HtmlNode {
  [key: string]: any
  constructor(props: any) {
    super(props)
    this.app = createApp(() => {
      const { properties } = this.props.model
      return (
        <BaseNode
          width="116px"
          height="30px"
          borderColor="rgb(146, 101, 243)"
          icon="end"
          node={properties}
          background="#fff"
          color="#333"
        />
      )
    })
  }
  setHtml(rootEl: HTMLElement) {
    const dom = document.createElement('div')
    rootEl.appendChild(dom)
    this.app.mount(dom)
  }
}
export default {
  type: NODES.END_ACTIVITY,
  view: StartNode,
  model: StartModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/FlowNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,113 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp, defineComponent, computed } from 'vue'
import styles from './index.module.scss'
import Icon from '@/components/Icon/Icon'
import { injectStore } from '../../core/store'
interface NodeType {
  name: string
  value: string
  color?: string
}
export default defineComponent({
  name: 'BaseNode',
  props: {
    node: {
      type: Object,
      default: () => ({}),
    },
    color: {
      type: String,
      default: '#000',
    },
    background: {
      type: String,
      default: '#fff',
    },
    icon: {
      type: String,
    },
    width: {
      type: [String, Number],
      default: '206',
    },
    height: {
      type: [String, Number],
      default: '65px',
    },
  },
  setup(props) {
    const { selected, onSelectNode } = injectStore()
    const nodeFlows = computed(() => {
      const node = props.node?.source || {}
      const nodeBusiness: NodeType[] = []
      if (node.SplitType) {
        nodeBusiness.push({
          name: '迁出模式',
          value: node.SplitType,
          color: '#000',
        })
      }
      if (node.JoinType) {
        nodeBusiness.push({
          name: '迁入模式',
          value: node.JoinType,
          color: '#000',
        })
      }
      if (node.ExitMode) {
        nodeBusiness.push({
          name: '退出模式',
          value: node.ExitMode,
          color: '#000',
        })
      }
      if (node.EnterMode) {
        nodeBusiness.push({
          name: '进入模式',
          value: node.EnterMode,
          color: '#000',
        })
      }
      return nodeBusiness
    })
    return () => {
      const style = {
        background: props.background,
        color: props.color,
        width: props.width,
        height: props.height,
      }
      const node = props.node
      return (
        <div onClick={() => onSelectNode(node)} class={styles.flowNodeContent}>
          <div
            class={{
              [styles.flowNode]: true,
              [styles.flowNodeSelected]: node.id === selected.value,
            }}
          >
            <header style={style} class={styles.flowHeader}>
              <Icon icon={props.icon} width={20} height={20} />
              {node.name}
            </header>
            <main class={styles.flowNodeMain}>
              {nodeFlows.value.map((node: NodeType) => {
                return (
                  <div class={styles.flowInfo} style={{ color: node.color }}>
                    <label class={styles.label}>{node.name}:</label>
                    <span class={styles.info}>{node.value}</span>
                  </div>
                )
              })}
            </main>
          </div>
        </div>
      )
    }
  },
})
PipeLineLems/web/src/components/LogicFlow/components/Nodes/Node.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import BaseNode from './BaseNode'
import styles from './index.module.scss'
import { NODES } from '../../core/enum'
interface NodeOptionType {
  icon?: string
  background?: string
  color?: string
  width?: string
  height?: string
}
export class NodeModel extends HtmlNodeModel {
  setAttributes() {
    this.text.editable = false
    const width = 206
    const height = 136
    this.width = width
    this.height = height
    this.anchorsOffset = [
      // [width / 2, 0], //右边
      [0, height / 2], //下边
      // [-width / 2, 0], //左边
      [0, -height / 2], //上边
    ]
  }
}
export class Node extends HtmlNode {
  [key: string]: any
  constructor(props: any, option: NodeOptionType) {
    super(props)
    const nodeProps: NodeOptionType = {
      icon: 'lightsetting',
      background: '#fff',
      color: '#000000a6',
      ...option,
    }
    this.isMounted = false
    this.app = createApp(() => {
      const { properties } = this.props.model
      return <BaseNode node={properties} {...nodeProps} type="node" />
    })
  }
  setHtml(rootEl: HTMLElement) {
    const dom = document.createElement('div')
    rootEl.appendChild(dom)
    if (!this.isMounted) {
      this.app.mount(dom)
    }
    this.isMounted = true
  }
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/OrdinaryNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../core/enum'
import { Node, NodeModel } from './Node'
const style = {
  width: 165,
  height: 50,
}
class OrdinaryNodeNodeModel extends NodeModel {
  setAttributes() {
    this.text.editable = false
    this.width = style.width
    this.height = style.height
    this.anchorsOffset = [
      // [style.width / 2, 0], //右边
      [0, style.height / 2], //下边
      // [-style.width / 2, 0], //左边
      [0, -style.height / 2], //上边
    ]
  }
}
class OrdinaryNodeNodeNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: 'rgba(0, 0, 0, 0.85)',
      background: '#fff',
      height: `${style.height - 6}px`,
      width: `${style.width - 6}px`,
      icon: 'varsetting',
      borderColor: '#9265f3',
    }
    super(props, option)
  }
}
export default {
  type: NODES.ORDINARY_NODE,
  view: OrdinaryNodeNodeNode,
  model: OrdinaryNodeNodeModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/StartNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import BaseNode from './BaseNode'
import { NODES } from '../../core/enum'
class StartModel extends HtmlNodeModel {
  setAttributes() {
    this.text.editable = false
    const width = 120
    const height = 36
    this.width = width
    this.height = height
    this.anchorsOffset = [
      // [width / 2, 0],//右边
      [0, height / 2], //下边
      // [-width / 2, 0],
      // [0, -height / 2], //上边
    ]
  }
}
class StartNode extends HtmlNode {
  [key: string]: any
  constructor(props: any) {
    super(props)
    this.app = createApp(() => {
      const { properties } = this.props.model
      return (
        <BaseNode
          width="116px"
          height="30px"
          borderColor="rgb(146, 101, 243)"
          icon="start"
          node={properties}
          background="#fff"
          color="#333"
        />
      )
    })
  }
  setHtml(rootEl: HTMLElement) {
    const dom = document.createElement('div')
    rootEl.appendChild(dom)
    this.app.mount(dom)
  }
}
export default {
  type: NODES.ACTIVITY,
  view: StartNode,
  model: StartModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/BusinessNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class BusinessModel extends NodeModel {}
class BusinessNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.BUSINESS_ACTIVITY,
  view: BusinessNode,
  model: BusinessModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/DetermineProcessNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class DetermineProcessModel extends NodeModel {}
class DetermineProcessNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.DETERMINE_PROCESS_ACTIVITY,
  view: DetermineProcessNode,
  model: DetermineProcessModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/DuplicateCodeDetectionNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class DuplicateCodeDetectionModel extends NodeModel {}
class DuplicateCodeDetectionNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.DUPLICATE_CODE_DETECTION_ACTIVITY,
  view: DuplicateCodeDetectionNode,
  model: DuplicateCodeDetectionModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/InboundInitializeNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class InboundInitializeModel extends NodeModel {}
class InboundInitializeNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.VARIABLE_MONITOR_ACTIVITY,
  view: InboundInitializeNode,
  model: InboundInitializeModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/LocalQualificationJudgmentNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class LocalQualificationJudgmentModel extends NodeModel {}
class LocalQualificationJudgmentNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.LOCAL_QUALIFICATION_JUDGMENT_ACTIVITY,
  view: LocalQualificationJudgmentNode,
  model: LocalQualificationJudgmentModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/MaterialAssociationNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class MaterialAssociationModel extends NodeModel {}
class MaterialAssociationNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.MATERIAL_ASSOCIATION_ACTIVITY,
  view: MaterialAssociationNode,
  model: MaterialAssociationModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/MissingProcessDetectionNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class MissingProcessDetectionModel extends NodeModel {}
class MissingProcessDetectionNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.MISSING_PROCESS_DETECTION_ACTIVITY,
  view: MissingProcessDetectionNode,
  model: MissingProcessDetectionModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/OutboundInitializeNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class OutboundInitializeModel extends NodeModel {}
class OutboundInitializeNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.OUTBOUND_INITIALIZE_ACTIVITY,
  view: OutboundInitializeNode,
  model: OutboundInitializeModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/PLCQualificationJudgmentNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class PLCQualificationJudgmentModel extends NodeModel {}
class PLCQualificationJudgmentNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.PLC_QUALIFICATION_JUDGMENT_ACTIVITY,
  view: PLCQualificationJudgmentNode,
  model: PLCQualificationJudgmentModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/ParameterCollectNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class ParameterCollectModel extends NodeModel {}
class ParameterCollectNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.PARAMETER_COLLECT_ACTIVITY,
  view: ParameterCollectNode,
  model: ParameterCollectModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/ParameterSaveNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class ParameterSaveModel extends NodeModel {}
class ParameterSaveNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.PARAMETER_SAVE_ACTIVITY,
  view: ParameterSaveNode,
  model: ParameterSaveModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/ProductStateDetectionNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class ProductStateDetectionModel extends NodeModel {}
class ProductStateDetectionNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.PRODUCT_STATEDETECTION_ACTIVITY,
  view: ProductStateDetectionNode,
  model: ProductStateDetectionModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/TrayAssociationNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class TrayAssociationModel extends NodeModel {}
class TrayAssociationNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.TRAY_ASSOCIATION_ACTIVITY,
  view: TrayAssociationNode,
  model: TrayAssociationModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/VariableMonitorNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class VariableMonitorModel extends NodeModel {}
class VariableMonitorNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.VARIABLE_MONITOR_ACTIVITY,
  view: VariableMonitorNode,
  model: VariableMonitorModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/VariableReadNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class VariableReadNodeModel extends NodeModel {}
class VariableReadNodeNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.VARIABLE_READ_ACTIVITY,
  view: VariableReadNodeNode,
  model: VariableReadNodeModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/cache/VariableWriteNode.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { HtmlNodeModel, HtmlNode } from '@logicflow/core'
import { h, createApp } from 'vue'
import { NODES } from '../../../core/enum'
import { Node, NodeModel } from '../Node'
class VariableWriteNodeModel extends NodeModel {}
class VariableWriteNodeNode extends Node {
  [key: string]: any
  constructor(props: any) {
    const option = {
      color: '#ab47bc',
      background: '#fff',
      height: '36px',
      icon: 'varsetting',
    }
    super(props, option)
  }
}
export default {
  type: NODES.VARIABLE_WRITE_ACTIVITY,
  view: VariableWriteNodeNode,
  model: VariableWriteNodeModel,
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/index.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
// å¼€å§‹èŠ‚ç‚¹ä¸Žç»“æŸèŠ‚ç‚¹
.baseNodeContent {
  padding: 2px;
  .baseNode {
    width: 101px;
    height: 33px;
    border: 1px solid #ccc;
    border-radius: 5px;
    font-size: 12px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    box-shadow: 0 -1px 4px 0 rgba(247, 140, 227, 0.5),
      1px 1px 4px 0 rgba(230, 174, 220, 0.5);
    padding: 0 16px;
    position: relative;
    .detail {
      display: none;
    }
    &:hover {
      .detail {
        display: block;
      }
    }
    .nodeText {
      width: calc(100% - 20px);
      margin-left: 10px;
    }
  }
  .baseNodeSelected {
    border-color: #5a84ff !important;
    box-shadow: 0 0 0 3px #c8d3f2;
    background-color: #e7ecfd !important;
  }
  .detail {
    position: absolute;
    right: 3px;
    top: 3px;
    cursor: pointer;
    &:hover {
      filter: invert(48%) sepia(64%) saturate(2590%) hue-rotate(206deg)
        brightness(100%) contrast(102%);
    }
  }
}
// æµç¨‹èŠ‚ç‚¹
.flowNodeContent {
  padding: 2px;
  .flowNode {
    width: 200px;
    height: 130px;
    border: 1px solid #ccc;
    background-color: #fff;
    border-radius: 5px;
    font-size: 12px;
    display: flex;
    justify-content: flex-start;
    flex-direction: column;
    align-items: center;
    box-shadow: 0 -1px 4px 0 rgba(209, 209, 209, 50%),
      1px 1px 4px 0 rgba(217, 217, 217, 50%);
  }
  .flowNodeSelected {
    border-color: #5a84ff;
    box-shadow: 0 0 0 3px #c8d3f2;
  }
  .flowHeader {
    width: 100%;
    height: 36px;
    border-bottom: 1px solid #ccc;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 5px;
    color: #fff;
    background: #fff;
    font-weight: 500;
    font-size: 14px;
    border-top-left-radius: 5px;
    border-top-right-radius: 5px;
    flex-shrink: 0;
  }
  .flowNodeMain {
    width: 100%;
    padding: 5px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-direction: column;
    .flowInfo {
      display: flex;
      justify-content: space-between;
      align-items: center;
      width: 100%;
      margin-bottom: 4px;
      .label {
        font-size: 12px;
      }
      .info {
        font-size: 12px;
      }
    }
  }
}
PipeLineLems/web/src/components/LogicFlow/components/Nodes/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
import StartNode from './StartNode'
import EndNode from './EndNode'
// import Node from './Node'
export default {
  StartNode,
  EndNode,
  // Node,
}
PipeLineLems/web/src/components/LogicFlow/components/Renderer/Renderer.module.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
.renderer {
  :global(.lf-mini-map-header) {
    display: none;
  }
  :global(.lf-mini-map) {
    border: 1px solid #ccc;
    background-color: #fff;
  }
}
PipeLineLems/web/src/components/LogicFlow/components/Renderer/Renderer.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,208 @@
import {
  defineComponent,
  onMounted,
  ref,
  nextTick,
  watch,
  computed,
  getCurrentInstance,
  SetupContext,
} from 'vue'
import LogicFlow, { BaseEdgeModel } from '@logicflow/core'
import '@logicflow/core/dist/style/index.css'
import Dagre from '../../core/dagre'
import { createStore } from '../../core/store'
import { MiniMap } from '@logicflow/extension'
import Curve from '../Edges/Curve'
import StartNode from '../Nodes/StartNode'
import EndNode from '../Nodes/EndNode'
import OrdinaryNode from '../Nodes/OrdinaryNode'
import { eventMap } from '../../core/event'
import {
  toLowerCaseFirstLetter,
  getNodeTargetLines,
} from '../../core/transformHelp'
import { isFunction } from 'lodash'
import styles from './Renderer.module.scss'
import { emitter } from '../../core/store'
import Empty from '@/components/Empty/Empty'
interface PropsType {
  graphData: Record<string, any>
  [key: string]: any
  style: Record<string, any>
}
export default defineComponent<PropsType>({
  // @ts-ignore
  name: 'LogicFlowRenderer',
  props: {
    graphData: {
      type: Object,
      required: true,
    },
    style: { type: Object, default: () => ({}) },
    minimap: { type: [Boolean, Object], default: false },
    isEdgeAnimation: { type: Boolean, default: false },
  },
  // emits: Object.keys(eventMap),
  setup(props: PropsType, { expose, attrs, slots, emit }: SetupContext) {
    const lfRef = ref()
    const lf = ref()
    const store = createStore()
    const { onCancelSelect, showEdgeAnimation } = store
    const logicFlowConfig = computed(() => {
      return {
        ...attrs,
      }
    })
    /**
     * æ³¨å†Œè¾¹ä¸ŽèŠ‚ç‚¹
     */
    const batchRegister = () => {
      lf.value.batchRegister([Curve, StartNode, EndNode, OrdinaryNode])
    }
    /**
     * ä¸»é¢˜è®¾ç½®
     */
    const setTheme = () => {
      const theme = store.theme
      lf.value.setTheme(theme.value)
    }
    /**
     * è‡ªåŠ¨å¸ƒå±€
     */
    const autoLayout = () => {
      if (lf.value?.extension?.dagre) {
        lf.value.extension.dagre.layout({
          nodesep: 40,
          ranksep: 30,
          // radial: true,
          // controlPoints: true,
        })
      }
    }
    const showMiniMap = () => {
      if (!props.minimap) return
      let params: {
        leftPosition?: number | string
        topPosition?: number | string
      } = {
        leftPosition: 20,
        topPosition: 20,
      }
      if (typeof props.minimap === 'object') {
        params = props.minimap
      }
      lf.value?.extension.miniMap.show(params.leftPosition, params.topPosition)
    }
    /**
     * æ¸²æŸ“逻辑流
     * @param graphData
     */
    const renderLogicFlow = () => {
      if (!Object.keys(props.graphData).length) return
      lf.value.render(props.graphData)
      return nextTick(autoLayout)
    }
    /**
     * åˆå§‹åŒ–渲染
     */
    const initializeRenderer = async () => {
      batchRegister()
      setTheme()
      await renderLogicFlow()
      showMiniMap()
      initializeEvent()
    }
    /**
     * å®žä¾‹åŒ–LogicFlow
     */
    const instanceLogicFlow = () => {
      if (!Object.keys(props.graphData).length) return
      lf.value = new LogicFlow({
        container: lfRef.value,
        plugins: [Dagre, MiniMap],
        ...logicFlowConfig.value,
      })
      store.lf.value = lf.value
      initializeRenderer()
    }
    /**
     * æ³¨å†Œäº‹ä»¶
     */
    const initializeEvent = () => {
      const eventBox: string[] = []
      const eventNameMap: Record<string, any> = {}
      // æ³¨å†ŒèŠ‚ç‚¹äº‹ä»¶
      emitter.on('view', (node: any) => emit('view', node))
      // click事件单独做处理
      lf.value?.on(eventMap.nodeClick, (...arg: any) => {
        const { data } = arg[0]
        emit(eventMap.nodeClick, ...arg)
        if (props.isEdgeAnimation) {
          showEdgeAnimation(data)
        }
      })
      Object.entries(attrs).forEach(([eventName, fn]) => {
        if (eventName.includes('on')) {
          const name = toLowerCaseFirstLetter(eventName.replace('on', ''))
          eventNameMap[name] = fn
          if (eventMap.nodeClick !== name) {
            eventBox.push(name)
          }
        }
      })
      Object.entries(eventMap).forEach(([key, eventName]: string[]) => {
        if (eventBox.includes(key)) {
          lf.value?.on(eventName, (...arg: any) => {
            emit(key, ...arg)
          })
        }
      })
    }
    /**
     * èŽ·å–å½“å‰LogicFlow实例
     * @returns
     */
    const getCurrentInstance = () => {
      return lf.value
    }
    watch(
      () => props.graphData,
      (v, oldV) => {
        if (v !== oldV && v) {
          instanceLogicFlow()
        }
      }
    )
    onMounted(() => {
      instanceLogicFlow()
    })
    expose({
      autoLayout,
      getCurrentInstance,
    })
    return () => {
      return (
        <div
          class={styles.renderer}
          onClick={(event: Event) => onCancelSelect(event)}
          ref={lfRef}
          style={{ width: '100%', height: '100%', ...props.style }}
        >
          {slots.default?.()}
        </div>
      )
    }
  },
})
PipeLineLems/web/src/components/LogicFlow/components/Theme/Theme.tsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
import { defineComponent } from 'vue'
import { injectStore } from '../../core/store'
export default defineComponent({
  name: 'Theme',
  props: [
    'ellipse',
    'polygon',
    'outline',
    'edgeAdjust',
    'text',
    'snapline',
    'line',
    'anchorLine',
    'arrow',
    'edgeText',
    'bezier',
    'polyline',
    'baseEdge',
    'nodeText',
    'anchor',
  ],
  setup(props, { attrs }) {
    const { theme } = injectStore()
    theme.value = { ...theme.value, ...attrs, ...props }
    return () => null
  },
})
PipeLineLems/web/src/components/LogicFlow/core/bak.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,342 @@
// @ts-nocheck
import { DagreLayout, DagreLayoutOptions } from '@antv/layout'
import * as d3 from 'd3'
import * as d3Hierarchy from 'd3-hierarchy'
import { CURVE, NODES, ActivityKey, TransitionKey, ConditionKey } from './enum'
function convertToTree(nodes, edges, rootId) {
  // åˆ›å»ºä¸€ä¸ªç©ºå¯¹è±¡ï¼Œç”¨äºŽå°†èŠ‚ç‚¹ID映射到其自身对象
  const nodeMap = {}
  // éåŽ†èŠ‚ç‚¹åˆ—è¡¨ï¼Œå°†æ¯ä¸ªèŠ‚ç‚¹ID映射到其自身对象,并初始化children属性为空数组
  nodes.forEach((node) => {
    nodeMap[node.id] = { ...node, children: [] }
  })
  // éåŽ†è¾¹åˆ—è¡¨ï¼Œå°†æ¯ä¸ªè¾¹çš„æºèŠ‚ç‚¹è¿žæŽ¥åˆ°ç›®æ ‡èŠ‚ç‚¹
  edges.forEach((edge) => {
    const sourceNode = nodeMap[edge.source]
    const targetNode = nodeMap[edge.target]
    sourceNode.children.push(targetNode)
  })
  // è¿”回根节点对象
  return nodeMap[rootId]
}
export default class Dagre {
  static pluginName = 'dagre'
  lf: any
  option: DagreLayoutOptions
  render(lf: any) {
    this.lf = lf
  }
  getBytesLength(word: string): number {
    if (!word) {
      return 0
    }
    let totalLength = 0
    for (let i = 0; i < word.length; i++) {
      const c = word.charCodeAt(i)
      if (word.match(/[A-Z]/)) {
        totalLength += 1.5
      } else if ((c >= 0x0001 && c <= 0x007e) || (c >= 0xff60 && c <= 0xff9f)) {
        totalLength += 1
      } else {
        totalLength += 2
      }
    }
    return totalLength
  }
  /**
   * option: {
   *   rankdir: "TB", // layout æ–¹å‘, å¯é€‰ TB, BT, LR, RL
   *   align: undefined, // èŠ‚ç‚¹å¯¹é½æ–¹å¼ï¼Œå¯é€‰ UL, UR, DL, DR
   *   nodeSize: undefined, // èŠ‚ç‚¹å¤§å°
   *   nodesepFunc: undefined, // èŠ‚ç‚¹æ°´å¹³é—´è·(px)
   *   ranksepFunc: undefined, // æ¯ä¸€å±‚节点之间间距
   *   nodesep: 40, // èŠ‚ç‚¹æ°´å¹³é—´è·(px) æ³¨æ„ï¼šå¦‚果有grid,需要保证nodesep为grid的偶数倍
   *   ranksep: 40, // æ¯ä¸€å±‚节点之间间距 æ³¨æ„ï¼šå¦‚果有grid,需要保证ranksep为grid的偶数倍
   *   controlPoints: false, // æ˜¯å¦ä¿ç•™å¸ƒå±€è¿žçº¿çš„æŽ§åˆ¶ç‚¹
   *   radial: false, // æ˜¯å¦åŸºäºŽ dagre è¿›è¡Œè¾å°„布局
   *   focusNode: null, // radial ä¸º true æ—¶ç”Ÿæ•ˆï¼Œå…³æ³¨çš„节点
   * };
   */
  layout(option = {}) {
    const { nodes, edges, gridSize } = this.lf.graphModel
    // ä¸ºäº†ä¿è¯ç”Ÿæˆçš„节点在girdSize上,需要处理一下。
    let nodesep = 40
    let ranksep = 40
    if (gridSize > 20) {
      nodesep = gridSize * 2
      ranksep = gridSize * 2
    }
    this.option = {
      type: 'dagre',
      rankdir: 'TB',
      // align: 'UL',
      // align: 'UR',
      align: 'DR',
      nodesep,
      ranksep,
      begin: [120, 120],
      controlPoints: true,
      ...option,
    }
    const layoutInstance = new DagreLayout(this.option)
    const nodes1 = nodes.map((node) => ({
      id: node.id,
      type: node.type,
      model: node,
      size: {
        width: node.width,
        height: node.height,
      },
    }))
    const edges1 = edges.map((edge) => ({
      source: edge.sourceNodeId,
      target: edge.targetNodeId,
      model: edge,
    }))
    const rootId = nodes.find((node) => node.type === 'Activity')?.id
    const treeData = convertToTree(nodes1, edges1, rootId)
    var treemap = d3.tree().size([1066, 1145])
    const root = d3.hierarchy(treeData, function (d) {
      return d.children
    })
    treemap(root)
    function treeToMap(root) {
      const map = new Map()
      root.each((node) => {
        map.set(node.data.id, node)
      })
      return map
    }
    function getFlowLayout(nodeMap) {
      let edges = []
      const nodes = []
      nodeMap.forEach((node) => {
        nodes.push({
          id: node.data.id,
          type: node.data.type,
          model: node.data.model,
          x: node.x,
          y: node.y,
          properties: node.data.model?.properties,
          size: {
            width: node.data.model.width,
            height: node.data.model.height,
          },
        })
        const childData = []
        if (node?.children) {
          node?.children?.forEach((nodeChild) => {
            childData.push({
              properties: nodeChild.data.model?.properties,
              sourceNodeId: node?.parent?.data?.id,
              targetNodeId: nodeChild.data?.id,
              model: nodeChild.data.model,
              id: nodeChild.data.model?.id,
              type: CURVE,
            })
          })
        } else {
          childData.push({
            sourceNodeId: node?.parent?.data?.id,
            model: node.data.model,
            id: node.data.model?.id,
            properties: node.data.model?.properties,
            type: CURVE,
          })
        }
        edges = edges.concat(childData)
      })
      return {
        nodes,
        edges,
      }
    }
    const nodeMap = treeToMap(root)
    const v = getFlowLayout(nodeMap)
    const layoutData = layoutInstance.layout({
      nodes: nodes.map((node) => ({
        id: node.id,
        size: {
          width: node.width,
          height: node.height,
        },
        model: node,
      })),
      edges: edges.map((edge) => ({
        source: edge.sourceNodeId,
        target: edge.targetNodeId,
        model: edge,
      })),
    })
    const newGraphData = {
      nodes: [],
      edges: [],
    }
    layoutData.nodes.forEach((node) => {
      // @ts-ignore: pass node data
      const { model } = node
      const data = model.getData()
      // @ts-ignore: pass node data
      data.x = node.x
      // @ts-ignore: pass node data
      data.y = node.y
      if (data.text) {
        data.text = {
          x: node.x + 10,
          y: node.y - this.getBytesLength(data.text) * 6 - 10,
          value: data.text.value,
        }
      }
      newGraphData.nodes.push(data)
    })
    layoutData.edges.forEach((edge) => {
      // @ts-ignore: pass edge data
      const { model } = edge
      const data = model.getData()
      data.pointsList = this.calcPointsList(model, newGraphData.nodes)
      if (data.pointsList) {
        const first = data.pointsList[0]
        const last = data.pointsList[data.pointsList.length - 1]
        data.startPoint = { x: first.x, y: first.y }
        data.endPoint = { x: last.x, y: last.y }
        if (data.text && data.text.value) {
          data.text = {
            x: last.x,
            y: last.y - this.getBytesLength(data.text.value) * 8,
            value: data.text.value,
          }
        }
      } else {
        data.startPoint = undefined
        data.endPoint = undefined
        if (data.text && data.text.value) {
          data.text = data.text.value
        }
      }
      newGraphData.edges.push(data)
    })
    // console.log(v, newGraphData, '000---')
    this.lf.render(newGraphData)
  }
  pointFilter(points) {
    const allPoints = points
    let i = 1
    while (i < allPoints.length - 1) {
      const pre = allPoints[i - 1]
      const current = allPoints[i]
      const next = allPoints[i + 1]
      if (
        (pre.x === current.x && current.x === next.x) ||
        (pre.y === current.y && current.y === next.y)
      ) {
        allPoints.splice(i, 1)
      } else {
        i++
      }
    }
    return allPoints
  }
  calcPointsList(model, nodes) {
    // åœ¨èŠ‚ç‚¹ç¡®è®¤ä»Žå·¦å‘å³åŽï¼Œé€šè¿‡è®¡ç®—æ¥ä¿è¯èŠ‚ç‚¹è¿žçº¿æ¸…æ™°ã€‚
    // TODO: é¿éšœ
    const pointsList = []
    if (this.option.rankdir === 'LR') {
      const sourceNodeModel = this.lf.getNodeModelById(model.sourceNodeId)
      const targetNodeModel = this.lf.getNodeModelById(model.targetNodeId)
      const newSourceNodeData = nodes.find(
        (node) => node.id === model.sourceNodeId
      )
      const newTargetNodeData = nodes.find(
        (node) => node.id === model.targetNodeId
      )
      if (newSourceNodeData.x < newTargetNodeData.x) {
        pointsList.push({
          x: newSourceNodeData.x + sourceNodeModel.width / 2,
          y: newSourceNodeData.y,
        })
        pointsList.push({
          x:
            newSourceNodeData.x +
            sourceNodeModel.width / 2 +
            (model.offset || 50),
          y: newSourceNodeData.y,
        })
        pointsList.push({
          x:
            newSourceNodeData.x +
            sourceNodeModel.width / 2 +
            (model.offset || 50),
          y: newTargetNodeData.y,
        })
        pointsList.push({
          x: newTargetNodeData.x - targetNodeModel.width / 2,
          y: newTargetNodeData.y,
        })
        return this.pointFilter(pointsList)
      }
      // å‘回连线
      if (newSourceNodeData.x > newTargetNodeData.x) {
        if (newSourceNodeData.y >= newTargetNodeData.y) {
          pointsList.push({
            x: newSourceNodeData.x,
            y: newSourceNodeData.y + sourceNodeModel.height / 2,
          })
          pointsList.push({
            x: newSourceNodeData.x,
            y:
              newSourceNodeData.y +
              sourceNodeModel.height / 2 +
              (model.offset || 50),
          })
          pointsList.push({
            x: newTargetNodeData.x,
            y:
              newSourceNodeData.y +
              sourceNodeModel.height / 2 +
              (model.offset || 50),
          })
          pointsList.push({
            x: newTargetNodeData.x,
            y: newTargetNodeData.y + targetNodeModel.height / 2,
          })
        } else {
          pointsList.push({
            x: newSourceNodeData.x,
            y: newSourceNodeData.y - sourceNodeModel.height / 2,
          })
          pointsList.push({
            x: newSourceNodeData.x,
            y:
              newSourceNodeData.y -
              sourceNodeModel.height / 2 -
              (model.offset || 50),
          })
          pointsList.push({
            x: newTargetNodeData.x,
            y:
              newSourceNodeData.y -
              sourceNodeModel.height / 2 -
              (model.offset || 50),
          })
          pointsList.push({
            x: newTargetNodeData.x,
            y: newTargetNodeData.y - targetNodeModel.height / 2,
          })
        }
        console.log(pointsList, 'pointsList')
        return this.pointFilter(pointsList)
      }
    }
    return undefined
  }
}
在上述文件截断后对比
PipeLineLems/web/src/components/LogicFlow/core/dagre.ts PipeLineLems/web/src/components/LogicFlow/core/enum.ts PipeLineLems/web/src/components/LogicFlow/core/event.ts PipeLineLems/web/src/components/LogicFlow/core/layout.ts PipeLineLems/web/src/components/LogicFlow/core/store.ts PipeLineLems/web/src/components/LogicFlow/core/transformHelp.ts PipeLineLems/web/src/components/LogicFlow/type/index.d.ts PipeLineLems/web/src/components/Menu/index.vue PipeLineLems/web/src/components/MyPages/index.vue PipeLineLems/web/src/components/Pdf/index.vue PipeLineLems/web/src/components/PrintDialog/PrintDialog.module.scss PipeLineLems/web/src/components/PrintDialog/PrintDialog.tsx PipeLineLems/web/src/components/ProcessRouterDialog/ProcessRouterDialog.module.scss PipeLineLems/web/src/components/ProcessRouterDialog/ProcessRouterDialog.tsx PipeLineLems/web/src/components/ProcessRouterDialog/api.ts PipeLineLems/web/src/components/ProcessRouterDialog/hook.ts PipeLineLems/web/src/components/ProcessRoutes/ProcessRoutes.module.scss PipeLineLems/web/src/components/ProcessRoutes/ProcessRoutes.tsx PipeLineLems/web/src/components/ProductSelectDialog/ProductSelectDialog.module.scss PipeLineLems/web/src/components/ProductSelectDialog/ProductSelectDialog.tsx PipeLineLems/web/src/components/ProductSelectDialog/hook.ts PipeLineLems/web/src/components/ProjectConfig/ProjectConfig.module.scss PipeLineLems/web/src/components/ProjectConfig/ProjectConfig.tsx PipeLineLems/web/src/components/Radio/Radio.module.scss PipeLineLems/web/src/components/Radio/Radio.tsx PipeLineLems/web/src/components/RelationFlowDialog/RelationFlowDialog.module.scss PipeLineLems/web/src/components/RelationFlowDialog/RelationFlowDialog.tsx PipeLineLems/web/src/components/RelationFlowDialog/app.ts PipeLineLems/web/src/components/RelationFlowDialog/enum.ts PipeLineLems/web/src/components/Search/Search.tsx PipeLineLems/web/src/components/SearchSelect/Option.tsx PipeLineLems/web/src/components/SearchSelect/SearchSelect.tsx PipeLineLems/web/src/components/SearchSelect/Select.module.scss PipeLineLems/web/src/components/Segment/segment.module.scss PipeLineLems/web/src/components/Segment/segment.tsx PipeLineLems/web/src/components/Select/Option.tsx PipeLineLems/web/src/components/Select/Select.module.scss PipeLineLems/web/src/components/Select/Select.tsx PipeLineLems/web/src/components/Select/Select1.tsx PipeLineLems/web/src/components/SelectInput/SelectInput.tsx PipeLineLems/web/src/components/Setting/Setting.tsx PipeLineLems/web/src/components/SvgIcon/SvgIcon.module.scss PipeLineLems/web/src/components/SvgIcon/SvgIcon.tsx PipeLineLems/web/src/components/Tab/Tab.tsx PipeLineLems/web/src/components/Tab/TabPane.tsx PipeLineLems/web/src/components/Table/index.module.scss PipeLineLems/web/src/components/TableArray/TableArray.module.scss PipeLineLems/web/src/components/TableArray/TableArray.tsx PipeLineLems/web/src/components/TableFilter/TableFilter.module.scss PipeLineLems/web/src/components/TableFilter/TableFilter.tsx PipeLineLems/web/src/components/Tag/Tag.tsx PipeLineLems/web/src/components/TdButton/TdButton.module.scss PipeLineLems/web/src/components/TdButton/TdButton.tsx PipeLineLems/web/src/components/Text/Text.module.scss PipeLineLems/web/src/components/Text/Text.tsx PipeLineLems/web/src/components/Title/Title.module.scss PipeLineLems/web/src/components/Title/Title.tsx PipeLineLems/web/src/components/Variable/Variable.module.scss PipeLineLems/web/src/components/Variable/Variable.tsx PipeLineLems/web/src/components/WorkSectionDialog/WorkSectionDialog.module.scss PipeLineLems/web/src/components/WorkSectionDialog/WorkSectionDialog.tsx PipeLineLems/web/src/components/WorkSectionDialog/hook.ts PipeLineLems/web/src/components/WorkSectionParams/WorkSectionParams.module.scss PipeLineLems/web/src/components/WorkSectionParams/WorkSectionParams.tsx PipeLineLems/web/src/components/WorkSectionParams/api.ts PipeLineLems/web/src/components/WorkStationDialog/WorkStationDialog.module.scss PipeLineLems/web/src/components/WorkStationDialog/WorkStationDialog.tsx PipeLineLems/web/src/components/WorkStationDialog/hook.ts PipeLineLems/web/src/components/index.d.ts PipeLineLems/web/src/components/index.ts PipeLineLems/web/src/components/package.json PipeLineLems/web/src/components/vue3-context-menu/ContextMenuItem.vue PipeLineLems/web/src/hooks/Dialog.ts PipeLineLems/web/src/hooks/File.ts PipeLineLems/web/src/hooks/drawer.ts PipeLineLems/web/src/index.d.ts PipeLineLems/web/src/libs/Base/Base.ts PipeLineLems/web/src/libs/DownloadFile/DownLoadFile.ts PipeLineLems/web/src/libs/Permission/Permission.d.ts PipeLineLems/web/src/libs/Permission/Permission.ts PipeLineLems/web/src/libs/Provider/Provider.ts PipeLineLems/web/src/libs/Socket/Socket.ts PipeLineLems/web/src/libs/Socket/index.ts PipeLineLems/web/src/libs/Socket/toast.ts PipeLineLems/web/src/libs/Store/Store.d.ts PipeLineLems/web/src/libs/Store/Store.ts PipeLineLems/web/src/libs/Store/globalState.ts PipeLineLems/web/src/libs/system-enum.ts PipeLineLems/web/src/main.ts PipeLineLems/web/src/provider/index.vue PipeLineLems/web/src/utils/client.ts PipeLineLems/web/src/utils/columnConfig.ts PipeLineLems/web/src/utils/index.ts PipeLineLems/web/src/utils/request.ts PipeLineLems/web/src/utils/storage.ts PipeLineLems/web/src/utils/util.ts PipeLineLems/web/src/widgets/MyPluginName/Views/MyPluginName.tsx PipeLineLems/web/src/widgets/MyPluginName/index.ts PipeLineLems/web/vite.build.config.ts PipeLineLems/web/vite.config.ts PipeLineLems/web/vite.lib.config.ts PipeLineLems/web/yarn.lock