3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -21,8 +21,7 @@
|
||||
// CSS 变量提示
|
||||
"vunguyentuan.vscode-css-variables",
|
||||
// 在 package.json 中显示 PNPM catalog 的版本
|
||||
"antfu.pnpm-catalog-lens",
|
||||
"augment.vscode-augment"
|
||||
"antfu.pnpm-catalog-lens"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
// 和 volar 冲突
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
## 🐶 新手必读
|
||||
|
||||
- nodejs > 20.10.0 && pnpm > 10.10.0 (强制使用pnpm)
|
||||
- nodejs > 20.10.0 && pnpm > 10.14.0 (强制使用pnpm)
|
||||
- 演示地址【Vue3 + element-plus】:<http://dashboard-vue3.yudao.iocoder.cn>
|
||||
- 演示地址【Vue3 + vben5(ant-design-vue)】:<http://dashboard-vben.yudao.iocoder.cn>
|
||||
- 演示地址【Vue2 + element-ui】:<http://dashboard.yudao.iocoder.cn>
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
## 外包项目请联系【非项目需求请勿扫码,非客服,不解答项目问题】
|
||||
|
||||

|
||||

|
||||
|
||||
## 技术栈
|
||||
|
||||
|
||||
@@ -46,16 +46,22 @@
|
||||
"@vueuse/core": "catalog:",
|
||||
"@vueuse/integrations": "catalog:",
|
||||
"ant-design-vue": "catalog:",
|
||||
"bpmn-js": "catalog:",
|
||||
"bpmn-js-properties-panel": "catalog:",
|
||||
"bpmn-js-token-simulation": "catalog:",
|
||||
"camunda-bpmn-moddle": "catalog:",
|
||||
"cropperjs": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"diagram-js": "catalog:",
|
||||
"fast-xml-parser": "catalog:",
|
||||
"highlight.js": "catalog:",
|
||||
"min-dash": "catalog:",
|
||||
"pinia": "catalog:",
|
||||
"steady-xml": "catalog:",
|
||||
"tinymce": "catalog:",
|
||||
"vue": "catalog:",
|
||||
"vue-dompurify-html": "catalog:",
|
||||
"vue-router": "catalog:",
|
||||
"vue3-signature": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ const AutoComplete = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/auto-complete'),
|
||||
);
|
||||
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
|
||||
const Cascader = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/cascader'),
|
||||
);
|
||||
const Checkbox = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/checkbox'),
|
||||
);
|
||||
@@ -59,6 +62,9 @@ const Textarea = defineAsyncComponent(() =>
|
||||
const TimePicker = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/time-picker'),
|
||||
);
|
||||
const TimeRangePicker = defineAsyncComponent(() =>
|
||||
import('ant-design-vue/es/time-picker').then((res) => res.TimeRangePicker),
|
||||
);
|
||||
const TreeSelect = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/tree-select'),
|
||||
);
|
||||
@@ -100,6 +106,7 @@ const withDefaultPlaceholder = <T extends Component>(
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type ComponentType =
|
||||
| 'ApiCascader'
|
||||
| 'ApiSelect'
|
||||
| 'ApiTreeSelect'
|
||||
| 'AutoComplete'
|
||||
@@ -126,6 +133,7 @@ export type ComponentType =
|
||||
| 'Switch'
|
||||
| 'Textarea'
|
||||
| 'TimePicker'
|
||||
| 'TimeRangePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
@@ -135,6 +143,21 @@ async function initComponentAdapter() {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
// Button: () =>
|
||||
// import('xxx').then((res) => res.Button),
|
||||
ApiCascader: withDefaultPlaceholder(
|
||||
{
|
||||
...ApiComponent,
|
||||
name: 'ApiCascader',
|
||||
},
|
||||
'select',
|
||||
{
|
||||
component: Cascader,
|
||||
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||
loadingSlot: 'suffixIcon',
|
||||
modelPropName: 'value',
|
||||
optionsPropName: 'treeData',
|
||||
visibleEvent: 'onVisibleChange',
|
||||
},
|
||||
),
|
||||
ApiSelect: withDefaultPlaceholder(
|
||||
{
|
||||
...ApiComponent,
|
||||
@@ -195,6 +218,7 @@ async function initComponentAdapter() {
|
||||
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||
RichTextarea,
|
||||
TimePicker,
|
||||
TimeRangePicker,
|
||||
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||
Upload,
|
||||
FileUpload,
|
||||
|
||||
@@ -7,9 +7,7 @@ import type { ComponentType } from './component';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
/** 手机号正则表达式(中国) */
|
||||
const MOBILE_REGEX = /(?:0|86|\+86)?1[3-9]\d{9}/;
|
||||
import { isMobile } from '@vben/utils';
|
||||
|
||||
async function initSetupVbenForm() {
|
||||
setupVbenForm<ComponentType>({
|
||||
@@ -44,7 +42,7 @@ async function initSetupVbenForm() {
|
||||
mobile: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null || value.length === 0) {
|
||||
return true;
|
||||
} else if (!MOBILE_REGEX.test(value)) {
|
||||
} else if (!isMobile(value)) {
|
||||
return $t('ui.formRules.mobile', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
@@ -54,7 +52,7 @@ async function initSetupVbenForm() {
|
||||
if (value === undefined || value === null || value.length === 0) {
|
||||
return $t('ui.formRules.required', [ctx.label]);
|
||||
}
|
||||
if (!MOBILE_REGEX.test(value)) {
|
||||
if (!isMobile(value)) {
|
||||
return $t('ui.formRules.mobile', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/* 来自 @vben/plugins/vxe-table style.css,覆盖 vxe-table 原有的样式定义,使用 vben 的样式主题 */
|
||||
:root {
|
||||
--vxe-ui-font-color: hsl(var(--foreground));
|
||||
--vxe-ui-font-primary-color: hsl(var(--primary));
|
||||
|
||||
/* --vxe-ui-font-lighten-color: #babdc0;
|
||||
--vxe-ui-font-darken-color: #86898e; */
|
||||
--vxe-ui-font-disabled-color: hsl(var(--foreground) / 50%);
|
||||
|
||||
/* base */
|
||||
--vxe-ui-base-popup-border-color: hsl(var(--border));
|
||||
--vxe-ui-input-disabled-color: hsl(var(--border) / 60%);
|
||||
|
||||
/* --vxe-ui-base-popup-box-shadow: 0px 12px 30px 8px rgb(0 0 0 / 50%); */
|
||||
|
||||
/* layout */
|
||||
--vxe-ui-layout-background-color: hsl(var(--background));
|
||||
--vxe-ui-table-resizable-line-color: hsl(var(--heavy));
|
||||
|
||||
/* --vxe-ui-table-fixed-left-scrolling-box-shadow: 8px 0px 10px -5px hsl(var(--accent));
|
||||
--vxe-ui-table-fixed-right-scrolling-box-shadow: -8px 0px 10px -5px hsl(var(--accent)); */
|
||||
|
||||
/* input */
|
||||
--vxe-ui-input-border-color: hsl(var(--border));
|
||||
|
||||
/* --vxe-ui-input-placeholder-color: #8d9095; */
|
||||
|
||||
/* --vxe-ui-input-disabled-background-color: #262727; */
|
||||
|
||||
/* loading */
|
||||
--vxe-ui-loading-background-color: hsl(var(--overlay-content));
|
||||
|
||||
/* table */
|
||||
--vxe-ui-table-header-background-color: hsl(var(--accent));
|
||||
--vxe-ui-table-border-color: hsl(var(--border));
|
||||
--vxe-ui-table-row-hover-background-color: hsl(var(--accent-hover));
|
||||
--vxe-ui-table-row-striped-background-color: hsl(var(--accent) / 60%);
|
||||
--vxe-ui-table-row-hover-striped-background-color: hsl(var(--accent));
|
||||
--vxe-ui-table-row-radio-checked-background-color: hsl(var(--accent));
|
||||
--vxe-ui-table-row-hover-radio-checked-background-color: hsl(
|
||||
var(--accent-hover)
|
||||
);
|
||||
--vxe-ui-table-row-checkbox-checked-background-color: hsl(var(--accent));
|
||||
--vxe-ui-table-row-hover-checkbox-checked-background-color: hsl(
|
||||
var(--accent-hover)
|
||||
);
|
||||
--vxe-ui-table-row-current-background-color: hsl(var(--accent));
|
||||
--vxe-ui-table-row-hover-current-background-color: hsl(var(--accent-hover));
|
||||
--vxe-ui-font-primary-tinge-color: hsl(var(--primary));
|
||||
--vxe-ui-font-primary-lighten-color: hsl(var(--primary) / 60%);
|
||||
--vxe-ui-font-primary-darken-color: hsl(var(--primary));
|
||||
|
||||
/* height: auto !important; */
|
||||
|
||||
/* --vxe-ui-table-fixed-scrolling-box-shadow-color: rgb(0 0 0 / 80%); */
|
||||
}
|
||||
|
||||
.vxe-tools--operate {
|
||||
margin-right: 0.25rem;
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
|
||||
.vxe-table-custom--checkbox-option:hover {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
.vxe-toolbar {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.vxe-buttons--wrapper:not(:empty),
|
||||
.vxe-tools--operate:not(:empty),
|
||||
.vxe-tools--wrapper:not(:empty) {
|
||||
padding: 0.6em 0;
|
||||
}
|
||||
|
||||
.vxe-tools--operate:not(:has(button)) {
|
||||
margin-left: 0;
|
||||
}
|
||||
@@ -6,7 +6,8 @@ import { h } from 'vue';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { $te } from '@vben/locales';
|
||||
import {
|
||||
AsyncComponents,
|
||||
AsyncVxeColumn,
|
||||
AsyncVxeTable,
|
||||
createRequiredValidation,
|
||||
setupVbenVxeTable,
|
||||
useVbenVxeGrid,
|
||||
@@ -34,8 +35,6 @@ import { $t } from '#/locales';
|
||||
|
||||
import { useVbenForm } from './form';
|
||||
|
||||
import '#/adapter/style.css';
|
||||
|
||||
setupVbenVxeTable({
|
||||
configVxeTable: (vxeUI) => {
|
||||
vxeUI.setConfig({
|
||||
@@ -357,16 +356,8 @@ setupVbenVxeTable({
|
||||
|
||||
export { createRequiredValidation, useVbenVxeGrid };
|
||||
|
||||
const [VxeTable, VxeColumn, VxeToolbar] = AsyncComponents;
|
||||
export { VxeColumn, VxeTable, VxeToolbar };
|
||||
export const [VxeTable, VxeColumn] = [AsyncVxeTable, AsyncVxeColumn];
|
||||
|
||||
// add by 芋艿:from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/adapter/vxe-table.ts#L264-L270
|
||||
export type OnActionClickParams<T = Recordable<any>> = {
|
||||
code: string;
|
||||
row: T;
|
||||
};
|
||||
export type OnActionClickFn<T = Recordable<any>> = (
|
||||
params: OnActionClickParams<T>,
|
||||
) => void;
|
||||
export * from '#/components/table-action';
|
||||
|
||||
export type * from '@vben/plugins/vxe-table';
|
||||
|
||||
@@ -12,6 +12,7 @@ export namespace AiImageApi {
|
||||
// AI 绘图
|
||||
export interface Image {
|
||||
id: number; // 编号
|
||||
userId: number;
|
||||
platform: string; // 平台
|
||||
model: string; // 模型
|
||||
prompt: string; // 提示词
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { AiWriteTypeEnum } from '@vben/constants';
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import type { AiWriteTypeEnum } from '#/utils';
|
||||
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
import { fetchEventSource } from '@vben/request';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace ProductUnitApi {
|
||||
/** 产品单位信息 */
|
||||
export interface ProductUnit {
|
||||
id: number; // 编号
|
||||
groupId?: number; // 分组编号
|
||||
name?: string; // 单位名称
|
||||
basic?: number; // 基础单位
|
||||
number?: number; // 单位数量/相对于基础单位
|
||||
usageType: number; // 用途
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询产品单位分页 */
|
||||
export function getProductUnitPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ProductUnitApi.ProductUnit>>(
|
||||
'/basic/product-unit/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询产品单位详情 */
|
||||
export function getProductUnit(id: number) {
|
||||
return requestClient.get<ProductUnitApi.ProductUnit>(
|
||||
`/basic/product-unit/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 新增产品单位 */
|
||||
export function createProductUnit(data: ProductUnitApi.ProductUnit) {
|
||||
return requestClient.post('/basic/product-unit/create', data);
|
||||
}
|
||||
|
||||
/** 修改产品单位 */
|
||||
export function updateProductUnit(data: ProductUnitApi.ProductUnit) {
|
||||
return requestClient.put('/basic/product-unit/update', data);
|
||||
}
|
||||
|
||||
/** 删除产品单位 */
|
||||
export function deleteProductUnit(id: number) {
|
||||
return requestClient.delete(`/basic/product-unit/delete?id=${id}`);
|
||||
}
|
||||
|
||||
/** 批量删除产品单位 */
|
||||
export function deleteProductUnitListByIds(ids: number[]) {
|
||||
return requestClient.delete(
|
||||
`/basic/product-unit/delete-list?ids=${ids.join(',')}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 导出产品单位 */
|
||||
export function exportProductUnit(params: any) {
|
||||
return requestClient.download('/basic/product-unit/export-excel', params);
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace ProductUnitGroupApi {
|
||||
/** 产品单位组信息 */
|
||||
export interface ProductUnitGroup {
|
||||
id: number; // 编号
|
||||
name?: string; // 产品单位组名称
|
||||
status?: number; // 开启状态
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询产品单位组分页 */
|
||||
export function getProductUnitGroupPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<ProductUnitGroupApi.ProductUnitGroup>>(
|
||||
'/basic/product-unit-group/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询产品单位组详情 */
|
||||
export function getProductUnitGroup(id: number) {
|
||||
return requestClient.get<ProductUnitGroupApi.ProductUnitGroup>(
|
||||
`/basic/product-unit-group/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 新增产品单位组 */
|
||||
export function createProductUnitGroup(
|
||||
data: ProductUnitGroupApi.ProductUnitGroup,
|
||||
) {
|
||||
return requestClient.post('/basic/product-unit-group/create', data);
|
||||
}
|
||||
|
||||
/** 修改产品单位组 */
|
||||
export function updateProductUnitGroup(
|
||||
data: ProductUnitGroupApi.ProductUnitGroup,
|
||||
) {
|
||||
return requestClient.put('/basic/product-unit-group/update', data);
|
||||
}
|
||||
|
||||
/** 删除产品单位组 */
|
||||
export function deleteProductUnitGroup(id: number) {
|
||||
return requestClient.delete(`/basic/product-unit-group/delete?id=${id}`);
|
||||
}
|
||||
|
||||
/** 批量删除产品单位组 */
|
||||
export function deleteProductUnitGroupListByIds(ids: number[]) {
|
||||
return requestClient.delete(
|
||||
`/basic/product-unit-group/delete-list?ids=${ids.join(',')}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 导出产品单位组 */
|
||||
export function exportProductUnitGroup(params: any) {
|
||||
return requestClient.download(
|
||||
'/basic/product-unit-group/export-excel',
|
||||
params,
|
||||
);
|
||||
}
|
||||
@@ -49,5 +49,7 @@ export async function deleteProcessExpression(id: number) {
|
||||
|
||||
/** 导出流程表达式 */
|
||||
export async function exportProcessExpression(params: any) {
|
||||
return requestClient.download('/bpm/process-expression/export-excel', params);
|
||||
return requestClient.download('/bpm/process-expression/export-excel', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import type {
|
||||
BpmCandidateStrategyEnum,
|
||||
BpmNodeTypeEnum,
|
||||
} from '@vben/constants';
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import type { BpmTaskApi } from '../task';
|
||||
|
||||
import type { BpmModelApi } from '#/api/bpm/model';
|
||||
import type { BpmCandidateStrategyEnum, BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
|
||||
@@ -133,5 +133,7 @@ export const getChildrenTaskList = async (id: string) => {
|
||||
|
||||
// 撤回任务
|
||||
export const withdrawTask = async (taskId: string) => {
|
||||
return await requestClient.put('/bpm/task/withdraw', null, { params: { taskId } });
|
||||
return await requestClient.put('/bpm/task/withdraw', null, {
|
||||
params: { taskId },
|
||||
});
|
||||
};
|
||||
|
||||
@@ -108,7 +108,7 @@ export function deleteBusiness(id: number) {
|
||||
|
||||
/** 导出商机 */
|
||||
export function exportBusiness(params: any) {
|
||||
return requestClient.download('/crm/business/export-excel', params);
|
||||
return requestClient.download('/crm/business/export-excel', { params });
|
||||
}
|
||||
|
||||
/** 联系人关联商机列表 */
|
||||
|
||||
@@ -67,7 +67,7 @@ export function deleteClue(id: number) {
|
||||
|
||||
/** 导出线索 */
|
||||
export function exportClue(params: any) {
|
||||
return requestClient.download('/crm/clue/export-excel', params);
|
||||
return requestClient.download('/crm/clue/export-excel', { params });
|
||||
}
|
||||
|
||||
/** 线索转移 */
|
||||
|
||||
@@ -96,7 +96,7 @@ export function deleteContact(id: number) {
|
||||
|
||||
/** 导出联系人 */
|
||||
export function exportContact(params: any) {
|
||||
return requestClient.download('/crm/contact/export-excel', params);
|
||||
return requestClient.download('/crm/contact/export-excel', { params });
|
||||
}
|
||||
|
||||
/** 获得联系人列表(精简) */
|
||||
|
||||
@@ -108,7 +108,7 @@ export function deleteContract(id: number) {
|
||||
|
||||
/** 导出合同 */
|
||||
export function exportContract(params: any) {
|
||||
return requestClient.download('/crm/contract/export-excel', params);
|
||||
return requestClient.download('/crm/contract/export-excel', { params });
|
||||
}
|
||||
|
||||
/** 提交审核 */
|
||||
|
||||
@@ -74,7 +74,7 @@ export function deleteCustomer(id: number) {
|
||||
|
||||
/** 导出客户 */
|
||||
export function exportCustomer(params: any) {
|
||||
return requestClient.download('/crm/customer/export-excel', params);
|
||||
return requestClient.download('/crm/customer/export-excel', { params });
|
||||
}
|
||||
|
||||
/** 下载客户导入模板 */
|
||||
|
||||
@@ -53,5 +53,5 @@ export function deleteProduct(id: number) {
|
||||
|
||||
/** 导出产品 */
|
||||
export function exportProduct(params: any) {
|
||||
return requestClient.download('/crm/product/export-excel', params);
|
||||
return requestClient.download('/crm/product/export-excel', { params });
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ export function deleteReceivable(id: number) {
|
||||
|
||||
/** 导出回款 */
|
||||
export function exportReceivable(params: any) {
|
||||
return requestClient.download('/crm/receivable/export-excel', params);
|
||||
return requestClient.download('/crm/receivable/export-excel', { params });
|
||||
}
|
||||
|
||||
/** 提交审核 */
|
||||
|
||||
@@ -50,6 +50,7 @@ export namespace ErpPurchaseInApi {
|
||||
status?: number;
|
||||
}
|
||||
|
||||
// TODO @nehc:updatePurchaseInStatus 是不是需要?
|
||||
/** 采购入库状态更新参数 */
|
||||
export interface PurchaseInStatusParams {
|
||||
id: number;
|
||||
|
||||
@@ -24,7 +24,6 @@ export namespace ErpSaleOrderApi {
|
||||
depositPrice?: number; // 定金金额,单位:元
|
||||
items?: SaleOrderItem[]; // 销售订单产品明细列表
|
||||
}
|
||||
/** ERP 销售订单产品明细 */
|
||||
export interface SaleOrderItem {
|
||||
id?: number; // 订单项编号
|
||||
orderId?: number; // 采购订单编号
|
||||
|
||||
@@ -17,7 +17,6 @@ export namespace ErpStockCheckApi {
|
||||
creatorName?: string; // 创建人
|
||||
items?: StockCheckItem[]; // 盘点产品清单
|
||||
}
|
||||
// 库存盘点单产品信息
|
||||
export interface StockCheckItem {
|
||||
id?: number; // 编号
|
||||
warehouseId?: number; // 仓库编号
|
||||
@@ -33,6 +32,7 @@ export namespace ErpStockCheckApi {
|
||||
stockCount?: number; // 账面库存
|
||||
remark?: string; // 备注
|
||||
}
|
||||
|
||||
/** 库存盘点单分页查询参数 */
|
||||
export interface StockCheckPageParams extends PageParam {
|
||||
no?: string;
|
||||
|
||||
@@ -71,7 +71,7 @@ export namespace InfraCodegenApi {
|
||||
}
|
||||
|
||||
/** 创建代码生成请求 */
|
||||
export interface CodegenCreateListReq {
|
||||
export interface CodegenCreateListReqVO {
|
||||
dataSourceConfigId?: number;
|
||||
tableNames: string[];
|
||||
}
|
||||
@@ -136,7 +136,9 @@ export function getSchemaTableList(params: any) {
|
||||
}
|
||||
|
||||
/** 基于数据库的表结构,创建代码生成器的表定义 */
|
||||
export function createCodegenList(data: InfraCodegenApi.CodegenCreateListReq) {
|
||||
export function createCodegenList(
|
||||
data: InfraCodegenApi.CodegenCreateListReqVO,
|
||||
) {
|
||||
return requestClient.post('/infra/codegen/create-list', data);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,5 +55,7 @@ export function deleteDemo01ContactList(ids: number[]) {
|
||||
|
||||
/** 导出示例联系人 */
|
||||
export function exportDemo01Contact(params: any) {
|
||||
return requestClient.download('/infra/demo01-contact/export-excel', params);
|
||||
return requestClient.download('/infra/demo01-contact/export-excel', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -42,5 +42,7 @@ export function deleteDemo02Category(id: number) {
|
||||
|
||||
/** 导出示例分类 */
|
||||
export function exportDemo02Category(params: any) {
|
||||
return requestClient.download('/infra/demo02-category/export-excel', params);
|
||||
return requestClient.download('/infra/demo02-category/export-excel', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,10 +70,9 @@ export function deleteDemo03StudentList(ids: number[]) {
|
||||
|
||||
/** 导出学生 */
|
||||
export function exportDemo03Student(params: any) {
|
||||
return requestClient.download(
|
||||
'/infra/demo03-student-erp/export-excel',
|
||||
return requestClient.download('/infra/demo03-student-erp/export-excel', {
|
||||
params,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 子表(学生课程) ====================
|
||||
|
||||
@@ -72,10 +72,9 @@ export function deleteDemo03StudentList(ids: number[]) {
|
||||
|
||||
/** 导出学生 */
|
||||
export function exportDemo03Student(params: any) {
|
||||
return requestClient.download(
|
||||
'/infra/demo03-student-inner/export-excel',
|
||||
return requestClient.download('/infra/demo03-student-inner/export-excel', {
|
||||
params,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 子表(学生课程) ====================
|
||||
|
||||
@@ -72,10 +72,9 @@ export function deleteDemo03StudentList(ids: number[]) {
|
||||
|
||||
/** 导出学生 */
|
||||
export function exportDemo03Student(params: any) {
|
||||
return requestClient.download(
|
||||
'/infra/demo03-student-normal/export-excel',
|
||||
return requestClient.download('/infra/demo03-student-normal/export-excel', {
|
||||
params,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 子表(学生课程) ====================
|
||||
|
||||
@@ -113,6 +113,8 @@ export namespace MallSpuApi {
|
||||
createTime?: Date;
|
||||
/** 商品状态 */
|
||||
status?: number;
|
||||
/** 浏览量 */
|
||||
browseCount?: number;
|
||||
}
|
||||
|
||||
/** 商品状态更新 */
|
||||
|
||||
@@ -31,6 +31,8 @@ export namespace MallDeliveryPickUpStoreApi {
|
||||
status: number;
|
||||
/** 绑定用户编号组数 */
|
||||
verifyUserIds: number[];
|
||||
/** 营业时间 用于fieldMappingTime */
|
||||
rangeTime: any[];
|
||||
}
|
||||
|
||||
/** 绑定自提店员请求 */
|
||||
|
||||
@@ -16,6 +16,7 @@ export namespace PayAppApi {
|
||||
merchantId: number;
|
||||
merchantName: string;
|
||||
createTime?: Date;
|
||||
channelCodes: string[];
|
||||
}
|
||||
|
||||
/** 更新状态请求 */
|
||||
|
||||
@@ -19,7 +19,7 @@ export namespace SystemMailTemplateApi {
|
||||
}
|
||||
|
||||
/** 邮件发送信息 */
|
||||
export interface MailSendReq {
|
||||
export interface MailSendReqVO {
|
||||
toMails: string[];
|
||||
ccMails?: string[];
|
||||
bccMails?: string[];
|
||||
@@ -66,6 +66,6 @@ export function deleteMailTemplateList(ids: number[]) {
|
||||
}
|
||||
|
||||
/** 发送邮件 */
|
||||
export function sendMail(data: SystemMailTemplateApi.MailSendReq) {
|
||||
export function sendMail(data: SystemMailTemplateApi.MailSendReqVO) {
|
||||
return requestClient.post('/system/mail-template/send-mail', data);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export namespace SystemNotifyTemplateApi {
|
||||
}
|
||||
|
||||
/** 发送站内信请求 */
|
||||
export interface NotifySendReq {
|
||||
export interface NotifySendReqVO {
|
||||
userId: number;
|
||||
userType: number;
|
||||
templateCode: string;
|
||||
@@ -74,6 +74,6 @@ export function exportNotifyTemplate(params: any) {
|
||||
}
|
||||
|
||||
/** 发送站内信 */
|
||||
export function sendNotify(data: SystemNotifyTemplateApi.NotifySendReq) {
|
||||
export function sendNotify(data: SystemNotifyTemplateApi.NotifySendReqVO) {
|
||||
return requestClient.post('/system/notify-template/send-notify', data);
|
||||
}
|
||||
|
||||
@@ -55,3 +55,10 @@ export function updateOAuth2Client(data: SystemOAuth2ClientApi.OAuth2Client) {
|
||||
export function deleteOAuth2Client(id: number) {
|
||||
return requestClient.delete(`/system/oauth2-client/delete?id=${id}`);
|
||||
}
|
||||
|
||||
/** 批量删除 OAuth2.0 客户端 */
|
||||
export function deleteOAuth2ClientList(ids: number[]) {
|
||||
return requestClient.delete(
|
||||
`/system/oauth2-client/delete-list?ids=${ids.join(',')}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export namespace SystemSmsTemplateApi {
|
||||
}
|
||||
|
||||
/** 发送短信请求 */
|
||||
export interface SmsSendReq {
|
||||
export interface SmsSendReqVO {
|
||||
mobile: string;
|
||||
templateCode: string;
|
||||
templateParams: Record<string, any>;
|
||||
@@ -72,6 +72,6 @@ export function exportSmsTemplate(params: any) {
|
||||
}
|
||||
|
||||
/** 发送短信 */
|
||||
export function sendSms(data: SystemSmsTemplateApi.SmsSendReq) {
|
||||
export function sendSms(data: SystemSmsTemplateApi.SmsSendReqVO) {
|
||||
return requestClient.post('/system/sms-template/send-sms', data);
|
||||
}
|
||||
|
||||
@@ -46,3 +46,10 @@ export function updateSocialClient(data: SystemSocialClientApi.SocialClient) {
|
||||
export function deleteSocialClient(id: number) {
|
||||
return requestClient.delete(`/system/social-client/delete?id=${id}`);
|
||||
}
|
||||
|
||||
/** 批量删除社交客户端 */
|
||||
export function deleteSocialClientList(ids: number[]) {
|
||||
return requestClient.delete(
|
||||
`/system/social-client/delete-list?ids=${ids.join(',')}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export function deleteUserList(ids: number[]) {
|
||||
|
||||
/** 导出用户 */
|
||||
export function exportUser(params: any) {
|
||||
return requestClient.download('/system/user/export-excel', params);
|
||||
return requestClient.download('/system/user/export-excel', { params });
|
||||
}
|
||||
|
||||
/** 下载用户导入模板 */
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { Item } from './ui/typing';
|
||||
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
import { Tinyflow as TinyflowNative } from './ui/index';
|
||||
|
||||
import './ui/index.css';
|
||||
|
||||
const props = defineProps<{
|
||||
className?: string;
|
||||
data?: Record<string, any>;
|
||||
provider?: {
|
||||
internal?: () => Item[] | Promise<Item[]>;
|
||||
knowledge?: () => Item[] | Promise<Item[]>;
|
||||
llm?: () => Item[] | Promise<Item[]>;
|
||||
};
|
||||
style?: Record<string, string>;
|
||||
}>();
|
||||
|
||||
const divRef = ref<HTMLDivElement | null>(null);
|
||||
let tinyflow: null | TinyflowNative = null;
|
||||
// 定义默认的 provider 方法
|
||||
const defaultProvider = {
|
||||
llm: () => [] as Item[],
|
||||
knowledge: () => [] as Item[],
|
||||
internal: () => [] as Item[],
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (divRef.value) {
|
||||
// 合并默认 provider 和传入的 props.provider
|
||||
const mergedProvider = {
|
||||
...defaultProvider,
|
||||
...props.provider,
|
||||
};
|
||||
tinyflow = new TinyflowNative({
|
||||
element: divRef.value as Element,
|
||||
data: props.data || {},
|
||||
provider: mergedProvider,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (tinyflow) {
|
||||
tinyflow.destroy();
|
||||
tinyflow = null;
|
||||
}
|
||||
});
|
||||
|
||||
const getData = () => {
|
||||
if (tinyflow) {
|
||||
return tinyflow.getData();
|
||||
}
|
||||
console.warn('Tinyflow instance is not initialized');
|
||||
return null;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
getData,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="divRef"
|
||||
class="tinyflow"
|
||||
:class="[className]"
|
||||
:style="style"
|
||||
style="height: 100%"
|
||||
></div>
|
||||
</template>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,68 +0,0 @@
|
||||
export interface Item {
|
||||
children?: Item[];
|
||||
label: string;
|
||||
value: number | string;
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Viewport {
|
||||
x: number;
|
||||
y: number;
|
||||
zoom: number;
|
||||
}
|
||||
|
||||
export interface Node {
|
||||
data?: Record<string, any>;
|
||||
draggable?: boolean;
|
||||
height?: number;
|
||||
id: string;
|
||||
position: Position;
|
||||
selected?: boolean;
|
||||
type?: string;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export interface Edge {
|
||||
animated?: boolean;
|
||||
id: string;
|
||||
label?: string;
|
||||
source: string;
|
||||
target: string;
|
||||
type?: string;
|
||||
}
|
||||
export type TinyflowData = Partial<{
|
||||
edges: Edge[];
|
||||
nodes: Node[];
|
||||
viewport: Viewport;
|
||||
}>;
|
||||
|
||||
export interface TinyflowOptions {
|
||||
data?: TinyflowData;
|
||||
element: Element | string;
|
||||
provider?: {
|
||||
internal?: () => Item[] | Promise<Item[]>;
|
||||
knowledge?: () => Item[] | Promise<Item[]>;
|
||||
llm?: () => Item[] | Promise<Item[]>;
|
||||
};
|
||||
}
|
||||
|
||||
export declare class Tinyflow {
|
||||
private _init;
|
||||
private _setOptions;
|
||||
private options;
|
||||
private rootEl;
|
||||
private svelteFlowInstance;
|
||||
constructor(options: TinyflowOptions);
|
||||
destroy(): void;
|
||||
getData(): {
|
||||
edges: Edge[];
|
||||
nodes: Node[];
|
||||
viewport: Viewport;
|
||||
};
|
||||
getOptions(): TinyflowOptions;
|
||||
setData(data: TinyflowData): void;
|
||||
}
|
||||
@@ -0,0 +1,685 @@
|
||||
<script lang="ts" setup>
|
||||
// import 'bpmn-js/dist/assets/diagram-js.css' // 左边工具栏以及编辑节点的样式
|
||||
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
|
||||
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'
|
||||
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'
|
||||
// import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // 右侧框样式
|
||||
import {
|
||||
computed,
|
||||
defineEmits,
|
||||
defineOptions,
|
||||
defineProps,
|
||||
h,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
provide,
|
||||
ref,
|
||||
} from 'vue';
|
||||
|
||||
import {
|
||||
AlignLeftOutlined,
|
||||
ApiOutlined,
|
||||
DownloadOutlined,
|
||||
EyeOutlined,
|
||||
FolderOpenOutlined,
|
||||
RedoOutlined,
|
||||
ReloadOutlined,
|
||||
UndoOutlined,
|
||||
WarningOutlined,
|
||||
ZoomInOutlined,
|
||||
ZoomOutOutlined,
|
||||
} from '@vben/icons';
|
||||
|
||||
import { Button, ButtonGroup, message, Modal, Tooltip } from 'ant-design-vue';
|
||||
// 模拟流转流程
|
||||
// @ts-ignore
|
||||
import tokenSimulation from 'bpmn-js-token-simulation';
|
||||
import BpmnModeler from 'bpmn-js/lib/Modeler';
|
||||
// 代码高亮插件
|
||||
// import hljs from 'highlight.js/lib/highlight'
|
||||
// import 'highlight.js/styles/github-gist.css'
|
||||
// hljs.registerLanguage('xml', 'highlight.js/lib/languages/xml')
|
||||
// hljs.registerLanguage('json', 'highlight.js/lib/languages/json')
|
||||
// const eventName = reactive({
|
||||
// name: ''
|
||||
// })
|
||||
import hljs from 'highlight.js'; // 导入代码高亮文件
|
||||
// 引入json转换与高亮
|
||||
// import xml2js from 'xml-js'
|
||||
// import xml2js from 'fast-xml-parser'
|
||||
import { parseXmlString, XmlNode } from 'steady-xml';
|
||||
|
||||
import DefaultEmptyXML from './plugins/defaultEmpty';
|
||||
import activitiModdleDescriptor from './plugins/descriptor/activitiDescriptor.json';
|
||||
// 标签解析构建器
|
||||
// import bpmnPropertiesProvider from "bpmn-js-properties-panel/lib/provider/bpmn";
|
||||
// import propertiesPanelModule from 'bpmn-js-properties-panel'
|
||||
// import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'
|
||||
// 标签解析 Moddle
|
||||
import camundaModdleDescriptor from './plugins/descriptor/camundaDescriptor.json';
|
||||
import flowableModdleDescriptor from './plugins/descriptor/flowableDescriptor.json';
|
||||
import activitiModdleExtension from './plugins/extension-moddle/activiti';
|
||||
// 标签解析 Extension
|
||||
import camundaModdleExtension from './plugins/extension-moddle/camunda';
|
||||
import flowableModdleExtension from './plugins/extension-moddle/flowable';
|
||||
// 翻译方法
|
||||
import customTranslate from './plugins/translate/customTranslate';
|
||||
import translationsCN from './plugins/translate/zh';
|
||||
|
||||
import 'highlight.js/styles/github.css';
|
||||
|
||||
defineOptions({ name: 'MyProcessDesigner' });
|
||||
|
||||
const props = defineProps({
|
||||
value: { type: String, default: '' }, // xml 字符串
|
||||
// valueWatch: true, // xml 字符串的 watch 状态
|
||||
processId: { type: String, default: '' }, // 流程 key 标识
|
||||
processName: { type: String, default: '' }, // 流程 name 名字
|
||||
formId: { type: Number, default: undefined }, // 流程 form 表单编号
|
||||
translations: {
|
||||
// 自定义的翻译文件
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
// eslint-disable-next-line vue/require-default-prop
|
||||
additionalModel: [Object, Array], // 自定义model
|
||||
moddleExtension: {
|
||||
// 自定义moddle
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
onlyCustomizeAddi: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
onlyCustomizeModdle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
simulation: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
keyboard: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
prefix: {
|
||||
type: String,
|
||||
default: 'camunda',
|
||||
},
|
||||
events: {
|
||||
type: Array,
|
||||
default: () => ['element.click'],
|
||||
},
|
||||
headerButtonSize: {
|
||||
type: String,
|
||||
default: 'small',
|
||||
validator: (value: string) =>
|
||||
['default', 'medium', 'mini', 'small'].includes(value),
|
||||
},
|
||||
headerButtonType: {
|
||||
type: String,
|
||||
default: 'primary',
|
||||
validator: (value: string) =>
|
||||
['danger', 'default', 'info', 'primary', 'success', 'warning'].includes(
|
||||
value,
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
// 导入代码高亮样式
|
||||
|
||||
const emit = defineEmits([
|
||||
'destroy',
|
||||
'init-finished',
|
||||
'save',
|
||||
'commandStack-changed',
|
||||
'input',
|
||||
'change',
|
||||
'canvas-viewbox-changed',
|
||||
// eventName.name
|
||||
'element-click',
|
||||
]);
|
||||
|
||||
const bpmnCanvas = ref();
|
||||
const refFile = ref();
|
||||
|
||||
/**
|
||||
* 代码高亮
|
||||
*/
|
||||
const highlightedCode = (code: string) => {
|
||||
// 高亮
|
||||
if (previewType.value === 'json') {
|
||||
code = JSON.stringify(code, null, 2);
|
||||
}
|
||||
const result = hljs.highlight(code, {
|
||||
language: previewType.value,
|
||||
ignoreIllegals: true,
|
||||
});
|
||||
return result.value || ' ';
|
||||
};
|
||||
|
||||
provide('configGlobal', props);
|
||||
let bpmnModeler: any = null;
|
||||
const defaultZoom = ref(1);
|
||||
const previewModelVisible = ref(false);
|
||||
const simulationStatus = ref(false);
|
||||
const previewResult = ref('');
|
||||
const previewType = ref('xml');
|
||||
const recoverable = ref(false);
|
||||
const revocable = ref(false);
|
||||
const additionalModules = computed(() => {
|
||||
// console.log(props.additionalModel, 'additionalModel');
|
||||
const Modules: any[] = [];
|
||||
// 仅保留用户自定义扩展模块
|
||||
if (props.onlyCustomizeAddi) {
|
||||
if (
|
||||
Object.prototype.toString.call(props.additionalModel) === '[object Array]'
|
||||
) {
|
||||
return props.additionalModel || [];
|
||||
}
|
||||
return [props.additionalModel];
|
||||
}
|
||||
|
||||
// 插入用户自定义扩展模块
|
||||
if (
|
||||
Object.prototype.toString.call(props.additionalModel) === '[object Array]'
|
||||
) {
|
||||
Modules.push(...(props.additionalModel as any[]));
|
||||
} else {
|
||||
props.additionalModel && Modules.push(props.additionalModel);
|
||||
}
|
||||
|
||||
// 翻译模块
|
||||
const TranslateModule = {
|
||||
translate: ['value', customTranslate(props.translations || translationsCN)],
|
||||
};
|
||||
Modules.push(TranslateModule);
|
||||
|
||||
// 模拟流转模块
|
||||
if (props.simulation) {
|
||||
Modules.push(tokenSimulation);
|
||||
}
|
||||
|
||||
// 根据需要的流程类型设置扩展元素构建模块
|
||||
// if (this.prefix === "bpmn") {
|
||||
// Modules.push(bpmnModdleExtension);
|
||||
// }
|
||||
// console.log(props.prefix, 'props.prefix ');
|
||||
if (props.prefix === 'camunda') {
|
||||
Modules.push(camundaModdleExtension);
|
||||
}
|
||||
if (props.prefix === 'flowable') {
|
||||
Modules.push(flowableModdleExtension);
|
||||
}
|
||||
if (props.prefix === 'activiti') {
|
||||
Modules.push(activitiModdleExtension);
|
||||
}
|
||||
|
||||
return Modules;
|
||||
});
|
||||
const moddleExtensions = computed(() => {
|
||||
// console.log(props.onlyCustomizeModdle, 'props.onlyCustomizeModdle');
|
||||
// console.log(props.moddleExtension, 'props.moddleExtension');
|
||||
// console.log(props.prefix, 'props.prefix');
|
||||
const Extensions: any = {};
|
||||
// 仅使用用户自定义模块
|
||||
if (props.onlyCustomizeModdle) {
|
||||
return props.moddleExtension || null;
|
||||
}
|
||||
|
||||
// 插入用户自定义模块
|
||||
if (props.moddleExtension) {
|
||||
for (const key in props.moddleExtension) {
|
||||
Extensions[key] = props.moddleExtension[key];
|
||||
}
|
||||
}
|
||||
|
||||
// 根据需要的 "流程类型" 设置 对应的解析文件
|
||||
if (props.prefix === 'activiti') {
|
||||
Extensions.activiti = activitiModdleDescriptor;
|
||||
}
|
||||
if (props.prefix === 'flowable') {
|
||||
Extensions.flowable = flowableModdleDescriptor;
|
||||
}
|
||||
if (props.prefix === 'camunda') {
|
||||
Extensions.camunda = camundaModdleDescriptor;
|
||||
}
|
||||
return Extensions;
|
||||
});
|
||||
// console.log(additionalModules, 'additionalModules()');
|
||||
// console.log(moddleExtensions, 'moddleExtensions()');
|
||||
const initBpmnModeler = () => {
|
||||
if (bpmnModeler) return;
|
||||
const data: any = document.querySelector('#bpmnCanvas');
|
||||
// console.log(data, 'data');
|
||||
// console.log(props.keyboard, 'props.keyboard');
|
||||
// console.log(additionalModules, 'additionalModules()');
|
||||
// console.log(moddleExtensions, 'moddleExtensions()');
|
||||
|
||||
bpmnModeler = new BpmnModeler({
|
||||
// container: this.$refs['bpmn-canvas'],
|
||||
// container: getCurrentInstance(),
|
||||
// container: needClass,
|
||||
// container: bpmnCanvas.value,
|
||||
container: data,
|
||||
// width: '100%',
|
||||
// 添加控制板
|
||||
// propertiesPanel: {
|
||||
// parent: '#js-properties-panel'
|
||||
// },
|
||||
keyboard: props.keyboard ? { bindTo: document } : null,
|
||||
// additionalModules: additionalModules.value,
|
||||
additionalModules: additionalModules.value as any[],
|
||||
moddleExtensions: moddleExtensions.value,
|
||||
|
||||
// additionalModules: [
|
||||
// additionalModules.value
|
||||
// propertiesPanelModule,
|
||||
// propertiesProviderModule
|
||||
// propertiesProviderModule
|
||||
// ],
|
||||
// moddleExtensions: { camunda: moddleExtensions.value }
|
||||
});
|
||||
|
||||
// bpmnModeler.createDiagram()
|
||||
|
||||
// console.log(bpmnModeler, 'bpmnModeler111111')
|
||||
// eslint-disable-next-line vue/custom-event-name-casing
|
||||
emit('init-finished', bpmnModeler);
|
||||
initModelListeners();
|
||||
};
|
||||
|
||||
const initModelListeners = () => {
|
||||
const EventBus = bpmnModeler.get('eventBus');
|
||||
// console.log(EventBus, 'EventBus');
|
||||
// 注册需要的监听事件, 将. 替换为 - , 避免解析异常
|
||||
props.events.forEach((event: any) => {
|
||||
EventBus.on(event, (eventObj: any) => {
|
||||
// const eventName = event.replaceAll('.', '-');
|
||||
// eventName.name = eventName
|
||||
const element = eventObj ? eventObj.element : null;
|
||||
// console.log(eventName, 'eventName');
|
||||
// console.log(element, 'element');
|
||||
// eslint-disable-next-line vue/custom-event-name-casing
|
||||
emit('element-click', element, eventObj);
|
||||
// emit(eventName, element, eventObj)
|
||||
});
|
||||
});
|
||||
// 监听图形改变返回xml
|
||||
EventBus.on('commandStack.changed', async (event: any) => {
|
||||
try {
|
||||
recoverable.value = bpmnModeler.get('commandStack').canRedo();
|
||||
revocable.value = bpmnModeler.get('commandStack').canUndo();
|
||||
const { xml } = await bpmnModeler.saveXML({ format: true });
|
||||
// eslint-disable-next-line vue/custom-event-name-casing
|
||||
emit('commandStack-changed', event);
|
||||
emit('input', xml);
|
||||
emit('change', xml);
|
||||
emit('save', xml);
|
||||
} catch {
|
||||
// console.error(`[Process Designer Warn]: ${e.message || e}`);
|
||||
}
|
||||
});
|
||||
// 监听视图缩放变化
|
||||
bpmnModeler.on('canvas.viewbox.changed', ({ viewbox }: { viewbox: any }) => {
|
||||
// eslint-disable-next-line vue/custom-event-name-casing
|
||||
emit('canvas-viewbox-changed', { viewbox });
|
||||
const { scale } = viewbox;
|
||||
defaultZoom.value = Math.floor(scale * 100) / 100;
|
||||
});
|
||||
};
|
||||
/* 创建新的流程图 */
|
||||
const createNewDiagram = async (xml: any) => {
|
||||
// console.log(xml, 'xml');
|
||||
// 将字符串转换成图显示出来
|
||||
const newId = props.processId || `Process_${Date.now()}`;
|
||||
const newName = props.processName || `业务流程_${Date.now()}`;
|
||||
const xmlString = xml || DefaultEmptyXML(newId, newName, props.prefix);
|
||||
try {
|
||||
// console.log(xmlString, 'xmlString')
|
||||
// console.log(this.bpmnModeler.importXML);
|
||||
const { warnings } = await bpmnModeler.importXML(xmlString);
|
||||
// console.log(warnings, 'warnings');
|
||||
if (warnings && warnings.length > 0) {
|
||||
// warnings.forEach((warn: any) => console.warn(warn));
|
||||
}
|
||||
} catch {
|
||||
// console.error(`[Process Designer Warn]: ${e.message || e}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 下载流程图到本地
|
||||
const downloadProcess = async (type: string) => {
|
||||
try {
|
||||
// 按需要类型创建文件并下载
|
||||
if (type === 'xml' || type === 'bpmn') {
|
||||
const { err, xml } = await bpmnModeler.saveXML();
|
||||
// 读取异常时抛出异常
|
||||
if (err) {
|
||||
// console.error(`[Process Designer Warn ]: ${err.message || err}`);
|
||||
}
|
||||
const { href, filename } = setEncoded(type.toUpperCase(), xml);
|
||||
downloadFunc(href, filename);
|
||||
} else {
|
||||
const { err, svg } = await bpmnModeler.saveSVG();
|
||||
// 读取异常时抛出异常
|
||||
if (err) {
|
||||
// return console.error(err);
|
||||
}
|
||||
const { href, filename } = setEncoded('SVG', svg);
|
||||
downloadFunc(href, filename);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`[Process Designer Warn ]: ${error.message || error}`);
|
||||
}
|
||||
// 文件下载方法
|
||||
function downloadFunc(href: string, filename: string) {
|
||||
if (href && filename) {
|
||||
const a = document.createElement('a');
|
||||
a.download = filename; // 指定下载的文件名
|
||||
a.href = href; // URL对象
|
||||
a.click(); // 模拟点击
|
||||
URL.revokeObjectURL(a.href); // 释放URL 对象
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 根据所需类型进行转码并返回下载地址
|
||||
const setEncoded = (type: string, data: string) => {
|
||||
const filename = 'diagram';
|
||||
const encodedData = encodeURIComponent(data);
|
||||
return {
|
||||
filename: `${filename}.${type}`,
|
||||
href: `data:application/${
|
||||
type === 'svg' ? 'text/xml' : 'bpmn20-xml'
|
||||
};charset=UTF-8,${encodedData}`,
|
||||
data,
|
||||
};
|
||||
};
|
||||
|
||||
// 加载本地文件
|
||||
const importLocalFile = () => {
|
||||
const file = refFile.value.files[0];
|
||||
const reader = new FileReader();
|
||||
// eslint-disable-next-line unicorn/prefer-blob-reading-methods
|
||||
reader.readAsText(file);
|
||||
reader.addEventListener('load', function () {
|
||||
const xmlStr = this.result;
|
||||
createNewDiagram(xmlStr);
|
||||
emit('save', xmlStr);
|
||||
});
|
||||
};
|
||||
/* ------------------------------------------------ refs methods ------------------------------------------------------ */
|
||||
const downloadProcessAsXml = () => {
|
||||
downloadProcess('xml');
|
||||
};
|
||||
const downloadProcessAsBpmn = () => {
|
||||
downloadProcess('bpmn');
|
||||
};
|
||||
const downloadProcessAsSvg = () => {
|
||||
downloadProcess('svg');
|
||||
};
|
||||
const processSimulation = () => {
|
||||
simulationStatus.value = !simulationStatus.value;
|
||||
// console.log(
|
||||
// bpmnModeler.get('toggleMode', 'strict'),
|
||||
// "bpmnModeler.get('toggleMode')",
|
||||
// );
|
||||
props.simulation && bpmnModeler.get('toggleMode', 'strict').toggleMode();
|
||||
};
|
||||
const processRedo = () => {
|
||||
bpmnModeler.get('commandStack').redo();
|
||||
};
|
||||
const processUndo = () => {
|
||||
bpmnModeler.get('commandStack').undo();
|
||||
};
|
||||
const processZoomIn = (zoomStep = 0.1) => {
|
||||
const newZoom = Math.floor(defaultZoom.value * 100 + zoomStep * 100) / 100;
|
||||
if (newZoom > 4) {
|
||||
throw new Error(
|
||||
'[Process Designer Warn ]: The zoom ratio cannot be greater than 4',
|
||||
);
|
||||
}
|
||||
defaultZoom.value = newZoom;
|
||||
bpmnModeler.get('canvas').zoom(defaultZoom.value);
|
||||
};
|
||||
const processZoomOut = (zoomStep = 0.1) => {
|
||||
const newZoom = Math.floor(defaultZoom.value * 100 - zoomStep * 100) / 100;
|
||||
if (newZoom < 0.2) {
|
||||
throw new Error(
|
||||
'[Process Designer Warn ]: The zoom ratio cannot be less than 0.2',
|
||||
);
|
||||
}
|
||||
defaultZoom.value = newZoom;
|
||||
bpmnModeler.get('canvas').zoom(defaultZoom.value);
|
||||
};
|
||||
const processReZoom = () => {
|
||||
defaultZoom.value = 1;
|
||||
bpmnModeler.get('canvas').zoom('fit-viewport', 'auto');
|
||||
};
|
||||
const processRestart = () => {
|
||||
recoverable.value = false;
|
||||
revocable.value = false;
|
||||
createNewDiagram(null);
|
||||
};
|
||||
const elementsAlign = (align: string) => {
|
||||
const Align = bpmnModeler.get('alignElements');
|
||||
const Selection = bpmnModeler.get('selection');
|
||||
const SelectedElements = Selection.get();
|
||||
if (!SelectedElements || SelectedElements.length <= 1) {
|
||||
message.warning('请按住 Shift 键选择多个元素对齐');
|
||||
// alert('请按住 Ctrl 键选择多个元素对齐
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '警告',
|
||||
content: '自动对齐可能造成图形变形,是否继续?',
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
icon: h(WarningOutlined) as any,
|
||||
onOk() {
|
||||
Align.trigger(SelectedElements, align);
|
||||
},
|
||||
});
|
||||
};
|
||||
/* ----------------------------- 方法结束 ---------------------------------*/
|
||||
const previewProcessXML = () => {
|
||||
// console.log(bpmnModeler.saveXML, 'bpmnModeler');
|
||||
bpmnModeler.saveXML({ format: true }).then(({ xml }: { xml: string }) => {
|
||||
// console.log(xml, 'xml111111')
|
||||
previewResult.value = xml;
|
||||
previewType.value = 'xml';
|
||||
previewModelVisible.value = true;
|
||||
});
|
||||
};
|
||||
const previewProcessJson = () => {
|
||||
bpmnModeler.saveXML({ format: true }).then(({ xml }: { xml: string }) => {
|
||||
const rootNodes = new XmlNode('root' as any, parseXmlString(xml));
|
||||
previewResult.value = rootNodes.parent?.toJSON() as unknown as string;
|
||||
previewType.value = 'json';
|
||||
previewModelVisible.value = true;
|
||||
});
|
||||
};
|
||||
|
||||
/* ------------------------------------------------ 芋道源码 methods ------------------------------------------------------ */
|
||||
onMounted(() => {
|
||||
initBpmnModeler();
|
||||
createNewDiagram(props.value);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
if (bpmnModeler) bpmnModeler.destroy();
|
||||
emit('destroy', bpmnModeler);
|
||||
bpmnModeler = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-process-designer">
|
||||
<div
|
||||
class="my-process-designer__header"
|
||||
style="z-index: 999; display: table-row-group"
|
||||
>
|
||||
<slot name="control-header"></slot>
|
||||
<template v-if="!$slots['control-header']">
|
||||
<ButtonGroup key="file-control">
|
||||
<Button
|
||||
:icon="h(FolderOpenOutlined)"
|
||||
title="打开文件"
|
||||
@click="refFile.click()"
|
||||
/>
|
||||
<Tooltip placement="bottom">
|
||||
<template #title>
|
||||
<div>
|
||||
<Button type="link" @click="downloadProcessAsXml()">
|
||||
下载为XML文件
|
||||
</Button>
|
||||
<br />
|
||||
<Button type="link" @click="downloadProcessAsSvg()">
|
||||
下载为SVG文件
|
||||
</Button>
|
||||
<br />
|
||||
<Button type="link" @click="downloadProcessAsBpmn()">
|
||||
下载为BPMN文件
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
<Button :icon="h(DownloadOutlined)" title="下载文件" />
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<template #title>
|
||||
<Button type="link" @click="previewProcessXML">预览XML</Button>
|
||||
<br />
|
||||
<Button type="link" @click="previewProcessJson">预览JSON</Button>
|
||||
</template>
|
||||
<Button :icon="h(EyeOutlined)" title="浏览" />
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
v-if="props.simulation"
|
||||
:title="simulationStatus ? '退出模拟' : '开启模拟'"
|
||||
>
|
||||
<Button
|
||||
:icon="h(ApiOutlined)"
|
||||
title="模拟"
|
||||
@click="processSimulation"
|
||||
/>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup key="align-control">
|
||||
<Tooltip title="向左对齐">
|
||||
<Button
|
||||
:icon="h(AlignLeftOutlined)"
|
||||
class="align align-bottom"
|
||||
@click="elementsAlign('left')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="向右对齐">
|
||||
<Button
|
||||
:icon="h(AlignLeftOutlined)"
|
||||
class="align align-top"
|
||||
@click="elementsAlign('right')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="向上对齐">
|
||||
<Button
|
||||
:icon="h(AlignLeftOutlined)"
|
||||
class="align align-left"
|
||||
@click="elementsAlign('top')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="向下对齐">
|
||||
<Button
|
||||
:icon="h(AlignLeftOutlined)"
|
||||
class="align align-right"
|
||||
@click="elementsAlign('bottom')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="水平居中">
|
||||
<Button
|
||||
:icon="h(AlignLeftOutlined)"
|
||||
class="align align-center"
|
||||
@click="elementsAlign('center')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="垂直居中">
|
||||
<Button
|
||||
:icon="h(AlignLeftOutlined)"
|
||||
class="align align-middle"
|
||||
@click="elementsAlign('middle')"
|
||||
/>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup key="scale-control">
|
||||
<Tooltip title="缩小视图">
|
||||
<Button
|
||||
:icon="h(ZoomOutOutlined)"
|
||||
@click="processZoomOut()"
|
||||
:disabled="defaultZoom < 0.2"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button>{{ `${Math.floor(defaultZoom * 10 * 10)}%` }}</Button>
|
||||
<Tooltip title="放大视图">
|
||||
<Button
|
||||
:icon="h(ZoomInOutlined)"
|
||||
@click="processZoomIn()"
|
||||
:disabled="defaultZoom > 4"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="重置视图并居中">
|
||||
<Button :icon="h(ReloadOutlined)" @click="processReZoom()" />
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup key="stack-control">
|
||||
<Tooltip title="撤销">
|
||||
<Button
|
||||
:icon="h(UndoOutlined)"
|
||||
@click="processUndo()"
|
||||
:disabled="!revocable"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="恢复">
|
||||
<Button
|
||||
:icon="h(RedoOutlined)"
|
||||
@click="processRedo()"
|
||||
:disabled="!recoverable"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="重新绘制">
|
||||
<Button :icon="h(ReloadOutlined)" @click="processRestart()" />
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
</template>
|
||||
<!-- 用于打开本地文件-->
|
||||
<input
|
||||
type="file"
|
||||
id="files"
|
||||
ref="refFile"
|
||||
style="display: none"
|
||||
accept=".xml, .bpmn"
|
||||
@change="importLocalFile"
|
||||
/>
|
||||
</div>
|
||||
<div class="my-process-designer__container">
|
||||
<div
|
||||
class="my-process-designer__canvas"
|
||||
ref="bpmnCanvas"
|
||||
id="bpmnCanvas"
|
||||
style="width: 1680px; height: 800px"
|
||||
></div>
|
||||
<!-- <div id="js-properties-panel" class="panel"></div> -->
|
||||
<!-- <div class="my-process-designer__canvas" ref="bpmn-canvas"></div> -->
|
||||
</div>
|
||||
<Dialog
|
||||
title="预览"
|
||||
v-model:open="previewModelVisible"
|
||||
width="80%"
|
||||
:scroll="true"
|
||||
style="max-height: 600px"
|
||||
>
|
||||
<div>
|
||||
<pre><code v-dompurify-html="highlightedCode(previewResult)" class="hljs"></code></pre>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,417 @@
|
||||
<script lang="ts" setup>
|
||||
import { defineProps, h, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { BpmProcessInstanceStatus, DICT_TYPE } from '@vben/constants';
|
||||
import { UndoOutlined, ZoomInOutlined, ZoomOutOutlined } from '@vben/icons';
|
||||
import { dateFormatter, formatPast2 } from '@vben/utils';
|
||||
|
||||
import { Button, ButtonGroup, Modal, Row, Table } from 'ant-design-vue';
|
||||
import BpmnViewer from 'bpmn-js/lib/Viewer';
|
||||
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
|
||||
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
|
||||
import '../theme/index.scss';
|
||||
|
||||
const props = defineProps({
|
||||
xml: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
view: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const processCanvas = ref();
|
||||
const bpmnViewer = ref<any | BpmnViewer>(null);
|
||||
const customDefs = ref();
|
||||
const defaultZoom = ref(1); // 默认缩放比例
|
||||
const isLoading = ref(false); // 是否加载中
|
||||
|
||||
const processInstance = ref<any>({}); // 流程实例
|
||||
const tasks = ref([]); // 流程任务
|
||||
|
||||
const dialogVisible = ref(false); // 弹窗可见性
|
||||
const dialogTitle = ref<string | undefined>(undefined); // 弹窗标题
|
||||
const selectActivityType = ref<string | undefined>(undefined); // 选中 Task 的活动编号
|
||||
const selectTasks = ref<any[]>([]); // 选中的任务数组
|
||||
|
||||
/** Zoom:恢复 */
|
||||
const processReZoom = () => {
|
||||
defaultZoom.value = 1;
|
||||
bpmnViewer.value?.get('canvas').zoom('fit-viewport', 'auto');
|
||||
};
|
||||
|
||||
/** Zoom:放大 */
|
||||
const processZoomIn = (zoomStep = 0.1) => {
|
||||
const newZoom = Math.floor(defaultZoom.value * 100 + zoomStep * 100) / 100;
|
||||
if (newZoom > 4) {
|
||||
throw new Error(
|
||||
'[Process Designer Warn ]: The zoom ratio cannot be greater than 4',
|
||||
);
|
||||
}
|
||||
defaultZoom.value = newZoom;
|
||||
bpmnViewer.value?.get('canvas').zoom(defaultZoom.value);
|
||||
};
|
||||
|
||||
/** Zoom:缩小 */
|
||||
const processZoomOut = (zoomStep = 0.1) => {
|
||||
const newZoom = Math.floor(defaultZoom.value * 100 - zoomStep * 100) / 100;
|
||||
if (newZoom < 0.2) {
|
||||
throw new Error(
|
||||
'[Process Designer Warn ]: The zoom ratio cannot be less than 0.2',
|
||||
);
|
||||
}
|
||||
defaultZoom.value = newZoom;
|
||||
bpmnViewer.value?.get('canvas').zoom(defaultZoom.value);
|
||||
};
|
||||
|
||||
/** 流程图预览清空 */
|
||||
const clearViewer = () => {
|
||||
if (processCanvas.value) {
|
||||
processCanvas.value.innerHTML = '';
|
||||
}
|
||||
if (bpmnViewer.value) {
|
||||
bpmnViewer.value.destroy();
|
||||
}
|
||||
bpmnViewer.value = null;
|
||||
};
|
||||
|
||||
/** 添加自定义箭头 */
|
||||
// TODO 芋艿:自定义箭头不生效,有点奇怪!!!!相关的 marker-end、marker-start 暂时也注释了!!!
|
||||
const addCustomDefs = () => {
|
||||
if (!bpmnViewer.value) {
|
||||
return;
|
||||
}
|
||||
const canvas = bpmnViewer.value?.get('canvas');
|
||||
const svg = canvas?._svg;
|
||||
svg.append(customDefs.value);
|
||||
};
|
||||
|
||||
/** 节点选中 */
|
||||
const onSelectElement = (element: any) => {
|
||||
// 清空原选中
|
||||
selectActivityType.value = undefined;
|
||||
dialogTitle.value = undefined;
|
||||
if (!element || !processInstance.value?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// UserTask 的情况
|
||||
const activityType = element.type;
|
||||
selectActivityType.value = activityType;
|
||||
if (activityType === 'bpmn:UserTask') {
|
||||
dialogTitle.value = element.businessObject
|
||||
? element.businessObject.name
|
||||
: undefined;
|
||||
selectTasks.value = tasks.value.filter(
|
||||
(item: any) => item?.taskDefinitionKey === element.id,
|
||||
);
|
||||
dialogVisible.value = true;
|
||||
} else if (
|
||||
activityType === 'bpmn:EndEvent' ||
|
||||
activityType === 'bpmn:StartEvent'
|
||||
) {
|
||||
dialogTitle.value = '审批信息';
|
||||
selectTasks.value = [
|
||||
{
|
||||
assigneeUser: processInstance.value.startUser,
|
||||
createTime: processInstance.value.startTime,
|
||||
endTime: processInstance.value.endTime,
|
||||
status: processInstance.value.status,
|
||||
durationInMillis: processInstance.value.durationInMillis,
|
||||
},
|
||||
];
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
/** 初始化 BPMN 视图 */
|
||||
const importXML = async (xml: string) => {
|
||||
// 清空流程图
|
||||
clearViewer();
|
||||
|
||||
// 初始化流程图
|
||||
if (xml !== null && xml !== '') {
|
||||
try {
|
||||
bpmnViewer.value = new BpmnViewer({
|
||||
additionalModules: [MoveCanvasModule],
|
||||
container: processCanvas.value,
|
||||
});
|
||||
// 增加点击事件
|
||||
bpmnViewer.value.on('element.click', ({ element }: { element: any }) => {
|
||||
onSelectElement(element);
|
||||
});
|
||||
|
||||
// 初始化 BPMN 视图
|
||||
isLoading.value = true;
|
||||
await bpmnViewer.value.importXML(xml);
|
||||
// 自定义成功的箭头
|
||||
addCustomDefs();
|
||||
} catch {
|
||||
clearViewer();
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
// 高亮流程
|
||||
setProcessStatus(props.view);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 高亮流程 */
|
||||
const setProcessStatus = (view: any) => {
|
||||
// 设置相关变量
|
||||
if (!view || !view.processInstance) {
|
||||
return;
|
||||
}
|
||||
processInstance.value = view.processInstance;
|
||||
tasks.value = view.tasks;
|
||||
if (isLoading.value || !bpmnViewer.value) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
unfinishedTaskActivityIds,
|
||||
finishedTaskActivityIds,
|
||||
finishedSequenceFlowActivityIds,
|
||||
rejectedTaskActivityIds,
|
||||
} = view;
|
||||
const canvas: any = bpmnViewer.value.get('canvas');
|
||||
const elementRegistry: any = bpmnViewer.value.get('elementRegistry');
|
||||
|
||||
// 已完成节点
|
||||
if (Array.isArray(finishedSequenceFlowActivityIds)) {
|
||||
finishedSequenceFlowActivityIds.forEach((item: any) => {
|
||||
if (item !== null) {
|
||||
canvas.addMarker(item, 'success');
|
||||
const element = elementRegistry.get(item);
|
||||
const conditionExpression = element.businessObject.conditionExpression;
|
||||
if (conditionExpression) {
|
||||
canvas.addMarker(item, 'condition-expression');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (Array.isArray(finishedTaskActivityIds)) {
|
||||
finishedTaskActivityIds.forEach((item: any) =>
|
||||
canvas.addMarker(item, 'success'),
|
||||
);
|
||||
}
|
||||
|
||||
// 未完成节点
|
||||
if (Array.isArray(unfinishedTaskActivityIds)) {
|
||||
unfinishedTaskActivityIds.forEach((item: any) =>
|
||||
canvas.addMarker(item, 'primary'),
|
||||
);
|
||||
}
|
||||
|
||||
// 被拒绝节点
|
||||
if (Array.isArray(rejectedTaskActivityIds)) {
|
||||
rejectedTaskActivityIds.forEach((item: any) => {
|
||||
if (item !== null) {
|
||||
canvas.addMarker(item, 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 特殊:处理 end 节点的高亮。因为 end 在拒绝、取消时,被后端计算成了 finishedTaskActivityIds 里
|
||||
if (
|
||||
[BpmProcessInstanceStatus.CANCEL, BpmProcessInstanceStatus.REJECT].includes(
|
||||
processInstance.value.status,
|
||||
)
|
||||
) {
|
||||
const endNodes = elementRegistry.filter(
|
||||
(element: any) => element.type === 'bpmn:EndEvent',
|
||||
);
|
||||
endNodes.forEach((item: any) => {
|
||||
canvas.removeMarker(item.id, 'success');
|
||||
if (processInstance.value.status === BpmProcessInstanceStatus.CANCEL) {
|
||||
canvas.addMarker(item.id, 'cancel');
|
||||
} else {
|
||||
canvas.addMarker(item.id, 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.xml,
|
||||
(newXml) => {
|
||||
importXML(newXml);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.view,
|
||||
(newView) => {
|
||||
setProcessStatus(newView);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
/** mounted:初始化 */
|
||||
onMounted(() => {
|
||||
importXML(props.xml);
|
||||
setProcessStatus(props.view);
|
||||
});
|
||||
|
||||
/** unmount:销毁 */
|
||||
onBeforeUnmount(() => {
|
||||
clearViewer();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="process-viewer">
|
||||
<div style="height: 100%" ref="processCanvas" v-show="!isLoading"></div>
|
||||
<!-- 自定义箭头样式,用于已完成状态下流程连线箭头 -->
|
||||
<defs ref="customDefs">
|
||||
<marker
|
||||
id="sequenceflow-end-white-success"
|
||||
viewBox="0 0 20 20"
|
||||
refX="11"
|
||||
refY="10"
|
||||
markerWidth="10"
|
||||
markerHeight="10"
|
||||
orient="auto"
|
||||
>
|
||||
<path
|
||||
class="success-arrow"
|
||||
d="M 1 5 L 11 10 L 1 15 Z"
|
||||
style="
|
||||
stroke-width: 1px;
|
||||
stroke-linecap: round;
|
||||
stroke-dasharray: 10000, 1;
|
||||
"
|
||||
/>
|
||||
</marker>
|
||||
<marker
|
||||
id="conditional-flow-marker-white-success"
|
||||
viewBox="0 0 20 20"
|
||||
refX="-1"
|
||||
refY="10"
|
||||
markerWidth="10"
|
||||
markerHeight="10"
|
||||
orient="auto"
|
||||
>
|
||||
<path
|
||||
class="success-conditional"
|
||||
d="M 0 10 L 8 6 L 16 10 L 8 14 Z"
|
||||
style="
|
||||
stroke-width: 1px;
|
||||
stroke-linecap: round;
|
||||
stroke-dasharray: 10000, 1;
|
||||
"
|
||||
/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- 审批记录 -->
|
||||
<Modal
|
||||
:title="dialogTitle || '审批记录'"
|
||||
v-model:open="dialogVisible"
|
||||
:width="1000"
|
||||
>
|
||||
<Row>
|
||||
<Table :data-source="selectTasks" size="small" :bordered="true">
|
||||
<Table.Column title="序号" align="center" width="50">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column
|
||||
title="审批人"
|
||||
width="100"
|
||||
align="center"
|
||||
v-if="selectActivityType === 'bpmn:UserTask'"
|
||||
>
|
||||
<template #default="{ record }">
|
||||
{{ record.assigneeUser?.nickname || record.ownerUser?.nickname }}
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column
|
||||
title="发起人"
|
||||
data-index="assigneeUser.nickname"
|
||||
width="100"
|
||||
align="center"
|
||||
v-else
|
||||
/>
|
||||
<Table.Column title="部门" width="100" align="center">
|
||||
<template #default="{ record }">
|
||||
{{ record.assigneeUser?.deptName || record.ownerUser?.deptName }}
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column
|
||||
:custom-render="({ text }) => dateFormatter(text)"
|
||||
align="center"
|
||||
title="开始时间"
|
||||
data-index="createTime"
|
||||
width="140"
|
||||
/>
|
||||
<Table.Column
|
||||
:custom-render="({ text }) => dateFormatter(text)"
|
||||
align="center"
|
||||
title="结束时间"
|
||||
data-index="endTime"
|
||||
width="140"
|
||||
/>
|
||||
<Table.Column
|
||||
align="center"
|
||||
title="审批状态"
|
||||
data-index="status"
|
||||
width="90"
|
||||
>
|
||||
<template #default="{ record }">
|
||||
<DictTag
|
||||
:type="DICT_TYPE.BPM_TASK_STATUS"
|
||||
:value="record.status"
|
||||
/>
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column
|
||||
align="center"
|
||||
title="审批建议"
|
||||
data-index="reason"
|
||||
width="120"
|
||||
v-if="selectActivityType === 'bpmn:UserTask'"
|
||||
/>
|
||||
<Table.Column
|
||||
align="center"
|
||||
title="耗时"
|
||||
data-index="durationInMillis"
|
||||
width="100"
|
||||
>
|
||||
<template #default="{ record }">
|
||||
{{ formatPast2(record.durationInMillis) }}
|
||||
</template>
|
||||
</Table.Column>
|
||||
</Table>
|
||||
</Row>
|
||||
</Modal>
|
||||
|
||||
<!-- Zoom:放大、缩小 -->
|
||||
<div style="position: absolute; top: 0; left: 0; width: 100%">
|
||||
<Row justify="end">
|
||||
<ButtonGroup key="scale-control">
|
||||
<Button
|
||||
:disabled="defaultZoom <= 0.3"
|
||||
:icon="h(ZoomOutOutlined)"
|
||||
@click="processZoomOut()"
|
||||
/>
|
||||
<Button style="width: 90px">
|
||||
{{ `${Math.floor(defaultZoom * 10 * 10)}%` }}
|
||||
</Button>
|
||||
<Button
|
||||
:disabled="defaultZoom >= 3.9"
|
||||
:icon="h(ZoomInOutlined)"
|
||||
@click="processZoomIn()"
|
||||
/>
|
||||
<Button :icon="h(UndoOutlined)" @click="processReZoom()" />
|
||||
</ButtonGroup>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,8 @@
|
||||
import MyProcessDesigner from './ProcessDesigner.vue';
|
||||
|
||||
MyProcessDesigner.install = function (Vue: any) {
|
||||
Vue.component(MyProcessDesigner.name, MyProcessDesigner);
|
||||
};
|
||||
|
||||
// 流程图的设计器,可编辑
|
||||
export default MyProcessDesigner;
|
||||
@@ -0,0 +1,8 @@
|
||||
import MyProcessViewer from './ProcessViewer.vue';
|
||||
|
||||
MyProcessViewer.install = function (Vue: any) {
|
||||
Vue.component(MyProcessViewer.name, MyProcessViewer);
|
||||
};
|
||||
|
||||
// 流程图的查看器,不可编辑
|
||||
export default MyProcessViewer;
|
||||
@@ -0,0 +1,440 @@
|
||||
import { getChildLanes } from 'bpmn-js/lib/features/modeling/util/LaneUtil';
|
||||
import { isAny } from 'bpmn-js/lib/features/modeling/util/ModelingUtil';
|
||||
import { isEventSubProcess, isExpanded } from 'bpmn-js/lib/util/DiUtil';
|
||||
import { is } from 'bpmn-js/lib/util/ModelUtil';
|
||||
import { hasPrimaryModifier } from 'diagram-js/lib/util/Mouse';
|
||||
import { assign, forEach, isArray } from 'min-dash';
|
||||
|
||||
/**
|
||||
* A provider for BPMN 2.0 elements context pad
|
||||
*/
|
||||
export default function ContextPadProvider(
|
||||
config,
|
||||
injector,
|
||||
eventBus,
|
||||
contextPad,
|
||||
modeling,
|
||||
elementFactory,
|
||||
connect,
|
||||
create,
|
||||
popupMenu,
|
||||
canvas,
|
||||
rules,
|
||||
translate,
|
||||
) {
|
||||
config = config || {};
|
||||
|
||||
contextPad.registerProvider(this);
|
||||
|
||||
this._contextPad = contextPad;
|
||||
|
||||
this._modeling = modeling;
|
||||
|
||||
this._elementFactory = elementFactory;
|
||||
this._connect = connect;
|
||||
this._create = create;
|
||||
this._popupMenu = popupMenu;
|
||||
this._canvas = canvas;
|
||||
this._rules = rules;
|
||||
this._translate = translate;
|
||||
|
||||
if (config.autoPlace !== false) {
|
||||
this._autoPlace = injector.get('autoPlace', false);
|
||||
}
|
||||
|
||||
eventBus.on('create.end', 250, (event) => {
|
||||
const context = event.context;
|
||||
const shape = context.shape;
|
||||
|
||||
if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entries = contextPad.getEntries(shape);
|
||||
|
||||
if (entries.replace) {
|
||||
entries.replace.action.click(event, shape);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ContextPadProvider.$inject = [
|
||||
'config.contextPad',
|
||||
'injector',
|
||||
'eventBus',
|
||||
'contextPad',
|
||||
'modeling',
|
||||
'elementFactory',
|
||||
'connect',
|
||||
'create',
|
||||
'popupMenu',
|
||||
'canvas',
|
||||
'rules',
|
||||
'translate',
|
||||
'elementRegistry',
|
||||
];
|
||||
|
||||
ContextPadProvider.prototype.getContextPadEntries = function (element) {
|
||||
const autoPlace = this._autoPlace;
|
||||
const canvas = this._canvas;
|
||||
const connect = this._connect;
|
||||
const contextPad = this._contextPad;
|
||||
const create = this._create;
|
||||
const elementFactory = this._elementFactory;
|
||||
const modeling = this._modeling;
|
||||
const popupMenu = this._popupMenu;
|
||||
const rules = this._rules;
|
||||
const translate = this._translate;
|
||||
|
||||
const actions = {};
|
||||
|
||||
if (element.type === 'label') {
|
||||
return actions;
|
||||
}
|
||||
|
||||
const businessObject = element.businessObject;
|
||||
|
||||
function startConnect(event, element) {
|
||||
connect.start(event, element);
|
||||
}
|
||||
|
||||
function removeElement() {
|
||||
modeling.removeElements([element]);
|
||||
}
|
||||
|
||||
function getReplaceMenuPosition(element) {
|
||||
const Y_OFFSET = 5;
|
||||
|
||||
const diagramContainer = canvas.getContainer();
|
||||
const pad = contextPad.getPad(element).html;
|
||||
|
||||
const diagramRect = diagramContainer.getBoundingClientRect();
|
||||
const padRect = pad.getBoundingClientRect();
|
||||
|
||||
const top = padRect.top - diagramRect.top;
|
||||
const left = padRect.left - diagramRect.left;
|
||||
|
||||
const pos = {
|
||||
x: left,
|
||||
y: top + padRect.height + Y_OFFSET,
|
||||
};
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an append action
|
||||
*
|
||||
* @param {string} type
|
||||
* @param {string} className
|
||||
* @param {string} [title]
|
||||
* @param {object} [options]
|
||||
*
|
||||
* @return {object} descriptor
|
||||
*/
|
||||
function appendAction(type, className, title, options) {
|
||||
if (typeof title !== 'string') {
|
||||
options = title;
|
||||
title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') });
|
||||
}
|
||||
|
||||
function appendStart(event, element) {
|
||||
const shape = elementFactory.createShape(assign({ type }, options));
|
||||
create.start(event, shape, {
|
||||
source: element,
|
||||
});
|
||||
}
|
||||
|
||||
const append = autoPlace
|
||||
? function (event, element) {
|
||||
const shape = elementFactory.createShape(assign({ type }, options));
|
||||
|
||||
autoPlace.append(element, shape);
|
||||
}
|
||||
: appendStart;
|
||||
|
||||
return {
|
||||
group: 'model',
|
||||
className,
|
||||
title,
|
||||
action: {
|
||||
dragstart: appendStart,
|
||||
click: append,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function splitLaneHandler(count) {
|
||||
return function (event, element) {
|
||||
// actual split
|
||||
modeling.splitLane(element, count);
|
||||
|
||||
// refresh context pad after split to
|
||||
// get rid of split icons
|
||||
contextPad.open(element, true);
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
isAny(businessObject, ['bpmn:Lane', 'bpmn:Participant']) &&
|
||||
isExpanded(businessObject)
|
||||
) {
|
||||
const childLanes = getChildLanes(element);
|
||||
|
||||
assign(actions, {
|
||||
'lane-insert-above': {
|
||||
group: 'lane-insert-above',
|
||||
className: 'bpmn-icon-lane-insert-above',
|
||||
title: translate('Add Lane above'),
|
||||
action: {
|
||||
click(event, element) {
|
||||
modeling.addLane(element, 'top');
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (childLanes.length < 2) {
|
||||
if (element.height >= 120) {
|
||||
assign(actions, {
|
||||
'lane-divide-two': {
|
||||
group: 'lane-divide',
|
||||
className: 'bpmn-icon-lane-divide-two',
|
||||
title: translate('Divide into two Lanes'),
|
||||
action: {
|
||||
click: splitLaneHandler(2),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (element.height >= 180) {
|
||||
assign(actions, {
|
||||
'lane-divide-three': {
|
||||
group: 'lane-divide',
|
||||
className: 'bpmn-icon-lane-divide-three',
|
||||
title: translate('Divide into three Lanes'),
|
||||
action: {
|
||||
click: splitLaneHandler(3),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
assign(actions, {
|
||||
'lane-insert-below': {
|
||||
group: 'lane-insert-below',
|
||||
className: 'bpmn-icon-lane-insert-below',
|
||||
title: translate('Add Lane below'),
|
||||
action: {
|
||||
click(event, element) {
|
||||
modeling.addLane(element, 'bottom');
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (is(businessObject, 'bpmn:FlowNode')) {
|
||||
if (is(businessObject, 'bpmn:EventBasedGateway')) {
|
||||
assign(actions, {
|
||||
'append.receive-task': appendAction(
|
||||
'bpmn:ReceiveTask',
|
||||
'bpmn-icon-receive-task',
|
||||
translate('Append ReceiveTask'),
|
||||
),
|
||||
'append.message-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-message',
|
||||
translate('Append MessageIntermediateCatchEvent'),
|
||||
{ eventDefinitionType: 'bpmn:MessageEventDefinition' },
|
||||
),
|
||||
'append.timer-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-timer',
|
||||
translate('Append TimerIntermediateCatchEvent'),
|
||||
{ eventDefinitionType: 'bpmn:TimerEventDefinition' },
|
||||
),
|
||||
'append.condition-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-condition',
|
||||
translate('Append ConditionIntermediateCatchEvent'),
|
||||
{ eventDefinitionType: 'bpmn:ConditionalEventDefinition' },
|
||||
),
|
||||
'append.signal-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-signal',
|
||||
translate('Append SignalIntermediateCatchEvent'),
|
||||
{ eventDefinitionType: 'bpmn:SignalEventDefinition' },
|
||||
),
|
||||
});
|
||||
} else if (
|
||||
isEventType(
|
||||
businessObject,
|
||||
'bpmn:BoundaryEvent',
|
||||
'bpmn:CompensateEventDefinition',
|
||||
)
|
||||
) {
|
||||
assign(actions, {
|
||||
'append.compensation-activity': appendAction(
|
||||
'bpmn:Task',
|
||||
'bpmn-icon-task',
|
||||
translate('Append compensation activity'),
|
||||
{
|
||||
isForCompensation: true,
|
||||
},
|
||||
),
|
||||
});
|
||||
} else if (
|
||||
!is(businessObject, 'bpmn:EndEvent') &&
|
||||
!businessObject.isForCompensation &&
|
||||
!isEventType(
|
||||
businessObject,
|
||||
'bpmn:IntermediateThrowEvent',
|
||||
'bpmn:LinkEventDefinition',
|
||||
) &&
|
||||
!isEventSubProcess(businessObject)
|
||||
) {
|
||||
assign(actions, {
|
||||
'append.end-event': appendAction(
|
||||
'bpmn:EndEvent',
|
||||
'bpmn-icon-end-event-none',
|
||||
translate('Append EndEvent'),
|
||||
),
|
||||
'append.gateway': appendAction(
|
||||
'bpmn:ExclusiveGateway',
|
||||
'bpmn-icon-gateway-none',
|
||||
translate('Append Gateway'),
|
||||
),
|
||||
'append.append-task': appendAction(
|
||||
'bpmn:UserTask',
|
||||
'bpmn-icon-user-task',
|
||||
translate('Append Task'),
|
||||
),
|
||||
'append.intermediate-event': appendAction(
|
||||
'bpmn:IntermediateThrowEvent',
|
||||
'bpmn-icon-intermediate-event-none',
|
||||
translate('Append Intermediate/Boundary Event'),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!popupMenu.isEmpty(element, 'bpmn-replace')) {
|
||||
// Replace menu entry
|
||||
assign(actions, {
|
||||
replace: {
|
||||
group: 'edit',
|
||||
className: 'bpmn-icon-screw-wrench',
|
||||
title: '修改类型',
|
||||
action: {
|
||||
click(event, element) {
|
||||
const position = assign(getReplaceMenuPosition(element), {
|
||||
cursor: { x: event.x, y: event.y },
|
||||
});
|
||||
|
||||
popupMenu.open(element, 'bpmn-replace', position);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
isAny(businessObject, [
|
||||
'bpmn:FlowNode',
|
||||
'bpmn:InteractionNode',
|
||||
'bpmn:DataObjectReference',
|
||||
'bpmn:DataStoreReference',
|
||||
])
|
||||
) {
|
||||
assign(actions, {
|
||||
'append.text-annotation': appendAction(
|
||||
'bpmn:TextAnnotation',
|
||||
'bpmn-icon-text-annotation',
|
||||
),
|
||||
|
||||
connect: {
|
||||
group: 'connect',
|
||||
className: 'bpmn-icon-connection-multi',
|
||||
title: translate(
|
||||
`Connect using ${
|
||||
businessObject.isForCompensation ? '' : 'Sequence/MessageFlow or '
|
||||
}Association`,
|
||||
),
|
||||
action: {
|
||||
click: startConnect,
|
||||
dragstart: startConnect,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
isAny(businessObject, [
|
||||
'bpmn:DataObjectReference',
|
||||
'bpmn:DataStoreReference',
|
||||
])
|
||||
) {
|
||||
assign(actions, {
|
||||
connect: {
|
||||
group: 'connect',
|
||||
className: 'bpmn-icon-connection-multi',
|
||||
title: translate('Connect using DataInputAssociation'),
|
||||
action: {
|
||||
click: startConnect,
|
||||
dragstart: startConnect,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (is(businessObject, 'bpmn:Group')) {
|
||||
assign(actions, {
|
||||
'append.text-annotation': appendAction(
|
||||
'bpmn:TextAnnotation',
|
||||
'bpmn-icon-text-annotation',
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
// delete element entry, only show if allowed by rules
|
||||
let deleteAllowed = rules.allowed('elements.delete', { elements: [element] });
|
||||
|
||||
if (isArray(deleteAllowed)) {
|
||||
// was the element returned as a deletion candidate?
|
||||
deleteAllowed = deleteAllowed[0] === element;
|
||||
}
|
||||
|
||||
if (deleteAllowed) {
|
||||
assign(actions, {
|
||||
delete: {
|
||||
group: 'edit',
|
||||
className: 'bpmn-icon-trash',
|
||||
title: translate('Remove'),
|
||||
action: {
|
||||
click: removeElement,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
// helpers /////////
|
||||
|
||||
function isEventType(eventBo, type, definition) {
|
||||
const isType = eventBo.$instanceOf(type);
|
||||
let isDefinition = false;
|
||||
|
||||
const definitions = eventBo.eventDefinitions || [];
|
||||
forEach(definitions, (def) => {
|
||||
if (def.$type === definition) {
|
||||
isDefinition = true;
|
||||
}
|
||||
});
|
||||
|
||||
return isType && isDefinition;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import CustomContextPadProvider from './contentPadProvider';
|
||||
|
||||
export default {
|
||||
__init__: ['contextPadProvider'],
|
||||
contextPadProvider: ['type', CustomContextPadProvider],
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
export default (key, name, type) => {
|
||||
if (!type) type = 'camunda';
|
||||
const TYPE_TARGET = {
|
||||
activiti: 'http://activiti.org/bpmn',
|
||||
camunda: 'http://bpmn.io/schema/bpmn',
|
||||
flowable: 'http://flowable.org/bpmn',
|
||||
};
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn2:definitions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
|
||||
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
|
||||
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
|
||||
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
|
||||
id="diagram_${key}"
|
||||
targetNamespace="${TYPE_TARGET[type]}">
|
||||
<bpmn2:process id="${key}" name="${name}" isExecutable="true">
|
||||
</bpmn2:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="${key}">
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn2:definitions>`;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,101 @@
|
||||
'use strict';
|
||||
|
||||
import { some } from 'min-dash';
|
||||
|
||||
// const some = require('min-dash').some
|
||||
// const some = some
|
||||
|
||||
const ALLOWED_TYPES = {
|
||||
FailedJobRetryTimeCycle: [
|
||||
'bpmn:StartEvent',
|
||||
'bpmn:BoundaryEvent',
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn:Activity',
|
||||
],
|
||||
Connector: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent'],
|
||||
Field: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent'],
|
||||
};
|
||||
|
||||
function is(element, type) {
|
||||
return (
|
||||
element &&
|
||||
typeof element.$instanceOf === 'function' &&
|
||||
element.$instanceOf(type)
|
||||
);
|
||||
}
|
||||
|
||||
function exists(element) {
|
||||
return element && element.length;
|
||||
}
|
||||
|
||||
function includesType(collection, type) {
|
||||
return (
|
||||
exists(collection) &&
|
||||
some(collection, (element) => {
|
||||
return is(element, type);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function anyType(element, types) {
|
||||
return some(types, (type) => {
|
||||
return is(element, type);
|
||||
});
|
||||
}
|
||||
|
||||
function isAllowed(propName, propDescriptor, newElement) {
|
||||
const name = propDescriptor.name;
|
||||
const types = ALLOWED_TYPES[name.replace(/activiti:/, '')];
|
||||
|
||||
return name === propName && anyType(newElement, types);
|
||||
}
|
||||
|
||||
function ActivitiModdleExtension(eventBus) {
|
||||
eventBus.on(
|
||||
'property.clone',
|
||||
function (context) {
|
||||
const newElement = context.newElement;
|
||||
const propDescriptor = context.propertyDescriptor;
|
||||
|
||||
this.canCloneProperty(newElement, propDescriptor);
|
||||
},
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
ActivitiModdleExtension.$inject = ['eventBus'];
|
||||
|
||||
ActivitiModdleExtension.prototype.canCloneProperty = function (
|
||||
newElement,
|
||||
propDescriptor,
|
||||
) {
|
||||
if (
|
||||
isAllowed('activiti:FailedJobRetryTimeCycle', propDescriptor, newElement)
|
||||
) {
|
||||
return (
|
||||
includesType(newElement.eventDefinitions, 'bpmn:TimerEventDefinition') ||
|
||||
includesType(newElement.eventDefinitions, 'bpmn:SignalEventDefinition') ||
|
||||
is(
|
||||
newElement.loopCharacteristics,
|
||||
'bpmn:MultiInstanceLoopCharacteristics',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (isAllowed('activiti:Connector', propDescriptor, newElement)) {
|
||||
return includesType(
|
||||
newElement.eventDefinitions,
|
||||
'bpmn:MessageEventDefinition',
|
||||
);
|
||||
}
|
||||
|
||||
if (isAllowed('activiti:Field', propDescriptor, newElement)) {
|
||||
return includesType(
|
||||
newElement.eventDefinitions,
|
||||
'bpmn:MessageEventDefinition',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// module.exports = ActivitiModdleExtension;
|
||||
export default ActivitiModdleExtension;
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* @author igdianov
|
||||
* address https://github.com/igdianov/activiti-bpmn-moddle
|
||||
* */
|
||||
|
||||
import activitiExtension from './activitiExtension';
|
||||
|
||||
export default {
|
||||
__init__: ['ActivitiModdleExtension'],
|
||||
ActivitiModdleExtension: ['type', activitiExtension],
|
||||
};
|
||||
@@ -0,0 +1,165 @@
|
||||
'use strict';
|
||||
|
||||
import { isFunction, isObject, some } from 'min-dash';
|
||||
|
||||
// const isFunction = isFunction,
|
||||
// isObject = isObject,
|
||||
// some = some
|
||||
// const isFunction = require('min-dash').isFunction,
|
||||
// isObject = require('min-dash').isObject,
|
||||
// some = require('min-dash').some
|
||||
|
||||
const WILDCARD = '*';
|
||||
|
||||
function CamundaModdleExtension(eventBus) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const self = this;
|
||||
|
||||
eventBus.on('moddleCopy.canCopyProperty', (context) => {
|
||||
const parent = context.parent;
|
||||
const property = context.property;
|
||||
|
||||
return self.canCopyProperty(property, parent);
|
||||
});
|
||||
}
|
||||
|
||||
CamundaModdleExtension.$inject = ['eventBus'];
|
||||
|
||||
/**
|
||||
* Check wether to disallow copying property.
|
||||
*/
|
||||
CamundaModdleExtension.prototype.canCopyProperty = function (property, parent) {
|
||||
// (1) check wether property is allowed in parent
|
||||
if (isObject(property) && !isAllowedInParent(property, parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// (2) check more complex scenarios
|
||||
|
||||
if (is(property, 'camunda:InputOutput') && !this.canHostInputOutput(parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
isAny(property, ['camunda:Connector', 'camunda:Field']) &&
|
||||
!this.canHostConnector(parent)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is(property, 'camunda:In') && !this.canHostIn(parent)) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
CamundaModdleExtension.prototype.canHostInputOutput = function (parent) {
|
||||
// allowed in camunda:Connector
|
||||
const connector = getParent(parent, 'camunda:Connector');
|
||||
|
||||
if (connector) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// special rules inside bpmn:FlowNode
|
||||
const flowNode = getParent(parent, 'bpmn:FlowNode');
|
||||
|
||||
if (!flowNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
isAny(flowNode, ['bpmn:StartEvent', 'bpmn:Gateway', 'bpmn:BoundaryEvent'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !(is(flowNode, 'bpmn:SubProcess') && flowNode.get('triggeredByEvent'));
|
||||
};
|
||||
|
||||
CamundaModdleExtension.prototype.canHostConnector = function (parent) {
|
||||
const serviceTaskLike = getParent(parent, 'camunda:ServiceTaskLike');
|
||||
|
||||
if (is(serviceTaskLike, 'bpmn:MessageEventDefinition')) {
|
||||
// only allow on throw and end events
|
||||
return (
|
||||
getParent(parent, 'bpmn:IntermediateThrowEvent') ||
|
||||
getParent(parent, 'bpmn:EndEvent')
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
CamundaModdleExtension.prototype.canHostIn = function (parent) {
|
||||
const callActivity = getParent(parent, 'bpmn:CallActivity');
|
||||
|
||||
if (callActivity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const signalEventDefinition = getParent(parent, 'bpmn:SignalEventDefinition');
|
||||
|
||||
if (signalEventDefinition) {
|
||||
// only allow on throw and end events
|
||||
return (
|
||||
getParent(parent, 'bpmn:IntermediateThrowEvent') ||
|
||||
getParent(parent, 'bpmn:EndEvent')
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// module.exports = CamundaModdleExtension;
|
||||
export default CamundaModdleExtension;
|
||||
|
||||
// helpers //////////
|
||||
|
||||
function is(element, type) {
|
||||
return (
|
||||
element && isFunction(element.$instanceOf) && element.$instanceOf(type)
|
||||
);
|
||||
}
|
||||
|
||||
function isAny(element, types) {
|
||||
return some(types, (t) => {
|
||||
return is(element, t);
|
||||
});
|
||||
}
|
||||
|
||||
function getParent(element, type) {
|
||||
if (!type) {
|
||||
return element.$parent;
|
||||
}
|
||||
|
||||
if (is(element, type)) {
|
||||
return element;
|
||||
}
|
||||
|
||||
if (!element.$parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
return getParent(element.$parent, type);
|
||||
}
|
||||
|
||||
function isAllowedInParent(property, parent) {
|
||||
// (1) find property descriptor
|
||||
const descriptor =
|
||||
property.$type && property.$model.getTypeDescriptor(property.$type);
|
||||
|
||||
const allowedIn = descriptor && descriptor.meta && descriptor.meta.allowedIn;
|
||||
|
||||
if (!allowedIn || isWildcard(allowedIn)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// (2) check wether property has parent of allowed type
|
||||
return some(allowedIn, (type) => {
|
||||
return getParent(parent, type);
|
||||
});
|
||||
}
|
||||
|
||||
function isWildcard(allowedIn) {
|
||||
return allowedIn.includes(WILDCARD);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
import extension from './extension';
|
||||
|
||||
export default {
|
||||
__init__: ['camundaModdleExtension'],
|
||||
camundaModdleExtension: ['type', extension],
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
'use strict';
|
||||
|
||||
import { some } from 'min-dash';
|
||||
|
||||
// const some = some
|
||||
// const some = require('min-dash').some
|
||||
|
||||
const ALLOWED_TYPES = {
|
||||
FailedJobRetryTimeCycle: [
|
||||
'bpmn:StartEvent',
|
||||
'bpmn:BoundaryEvent',
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn:Activity',
|
||||
],
|
||||
Connector: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent'],
|
||||
Field: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent'],
|
||||
};
|
||||
|
||||
function is(element, type) {
|
||||
return (
|
||||
element &&
|
||||
typeof element.$instanceOf === 'function' &&
|
||||
element.$instanceOf(type)
|
||||
);
|
||||
}
|
||||
|
||||
function exists(element) {
|
||||
return element && element.length;
|
||||
}
|
||||
|
||||
function includesType(collection, type) {
|
||||
return (
|
||||
exists(collection) &&
|
||||
some(collection, (element) => {
|
||||
return is(element, type);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function anyType(element, types) {
|
||||
return some(types, (type) => {
|
||||
return is(element, type);
|
||||
});
|
||||
}
|
||||
|
||||
function isAllowed(propName, propDescriptor, newElement) {
|
||||
const name = propDescriptor.name;
|
||||
const types = ALLOWED_TYPES[name.replace(/flowable:/, '')];
|
||||
|
||||
return name === propName && anyType(newElement, types);
|
||||
}
|
||||
|
||||
function FlowableModdleExtension(eventBus) {
|
||||
eventBus.on(
|
||||
'property.clone',
|
||||
function (context) {
|
||||
const newElement = context.newElement;
|
||||
const propDescriptor = context.propertyDescriptor;
|
||||
|
||||
this.canCloneProperty(newElement, propDescriptor);
|
||||
},
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
FlowableModdleExtension.$inject = ['eventBus'];
|
||||
|
||||
FlowableModdleExtension.prototype.canCloneProperty = function (
|
||||
newElement,
|
||||
propDescriptor,
|
||||
) {
|
||||
if (
|
||||
isAllowed('flowable:FailedJobRetryTimeCycle', propDescriptor, newElement)
|
||||
) {
|
||||
return (
|
||||
includesType(newElement.eventDefinitions, 'bpmn:TimerEventDefinition') ||
|
||||
includesType(newElement.eventDefinitions, 'bpmn:SignalEventDefinition') ||
|
||||
is(
|
||||
newElement.loopCharacteristics,
|
||||
'bpmn:MultiInstanceLoopCharacteristics',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (isAllowed('flowable:Connector', propDescriptor, newElement)) {
|
||||
return includesType(
|
||||
newElement.eventDefinitions,
|
||||
'bpmn:MessageEventDefinition',
|
||||
);
|
||||
}
|
||||
|
||||
if (isAllowed('flowable:Field', propDescriptor, newElement)) {
|
||||
return includesType(
|
||||
newElement.eventDefinitions,
|
||||
'bpmn:MessageEventDefinition',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// module.exports = FlowableModdleExtension;
|
||||
export default FlowableModdleExtension;
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* @author igdianov
|
||||
* address https://github.com/igdianov/activiti-bpmn-moddle
|
||||
* */
|
||||
import flowableExtension from './flowableExtension';
|
||||
|
||||
export default {
|
||||
__init__: ['FlowableModdleExtension'],
|
||||
FlowableModdleExtension: ['type', flowableExtension],
|
||||
};
|
||||
@@ -0,0 +1,233 @@
|
||||
import PaletteProvider from 'bpmn-js/lib/features/palette/PaletteProvider';
|
||||
import { assign } from 'min-dash';
|
||||
|
||||
export default function CustomPalette(
|
||||
palette,
|
||||
create,
|
||||
elementFactory,
|
||||
spaceTool,
|
||||
lassoTool,
|
||||
handTool,
|
||||
globalConnect,
|
||||
translate,
|
||||
) {
|
||||
PaletteProvider.call(
|
||||
this,
|
||||
palette,
|
||||
create,
|
||||
elementFactory,
|
||||
spaceTool,
|
||||
lassoTool,
|
||||
handTool,
|
||||
globalConnect,
|
||||
translate,
|
||||
2000,
|
||||
);
|
||||
}
|
||||
|
||||
const F = function () {}; // 核心,利用空对象作为中介;
|
||||
F.prototype = PaletteProvider.prototype; // 核心,将父类的原型赋值给空对象F;
|
||||
|
||||
// 利用中介函数重写原型链方法
|
||||
F.prototype.getPaletteEntries = function () {
|
||||
const actions = {};
|
||||
const create = this._create;
|
||||
const elementFactory = this._elementFactory;
|
||||
const spaceTool = this._spaceTool;
|
||||
const lassoTool = this._lassoTool;
|
||||
const handTool = this._handTool;
|
||||
const globalConnect = this._globalConnect;
|
||||
const translate = this._translate;
|
||||
|
||||
function createAction(type, group, className, title, options) {
|
||||
function createListener(event) {
|
||||
const shape = elementFactory.createShape(assign({ type }, options));
|
||||
|
||||
if (options) {
|
||||
shape.businessObject.di.isExpanded = options.isExpanded;
|
||||
}
|
||||
|
||||
create.start(event, shape);
|
||||
}
|
||||
|
||||
const shortType = type.replace(/^bpmn:/, '');
|
||||
|
||||
return {
|
||||
group,
|
||||
className,
|
||||
title: title || translate('Create {type}', { type: shortType }),
|
||||
action: {
|
||||
dragstart: createListener,
|
||||
click: createListener,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createSubprocess(event) {
|
||||
const subProcess = elementFactory.createShape({
|
||||
type: 'bpmn:SubProcess',
|
||||
x: 0,
|
||||
y: 0,
|
||||
isExpanded: true,
|
||||
});
|
||||
|
||||
const startEvent = elementFactory.createShape({
|
||||
type: 'bpmn:StartEvent',
|
||||
x: 40,
|
||||
y: 82,
|
||||
parent: subProcess,
|
||||
});
|
||||
|
||||
create.start(event, [subProcess, startEvent], {
|
||||
hints: {
|
||||
autoSelect: [startEvent],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function createParticipant(event) {
|
||||
create.start(event, elementFactory.createParticipantShape());
|
||||
}
|
||||
|
||||
assign(actions, {
|
||||
'hand-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-hand-tool',
|
||||
title: '激活抓手工具',
|
||||
// title: translate("Activate the hand tool"),
|
||||
action: {
|
||||
click(event) {
|
||||
handTool.activateHand(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'lasso-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-lasso-tool',
|
||||
title: translate('Activate the lasso tool'),
|
||||
action: {
|
||||
click(event) {
|
||||
lassoTool.activateSelection(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'space-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-space-tool',
|
||||
title: translate('Activate the create/remove space tool'),
|
||||
action: {
|
||||
click(event) {
|
||||
spaceTool.activateSelection(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'global-connect-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-connection-multi',
|
||||
title: translate('Activate the global connect tool'),
|
||||
action: {
|
||||
click(event) {
|
||||
globalConnect.toggle(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'tool-separator': {
|
||||
group: 'tools',
|
||||
separator: true,
|
||||
},
|
||||
'create.start-event': createAction(
|
||||
'bpmn:StartEvent',
|
||||
'event',
|
||||
'bpmn-icon-start-event-none',
|
||||
translate('Create StartEvent'),
|
||||
),
|
||||
'create.intermediate-event': createAction(
|
||||
'bpmn:IntermediateThrowEvent',
|
||||
'event',
|
||||
'bpmn-icon-intermediate-event-none',
|
||||
translate('Create Intermediate/Boundary Event'),
|
||||
),
|
||||
'create.end-event': createAction(
|
||||
'bpmn:EndEvent',
|
||||
'event',
|
||||
'bpmn-icon-end-event-none',
|
||||
translate('Create EndEvent'),
|
||||
),
|
||||
'create.exclusive-gateway': createAction(
|
||||
'bpmn:ExclusiveGateway',
|
||||
'gateway',
|
||||
'bpmn-icon-gateway-none',
|
||||
translate('Create Gateway'),
|
||||
),
|
||||
'create.user-task': createAction(
|
||||
'bpmn:UserTask',
|
||||
'activity',
|
||||
'bpmn-icon-user-task',
|
||||
translate('Create User Task'),
|
||||
),
|
||||
'create.call-activity': createAction(
|
||||
'bpmn:CallActivity',
|
||||
'activity',
|
||||
'bpmn-icon-call-activity',
|
||||
translate('Create Call Activity'),
|
||||
),
|
||||
'create.service-task': createAction(
|
||||
'bpmn:ServiceTask',
|
||||
'activity',
|
||||
'bpmn-icon-service',
|
||||
translate('Create Service Task'),
|
||||
),
|
||||
'create.data-object': createAction(
|
||||
'bpmn:DataObjectReference',
|
||||
'data-object',
|
||||
'bpmn-icon-data-object',
|
||||
translate('Create DataObjectReference'),
|
||||
),
|
||||
'create.data-store': createAction(
|
||||
'bpmn:DataStoreReference',
|
||||
'data-store',
|
||||
'bpmn-icon-data-store',
|
||||
translate('Create DataStoreReference'),
|
||||
),
|
||||
'create.subprocess-expanded': {
|
||||
group: 'activity',
|
||||
className: 'bpmn-icon-subprocess-expanded',
|
||||
title: translate('Create expanded SubProcess'),
|
||||
action: {
|
||||
dragstart: createSubprocess,
|
||||
click: createSubprocess,
|
||||
},
|
||||
},
|
||||
'create.participant-expanded': {
|
||||
group: 'collaboration',
|
||||
className: 'bpmn-icon-participant',
|
||||
title: translate('Create Pool/Participant'),
|
||||
action: {
|
||||
dragstart: createParticipant,
|
||||
click: createParticipant,
|
||||
},
|
||||
},
|
||||
'create.group': createAction(
|
||||
'bpmn:Group',
|
||||
'artifact',
|
||||
'bpmn-icon-group',
|
||||
translate('Create Group'),
|
||||
),
|
||||
});
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
CustomPalette.$inject = [
|
||||
'palette',
|
||||
'create',
|
||||
'elementFactory',
|
||||
'spaceTool',
|
||||
'lassoTool',
|
||||
'handTool',
|
||||
'globalConnect',
|
||||
'translate',
|
||||
];
|
||||
|
||||
CustomPalette.prototype = new F(); // 核心,将 F的实例赋值给子类;
|
||||
CustomPalette.prototype.constructor = CustomPalette; // 修复子类CustomPalette的构造器指向,防止原型链的混乱;
|
||||
@@ -0,0 +1,22 @@
|
||||
// import PaletteModule from "diagram-js/lib/features/palette";
|
||||
// import CreateModule from "diagram-js/lib/features/create";
|
||||
// import SpaceToolModule from "diagram-js/lib/features/space-tool";
|
||||
// import LassoToolModule from "diagram-js/lib/features/lasso-tool";
|
||||
// import HandToolModule from "diagram-js/lib/features/hand-tool";
|
||||
// import GlobalConnectModule from "diagram-js/lib/features/global-connect";
|
||||
// import translate from "diagram-js/lib/i18n/translate";
|
||||
//
|
||||
// import PaletteProvider from "./paletteProvider";
|
||||
//
|
||||
// export default {
|
||||
// __depends__: [PaletteModule, CreateModule, SpaceToolModule, LassoToolModule, HandToolModule, GlobalConnectModule, translate],
|
||||
// __init__: ["paletteProvider"],
|
||||
// paletteProvider: ["type", PaletteProvider]
|
||||
// };
|
||||
|
||||
import CustomPalette from './CustomPalette';
|
||||
|
||||
export default {
|
||||
__init__: ['paletteProvider'],
|
||||
paletteProvider: ['type', CustomPalette],
|
||||
};
|
||||
@@ -0,0 +1,219 @@
|
||||
import { assign } from 'min-dash';
|
||||
|
||||
/**
|
||||
* A palette provider for BPMN 2.0 elements.
|
||||
*/
|
||||
export default function PaletteProvider(
|
||||
palette,
|
||||
create,
|
||||
elementFactory,
|
||||
spaceTool,
|
||||
lassoTool,
|
||||
handTool,
|
||||
globalConnect,
|
||||
translate,
|
||||
) {
|
||||
this._palette = palette;
|
||||
this._create = create;
|
||||
this._elementFactory = elementFactory;
|
||||
this._spaceTool = spaceTool;
|
||||
this._lassoTool = lassoTool;
|
||||
this._handTool = handTool;
|
||||
this._globalConnect = globalConnect;
|
||||
this._translate = translate;
|
||||
|
||||
palette.registerProvider(this);
|
||||
}
|
||||
|
||||
PaletteProvider.$inject = [
|
||||
'palette',
|
||||
'create',
|
||||
'elementFactory',
|
||||
'spaceTool',
|
||||
'lassoTool',
|
||||
'handTool',
|
||||
'globalConnect',
|
||||
'translate',
|
||||
];
|
||||
|
||||
PaletteProvider.prototype.getPaletteEntries = function () {
|
||||
const actions = {};
|
||||
const create = this._create;
|
||||
const elementFactory = this._elementFactory;
|
||||
const spaceTool = this._spaceTool;
|
||||
const lassoTool = this._lassoTool;
|
||||
const handTool = this._handTool;
|
||||
const globalConnect = this._globalConnect;
|
||||
const translate = this._translate;
|
||||
|
||||
function createAction(type, group, className, title, options) {
|
||||
function createListener(event) {
|
||||
const shape = elementFactory.createShape(assign({ type }, options));
|
||||
|
||||
if (options) {
|
||||
shape.businessObject.di.isExpanded = options.isExpanded;
|
||||
}
|
||||
|
||||
create.start(event, shape);
|
||||
}
|
||||
|
||||
const shortType = type.replace(/^bpmn:/, '');
|
||||
|
||||
return {
|
||||
group,
|
||||
className,
|
||||
title: title || translate('Create {type}', { type: shortType }),
|
||||
action: {
|
||||
dragstart: createListener,
|
||||
click: createListener,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createSubprocess(event) {
|
||||
const subProcess = elementFactory.createShape({
|
||||
type: 'bpmn:SubProcess',
|
||||
x: 0,
|
||||
y: 0,
|
||||
isExpanded: true,
|
||||
});
|
||||
|
||||
const startEvent = elementFactory.createShape({
|
||||
type: 'bpmn:StartEvent',
|
||||
x: 40,
|
||||
y: 82,
|
||||
parent: subProcess,
|
||||
});
|
||||
|
||||
create.start(event, [subProcess, startEvent], {
|
||||
hints: {
|
||||
autoSelect: [startEvent],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function createParticipant(event) {
|
||||
create.start(event, elementFactory.createParticipantShape());
|
||||
}
|
||||
|
||||
assign(actions, {
|
||||
'hand-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-hand-tool',
|
||||
title: translate('Activate the hand tool'),
|
||||
action: {
|
||||
click(event) {
|
||||
handTool.activateHand(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'lasso-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-lasso-tool',
|
||||
title: translate('Activate the lasso tool'),
|
||||
action: {
|
||||
click(event) {
|
||||
lassoTool.activateSelection(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'space-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-space-tool',
|
||||
title: translate('Activate the create/remove space tool'),
|
||||
action: {
|
||||
click(event) {
|
||||
spaceTool.activateSelection(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'global-connect-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-connection-multi',
|
||||
title: translate('Activate the global connect tool'),
|
||||
action: {
|
||||
click(event) {
|
||||
globalConnect.toggle(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'tool-separator': {
|
||||
group: 'tools',
|
||||
separator: true,
|
||||
},
|
||||
'create.start-event': createAction(
|
||||
'bpmn:StartEvent',
|
||||
'event',
|
||||
'bpmn-icon-start-event-none',
|
||||
translate('Create StartEvent'),
|
||||
),
|
||||
'create.intermediate-event': createAction(
|
||||
'bpmn:IntermediateThrowEvent',
|
||||
'event',
|
||||
'bpmn-icon-intermediate-event-none',
|
||||
translate('Create Intermediate/Boundary Event'),
|
||||
),
|
||||
'create.end-event': createAction(
|
||||
'bpmn:EndEvent',
|
||||
'event',
|
||||
'bpmn-icon-end-event-none',
|
||||
translate('Create EndEvent'),
|
||||
),
|
||||
'create.exclusive-gateway': createAction(
|
||||
'bpmn:ExclusiveGateway',
|
||||
'gateway',
|
||||
'bpmn-icon-gateway-none',
|
||||
translate('Create Gateway'),
|
||||
),
|
||||
'create.user-task': createAction(
|
||||
'bpmn:UserTask',
|
||||
'activity',
|
||||
'bpmn-icon-user-task',
|
||||
translate('Create User Task'),
|
||||
),
|
||||
'create.service-task': createAction(
|
||||
'bpmn:ServiceTask',
|
||||
'activity',
|
||||
'bpmn-icon-service',
|
||||
translate('Create Service Task'),
|
||||
),
|
||||
'create.data-object': createAction(
|
||||
'bpmn:DataObjectReference',
|
||||
'data-object',
|
||||
'bpmn-icon-data-object',
|
||||
translate('Create DataObjectReference'),
|
||||
),
|
||||
'create.data-store': createAction(
|
||||
'bpmn:DataStoreReference',
|
||||
'data-store',
|
||||
'bpmn-icon-data-store',
|
||||
translate('Create DataStoreReference'),
|
||||
),
|
||||
'create.subprocess-expanded': {
|
||||
group: 'activity',
|
||||
className: 'bpmn-icon-subprocess-expanded',
|
||||
title: translate('Create expanded SubProcess'),
|
||||
action: {
|
||||
dragstart: createSubprocess,
|
||||
click: createSubprocess,
|
||||
},
|
||||
},
|
||||
'create.participant-expanded': {
|
||||
group: 'collaboration',
|
||||
className: 'bpmn-icon-participant',
|
||||
title: translate('Create Pool/Participant'),
|
||||
action: {
|
||||
dragstart: createParticipant,
|
||||
click: createParticipant,
|
||||
},
|
||||
},
|
||||
'create.group': createAction(
|
||||
'bpmn:Group',
|
||||
'artifact',
|
||||
'bpmn-icon-group',
|
||||
translate('Create Group'),
|
||||
),
|
||||
});
|
||||
|
||||
return actions;
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
// import translations from "./zh";
|
||||
//
|
||||
// export default function customTranslate(template, replacements) {
|
||||
// replacements = replacements || {};
|
||||
//
|
||||
// // Translate
|
||||
// template = translations[template] || template;
|
||||
//
|
||||
// // Replace
|
||||
// return template.replace(/{([^}]+)}/g, function(_, key) {
|
||||
// let str = replacements[key];
|
||||
// if (
|
||||
// translations[replacements[key]] !== null &&
|
||||
// translations[replacements[key]] !== "undefined"
|
||||
// ) {
|
||||
// // eslint-disable-next-line no-mixed-spaces-and-tabs
|
||||
// str = translations[replacements[key]];
|
||||
// // eslint-disable-next-line no-mixed-spaces-and-tabs
|
||||
// }
|
||||
// return str || "{" + key + "}";
|
||||
// });
|
||||
// }
|
||||
|
||||
export default function customTranslate(translations) {
|
||||
return function (template, replacements) {
|
||||
replacements = replacements || {};
|
||||
// 将模板和翻译字典的键统一转换为小写进行匹配
|
||||
const lowerTemplate = template.toLowerCase();
|
||||
const translation = Object.keys(translations).find(
|
||||
(key) => key.toLowerCase() === lowerTemplate,
|
||||
);
|
||||
|
||||
// 如果找到匹配的翻译,使用翻译后的模板
|
||||
if (translation) {
|
||||
template = translations[translation];
|
||||
}
|
||||
|
||||
// 替换模板中的占位符
|
||||
return template.replaceAll(/\{([^}]+)\}/g, (_, key) => {
|
||||
// 如果替换值存在,返回替换值;否则返回原始占位符
|
||||
return replacements[key] === undefined ? `{${key}}` : replacements[key];
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* This is a sample file that should be replaced with the actual translation.
|
||||
*
|
||||
* Checkout https://github.com/bpmn-io/bpmn-js-i18n for a list of available
|
||||
* translations and labels to translate.
|
||||
*/
|
||||
export default {
|
||||
// 添加部分
|
||||
'Append EndEvent': '追加结束事件',
|
||||
'Append Gateway': '追加网关',
|
||||
'Append Task': '追加任务',
|
||||
'Append Intermediate/Boundary Event': '追加中间抛出事件/边界事件',
|
||||
|
||||
'Activate the global connect tool': '激活全局连接工具',
|
||||
'Append {type}': '添加 {type}',
|
||||
'Add Lane above': '在上面添加道',
|
||||
'Divide into two Lanes': '分割成两个道',
|
||||
'Divide into three Lanes': '分割成三个道',
|
||||
'Add Lane below': '在下面添加道',
|
||||
'Append compensation activity': '追加补偿活动',
|
||||
'Change type': '修改类型',
|
||||
'Connect using Association': '使用关联连接',
|
||||
'Connect using Sequence/MessageFlow or Association':
|
||||
'使用顺序/消息流或者关联连接',
|
||||
'Connect using DataInputAssociation': '使用数据输入关联连接',
|
||||
Remove: '移除',
|
||||
'Activate the hand tool': '激活抓手工具',
|
||||
'Activate the lasso tool': '激活套索工具',
|
||||
'Activate the create/remove space tool': '激活创建/删除空间工具',
|
||||
'Create expanded SubProcess': '创建扩展子过程',
|
||||
'Create IntermediateThrowEvent/BoundaryEvent': '创建中间抛出事件/边界事件',
|
||||
'Create Pool/Participant': '创建池/参与者',
|
||||
'Parallel Multi Instance': '并行多重事件',
|
||||
'Sequential Multi Instance': '时序多重事件',
|
||||
DataObjectReference: '数据对象参考',
|
||||
DataStoreReference: '数据存储参考',
|
||||
Loop: '循环',
|
||||
'Ad-hoc': '即席',
|
||||
'Create {type}': '创建 {type}',
|
||||
Task: '任务',
|
||||
'Send Task': '发送任务',
|
||||
'Receive Task': '接收任务',
|
||||
'User Task': '用户任务',
|
||||
'Manual Task': '手工任务',
|
||||
'Business Rule Task': '业务规则任务',
|
||||
'Service Task': '服务任务',
|
||||
'Script Task': '脚本任务',
|
||||
'Call Activity': '调用活动',
|
||||
'Sub-Process (collapsed)': '子流程(折叠的)',
|
||||
'Sub-Process (expanded)': '子流程(展开的)',
|
||||
'Start Event': '开始事件',
|
||||
StartEvent: '开始事件',
|
||||
'Intermediate Throw Event': '中间事件',
|
||||
'End Event': '结束事件',
|
||||
EndEvent: '结束事件',
|
||||
'Create StartEvent': '创建开始事件',
|
||||
'Create EndEvent': '创建结束事件',
|
||||
'Create Task': '创建任务',
|
||||
'Create User Task': '创建用户任务',
|
||||
'Create Call Activity': '创建调用活动',
|
||||
'Create Service Task': '创建服务任务',
|
||||
'Create Gateway': '创建网关',
|
||||
'Create DataObjectReference': '创建数据对象',
|
||||
'Create DataStoreReference': '创建数据存储',
|
||||
'Create Group': '创建分组',
|
||||
'Create Intermediate/Boundary Event': '创建中间/边界事件',
|
||||
'Message Start Event': '消息开始事件',
|
||||
'Timer Start Event': '定时开始事件',
|
||||
'Conditional Start Event': '条件开始事件',
|
||||
'Signal Start Event': '信号开始事件',
|
||||
'Error Start Event': '错误开始事件',
|
||||
'Escalation Start Event': '升级开始事件',
|
||||
'Compensation Start Event': '补偿开始事件',
|
||||
'Message Start Event (non-interrupting)': '消息开始事件(非中断)',
|
||||
'Timer Start Event (non-interrupting)': '定时开始事件(非中断)',
|
||||
'Conditional Start Event (non-interrupting)': '条件开始事件(非中断)',
|
||||
'Signal Start Event (non-interrupting)': '信号开始事件(非中断)',
|
||||
'Escalation Start Event (non-interrupting)': '升级开始事件(非中断)',
|
||||
'Message Intermediate Catch Event': '消息中间捕获事件',
|
||||
'Message Intermediate Throw Event': '消息中间抛出事件',
|
||||
'Timer Intermediate Catch Event': '定时中间捕获事件',
|
||||
'Escalation Intermediate Throw Event': '升级中间抛出事件',
|
||||
'Conditional Intermediate Catch Event': '条件中间捕获事件',
|
||||
'Link Intermediate Catch Event': '链接中间捕获事件',
|
||||
'Link Intermediate Throw Event': '链接中间抛出事件',
|
||||
'Compensation Intermediate Throw Event': '补偿中间抛出事件',
|
||||
'Signal Intermediate Catch Event': '信号中间捕获事件',
|
||||
'Signal Intermediate Throw Event': '信号中间抛出事件',
|
||||
'Message End Event': '消息结束事件',
|
||||
'Escalation End Event': '定时结束事件',
|
||||
'Error End Event': '错误结束事件',
|
||||
'Cancel End Event': '取消结束事件',
|
||||
'Compensation End Event': '补偿结束事件',
|
||||
'Signal End Event': '信号结束事件',
|
||||
'Terminate End Event': '终止结束事件',
|
||||
'Message Boundary Event': '消息边界事件',
|
||||
'Message Boundary Event (non-interrupting)': '消息边界事件(非中断)',
|
||||
'Timer Boundary Event': '定时边界事件',
|
||||
'Timer Boundary Event (non-interrupting)': '定时边界事件(非中断)',
|
||||
'Escalation Boundary Event': '升级边界事件',
|
||||
'Escalation Boundary Event (non-interrupting)': '升级边界事件(非中断)',
|
||||
'Conditional Boundary Event': '条件边界事件',
|
||||
'Conditional Boundary Event (non-interrupting)': '条件边界事件(非中断)',
|
||||
'Error Boundary Event': '错误边界事件',
|
||||
'Cancel Boundary Event': '取消边界事件',
|
||||
'Signal Boundary Event': '信号边界事件',
|
||||
'Signal Boundary Event (non-interrupting)': '信号边界事件(非中断)',
|
||||
'Compensation Boundary Event': '补偿边界事件',
|
||||
'Exclusive Gateway': '互斥网关',
|
||||
'Parallel Gateway': '并行网关',
|
||||
'Inclusive Gateway': '相容网关',
|
||||
'Complex Gateway': '复杂网关',
|
||||
'Event based Gateway': '事件网关',
|
||||
Transaction: '转运',
|
||||
'Sub Process': '子流程',
|
||||
'Event Sub Process': '事件子流程',
|
||||
'Collapsed Pool': '折叠池',
|
||||
'Expanded Pool': '展开池',
|
||||
|
||||
// Errors
|
||||
'no parent for {element} in {parent}': '在{parent}里,{element}没有父类',
|
||||
'no shape type specified': '没有指定的形状类型',
|
||||
'flow elements must be children of pools/participants':
|
||||
'流元素必须是池/参与者的子类',
|
||||
'out of bounds release': 'out of bounds release',
|
||||
'more than {count} child lanes': '子道大于{count} ',
|
||||
'element required': '元素不能为空',
|
||||
'diagram not part of bpmn:Definitions': '流程图不符合bpmn规范',
|
||||
'no diagram to display': '没有可展示的流程图',
|
||||
'no process or collaboration to display': '没有可展示的流程/协作',
|
||||
'element {element} referenced by {referenced}#{property} not yet drawn':
|
||||
'由{referenced}#{property}引用的{element}元素仍未绘制',
|
||||
'already rendered {element}': '{element} 已被渲染',
|
||||
'failed to import {element}': '导入{element}失败',
|
||||
// 属性面板的参数
|
||||
Id: '编号',
|
||||
Name: '名称',
|
||||
General: '常规',
|
||||
Details: '详情',
|
||||
'Message Name': '消息名称',
|
||||
Message: '消息',
|
||||
Initiator: '创建者',
|
||||
'Asynchronous Continuations': '持续异步',
|
||||
'Asynchronous Before': '异步前',
|
||||
'Asynchronous After': '异步后',
|
||||
'Job Configuration': '工作配置',
|
||||
Exclusive: '排除',
|
||||
'Job Priority': '工作优先级',
|
||||
'Retry Time Cycle': '重试时间周期',
|
||||
Documentation: '文档',
|
||||
'Element Documentation': '元素文档',
|
||||
'History Configuration': '历史配置',
|
||||
'History Time To Live': '历史的生存时间',
|
||||
Forms: '表单',
|
||||
'Form Key': '表单key',
|
||||
'Form Fields': '表单字段',
|
||||
'Business Key': '业务key',
|
||||
'Form Field': '表单字段',
|
||||
ID: '编号',
|
||||
Type: '类型',
|
||||
Label: '名称',
|
||||
'Default Value': '默认值',
|
||||
'Default Flow': '默认流转路径',
|
||||
'Conditional Flow': '条件流转路径',
|
||||
'Sequence Flow': '普通流转路径',
|
||||
Validation: '校验',
|
||||
'Add Constraint': '添加约束',
|
||||
Config: '配置',
|
||||
Properties: '属性',
|
||||
'Add Property': '添加属性',
|
||||
Value: '值',
|
||||
Listeners: '监听器',
|
||||
'Execution Listener': '执行监听',
|
||||
'Event Type': '事件类型',
|
||||
'Listener Type': '监听器类型',
|
||||
'Java Class': 'Java类',
|
||||
Expression: '表达式',
|
||||
'Must provide a value': '必须提供一个值',
|
||||
'Delegate Expression': '代理表达式',
|
||||
Script: '脚本',
|
||||
'Script Format': '脚本格式',
|
||||
'Script Type': '脚本类型',
|
||||
'Inline Script': '内联脚本',
|
||||
'External Script': '外部脚本',
|
||||
Resource: '资源',
|
||||
'Field Injection': '字段注入',
|
||||
Extensions: '扩展',
|
||||
'Input/Output': '输入/输出',
|
||||
'Input Parameters': '输入参数',
|
||||
'Output Parameters': '输出参数',
|
||||
Parameters: '参数',
|
||||
'Output Parameter': '输出参数',
|
||||
'Timer Definition Type': '定时器定义类型',
|
||||
'Timer Definition': '定时器定义',
|
||||
Date: '日期',
|
||||
Duration: '持续',
|
||||
Cycle: '循环',
|
||||
Signal: '信号',
|
||||
'Signal Name': '信号名称',
|
||||
Escalation: '升级',
|
||||
Error: '错误',
|
||||
'Link Name': '链接名称',
|
||||
Condition: '条件名称',
|
||||
'Variable Name': '变量名称',
|
||||
'Variable Event': '变量事件',
|
||||
'Specify more than one variable change event as a comma separated list.':
|
||||
'多个变量事件以逗号隔开',
|
||||
'Wait for Completion': '等待完成',
|
||||
'Activity Ref': '活动参考',
|
||||
'Version Tag': '版本标签',
|
||||
Executable: '可执行文件',
|
||||
'External Task Configuration': '扩展任务配置',
|
||||
'Task Priority': '任务优先级',
|
||||
External: '外部',
|
||||
Connector: '连接器',
|
||||
'Must configure Connector': '必须配置连接器',
|
||||
'Connector Id': '连接器编号',
|
||||
Implementation: '实现方式',
|
||||
'Field Injections': '字段注入',
|
||||
Fields: '字段',
|
||||
'Result Variable': '结果变量',
|
||||
Topic: '主题',
|
||||
'Configure Connector': '配置连接器',
|
||||
'Input Parameter': '输入参数',
|
||||
Assignee: '代理人',
|
||||
'Candidate Users': '候选用户',
|
||||
'Candidate Groups': '候选组',
|
||||
'Due Date': '到期时间',
|
||||
'Follow Up Date': '跟踪日期',
|
||||
Priority: '优先级',
|
||||
'The follow up date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)':
|
||||
'跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00',
|
||||
'The due date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)':
|
||||
'跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00',
|
||||
Variables: '变量',
|
||||
'Candidate Starter Configuration': '候选人起动器配置',
|
||||
'Candidate Starter Groups': '候选人起动器组',
|
||||
'This maps to the process definition key.': '这映射到流程定义键。',
|
||||
'Candidate Starter Users': '候选人起动器的用户',
|
||||
'Specify more than one user as a comma separated list.':
|
||||
'指定多个用户作为逗号分隔的列表。',
|
||||
'Tasklist Configuration': 'Tasklist配置',
|
||||
Startable: '启动',
|
||||
'Specify more than one group as a comma separated list.':
|
||||
'指定多个组作为逗号分隔的列表。',
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
import './theme/index.scss';
|
||||
import 'bpmn-js/dist/assets/diagram-js.css';
|
||||
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
|
||||
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
|
||||
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
|
||||
|
||||
export { default as MyProcessDesigner } from './designer';
|
||||
export { default as MyProcessViewer } from './designer/index2';
|
||||
export { default as MyProcessPenal } from './penal';
|
||||
@@ -0,0 +1,37 @@
|
||||
<script lang="ts" setup>
|
||||
import { Button } from 'ant-design-vue';
|
||||
import { assign } from 'min-dash';
|
||||
|
||||
defineOptions({ name: 'MyProcessPalette' });
|
||||
|
||||
const bpmnInstances = () =>
|
||||
(window as typeof window & { bpmnInstances?: any }).bpmnInstances;
|
||||
const addTask = (event: MouseEvent, options: any = {}) => {
|
||||
const ElementFactory = bpmnInstances().elementFactory;
|
||||
const create = bpmnInstances().modeler.get('create');
|
||||
|
||||
const shape = ElementFactory.createShape(
|
||||
assign({ type: 'bpmn:UserTask' }, options),
|
||||
);
|
||||
|
||||
if (options) {
|
||||
shape.businessObject.di.isExpanded = options.isExpanded;
|
||||
}
|
||||
create.start(event, shape);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-process-palette p-20 pt-80">
|
||||
<Button type="primary" @click="addTask" @mousedown="addTask">
|
||||
测试任务
|
||||
</Button>
|
||||
<div class="test-container" id="palette-container">1</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.test-container {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,400 @@
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { Collapse } from 'ant-design-vue';
|
||||
|
||||
import ElementCustomConfig from '#/components/bpmn-process-designer/package/penal/custom-config/ElementCustomConfig.vue';
|
||||
import ElementForm from '#/components/bpmn-process-designer/package/penal/form/ElementForm.vue';
|
||||
|
||||
import ElementBaseInfo from './base/ElementBaseInfo.vue';
|
||||
import FlowCondition from './flow-condition/FlowCondition.vue';
|
||||
import ElementListeners from './listeners/ElementListeners.vue';
|
||||
// import ElementForm from './form/ElementForm.vue'
|
||||
import UserTaskListeners from './listeners/UserTaskListeners.vue';
|
||||
import ElementMultiInstance from './multi-instance/ElementMultiInstance.vue';
|
||||
import ElementOtherConfig from './other/ElementOtherConfig.vue';
|
||||
import ElementProperties from './properties/ElementProperties.vue';
|
||||
import SignalAndMassage from './signal-message/SignalAndMessage.vue';
|
||||
import { getTaskCollapseItemName, isTaskCollapseItemShow } from './task/data';
|
||||
import ElementTask from './task/ElementTask.vue';
|
||||
import TimeEventConfig from './time-event-config/TimeEventConfig.vue';
|
||||
|
||||
defineOptions({ name: 'MyPropertiesPanel' });
|
||||
|
||||
/**
|
||||
* 侧边栏
|
||||
* @Author MiyueFE
|
||||
* @Home https://github.com/miyuesc
|
||||
* @Date 2021年3月31日18:57:51
|
||||
*/
|
||||
const props = defineProps({
|
||||
bpmnModeler: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
prefix: {
|
||||
type: String,
|
||||
default: 'camunda',
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 480,
|
||||
},
|
||||
idEditDisabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
businessObject: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
model: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
}, // 流程模型的数据
|
||||
});
|
||||
|
||||
const CollapsePanel = Collapse.Panel;
|
||||
|
||||
const activeTab = ref('base');
|
||||
const elementId = ref('');
|
||||
const elementType = ref<any>('');
|
||||
const elementBusinessObject = ref<any>({}); // 元素 businessObject 镜像,提供给需要做判断的组件使用
|
||||
const conditionFormVisible = ref(false); // 流转条件设置
|
||||
const formVisible = ref(false); // 表单配置
|
||||
const bpmnElement = ref();
|
||||
const isReady = ref(false);
|
||||
|
||||
const type = ref('time');
|
||||
const condition = ref('');
|
||||
provide('prefix', props.prefix);
|
||||
provide('width', props.width);
|
||||
|
||||
// 初始化 bpmnInstances
|
||||
const initBpmnInstances = () => {
|
||||
if (!props.bpmnModeler) return false;
|
||||
try {
|
||||
const instances = {
|
||||
modeler: props.bpmnModeler,
|
||||
modeling: props.bpmnModeler.get('modeling'),
|
||||
moddle: props.bpmnModeler.get('moddle'),
|
||||
eventBus: props.bpmnModeler.get('eventBus'),
|
||||
bpmnFactory: props.bpmnModeler.get('bpmnFactory'),
|
||||
elementFactory: props.bpmnModeler.get('elementFactory'),
|
||||
elementRegistry: props.bpmnModeler.get('elementRegistry'),
|
||||
replace: props.bpmnModeler.get('replace'),
|
||||
selection: props.bpmnModeler.get('selection'),
|
||||
};
|
||||
|
||||
// 检查所有实例是否都存在
|
||||
const allInstancesExist = Object.values(instances).every(Boolean);
|
||||
if (allInstancesExist) {
|
||||
const w = window as any;
|
||||
w.bpmnInstances = instances;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('初始化 bpmnInstances 失败:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
// 监听 props.bpmnModeler 然后 initModels
|
||||
watch(
|
||||
() => props.bpmnModeler,
|
||||
async () => {
|
||||
// 避免加载时 流程图 并未加载完成
|
||||
if (!props.bpmnModeler) {
|
||||
// console.log('缺少props.bpmnModeler');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 等待 modeler 初始化完成
|
||||
await nextTick();
|
||||
if (initBpmnInstances()) {
|
||||
isReady.value = true;
|
||||
await nextTick();
|
||||
getActiveElement();
|
||||
} else {
|
||||
console.error('modeler 实例未完全初始化');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('初始化失败:', error);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
const getActiveElement = () => {
|
||||
if (!isReady.value || !props.bpmnModeler) return;
|
||||
|
||||
// 初始第一个选中元素 bpmn:Process
|
||||
initFormOnChanged(null);
|
||||
props.bpmnModeler.on('import.done', (_: any) => {
|
||||
// console.log(e, 'eeeee');
|
||||
initFormOnChanged(null);
|
||||
});
|
||||
// 监听选择事件,修改当前激活的元素以及表单
|
||||
props.bpmnModeler.on(
|
||||
'selection.changed',
|
||||
({ newSelection }: { newSelection: any }) => {
|
||||
initFormOnChanged(newSelection[0] || null);
|
||||
},
|
||||
);
|
||||
props.bpmnModeler.on('element.changed', ({ element }: { element: any }) => {
|
||||
// 保证 修改 "默认流转路径" 类似需要修改多个元素的事件发生的时候,更新表单的元素与原选中元素不一致。
|
||||
if (element && element.id === elementId.value) {
|
||||
initFormOnChanged(element);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化数据
|
||||
const initFormOnChanged = (element: any) => {
|
||||
if (!isReady.value || !bpmnInstances()) return;
|
||||
|
||||
let activatedElement = element;
|
||||
if (!activatedElement) {
|
||||
activatedElement =
|
||||
bpmnInstances().elementRegistry.find(
|
||||
(el: any) => el.type === 'bpmn:Process',
|
||||
) ??
|
||||
bpmnInstances().elementRegistry.find(
|
||||
(el: any) => el.type === 'bpmn:Collaboration',
|
||||
);
|
||||
}
|
||||
if (!activatedElement) return;
|
||||
|
||||
try {
|
||||
// console.log(`
|
||||
// ----------
|
||||
// select element changed:
|
||||
// id: ${activatedElement.id}
|
||||
// type: ${activatedElement.businessObject.$type}
|
||||
// ----------
|
||||
// `);
|
||||
// console.log('businessObject:', activatedElement.businessObject);
|
||||
bpmnInstances().bpmnElement = activatedElement;
|
||||
bpmnElement.value = activatedElement;
|
||||
elementId.value = activatedElement.id;
|
||||
elementType.value = activatedElement.type.split(':')[1] || '';
|
||||
elementBusinessObject.value = cloneDeep(activatedElement.businessObject);
|
||||
conditionFormVisible.value =
|
||||
elementType.value === 'SequenceFlow' &&
|
||||
activatedElement.source &&
|
||||
(activatedElement.source.type as string).includes('StartEvent');
|
||||
formVisible.value =
|
||||
elementType.value === 'UserTask' || elementType.value === 'StartEvent';
|
||||
} catch (error) {
|
||||
console.error('初始化表单数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const w = window as any;
|
||||
w.bpmnInstances = null;
|
||||
isReady.value = false;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => elementId.value,
|
||||
() => {
|
||||
activeTab.value = 'base';
|
||||
},
|
||||
);
|
||||
//
|
||||
// function updateNode() {
|
||||
// const moddle = window.bpmnInstances?.moddle;
|
||||
// const modeling = window.bpmnInstances?.modeling;
|
||||
// const elementRegistry = window.bpmnInstances?.elementRegistry;
|
||||
// if (!moddle || !modeling || !elementRegistry) return;
|
||||
//
|
||||
// const element = elementRegistry.get(props.businessObject.id);
|
||||
// if (!element) return;
|
||||
//
|
||||
// const timerDef = moddle.create('bpmn:TimerEventDefinition', {});
|
||||
// switch (type.value) {
|
||||
// case 'cycle': {
|
||||
// timerDef.timeCycle = moddle.create('bpmn:FormalExpression', {
|
||||
// body: condition.value,
|
||||
// });
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
// case 'duration': {
|
||||
// timerDef.timeDuration = moddle.create('bpmn:FormalExpression', {
|
||||
// body: condition.value,
|
||||
// });
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
// case 'time': {
|
||||
// timerDef.timeDate = moddle.create('bpmn:FormalExpression', {
|
||||
// body: condition.value,
|
||||
// });
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
// // No default
|
||||
// }
|
||||
//
|
||||
// modeling.updateModdleProperties(element, element.businessObject, {
|
||||
// eventDefinitions: [timerDef],
|
||||
// });
|
||||
// }
|
||||
|
||||
// 初始化和监听
|
||||
function syncFromBusinessObject() {
|
||||
if (props.businessObject) {
|
||||
const timerDef = (props.businessObject.eventDefinitions || [])[0];
|
||||
if (timerDef) {
|
||||
if (timerDef.timeDate) {
|
||||
type.value = 'time';
|
||||
condition.value = timerDef.timeDate.body;
|
||||
} else if (timerDef.timeDuration) {
|
||||
type.value = 'duration';
|
||||
condition.value = timerDef.timeDuration.body;
|
||||
} else if (timerDef.timeCycle) {
|
||||
type.value = 'cycle';
|
||||
condition.value = timerDef.timeCycle.body;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onMounted(syncFromBusinessObject);
|
||||
watch(() => props.businessObject, syncFromBusinessObject, { deep: true });
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="process-panel__container"
|
||||
:style="{ width: `${width}px`, maxHeight: '600px' }"
|
||||
>
|
||||
<Collapse v-model:active-key="activeTab" v-if="isReady">
|
||||
<CollapsePanel key="base" header="常规">
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:info-filled" />
|
||||
</template>
|
||||
<ElementBaseInfo
|
||||
:id-edit-disabled="idEditDisabled"
|
||||
:business-object="elementBusinessObject"
|
||||
:type="elementType"
|
||||
:model="model"
|
||||
/>
|
||||
</CollapsePanel>
|
||||
<CollapsePanel
|
||||
key="message"
|
||||
header="消息与信号"
|
||||
v-if="elementType === 'Process'"
|
||||
>
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:comment" />
|
||||
</template>
|
||||
<SignalAndMassage />
|
||||
</CollapsePanel>
|
||||
<CollapsePanel
|
||||
key="condition"
|
||||
header="流转条件"
|
||||
v-if="conditionFormVisible"
|
||||
>
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:promotion" />
|
||||
</template>
|
||||
<FlowCondition
|
||||
:business-object="elementBusinessObject"
|
||||
:type="elementType"
|
||||
/>
|
||||
</CollapsePanel>
|
||||
<CollapsePanel key="form" header="表单" v-if="formVisible">
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:list" />
|
||||
</template>
|
||||
<ElementForm :id="elementId" :type="elementType" />
|
||||
</CollapsePanel>
|
||||
<CollapsePanel
|
||||
key="task"
|
||||
:header="getTaskCollapseItemName(elementType)"
|
||||
v-if="isTaskCollapseItemShow(elementType)"
|
||||
>
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:checked" />
|
||||
</template>
|
||||
<ElementTask :id="elementId" :type="elementType" />
|
||||
</CollapsePanel>
|
||||
<CollapsePanel
|
||||
key="multiInstance"
|
||||
header="多人审批方式"
|
||||
v-if="elementType.includes('Task')"
|
||||
>
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:help-filled" />
|
||||
</template>
|
||||
<ElementMultiInstance
|
||||
:id="elementId"
|
||||
:business-object="elementBusinessObject"
|
||||
:type="elementType"
|
||||
/>
|
||||
</CollapsePanel>
|
||||
<CollapsePanel key="listeners" header="执行监听器">
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:bell-filled" />
|
||||
</template>
|
||||
<ElementListeners :id="elementId" :type="elementType" />
|
||||
</CollapsePanel>
|
||||
<CollapsePanel
|
||||
key="taskListeners"
|
||||
header="任务监听器"
|
||||
v-if="elementType === 'UserTask'"
|
||||
>
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:bell-filled" />
|
||||
</template>
|
||||
<UserTaskListeners :id="elementId" :type="elementType" />
|
||||
</CollapsePanel>
|
||||
<CollapsePanel key="extensions" header="扩展属性">
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:circle-plus-filled" />
|
||||
</template>
|
||||
<ElementProperties :id="elementId" :type="elementType" />
|
||||
</CollapsePanel>
|
||||
<CollapsePanel key="other" header="其他">
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:promotion" />
|
||||
</template>
|
||||
<ElementOtherConfig :id="elementId" />
|
||||
</CollapsePanel>
|
||||
<CollapsePanel key="customConfig" header="自定义配置">
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:tools" />
|
||||
</template>
|
||||
<ElementCustomConfig
|
||||
:id="elementId"
|
||||
:type="elementType"
|
||||
:business-object="elementBusinessObject"
|
||||
/>
|
||||
</CollapsePanel>
|
||||
<!-- 新增的时间事件配置项 -->
|
||||
<CollapsePanel
|
||||
key="timeEvent"
|
||||
header="时间事件"
|
||||
v-if="elementType === 'IntermediateCatchEvent'"
|
||||
>
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:timer" />
|
||||
</template>
|
||||
<TimeEventConfig
|
||||
:business-object="bpmnElement.value?.businessObject"
|
||||
:key="elementId"
|
||||
/>
|
||||
</CollapsePanel>
|
||||
</Collapse>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,225 @@
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, reactive, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { Form, FormItem, Input } from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'ElementBaseInfo' });
|
||||
|
||||
const props = defineProps<{
|
||||
businessObject?: BusinessObject;
|
||||
model?: Model;
|
||||
}>();
|
||||
|
||||
interface BusinessObject {
|
||||
id?: string;
|
||||
name?: string;
|
||||
$type: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface Model {
|
||||
key?: string;
|
||||
name?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const needProps = ref<Record<string, any>>({});
|
||||
const bpmnElement = ref<any>();
|
||||
const elementBaseInfo = ref<BusinessObject>({} as any);
|
||||
// 流程表单的下拉框的数据
|
||||
// const forms = ref([])
|
||||
// 流程模型的校验
|
||||
const rules = reactive<any>({
|
||||
id: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
const bpmnInstances = () =>
|
||||
(window as any)?.bpmnInstances as {
|
||||
bpmnElement: any;
|
||||
modeling: {
|
||||
updateProperties: (element: any, properties: any) => void;
|
||||
};
|
||||
};
|
||||
const resetBaseInfo = () => {
|
||||
// console.log(window, 'window');
|
||||
// console.log(bpmnElement.value, 'bpmnElement');
|
||||
|
||||
bpmnElement.value = bpmnInstances()?.bpmnElement;
|
||||
// console.log(bpmnElement.value, 'resetBaseInfo11111111111')
|
||||
if (bpmnElement.value?.businessObject) {
|
||||
elementBaseInfo.value = bpmnElement.value.businessObject;
|
||||
needProps.value.type = bpmnElement.value.businessObject.$type;
|
||||
}
|
||||
// elementBaseInfo.value['typess'] = bpmnElement.value.businessObject.$type
|
||||
|
||||
// elementBaseInfo.value = JSON.parse(JSON.stringify(bpmnElement.value.businessObject))
|
||||
// console.log(elementBaseInfo.value, 'elementBaseInfo22222222222')
|
||||
};
|
||||
const handleKeyUpdate = (value: any) => {
|
||||
// 校验 value 的值,只有 XML NCName 通过的情况下,才进行赋值。否则,会导致流程图报错,无法绘制的问题
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
if (!/[a-z_][-\w.$]*/i.test(value)) {
|
||||
// console.log('key 不满足 XML NCName 规则,所以不进行赋值');
|
||||
return;
|
||||
}
|
||||
// console.log('key 满足 XML NCName 规则,所以进行赋值');
|
||||
|
||||
// 在 BPMN 的 XML 中,流程标识 key,其实对应的是 id 节点
|
||||
if (elementBaseInfo.value) {
|
||||
elementBaseInfo.value.id = value;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
updateBaseInfo('id');
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const handleNameUpdate = (value: any) => {
|
||||
// console.log(elementBaseInfo, 'elementBaseInfo');
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
if (elementBaseInfo.value) {
|
||||
elementBaseInfo.value.name = value;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
updateBaseInfo('name');
|
||||
}, 100);
|
||||
};
|
||||
// const handleDescriptionUpdate=(value)=> {
|
||||
// TODO 芋艿:documentation 暂时无法修改,后续在看看
|
||||
// this.elementBaseInfo['documentation'] = value;
|
||||
// this.updateBaseInfo('documentation');
|
||||
// }
|
||||
const updateBaseInfo = (key: string) => {
|
||||
// console.log(key, 'key');
|
||||
// 触发 elementBaseInfo 对应的字段
|
||||
const attrObj: Record<string, any> = Object.create(null);
|
||||
|
||||
// 安全检查
|
||||
if (!elementBaseInfo.value || !bpmnElement.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log(attrObj, 'attrObj')
|
||||
attrObj[key] = elementBaseInfo.value[key];
|
||||
// console.log(attrObj, 'attrObj111')
|
||||
// const attrObj = {
|
||||
// id: elementBaseInfo.value[key]
|
||||
// // di: { id: `${elementBaseInfo.value[key]}_di` }
|
||||
// }
|
||||
// console.log(elementBaseInfo, 'elementBaseInfo11111111111')
|
||||
needProps.value = { ...elementBaseInfo.value, ...needProps.value };
|
||||
|
||||
if (key === 'id') {
|
||||
// console.log('jinru')
|
||||
// console.log(window, 'window');
|
||||
// console.log(bpmnElement.value, 'bpmnElement');
|
||||
// console.log(toRaw(bpmnElement.value), 'bpmnElement');
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
id: elementBaseInfo.value[key],
|
||||
di: { id: `${elementBaseInfo.value[key]}_di` },
|
||||
});
|
||||
} else {
|
||||
// console.log(attrObj, 'attrObj');
|
||||
bpmnInstances().modeling.updateProperties(
|
||||
toRaw(bpmnElement.value),
|
||||
attrObj,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.businessObject,
|
||||
(val) => {
|
||||
// console.log(val, 'val11111111111111111111')
|
||||
if (val) {
|
||||
// nextTick(() => {
|
||||
resetBaseInfo();
|
||||
// })
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.model?.key,
|
||||
(val) => {
|
||||
// 针对上传的 bpmn 流程图时,保证 key 和 name 的更新
|
||||
if (val) {
|
||||
handleKeyUpdate(props.model?.key as any);
|
||||
handleNameUpdate(props.model?.name as any);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
// watch(
|
||||
// () => ({ ...props }),
|
||||
// (oldVal, newVal) => {
|
||||
// console.log(oldVal, 'oldVal')
|
||||
// console.log(newVal, 'newVal')
|
||||
// if (newVal) {
|
||||
// needProps.value = newVal
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// immediate: true
|
||||
// }
|
||||
// )
|
||||
// 'model.key': {
|
||||
// immediate: false,
|
||||
// handler: function (val) {
|
||||
// this.handleKeyUpdate(val)
|
||||
// }
|
||||
// }
|
||||
onBeforeUnmount(() => {
|
||||
bpmnElement.value = null;
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Form :model="needProps" :rules="rules" layout="vertical">
|
||||
<div v-if="needProps.type === 'bpmn:Process'">
|
||||
<!-- 如果是 Process 信息的时候,使用自定义表单 -->
|
||||
<FormItem label="流程标识" name="id">
|
||||
<Input
|
||||
v-model:value="needProps.id"
|
||||
placeholder="请输入流标标识"
|
||||
:disabled="needProps.id !== undefined && needProps.id.length > 0"
|
||||
@change="handleKeyUpdate"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="流程名称" name="name">
|
||||
<Input
|
||||
v-model:value="needProps.name"
|
||||
placeholder="请输入流程名称"
|
||||
allow-clear
|
||||
@change="handleNameUpdate"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
<div v-else>
|
||||
<FormItem label="ID">
|
||||
<Input
|
||||
v-model:value="elementBaseInfo.id"
|
||||
allow-clear
|
||||
@change="updateBaseInfo('id')"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="名称">
|
||||
<Input
|
||||
v-model:value="elementBaseInfo.name"
|
||||
allow-clear
|
||||
@change="updateBaseInfo('name')"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,58 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import { defineOptions, defineProps, ref, watch } from 'vue';
|
||||
|
||||
import { CustomConfigMap } from './data';
|
||||
|
||||
defineOptions({ name: 'ElementCustomConfig' });
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
businessObject: {
|
||||
type: Object as () => BusinessObject,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
interface BusinessObject {
|
||||
eventDefinitions?: Array<{ $type: string }>;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
const customConfigComponent = ref<Component | null>(null);
|
||||
|
||||
watch(
|
||||
() => props.businessObject,
|
||||
() => {
|
||||
if (props.type && props.businessObject) {
|
||||
let val = props.type;
|
||||
if (props.businessObject.eventDefinitions) {
|
||||
val +=
|
||||
props.businessObject.eventDefinitions[0]?.$type.split(':')[1] || '';
|
||||
}
|
||||
// @ts-ignore
|
||||
customConfigComponent.value = (
|
||||
CustomConfigMap as Record<string, { component: Component }>
|
||||
)[val]?.component;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<component :is="customConfigComponent" v-bind="$props" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,307 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
defineOptions,
|
||||
defineProps,
|
||||
inject,
|
||||
nextTick,
|
||||
ref,
|
||||
toRaw,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
import {
|
||||
Divider,
|
||||
FormItem,
|
||||
InputNumber,
|
||||
RadioButton,
|
||||
RadioGroup,
|
||||
Select,
|
||||
SelectOption,
|
||||
Switch,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { convertTimeUnit } from '#/components/simple-process-design/components/nodes-config/utils';
|
||||
import {
|
||||
TIME_UNIT_TYPES,
|
||||
TIMEOUT_HANDLER_TYPES,
|
||||
TimeUnitType,
|
||||
} from '#/components/simple-process-design/consts';
|
||||
|
||||
defineOptions({ name: 'ElementCustomConfig4BoundaryEventTimer' });
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const prefix = inject('prefix');
|
||||
|
||||
const bpmnElement = ref<any>();
|
||||
const bpmnInstances = () => (window as Record<string, any>)?.bpmnInstances;
|
||||
|
||||
const timeoutHandlerEnable = ref(false);
|
||||
const boundaryEventType = ref<any>();
|
||||
const timeoutHandlerType = ref<{
|
||||
value: number | undefined;
|
||||
}>({
|
||||
value: undefined,
|
||||
});
|
||||
const timeModdle = ref<any>();
|
||||
const timeDuration = ref(6);
|
||||
const timeUnit = ref(TimeUnitType.HOUR);
|
||||
const maxRemindCount = ref(1);
|
||||
|
||||
const elExtensionElements = ref<any>();
|
||||
const otherExtensions = ref<any[]>();
|
||||
const configExtensions = ref<any[]>([]);
|
||||
const eventDefinition = ref<any>();
|
||||
|
||||
const resetElement = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
eventDefinition.value = bpmnElement.value.businessObject.eventDefinitions[0];
|
||||
|
||||
// 获取元素扩展属性 或者 创建扩展属性
|
||||
elExtensionElements.value =
|
||||
bpmnElement.value.businessObject?.extensionElements ??
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] });
|
||||
|
||||
// 是否开启自定义用户任务超时处理
|
||||
boundaryEventType.value = elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:BoundaryEventType`,
|
||||
)?.[0];
|
||||
if (boundaryEventType.value && boundaryEventType.value.value === 1) {
|
||||
timeoutHandlerEnable.value = true;
|
||||
configExtensions.value.push(boundaryEventType.value);
|
||||
}
|
||||
|
||||
// 执行动作
|
||||
timeoutHandlerType.value = elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:TimeoutHandlerType`,
|
||||
)?.[0];
|
||||
if (timeoutHandlerType.value) {
|
||||
configExtensions.value.push(timeoutHandlerType.value);
|
||||
if (eventDefinition.value.timeCycle) {
|
||||
const timeStr = eventDefinition.value.timeCycle.body;
|
||||
const maxRemindCountStr = timeStr.split('/')[0];
|
||||
const timeDurationStr = timeStr.split('/')[1];
|
||||
maxRemindCount.value = Number.parseInt(maxRemindCountStr.slice(1));
|
||||
timeDuration.value = Number.parseInt(timeDurationStr.slice(2, -1));
|
||||
timeUnit.value = convertTimeUnit(timeDurationStr.slice(-1));
|
||||
timeModdle.value = eventDefinition.value.timeCycle;
|
||||
}
|
||||
if (eventDefinition.value.timeDuration) {
|
||||
const timeDurationStr = eventDefinition.value.timeDuration.body;
|
||||
timeDuration.value = Number.parseInt(timeDurationStr.slice(2, -1));
|
||||
timeUnit.value = convertTimeUnit(timeDurationStr.slice(-1));
|
||||
timeModdle.value = eventDefinition.value.timeDuration;
|
||||
}
|
||||
}
|
||||
|
||||
// 保留剩余扩展元素,便于后面更新该元素对应属性
|
||||
otherExtensions.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) =>
|
||||
ex.$type !== `${prefix}:BoundaryEventType` &&
|
||||
ex.$type !== `${prefix}:TimeoutHandlerType`,
|
||||
) ?? [];
|
||||
};
|
||||
|
||||
const timeoutHandlerChange = (checked: any) => {
|
||||
timeoutHandlerEnable.value = checked;
|
||||
if (checked) {
|
||||
// 启用自定义用户任务超时处理
|
||||
// 边界事件类型 --- 超时
|
||||
boundaryEventType.value = bpmnInstances().moddle.create(
|
||||
`${prefix}:BoundaryEventType`,
|
||||
{
|
||||
value: 1,
|
||||
},
|
||||
);
|
||||
configExtensions.value.push(boundaryEventType.value);
|
||||
// 超时处理类型
|
||||
timeoutHandlerType.value = bpmnInstances().moddle.create(
|
||||
`${prefix}:TimeoutHandlerType`,
|
||||
{
|
||||
value: 1,
|
||||
},
|
||||
);
|
||||
configExtensions.value.push(timeoutHandlerType.value);
|
||||
// 超时时间表达式
|
||||
timeDuration.value = 6;
|
||||
timeUnit.value = 2;
|
||||
maxRemindCount.value = 1;
|
||||
timeModdle.value = bpmnInstances().moddle.create(`bpmn:Expression`, {
|
||||
body: 'PT6H',
|
||||
});
|
||||
eventDefinition.value.timeDuration = timeModdle.value;
|
||||
} else {
|
||||
// 关闭自定义用户任务超时处理
|
||||
configExtensions.value = [];
|
||||
delete eventDefinition.value.timeDuration;
|
||||
delete eventDefinition.value.timeCycle;
|
||||
}
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const onTimeoutHandlerTypeChanged = () => {
|
||||
maxRemindCount.value = 1;
|
||||
updateElementExtensions();
|
||||
updateTimeModdle();
|
||||
};
|
||||
|
||||
const onTimeUnitChange = () => {
|
||||
// 分钟,默认是 60 分钟
|
||||
if (timeUnit.value === TimeUnitType.MINUTE) {
|
||||
timeDuration.value = 60;
|
||||
}
|
||||
// 小时,默认是 6 个小时
|
||||
if (timeUnit.value === TimeUnitType.HOUR) {
|
||||
timeDuration.value = 6;
|
||||
}
|
||||
// 天, 默认 1天
|
||||
if (timeUnit.value === TimeUnitType.DAY) {
|
||||
timeDuration.value = 1;
|
||||
}
|
||||
updateTimeModdle();
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateTimeModdle = () => {
|
||||
if (maxRemindCount.value > 1) {
|
||||
timeModdle.value.body = `R${maxRemindCount.value}/${isoTimeDuration()}`;
|
||||
if (!eventDefinition.value.timeCycle) {
|
||||
delete eventDefinition.value.timeDuration;
|
||||
eventDefinition.value.timeCycle = timeModdle.value;
|
||||
}
|
||||
} else {
|
||||
timeModdle.value.body = isoTimeDuration();
|
||||
if (!eventDefinition.value.timeDuration) {
|
||||
delete eventDefinition.value.timeCycle;
|
||||
eventDefinition.value.timeDuration = timeModdle.value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const isoTimeDuration = () => {
|
||||
let strTimeDuration = 'PT';
|
||||
if (timeUnit.value === TimeUnitType.MINUTE) {
|
||||
strTimeDuration += `${timeDuration.value}M`;
|
||||
}
|
||||
if (timeUnit.value === TimeUnitType.HOUR) {
|
||||
strTimeDuration += `${timeDuration.value}H`;
|
||||
}
|
||||
if (timeUnit.value === TimeUnitType.DAY) {
|
||||
strTimeDuration += `${timeDuration.value}D`;
|
||||
}
|
||||
return strTimeDuration;
|
||||
};
|
||||
|
||||
const updateElementExtensions = () => {
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [...(otherExtensions.value || []), ...configExtensions.value],
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions,
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
val &&
|
||||
val.length > 0 &&
|
||||
nextTick(() => {
|
||||
resetElement();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Divider orientation="left">审批人超时未处理时</Divider>
|
||||
<FormItem label="启用开关" name="timeoutHandlerEnable">
|
||||
<Switch
|
||||
v-model:checked="timeoutHandlerEnable"
|
||||
checked-children="开启"
|
||||
un-checked-children="关闭"
|
||||
@change="timeoutHandlerChange"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="执行动作"
|
||||
name="timeoutHandlerType"
|
||||
v-if="timeoutHandlerEnable"
|
||||
>
|
||||
<RadioGroup
|
||||
v-model:value="timeoutHandlerType.value"
|
||||
@change="onTimeoutHandlerTypeChanged"
|
||||
>
|
||||
<RadioButton
|
||||
v-for="item in TIMEOUT_HANDLER_TYPES"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</RadioButton>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
<FormItem label="超时时间设置" v-if="timeoutHandlerEnable">
|
||||
<span class="mr-2">当超过</span>
|
||||
<FormItem name="timeDuration">
|
||||
<InputNumber
|
||||
class="mr-2"
|
||||
:style="{ width: '100px' }"
|
||||
v-model:value="timeDuration"
|
||||
:min="1"
|
||||
:controls="true"
|
||||
@change="
|
||||
() => {
|
||||
updateTimeModdle();
|
||||
updateElementExtensions();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</FormItem>
|
||||
<Select
|
||||
v-model:value="timeUnit"
|
||||
class="mr-2"
|
||||
:style="{ width: '100px' }"
|
||||
@change="onTimeUnitChange"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in TIME_UNIT_TYPES"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
未处理
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="最大提醒次数"
|
||||
name="maxRemindCount"
|
||||
v-if="timeoutHandlerEnable && timeoutHandlerType.value === 1"
|
||||
>
|
||||
<InputNumber
|
||||
v-model:value="maxRemindCount"
|
||||
:min="1"
|
||||
:max="10"
|
||||
@change="
|
||||
() => {
|
||||
updateTimeModdle();
|
||||
updateElementExtensions();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,783 @@
|
||||
<!-- UserTask 自定义配置:
|
||||
1. 审批人与提交人为同一人时
|
||||
2. 审批人拒绝时
|
||||
3. 审批人为空时
|
||||
4. 操作按钮
|
||||
5. 字段权限
|
||||
6. 审批类型
|
||||
7. 是否需要签名
|
||||
-->
|
||||
<script lang="ts" setup>
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
import type { ButtonSetting } from '#/components/simple-process-design/consts';
|
||||
|
||||
import { inject, nextTick, onMounted, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { BpmModelFormType } from '@vben/constants';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Form,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Select,
|
||||
SelectOption,
|
||||
Switch,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import {
|
||||
APPROVE_TYPE,
|
||||
ApproveType,
|
||||
ASSIGN_EMPTY_HANDLER_TYPES,
|
||||
ASSIGN_START_USER_HANDLER_TYPES,
|
||||
AssignEmptyHandlerType,
|
||||
DEFAULT_BUTTON_SETTING,
|
||||
FieldPermissionType,
|
||||
OPERATION_BUTTON_NAME,
|
||||
REJECT_HANDLER_TYPES,
|
||||
RejectHandlerType,
|
||||
} from '#/components/simple-process-design/consts';
|
||||
import { useFormFieldsPermission } from '#/components/simple-process-design/helpers';
|
||||
|
||||
defineOptions({ name: 'ElementCustomConfig4UserTask' });
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const prefix = inject('prefix');
|
||||
|
||||
// 审批人与提交人为同一人时
|
||||
const assignStartUserHandlerTypeEl = ref<any>();
|
||||
const assignStartUserHandlerType = ref<any>();
|
||||
|
||||
// 审批人拒绝时
|
||||
const rejectHandlerTypeEl = ref<any>();
|
||||
const rejectHandlerType = ref<any>();
|
||||
const returnNodeIdEl = ref<any>();
|
||||
const returnNodeId = ref<any>();
|
||||
const returnTaskList = ref<any[]>([]);
|
||||
|
||||
// 审批人为空时
|
||||
const assignEmptyHandlerTypeEl = ref<any>();
|
||||
const assignEmptyHandlerType = ref<any>();
|
||||
const assignEmptyUserIdsEl = ref<any>();
|
||||
const assignEmptyUserIds = ref<any>();
|
||||
|
||||
// 操作按钮
|
||||
const buttonsSettingEl = ref<any>();
|
||||
const { btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } =
|
||||
useButtonsSetting();
|
||||
|
||||
// 字段权限
|
||||
const fieldsPermissionEl = ref<any[]>([]);
|
||||
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } =
|
||||
useFormFieldsPermission(FieldPermissionType.READ);
|
||||
|
||||
// 审批类型
|
||||
const approveType = ref({ value: ApproveType.USER });
|
||||
|
||||
// 是否需要签名
|
||||
const signEnable = ref({ value: false });
|
||||
|
||||
// 审批意见
|
||||
const reasonRequire = ref({ value: false });
|
||||
|
||||
const elExtensionElements = ref<any>();
|
||||
const otherExtensions = ref<any>();
|
||||
const bpmnElement = ref<any>();
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const resetCustomConfigList = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
|
||||
// 获取可回退的列表
|
||||
returnTaskList.value = findAllPredecessorsExcludingStart(
|
||||
bpmnElement.value.id,
|
||||
bpmnInstances().modeler,
|
||||
);
|
||||
// 获取元素扩展属性 或者 创建扩展属性
|
||||
elExtensionElements.value =
|
||||
bpmnElement.value.businessObject?.extensionElements ??
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] });
|
||||
|
||||
// 审批类型
|
||||
approveType.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:ApproveType`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:ApproveType`, {
|
||||
value: ApproveType.USER,
|
||||
});
|
||||
|
||||
// 审批人与提交人为同一人时
|
||||
assignStartUserHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:AssignStartUserHandlerType`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, {
|
||||
value: 1,
|
||||
});
|
||||
assignStartUserHandlerType.value = assignStartUserHandlerTypeEl.value.value;
|
||||
|
||||
// 审批人拒绝时
|
||||
rejectHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:RejectHandlerType`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 });
|
||||
rejectHandlerType.value = rejectHandlerTypeEl.value.value;
|
||||
returnNodeIdEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:RejectReturnTaskId`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, {
|
||||
value: '',
|
||||
});
|
||||
returnNodeId.value = returnNodeIdEl.value.value;
|
||||
|
||||
// 审批人为空时
|
||||
assignEmptyHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:AssignEmptyHandlerType`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, {
|
||||
value: 1,
|
||||
});
|
||||
assignEmptyHandlerType.value = assignEmptyHandlerTypeEl.value.value;
|
||||
assignEmptyUserIdsEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:AssignEmptyUserIds`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, {
|
||||
value: '',
|
||||
});
|
||||
assignEmptyUserIds.value = assignEmptyUserIdsEl.value.value
|
||||
?.split(',')
|
||||
.map((item: string) => {
|
||||
// 如果数字超出了最大安全整数范围,则将其作为字符串处理
|
||||
const num = Number(item);
|
||||
return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER
|
||||
? item
|
||||
: num;
|
||||
});
|
||||
|
||||
// 操作按钮
|
||||
buttonsSettingEl.value = elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:ButtonsSetting`,
|
||||
);
|
||||
if (buttonsSettingEl.value.length === 0) {
|
||||
DEFAULT_BUTTON_SETTING.forEach((item) => {
|
||||
buttonsSettingEl.value.push(
|
||||
bpmnInstances().moddle.create(`${prefix}:ButtonsSetting`, {
|
||||
'flowable:id': item.id,
|
||||
'flowable:displayName': item.displayName,
|
||||
'flowable:enable': item.enable,
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 字段权限
|
||||
if (formType.value === BpmModelFormType.NORMAL) {
|
||||
const fieldsPermissionList = elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:FieldsPermission`,
|
||||
);
|
||||
fieldsPermissionEl.value = [];
|
||||
getNodeConfigFormFields();
|
||||
fieldsPermissionConfig.value.forEach((element: any) => {
|
||||
element.permission =
|
||||
fieldsPermissionList?.find((obj: any) => obj.field === element.field)
|
||||
?.permission ?? '1';
|
||||
fieldsPermissionEl.value.push(
|
||||
bpmnInstances().moddle.create(`${prefix}:FieldsPermission`, element),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 是否需要签名
|
||||
signEnable.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:SignEnable`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:SignEnable`, { value: false });
|
||||
|
||||
// 审批意见
|
||||
reasonRequire.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:ReasonRequire`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:ReasonRequire`, { value: false });
|
||||
|
||||
// 保留剩余扩展元素,便于后面更新该元素对应属性
|
||||
otherExtensions.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) =>
|
||||
ex.$type !== `${prefix}:AssignStartUserHandlerType` &&
|
||||
ex.$type !== `${prefix}:RejectHandlerType` &&
|
||||
ex.$type !== `${prefix}:RejectReturnTaskId` &&
|
||||
ex.$type !== `${prefix}:AssignEmptyHandlerType` &&
|
||||
ex.$type !== `${prefix}:AssignEmptyUserIds` &&
|
||||
ex.$type !== `${prefix}:ButtonsSetting` &&
|
||||
ex.$type !== `${prefix}:FieldsPermission` &&
|
||||
ex.$type !== `${prefix}:ApproveType` &&
|
||||
ex.$type !== `${prefix}:SignEnable` &&
|
||||
ex.$type !== `${prefix}:ReasonRequire`,
|
||||
) ?? [];
|
||||
|
||||
// 更新元素扩展属性,避免后续报错
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateAssignStartUserHandlerType = () => {
|
||||
assignStartUserHandlerTypeEl.value.value = assignStartUserHandlerType.value;
|
||||
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateRejectHandlerType = () => {
|
||||
rejectHandlerTypeEl.value.value = rejectHandlerType.value;
|
||||
|
||||
returnNodeId.value = returnTaskList.value[0]?.id;
|
||||
returnNodeIdEl.value.value = returnNodeId.value;
|
||||
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateReturnNodeId = () => {
|
||||
returnNodeIdEl.value.value = returnNodeId.value;
|
||||
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateAssignEmptyHandlerType = () => {
|
||||
assignEmptyHandlerTypeEl.value.value = assignEmptyHandlerType.value;
|
||||
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateAssignEmptyUserIds = () => {
|
||||
assignEmptyUserIdsEl.value.value = assignEmptyUserIds.value.toString();
|
||||
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateElementExtensions = () => {
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [
|
||||
...otherExtensions.value,
|
||||
assignStartUserHandlerTypeEl.value,
|
||||
rejectHandlerTypeEl.value,
|
||||
returnNodeIdEl.value,
|
||||
assignEmptyHandlerTypeEl.value,
|
||||
assignEmptyUserIdsEl.value,
|
||||
approveType.value,
|
||||
...buttonsSettingEl.value,
|
||||
...fieldsPermissionEl.value,
|
||||
signEnable.value,
|
||||
reasonRequire.value,
|
||||
],
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions,
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
val &&
|
||||
val.length > 0 &&
|
||||
nextTick(() => {
|
||||
resetCustomConfigList();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function findAllPredecessorsExcludingStart(elementId: string, modeler: any) {
|
||||
const elementRegistry = modeler.get('elementRegistry');
|
||||
const allConnections = elementRegistry.filter(
|
||||
(element: any) => element.type === 'bpmn:SequenceFlow',
|
||||
);
|
||||
const predecessors = new Set(); // 使用 Set 来避免重复节点
|
||||
const visited = new Set(); // 用于记录已访问的节点
|
||||
|
||||
// 检查是否是开始事件节点
|
||||
function isStartEvent(element: any) {
|
||||
return element.type === 'bpmn:StartEvent';
|
||||
}
|
||||
|
||||
function findPredecessorsRecursively(element: any) {
|
||||
// 如果该节点已经访问过,直接返回,避免循环
|
||||
if (visited.has(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 标记当前节点为已访问
|
||||
visited.add(element);
|
||||
|
||||
// 获取与当前节点相连的所有连接
|
||||
const incomingConnections = allConnections.filter(
|
||||
(connection: any) => connection.target === element,
|
||||
);
|
||||
|
||||
incomingConnections.forEach((connection: any) => {
|
||||
const source = connection.source; // 获取前置节点
|
||||
|
||||
// 只添加不是开始事件的前置节点
|
||||
if (!isStartEvent(source)) {
|
||||
predecessors.add(source.businessObject);
|
||||
// 递归查找前置节点
|
||||
findPredecessorsRecursively(source);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const targetElement = elementRegistry.get(elementId);
|
||||
if (targetElement) {
|
||||
findPredecessorsRecursively(targetElement);
|
||||
}
|
||||
|
||||
return [...predecessors]; // 返回前置节点数组
|
||||
}
|
||||
|
||||
function useButtonsSetting() {
|
||||
const buttonsSetting = ref<ButtonSetting[]>();
|
||||
// 操作按钮显示名称可编辑
|
||||
const btnDisplayNameEdit = ref<boolean[]>([]);
|
||||
const changeBtnDisplayName = (index: number) => {
|
||||
btnDisplayNameEdit.value[index] = true;
|
||||
};
|
||||
const btnDisplayNameBlurEvent = (index: number) => {
|
||||
btnDisplayNameEdit.value[index] = false;
|
||||
const buttonItem = buttonsSetting.value?.[index];
|
||||
if (buttonItem) {
|
||||
buttonItem.displayName =
|
||||
buttonItem.displayName || OPERATION_BUTTON_NAME.get(buttonItem.id)!;
|
||||
}
|
||||
};
|
||||
return {
|
||||
buttonsSetting,
|
||||
btnDisplayNameEdit,
|
||||
changeBtnDisplayName,
|
||||
btnDisplayNameBlurEvent,
|
||||
};
|
||||
}
|
||||
|
||||
/** 批量更新权限 */
|
||||
// TODO @lesan:这个页面,有一些 idea 红色报错,咱要不要 fix 下!
|
||||
const updatePermission = (type: string) => {
|
||||
fieldsPermissionEl.value.forEach((field: any) => {
|
||||
if (type === 'READ') {
|
||||
field.permission = FieldPermissionType.READ;
|
||||
} else if (type === 'WRITE') {
|
||||
field.permission = FieldPermissionType.WRITE;
|
||||
} else {
|
||||
field.permission = FieldPermissionType.NONE;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const userOptions = ref<SystemUserApi.User[]>([]); // 用户列表
|
||||
onMounted(async () => {
|
||||
// 获得用户列表
|
||||
userOptions.value = await getSimpleUserList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Divider orientation="left">审批类型</Divider>
|
||||
<Form.Item name="approveType" label="审批类型">
|
||||
<RadioGroup v-model:value="approveType.value">
|
||||
<Radio
|
||||
v-for="(item, index) in APPROVE_TYPE"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</Radio>
|
||||
</RadioGroup>
|
||||
</Form.Item>
|
||||
|
||||
<Divider orientation="left">审批人拒绝时</Divider>
|
||||
<Form.Item name="rejectHandlerType" label="处理方式">
|
||||
<RadioGroup
|
||||
v-model:value="rejectHandlerType"
|
||||
:disabled="returnTaskList.length === 0"
|
||||
@change="updateRejectHandlerType"
|
||||
>
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in REJECT_HANDLER_TYPES" :key="index">
|
||||
<Radio :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</Radio>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
v-if="rejectHandlerType === RejectHandlerType.RETURN_USER_TASK"
|
||||
name="returnNodeId"
|
||||
label="驳回节点"
|
||||
>
|
||||
<Select
|
||||
v-model:value="returnNodeId"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
@change="updateReturnNodeId"
|
||||
placeholder="请选择驳回节点"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in returnTaskList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Divider orientation="left">审批人为空时</Divider>
|
||||
<Form.Item prop="assignEmptyHandlerType">
|
||||
<RadioGroup
|
||||
v-model:value="assignEmptyHandlerType"
|
||||
@change="updateAssignEmptyHandlerType"
|
||||
>
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in ASSIGN_EMPTY_HANDLER_TYPES" :key="index">
|
||||
<Radio :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</Radio>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
v-if="assignEmptyHandlerType === AssignEmptyHandlerType.ASSIGN_USER"
|
||||
label="指定用户"
|
||||
prop="assignEmptyHandlerUserIds"
|
||||
>
|
||||
<Select
|
||||
v-model:value="assignEmptyUserIds"
|
||||
allow-clear
|
||||
mode="multiple"
|
||||
style="width: 100%"
|
||||
@change="updateAssignEmptyUserIds"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.nickname }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Divider orientation="left">审批人与提交人为同一人时</Divider>
|
||||
<RadioGroup
|
||||
v-model:value="assignStartUserHandlerType"
|
||||
@change="updateAssignStartUserHandlerType"
|
||||
>
|
||||
<div class="flex-col">
|
||||
<div
|
||||
v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES"
|
||||
:key="index"
|
||||
>
|
||||
<Radio :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</Radio>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
||||
<Divider orientation="left">操作按钮</Divider>
|
||||
<div class="button-setting-pane">
|
||||
<div class="button-setting-title">
|
||||
<div class="button-title-label">操作按钮</div>
|
||||
<div class="button-title-label pl-4">显示名称</div>
|
||||
<div class="button-title-label">启用</div>
|
||||
</div>
|
||||
<div
|
||||
class="button-setting-item"
|
||||
v-for="(item, index) in buttonsSettingEl"
|
||||
:key="index"
|
||||
>
|
||||
<div class="button-setting-item-label">
|
||||
{{ OPERATION_BUTTON_NAME.get(item.id) }}
|
||||
</div>
|
||||
<div class="button-setting-item-label">
|
||||
<input
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="btnDisplayNameBlurEvent(index)"
|
||||
v-mounted-focus
|
||||
v-model="item.displayName"
|
||||
:placeholder="item.displayName"
|
||||
v-if="btnDisplayNameEdit[index]"
|
||||
/>
|
||||
<Button v-else type="text" @click="changeBtnDisplayName(index)">
|
||||
{{ item.displayName }}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="button-setting-item-label">
|
||||
<Switch v-model:checked="item.enable" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider orientation="left">字段权限</Divider>
|
||||
<div class="field-setting-pane" v-if="formType === BpmModelFormType.NORMAL">
|
||||
<div class="field-permit-title">
|
||||
<div class="setting-title-label first-title">字段名称</div>
|
||||
<div class="other-titles">
|
||||
<span
|
||||
class="setting-title-label cursor-pointer"
|
||||
@click="updatePermission('READ')"
|
||||
>只读
|
||||
</span>
|
||||
<span
|
||||
class="setting-title-label cursor-pointer"
|
||||
@click="updatePermission('WRITE')"
|
||||
>
|
||||
可编辑
|
||||
</span>
|
||||
<span
|
||||
class="setting-title-label cursor-pointer"
|
||||
@click="updatePermission('NONE')"
|
||||
>隐藏
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="field-setting-item"
|
||||
v-for="(item, index) in fieldsPermissionEl"
|
||||
:key="index"
|
||||
>
|
||||
<div class="field-setting-item-label">{{ item.title }}</div>
|
||||
<RadioGroup
|
||||
class="field-setting-item-group"
|
||||
v-model:value="item.permission"
|
||||
>
|
||||
<div class="item-radio-wrap">
|
||||
<Radio
|
||||
:value="FieldPermissionType.READ"
|
||||
size="large"
|
||||
@change="updateElementExtensions"
|
||||
>
|
||||
<span></span>
|
||||
</Radio>
|
||||
</div>
|
||||
<div class="item-radio-wrap">
|
||||
<Radio
|
||||
:value="FieldPermissionType.WRITE"
|
||||
size="large"
|
||||
@change="updateElementExtensions"
|
||||
>
|
||||
<span></span>
|
||||
</Radio>
|
||||
</div>
|
||||
<div class="item-radio-wrap">
|
||||
<Radio
|
||||
:value="FieldPermissionType.NONE"
|
||||
size="large"
|
||||
@change="updateElementExtensions"
|
||||
>
|
||||
<span></span>
|
||||
</Radio>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider orientation="left">是否需要签名</Divider>
|
||||
<Form.Item prop="signEnable">
|
||||
<Switch
|
||||
v-model:checked="signEnable.value"
|
||||
checked-children="是"
|
||||
un-checked-children="否"
|
||||
@change="updateElementExtensions"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Divider orientation="left">审批意见</Divider>
|
||||
<Form.Item prop="reasonRequire">
|
||||
<Switch
|
||||
v-model:checked="reasonRequire.value"
|
||||
checked-children="必填"
|
||||
un-checked-children="非必填"
|
||||
@change="updateElementExtensions"
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.button-setting-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
|
||||
.button-setting-desc {
|
||||
padding-right: 8px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.button-setting-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 45px;
|
||||
padding-left: 12px;
|
||||
background-color: #f8fafc0a;
|
||||
border: 1px solid #1f38581a;
|
||||
|
||||
& > :first-child {
|
||||
width: 100px !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
& > :last-child {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.button-title-label {
|
||||
width: 150px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.button-setting-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 38px;
|
||||
padding-left: 12px;
|
||||
border: 1px solid #1f38581a;
|
||||
border-top: 0;
|
||||
|
||||
& > :first-child {
|
||||
width: 100px !important;
|
||||
}
|
||||
|
||||
& > :last-child {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.button-setting-item-label {
|
||||
width: 150px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.editable-title-input {
|
||||
max-width: 130px;
|
||||
height: 24px;
|
||||
margin-left: 4px;
|
||||
line-height: 24px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
border-color: #40a9ff;
|
||||
box-shadow: 0 0 0 2px rgb(24 144 255 / 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.field-setting-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 14px;
|
||||
|
||||
.field-setting-desc {
|
||||
padding-right: 8px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.field-permit-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 45px;
|
||||
padding-left: 12px;
|
||||
line-height: 45px;
|
||||
background-color: #f8fafc0a;
|
||||
border: 1px solid #1f38581a;
|
||||
|
||||
.first-title {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.other-titles {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.setting-title-label {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
padding: 5px 0;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.field-setting-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 38px;
|
||||
padding-left: 12px;
|
||||
border: 1px solid #1f38581a;
|
||||
border-top: 0;
|
||||
|
||||
.field-setting-item-label {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
min-height: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.field-setting-item-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.item-radio-wrap {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,13 @@
|
||||
import BoundaryEventTimer from './components/BoundaryEventTimer.vue';
|
||||
import UserTaskCustomConfig from './components/UserTaskCustomConfig.vue';
|
||||
|
||||
export const CustomConfigMap = {
|
||||
UserTask: {
|
||||
name: '用户任务',
|
||||
component: UserTaskCustomConfig,
|
||||
},
|
||||
BoundaryEventTimerEventDefinition: {
|
||||
name: '定时边界事件(非中断)',
|
||||
component: BoundaryEventTimer,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,238 @@
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { Form, Input, Select } from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'FlowCondition' });
|
||||
|
||||
const props = defineProps({
|
||||
businessObject: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
const flowConditionForm = ref<any>({});
|
||||
const bpmnElement = ref();
|
||||
const bpmnElementSource = ref();
|
||||
const bpmnElementSourceRef = ref();
|
||||
const flowConditionRef = ref();
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const resetFlowCondition = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
bpmnElementSource.value = bpmnElement.value.source;
|
||||
bpmnElementSourceRef.value = bpmnElement.value.businessObject.sourceRef;
|
||||
// 初始化默认type为default
|
||||
flowConditionForm.value = { type: 'default' };
|
||||
if (
|
||||
bpmnElementSourceRef.value &&
|
||||
bpmnElementSourceRef.value.default &&
|
||||
bpmnElementSourceRef.value.default.id === bpmnElement.value.id
|
||||
) {
|
||||
flowConditionForm.value = { type: 'default' };
|
||||
} else if (bpmnElement.value.businessObject.conditionExpression) {
|
||||
// 带条件
|
||||
const conditionExpression =
|
||||
bpmnElement.value.businessObject.conditionExpression;
|
||||
flowConditionForm.value = { ...conditionExpression, type: 'condition' };
|
||||
// resource 可直接标识 是否是外部资源脚本
|
||||
if (flowConditionForm.value.resource) {
|
||||
// this.$set(this.flowConditionForm, "conditionType", "script");
|
||||
// this.$set(this.flowConditionForm, "scriptType", "externalScript");
|
||||
flowConditionForm.value.conditionType = 'script';
|
||||
flowConditionForm.value.scriptType = 'externalScript';
|
||||
return;
|
||||
}
|
||||
if (conditionExpression.language) {
|
||||
// this.$set(this.flowConditionForm, "conditionType", "script");
|
||||
// this.$set(this.flowConditionForm, "scriptType", "inlineScript");
|
||||
flowConditionForm.value.conditionType = 'script';
|
||||
flowConditionForm.value.scriptType = 'inlineScript';
|
||||
|
||||
return;
|
||||
}
|
||||
// this.$set(this.flowConditionForm, "conditionType", "expression");
|
||||
flowConditionForm.value.conditionType = 'expression';
|
||||
} else {
|
||||
// 普通
|
||||
flowConditionForm.value = { type: 'normal' };
|
||||
}
|
||||
};
|
||||
|
||||
const updateFlowType = (flowType: any) => {
|
||||
// 正常条件类
|
||||
if (flowType === 'condition') {
|
||||
flowConditionRef.value = bpmnInstances().moddle.create(
|
||||
'bpmn:FormalExpression',
|
||||
);
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
conditionExpression: flowConditionRef.value,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 默认路径
|
||||
if (flowType === 'default') {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
conditionExpression: null,
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElementSource.value), {
|
||||
default: toRaw(bpmnElement.value),
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 正常路径,如果来源节点的默认路径是当前连线时,清除父元素的默认路径配置
|
||||
if (
|
||||
bpmnElementSourceRef.value.default &&
|
||||
bpmnElementSourceRef.value.default.id === bpmnElement.value.id
|
||||
) {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElementSource.value), {
|
||||
default: null,
|
||||
});
|
||||
}
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
conditionExpression: null,
|
||||
});
|
||||
};
|
||||
|
||||
const updateFlowCondition = () => {
|
||||
const { conditionType, scriptType, body, resource, language } =
|
||||
flowConditionForm.value;
|
||||
let condition;
|
||||
if (conditionType === 'expression') {
|
||||
condition = bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
if (scriptType === 'inlineScript') {
|
||||
condition = bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||
body,
|
||||
language,
|
||||
});
|
||||
// this.$set(this.flowConditionForm, "resource", "");
|
||||
flowConditionForm.value.resource = '';
|
||||
} else {
|
||||
// this.$set(this.flowConditionForm, "body", "");
|
||||
flowConditionForm.value.body = '';
|
||||
condition = bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||
resource,
|
||||
language,
|
||||
});
|
||||
}
|
||||
}
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
conditionExpression: condition,
|
||||
});
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
bpmnElement.value = null;
|
||||
bpmnElementSource.value = null;
|
||||
bpmnElementSourceRef.value = null;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.businessObject,
|
||||
(_) => {
|
||||
// console.log(val, 'val');
|
||||
nextTick(() => {
|
||||
resetFlowCondition();
|
||||
});
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Form
|
||||
:model="flowConditionForm"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<Form.Item label="流转类型">
|
||||
<Select v-model:value="flowConditionForm.type" @change="updateFlowType">
|
||||
<Select.Option value="normal">普通流转路径</Select.Option>
|
||||
<Select.Option value="default">默认流转路径</Select.Option>
|
||||
<Select.Option value="condition">条件流转路径</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="条件格式"
|
||||
v-if="flowConditionForm.type === 'condition'"
|
||||
key="condition"
|
||||
>
|
||||
<Select v-model:value="flowConditionForm.conditionType">
|
||||
<Select.Option value="expression">表达式</Select.Option>
|
||||
<Select.Option value="script">脚本</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="表达式"
|
||||
v-if="
|
||||
flowConditionForm.conditionType &&
|
||||
flowConditionForm.conditionType === 'expression'
|
||||
"
|
||||
key="express"
|
||||
>
|
||||
<Input
|
||||
v-model:value="flowConditionForm.body"
|
||||
style="width: 192px"
|
||||
allow-clear
|
||||
@change="updateFlowCondition"
|
||||
/>
|
||||
</Form.Item>
|
||||
<template
|
||||
v-if="
|
||||
flowConditionForm.conditionType &&
|
||||
flowConditionForm.conditionType === 'script'
|
||||
"
|
||||
>
|
||||
<Form.Item label="脚本语言" key="language">
|
||||
<Input
|
||||
v-model:value="flowConditionForm.language"
|
||||
allow-clear
|
||||
@change="updateFlowCondition"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="脚本类型" key="scriptType">
|
||||
<Select v-model:value="flowConditionForm.scriptType">
|
||||
<Select.Option value="inlineScript">内联脚本</Select.Option>
|
||||
<Select.Option value="externalScript">外部脚本</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="脚本"
|
||||
v-if="flowConditionForm.scriptType === 'inlineScript'"
|
||||
key="body"
|
||||
>
|
||||
<TextArea
|
||||
v-model:value="flowConditionForm.body"
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
allow-clear
|
||||
@change="updateFlowCondition"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="资源地址"
|
||||
v-if="flowConditionForm.scriptType === 'externalScript'"
|
||||
key="resource"
|
||||
>
|
||||
<Input
|
||||
v-model:value="flowConditionForm.resource"
|
||||
allow-clear
|
||||
@change="updateFlowCondition"
|
||||
/>
|
||||
</Form.Item>
|
||||
</template>
|
||||
</Form>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,538 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, nextTick, onMounted, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { Form, FormItem, Select } from 'ant-design-vue';
|
||||
|
||||
import { getFormSimpleList } from '#/api/bpm/form';
|
||||
|
||||
defineOptions({ name: 'ElementForm' });
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const prefix = inject('prefix');
|
||||
|
||||
const formKey = ref<number | string | undefined>(undefined);
|
||||
const businessKey = ref('');
|
||||
const optionModelTitle = ref('');
|
||||
const fieldList = ref<any[]>([]);
|
||||
const formFieldForm = ref<any>({});
|
||||
const fieldType = ref({
|
||||
long: '长整型',
|
||||
string: '字符串',
|
||||
boolean: '布尔类',
|
||||
date: '日期类',
|
||||
enum: '枚举类',
|
||||
custom: '自定义类型',
|
||||
});
|
||||
const formFieldIndex = ref(-1); // 编辑中的字段, -1 为新增
|
||||
const formFieldOptionIndex = ref(-1); // 编辑中的字段配置项, -1 为新增
|
||||
const fieldModelVisible = ref(false);
|
||||
const fieldOptionModelVisible = ref(false);
|
||||
const fieldOptionForm = ref<any>({}); // 当前激活的字段配置项数据
|
||||
const fieldOptionType = ref(''); // 当前激活的字段配置项弹窗 类型
|
||||
const fieldEnumList = ref<any[]>([]); // 枚举值列表
|
||||
const fieldConstraintsList = ref<any[]>([]); // 约束条件列表
|
||||
const fieldPropertiesList = ref<any[]>([]); // 绑定属性列表
|
||||
const bpmnELement = ref();
|
||||
const elExtensionElements = ref();
|
||||
const formData = ref();
|
||||
const otherExtensions = ref();
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
const resetFormList = () => {
|
||||
bpmnELement.value = bpmnInstances().bpmnElement;
|
||||
formKey.value = bpmnELement.value.businessObject.formKey;
|
||||
// if (formKey.value?.length > 0) {
|
||||
// formKey.value = parseInt(formKey.value)
|
||||
// }
|
||||
// 获取元素扩展属性 或者 创建扩展属性
|
||||
elExtensionElements.value =
|
||||
bpmnELement.value.businessObject.get('extensionElements') ||
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] });
|
||||
// 获取元素表单配置 或者 创建新的表单配置
|
||||
formData.value =
|
||||
elExtensionElements.value.values.find(
|
||||
(ex: any) => ex.$type === `${prefix}:FormData`,
|
||||
) || bpmnInstances().moddle.create(`${prefix}:FormData`, { fields: [] });
|
||||
|
||||
// 业务标识 businessKey, 绑定在 formData 中
|
||||
businessKey.value = formData.value.businessKey;
|
||||
|
||||
// 保留剩余扩展元素,便于后面更新该元素对应属性
|
||||
otherExtensions.value = elExtensionElements.value.values.filter(
|
||||
(ex: any) => ex.$type !== `${prefix}:FormData`,
|
||||
);
|
||||
|
||||
// 复制原始值,填充表格
|
||||
fieldList.value = cloneDeep(formData.value.fields || []);
|
||||
|
||||
// 更新元素扩展属性,避免后续报错
|
||||
updateElementExtensions();
|
||||
};
|
||||
const updateElementFormKey = () => {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnELement.value), {
|
||||
formKey: formKey.value,
|
||||
});
|
||||
};
|
||||
const _updateElementBusinessKey = () => {
|
||||
bpmnInstances().modeling.updateModdleProperties(
|
||||
toRaw(bpmnELement.value),
|
||||
formData.value,
|
||||
{
|
||||
businessKey: businessKey.value,
|
||||
},
|
||||
);
|
||||
};
|
||||
// 根据类型调整字段type
|
||||
const _changeFieldTypeType = (type: any) => {
|
||||
formFieldForm.value.type = type === 'custom' ? '' : type;
|
||||
};
|
||||
|
||||
// 打开字段详情侧边栏
|
||||
const _openFieldForm = (field: any, index: any) => {
|
||||
formFieldIndex.value = index;
|
||||
if (index === -1) {
|
||||
formFieldForm.value = {};
|
||||
// 初始化枚举值列表
|
||||
fieldEnumList.value = [];
|
||||
// 初始化约束条件列表
|
||||
fieldConstraintsList.value = [];
|
||||
// 初始化自定义属性列表
|
||||
fieldPropertiesList.value = [];
|
||||
} else {
|
||||
const FieldObject = formData.value.fields[index];
|
||||
formFieldForm.value = cloneDeep(field);
|
||||
// 设置自定义类型
|
||||
// this.$set(this.formFieldForm, "typeType", !this.fieldType[field.type] ? "custom" : field.type);
|
||||
formFieldForm.value.typeType = fieldType.value[
|
||||
field.type as keyof typeof fieldType.value
|
||||
]
|
||||
? field.type
|
||||
: 'custom';
|
||||
// 初始化枚举值列表
|
||||
field.type === 'enum' &&
|
||||
(fieldEnumList.value = cloneDeep(FieldObject?.values || []));
|
||||
// 初始化约束条件列表
|
||||
fieldConstraintsList.value = cloneDeep(
|
||||
FieldObject?.validation?.constraints || [],
|
||||
);
|
||||
// 初始化自定义属性列表
|
||||
fieldPropertiesList.value = cloneDeep(
|
||||
FieldObject?.properties?.values || [],
|
||||
);
|
||||
}
|
||||
fieldModelVisible.value = true;
|
||||
};
|
||||
// 打开字段 某个 配置项 弹窗
|
||||
const _openFieldOptionForm = (option: any, index: any, type: any) => {
|
||||
fieldOptionModelVisible.value = true;
|
||||
fieldOptionType.value = type;
|
||||
formFieldOptionIndex.value = index;
|
||||
if (type === 'property') {
|
||||
fieldOptionForm.value = option ? cloneDeep(option) : {};
|
||||
return (optionModelTitle.value = '属性配置');
|
||||
}
|
||||
if (type === 'enum') {
|
||||
fieldOptionForm.value = option ? cloneDeep(option) : {};
|
||||
return (optionModelTitle.value = '枚举值配置');
|
||||
}
|
||||
fieldOptionForm.value = option ? cloneDeep(option) : {};
|
||||
return (optionModelTitle.value = '约束条件配置');
|
||||
};
|
||||
|
||||
// 保存字段 某个 配置项
|
||||
const _saveFieldOption = () => {
|
||||
if (formFieldOptionIndex.value === -1) {
|
||||
if (fieldOptionType.value === 'property') {
|
||||
fieldPropertiesList.value.push(fieldOptionForm.value);
|
||||
}
|
||||
if (fieldOptionType.value === 'constraint') {
|
||||
fieldConstraintsList.value.push(fieldOptionForm.value);
|
||||
}
|
||||
if (fieldOptionType.value === 'enum') {
|
||||
fieldEnumList.value.push(fieldOptionForm.value);
|
||||
}
|
||||
} else {
|
||||
fieldOptionType.value === 'property' &&
|
||||
fieldPropertiesList.value.splice(
|
||||
formFieldOptionIndex.value,
|
||||
1,
|
||||
fieldOptionForm.value,
|
||||
);
|
||||
fieldOptionType.value === 'constraint' &&
|
||||
fieldConstraintsList.value.splice(
|
||||
formFieldOptionIndex.value,
|
||||
1,
|
||||
fieldOptionForm.value,
|
||||
);
|
||||
fieldOptionType.value === 'enum' &&
|
||||
fieldEnumList.value.splice(
|
||||
formFieldOptionIndex.value,
|
||||
1,
|
||||
fieldOptionForm.value,
|
||||
);
|
||||
}
|
||||
fieldOptionModelVisible.value = false;
|
||||
fieldOptionForm.value = {};
|
||||
};
|
||||
// 保存字段配置
|
||||
const _saveField = () => {
|
||||
const { id, type, label, defaultValue, datePattern } = formFieldForm.value;
|
||||
const Field = bpmnInstances().moddle.create(`${prefix}:FormField`, {
|
||||
id,
|
||||
type,
|
||||
label,
|
||||
});
|
||||
defaultValue && (Field.defaultValue = defaultValue);
|
||||
datePattern && (Field.datePattern = datePattern);
|
||||
// 构建属性
|
||||
if (fieldPropertiesList.value && fieldPropertiesList.value.length > 0) {
|
||||
const fieldPropertyList = fieldPropertiesList.value.map((fp: any) => {
|
||||
return bpmnInstances().moddle.create(`${prefix}:Property`, {
|
||||
id: fp.id,
|
||||
value: fp.value,
|
||||
});
|
||||
});
|
||||
Field.properties = bpmnInstances().moddle.create(`${prefix}:Properties`, {
|
||||
values: fieldPropertyList,
|
||||
});
|
||||
}
|
||||
// 构建校验规则
|
||||
if (fieldConstraintsList.value && fieldConstraintsList.value.length > 0) {
|
||||
const fieldConstraintList = fieldConstraintsList.value.map((fc: any) => {
|
||||
return bpmnInstances().moddle.create(`${prefix}:Constraint`, {
|
||||
name: fc.name,
|
||||
config: fc.config,
|
||||
});
|
||||
});
|
||||
Field.validation = bpmnInstances().moddle.create(`${prefix}:Validation`, {
|
||||
constraints: fieldConstraintList,
|
||||
});
|
||||
}
|
||||
// 构建枚举值
|
||||
if (fieldEnumList.value && fieldEnumList.value.length > 0) {
|
||||
Field.values = fieldEnumList.value.map((fe: any) => {
|
||||
return bpmnInstances().moddle.create(`${prefix}:Value`, {
|
||||
name: fe.name,
|
||||
id: fe.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
// 更新数组 与 表单配置实例
|
||||
if (formFieldIndex.value === -1) {
|
||||
fieldList.value.push(formFieldForm.value);
|
||||
formData.value.fields.push(Field);
|
||||
} else {
|
||||
fieldList.value.splice(formFieldIndex.value, 1, formFieldForm.value);
|
||||
formData.value.fields.splice(formFieldIndex.value, 1, Field);
|
||||
}
|
||||
updateElementExtensions();
|
||||
fieldModelVisible.value = false;
|
||||
};
|
||||
|
||||
// 移除某个 字段的 配置项
|
||||
const _removeFieldOptionItem = (_option: any, index: any, type: any) => {
|
||||
// console.log(option, 'option')
|
||||
if (type === 'property') {
|
||||
fieldPropertiesList.value.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
if (type === 'enum') {
|
||||
fieldEnumList.value.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
fieldConstraintsList.value.splice(index, 1);
|
||||
};
|
||||
// 移除 字段
|
||||
const _removeField = (field: any, index: any) => {
|
||||
console.warn(field, 'field');
|
||||
fieldList.value.splice(index, 1);
|
||||
formData.value.fields.splice(index, 1);
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateElementExtensions = () => {
|
||||
// 更新回扩展元素
|
||||
const newElExtensionElements = bpmnInstances().moddle.create(
|
||||
`bpmn:ExtensionElements`,
|
||||
{
|
||||
values: [...otherExtensions.value, formData.value],
|
||||
},
|
||||
);
|
||||
// 更新到元素上
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnELement.value), {
|
||||
extensionElements: newElExtensionElements,
|
||||
});
|
||||
};
|
||||
|
||||
const formList = ref<any[]>([]); // 流程表单的下拉框的数据
|
||||
const formOptions = computed(() => {
|
||||
return formList.value.map((form: any) => ({
|
||||
value: form.id,
|
||||
label: form.name,
|
||||
}));
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
formList.value = await getFormSimpleList();
|
||||
formKey.value = formKey.value
|
||||
? Number.parseInt(formKey.value as string)
|
||||
: undefined;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val: any) => {
|
||||
val &&
|
||||
val.length > 0 &&
|
||||
nextTick(() => {
|
||||
resetFormList();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Form :label-col="{ style: { width: '80px' } }">
|
||||
<FormItem label="流程表单">
|
||||
<!-- <Input v-model:value="formKey" @change="updateElementFormKey" />-->
|
||||
<Select
|
||||
v-model:value="formKey"
|
||||
allow-clear
|
||||
@change="updateElementFormKey"
|
||||
:options="formOptions"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="业务标识">
|
||||
<Select
|
||||
v-model:value="businessKey"
|
||||
@change="_updateElementBusinessKey"
|
||||
allow-clear
|
||||
>
|
||||
<Select.Option v-for="i in fieldList" :key="i.id" :value="i.id">
|
||||
{{ i.label }}
|
||||
</Select.Option>
|
||||
<Select.Option value="">无</Select.Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<!--字段列表-->
|
||||
<!-- <div class="element-property list-property">-->
|
||||
<!-- <Divider><Icon icon="ep:coin" /> 表单字段</Divider>-->
|
||||
<!-- <Table :data-source="fieldList" :scroll="{ y: 240 }" bordered>-->
|
||||
<!-- <TableColumn title="序号" type="index" width="50px" />-->
|
||||
<!-- <TableColumn title="字段名称" dataIndex="label" width="80px" :ellipsis="true" />-->
|
||||
<!-- <TableColumn-->
|
||||
<!-- title="字段类型"-->
|
||||
<!-- dataIndex="type"-->
|
||||
<!-- width="80px"-->
|
||||
<!-- :customRender="({ text }) => fieldType[text] || text"-->
|
||||
<!-- :ellipsis="true"-->
|
||||
<!-- />-->
|
||||
<!-- <TableColumn-->
|
||||
<!-- title="默认值"-->
|
||||
<!-- dataIndex="defaultValue"-->
|
||||
<!-- width="80px"-->
|
||||
<!-- :ellipsis="true"-->
|
||||
<!-- />-->
|
||||
<!-- <TableColumn title="操作" width="90px">-->
|
||||
<!-- <template #default="scope">-->
|
||||
<!-- <Button type="link" @click="openFieldForm(scope, scope.$index)">-->
|
||||
<!-- 编辑-->
|
||||
<!-- </Button>-->
|
||||
<!-- <Divider type="vertical" />-->
|
||||
<!-- <Button-->
|
||||
<!-- type="link"-->
|
||||
<!-- danger-->
|
||||
<!-- @click="removeField(scope, scope.$index)"-->
|
||||
<!-- >-->
|
||||
<!-- 移除-->
|
||||
<!-- </Button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </TableColumn>-->
|
||||
<!-- </Table>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="element-drawer__button">-->
|
||||
<!-- <Button type="primary" @click="openFieldForm(null, -1)">添加字段</Button>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<!--字段配置侧边栏-->
|
||||
<!-- <Drawer-->
|
||||
<!-- v-model:open="fieldModelVisible"-->
|
||||
<!-- title="字段配置"-->
|
||||
<!-- :width="`${width}px`"-->
|
||||
<!-- destroyOnClose-->
|
||||
<!-- >-->
|
||||
<!-- <Form :model="formFieldForm" :label-col="{ style: { width: '90px' } }">-->
|
||||
<!-- <FormItem label="字段ID">-->
|
||||
<!-- <Input v-model:value="formFieldForm.id" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="类型">-->
|
||||
<!-- <Select-->
|
||||
<!-- v-model:value="formFieldForm.typeType"-->
|
||||
<!-- placeholder="请选择字段类型"-->
|
||||
<!-- allowClear-->
|
||||
<!-- @change="changeFieldTypeType"-->
|
||||
<!-- >-->
|
||||
<!-- <SelectOption v-for="(value, key) of fieldType" :key="key" :value="key">{{ value }}</SelectOption>-->
|
||||
<!-- </Select>-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="类型名称" v-if="formFieldForm.typeType === 'custom'">-->
|
||||
<!-- <Input v-model:value="formFieldForm.type" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="名称">-->
|
||||
<!-- <Input v-model:value="formFieldForm.label" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="时间格式" v-if="formFieldForm.typeType === 'date'">-->
|
||||
<!-- <Input v-model:value="formFieldForm.datePattern" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="默认值">-->
|
||||
<!-- <Input v-model:value="formFieldForm.defaultValue" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- </Form>-->
|
||||
|
||||
<!-- <!– 枚举值设置 –>-->
|
||||
<!-- <template v-if="formFieldForm.type === 'enum'">-->
|
||||
<!-- <Divider key="enum-divider" />-->
|
||||
<!-- <p class="listener-filed__title" key="enum-title">-->
|
||||
<!-- <span><Icon icon="ep:menu" />枚举值列表:</span>-->
|
||||
<!-- <Button type="primary" @click="openFieldOptionForm(null, -1, 'enum')"-->
|
||||
<!-- >添加枚举值</Button-->
|
||||
<!-- >-->
|
||||
<!-- </p>-->
|
||||
<!-- <Table :data-source="fieldEnumList" key="enum-table" :scroll="{ y: 240 }" bordered>-->
|
||||
<!-- <TableColumn title="序号" width="50px" type="index" />-->
|
||||
<!-- <TableColumn title="枚举值编号" dataIndex="id" width="100px" :ellipsis="true" />-->
|
||||
<!-- <TableColumn title="枚举值名称" dataIndex="name" width="100px" :ellipsis="true" />-->
|
||||
<!-- <TableColumn title="操作" width="90px">-->
|
||||
<!-- <template #default="scope">-->
|
||||
<!-- <Button-->
|
||||
<!-- type="link"-->
|
||||
<!-- @click="openFieldOptionForm(scope, scope.$index, 'enum')"-->
|
||||
<!-- >-->
|
||||
<!-- 编辑-->
|
||||
<!-- </Button>-->
|
||||
<!-- <Divider type="vertical" />-->
|
||||
<!-- <Button-->
|
||||
<!-- type="link"-->
|
||||
<!-- danger-->
|
||||
<!-- @click="removeFieldOptionItem(scope, scope.$index, 'enum')"-->
|
||||
<!-- >-->
|
||||
<!-- 移除-->
|
||||
<!-- </Button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </TableColumn>-->
|
||||
<!-- </Table>-->
|
||||
<!-- </template>-->
|
||||
|
||||
<!-- <!– 校验规则 –>-->
|
||||
<!-- <Divider key="validation-divider" />-->
|
||||
<!-- <p class="listener-filed__title" key="validation-title">-->
|
||||
<!-- <span><Icon icon="ep:menu" />约束条件列表:</span>-->
|
||||
<!-- <Button type="primary" @click="openFieldOptionForm(null, -1, 'constraint')"-->
|
||||
<!-- >添加约束</Button-->
|
||||
<!-- >-->
|
||||
<!-- </p>-->
|
||||
<!-- <Table :data-source="fieldConstraintsList" key="validation-table" :scroll="{ y: 240 }" bordered>-->
|
||||
<!-- <TableColumn title="序号" width="50px" type="index" />-->
|
||||
<!-- <TableColumn title="约束名称" dataIndex="name" width="100px" :ellipsis="true" />-->
|
||||
<!-- <TableColumn title="约束配置" dataIndex="config" width="100px" :ellipsis="true" />-->
|
||||
<!-- <TableColumn title="操作" width="90px">-->
|
||||
<!-- <template #default="scope">-->
|
||||
<!-- <Button-->
|
||||
<!-- type="link"-->
|
||||
<!-- @click="openFieldOptionForm(scope, scope.$index, 'constraint')"-->
|
||||
<!-- >-->
|
||||
<!-- 编辑-->
|
||||
<!-- </Button>-->
|
||||
<!-- <Divider type="vertical" />-->
|
||||
<!-- <Button-->
|
||||
<!-- type="link"-->
|
||||
<!-- danger-->
|
||||
<!-- @click="removeFieldOptionItem(scope, scope.$index, 'constraint')"-->
|
||||
<!-- >-->
|
||||
<!-- 移除-->
|
||||
<!-- </Button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </TableColumn>-->
|
||||
<!-- </Table>-->
|
||||
|
||||
<!-- <!– 表单属性 –>-->
|
||||
<!-- <Divider key="property-divider" />-->
|
||||
<!-- <p class="listener-filed__title" key="property-title">-->
|
||||
<!-- <span><Icon icon="ep:menu" />字段属性列表:</span>-->
|
||||
<!-- <Button type="primary" @click="openFieldOptionForm(null, -1, 'property')"-->
|
||||
<!-- >添加属性</Button-->
|
||||
<!-- >-->
|
||||
<!-- </p>-->
|
||||
<!-- <Table :data-source="fieldPropertiesList" key="property-table" :scroll="{ y: 240 }" bordered>-->
|
||||
<!-- <TableColumn title="序号" width="50px" type="index" />-->
|
||||
<!-- <TableColumn title="属性编号" dataIndex="id" width="100px" :ellipsis="true" />-->
|
||||
<!-- <TableColumn title="属性值" dataIndex="value" width="100px" :ellipsis="true" />-->
|
||||
<!-- <TableColumn title="操作" width="90px">-->
|
||||
<!-- <template #default="scope">-->
|
||||
<!-- <Button-->
|
||||
<!-- type="link"-->
|
||||
<!-- @click="openFieldOptionForm(scope, scope.$index, 'property')"-->
|
||||
<!-- >-->
|
||||
<!-- 编辑-->
|
||||
<!-- </Button>-->
|
||||
<!-- <Divider type="vertical" />-->
|
||||
<!-- <Button-->
|
||||
<!-- type="link"-->
|
||||
<!-- danger-->
|
||||
<!-- @click="removeFieldOptionItem(scope, scope.$index, 'property')"-->
|
||||
<!-- >-->
|
||||
<!-- 移除-->
|
||||
<!-- </Button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </TableColumn>-->
|
||||
<!-- </Table>-->
|
||||
|
||||
<!-- <!– 底部按钮 –>-->
|
||||
<!-- <div class="element-drawer__button">-->
|
||||
<!-- <Button>取 消</Button>-->
|
||||
<!-- <Button type="primary" @click="saveField">保 存</Button>-->
|
||||
<!-- </div>-->
|
||||
<!-- </Drawer>-->
|
||||
|
||||
<!-- <Modal-->
|
||||
<!-- v-model:open="fieldOptionModelVisible"-->
|
||||
<!-- :title="optionModelTitle"-->
|
||||
<!-- width="600px"-->
|
||||
<!-- destroyOnClose-->
|
||||
<!-- >-->
|
||||
<!-- <Form :model="fieldOptionForm" :label-col="{ style: { width: '96px' } }">-->
|
||||
<!-- <FormItem label="编号/ID" v-if="fieldOptionType !== 'constraint'" key="option-id">-->
|
||||
<!-- <Input v-model:value="fieldOptionForm.id" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="名称" v-if="fieldOptionType !== 'property'" key="option-name">-->
|
||||
<!-- <Input v-model:value="fieldOptionForm.name" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="配置" v-if="fieldOptionType === 'constraint'" key="option-config">-->
|
||||
<!-- <Input v-model:value="fieldOptionForm.config" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="值" v-if="fieldOptionType === 'property'" key="option-value">-->
|
||||
<!-- <Input v-model:value="fieldOptionForm.value" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- </Form>-->
|
||||
<!-- <template #footer>-->
|
||||
<!-- <Button @click="fieldOptionModelVisible = false">取 消</Button>-->
|
||||
<!-- <Button type="primary" @click="saveFieldOption">确 定</Button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </Modal>-->
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,7 @@
|
||||
import MyPropertiesPanel from './PropertiesPanel.vue';
|
||||
|
||||
MyPropertiesPanel.install = function (Vue) {
|
||||
Vue.component(MyPropertiesPanel.name, MyPropertiesPanel);
|
||||
};
|
||||
|
||||
export default MyPropertiesPanel;
|
||||
@@ -0,0 +1,623 @@
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, ref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon, PlusOutlined } from '@vben/icons';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Drawer,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
Modal,
|
||||
Select,
|
||||
SelectOption,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { createListenerObject, updateElementExtensions } from '../../utils';
|
||||
import ProcessListenerDialog from './ProcessListenerDialog.vue';
|
||||
import {
|
||||
fieldType,
|
||||
initListenerForm,
|
||||
initListenerForm2,
|
||||
initListenerType,
|
||||
listenerType,
|
||||
} from './utilSelf';
|
||||
|
||||
defineOptions({ name: 'ElementListeners' });
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const prefix = inject('prefix');
|
||||
const width = inject('width');
|
||||
const elementListenersList = ref<any[]>([]); // 监听器列表
|
||||
const listenerForm = ref<any>({}); // 监听器详情表单
|
||||
const listenerFormModelVisible = ref(false); // 监听器 编辑 侧边栏显示状态
|
||||
const fieldsListOfListener = ref<any[]>([]);
|
||||
const listenerFieldForm = ref<any>({}); // 监听器 注入字段 详情表单
|
||||
const listenerFieldFormModelVisible = ref(false); // 监听器 注入字段表单弹窗 显示状态
|
||||
const editingListenerIndex = ref(-1); // 监听器所在下标,-1 为新增
|
||||
const editingListenerFieldIndex = ref(-1); // 字段所在下标,-1 为新增
|
||||
const listenerTypeObject = ref(listenerType);
|
||||
const fieldTypeObject = ref(fieldType);
|
||||
const bpmnElement = ref();
|
||||
const otherExtensionList = ref();
|
||||
const bpmnElementListeners = ref();
|
||||
const listenerFormRef = ref();
|
||||
const listenerFieldFormRef = ref();
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const resetListenersList = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
otherExtensionList.value = [];
|
||||
bpmnElementListeners.value =
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:ExecutionListener`,
|
||||
) ?? [];
|
||||
elementListenersList.value = bpmnElementListeners.value.map((listener: any) =>
|
||||
initListenerType(listener),
|
||||
);
|
||||
};
|
||||
// 打开 监听器详情 侧边栏
|
||||
const openListenerForm = (listener: any, index: number) => {
|
||||
// debugger
|
||||
if (listener) {
|
||||
listenerForm.value = initListenerForm(listener);
|
||||
editingListenerIndex.value = index;
|
||||
} else {
|
||||
listenerForm.value = {};
|
||||
editingListenerIndex.value = -1; // 标记为新增
|
||||
}
|
||||
if (listener && listener.fields) {
|
||||
fieldsListOfListener.value = listener.fields.map((field: any) => ({
|
||||
...field,
|
||||
fieldType: field.string ? 'string' : 'expression',
|
||||
}));
|
||||
} else {
|
||||
fieldsListOfListener.value = [];
|
||||
listenerForm.value.fields = [];
|
||||
}
|
||||
// 打开侧边栏并清楚验证状态
|
||||
listenerFormModelVisible.value = true;
|
||||
nextTick(() => {
|
||||
if (listenerFormRef.value) {
|
||||
listenerFormRef.value.clearValidate();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 打开监听器字段编辑弹窗
|
||||
const openListenerFieldForm = (field: any, index: number) => {
|
||||
listenerFieldForm.value = field ? cloneDeep(field) : {};
|
||||
editingListenerFieldIndex.value = field ? index : -1;
|
||||
listenerFieldFormModelVisible.value = true;
|
||||
nextTick(() => {
|
||||
if (listenerFieldFormRef.value) {
|
||||
listenerFieldFormRef.value.clearValidate();
|
||||
}
|
||||
});
|
||||
};
|
||||
// 保存监听器注入字段
|
||||
const saveListenerFiled = async () => {
|
||||
// debugger
|
||||
const validateStatus = await listenerFieldFormRef.value.validate();
|
||||
if (!validateStatus) return; // 验证不通过直接返回
|
||||
if (editingListenerFieldIndex.value === -1) {
|
||||
fieldsListOfListener.value.push(listenerFieldForm.value);
|
||||
listenerForm.value.fields.push(listenerFieldForm.value);
|
||||
} else {
|
||||
fieldsListOfListener.value.splice(
|
||||
editingListenerFieldIndex.value,
|
||||
1,
|
||||
listenerFieldForm.value,
|
||||
);
|
||||
listenerForm.value.fields.splice(
|
||||
editingListenerFieldIndex.value,
|
||||
1,
|
||||
listenerFieldForm.value,
|
||||
);
|
||||
}
|
||||
listenerFieldFormModelVisible.value = false;
|
||||
nextTick(() => {
|
||||
listenerFieldForm.value = {};
|
||||
});
|
||||
};
|
||||
// 移除监听器字段
|
||||
const removeListenerField = (index: number) => {
|
||||
// debugger
|
||||
Modal.confirm({
|
||||
title: '确认移除该字段吗?',
|
||||
content: '此操作不可撤销',
|
||||
okText: '确 认',
|
||||
cancelText: '取 消',
|
||||
onOk() {
|
||||
fieldsListOfListener.value.splice(index, 1);
|
||||
listenerForm.value.fields.splice(index, 1);
|
||||
},
|
||||
onCancel() {
|
||||
console.warn('操作取消');
|
||||
},
|
||||
});
|
||||
};
|
||||
// 移除监听器
|
||||
const removeListener = (index: number) => {
|
||||
Modal.confirm({
|
||||
title: '确认移除该监听器吗?',
|
||||
content: '此操作不可撤销',
|
||||
okText: '确 认',
|
||||
cancelText: '取 消',
|
||||
onOk() {
|
||||
bpmnElementListeners.value.splice(index, 1);
|
||||
elementListenersList.value.splice(index, 1);
|
||||
updateElementExtensions(bpmnElement.value, [
|
||||
...otherExtensionList.value,
|
||||
...bpmnElementListeners.value,
|
||||
]);
|
||||
},
|
||||
onCancel() {
|
||||
console.warn('操作取消');
|
||||
},
|
||||
});
|
||||
};
|
||||
// 保存监听器配置
|
||||
const saveListenerConfig = async () => {
|
||||
// debugger
|
||||
const validateStatus = await listenerFormRef.value.validate();
|
||||
if (!validateStatus) return; // 验证不通过直接返回
|
||||
const listenerObject = createListenerObject(
|
||||
listenerForm.value,
|
||||
false,
|
||||
prefix,
|
||||
);
|
||||
if (editingListenerIndex.value === -1) {
|
||||
bpmnElementListeners.value.push(listenerObject);
|
||||
elementListenersList.value.push(listenerForm.value);
|
||||
} else {
|
||||
bpmnElementListeners.value.splice(
|
||||
editingListenerIndex.value,
|
||||
1,
|
||||
listenerObject,
|
||||
);
|
||||
elementListenersList.value.splice(
|
||||
editingListenerIndex.value,
|
||||
1,
|
||||
listenerForm.value,
|
||||
);
|
||||
}
|
||||
// 保存其他配置
|
||||
otherExtensionList.value =
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.filter(
|
||||
(ex: any) => ex.$type !== `${prefix}:ExecutionListener`,
|
||||
) ?? [];
|
||||
updateElementExtensions(bpmnElement.value, [
|
||||
...otherExtensionList.value,
|
||||
...bpmnElementListeners.value,
|
||||
]);
|
||||
// 4. 隐藏侧边栏
|
||||
listenerFormModelVisible.value = false;
|
||||
listenerForm.value = {};
|
||||
};
|
||||
|
||||
// 打开监听器弹窗
|
||||
const processListenerDialogRef = ref();
|
||||
const openProcessListenerDialog = async () => {
|
||||
processListenerDialogRef.value.open('execution');
|
||||
};
|
||||
const selectProcessListener = (listener: any) => {
|
||||
const listenerForm = initListenerForm2(listener);
|
||||
const listenerObject = createListenerObject(listenerForm, false, prefix);
|
||||
bpmnElementListeners.value.push(listenerObject);
|
||||
elementListenersList.value.push(listenerForm);
|
||||
|
||||
// 保存其他配置
|
||||
otherExtensionList.value =
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.filter(
|
||||
(ex: any) => ex.$type !== `${prefix}:ExecutionListener`,
|
||||
) ?? [];
|
||||
updateElementExtensions(bpmnElement.value, [
|
||||
...otherExtensionList.value,
|
||||
...bpmnElementListeners.value,
|
||||
]);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val: string) => {
|
||||
if (val && val.length > 0) {
|
||||
nextTick(() => {
|
||||
resetListenersList();
|
||||
});
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Table
|
||||
:data-source="elementListenersList"
|
||||
size="small"
|
||||
bordered
|
||||
:pagination="false"
|
||||
>
|
||||
<TableColumn title="序号" width="50px">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</TableColumn>
|
||||
<TableColumn title="事件类型" width="100px" data-index="event" />
|
||||
<TableColumn
|
||||
title="监听器类型"
|
||||
width="100px"
|
||||
:custom-render="
|
||||
({ record }: any) =>
|
||||
listenerTypeObject[record.listenerType as keyof typeof listenerType]
|
||||
"
|
||||
/>
|
||||
<TableColumn title="操作" width="100px">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
@click="openListenerForm(record, index)"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
danger
|
||||
@click="removeListener(index)"
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
<div class="element-drawer__button">
|
||||
<Button type="primary" size="small" @click="openListenerForm(null, -1)">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
添加监听器
|
||||
</Button>
|
||||
<Button size="small" @click="openProcessListenerDialog">
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:select" />
|
||||
</template>
|
||||
选择监听器
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 监听器 编辑/创建 部分 -->
|
||||
<Drawer
|
||||
v-model:open="listenerFormModelVisible"
|
||||
title="执行监听器"
|
||||
:width="width as any"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="listenerForm"
|
||||
ref="listenerFormRef"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<FormItem
|
||||
label="事件类型"
|
||||
name="event"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择事件类型',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Select v-model:value="listenerForm.event">
|
||||
<SelectOption value="start">start</SelectOption>
|
||||
<SelectOption value="end">end</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="监听器类型"
|
||||
name="listenerType"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择监听器类型',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Select v-model:value="listenerForm.listenerType">
|
||||
<SelectOption
|
||||
v-for="i in Object.keys(listenerTypeObject)"
|
||||
:key="i"
|
||||
:value="i"
|
||||
>
|
||||
{{ listenerTypeObject[i as keyof typeof listenerType] }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.listenerType === 'classListener'"
|
||||
label="Java类"
|
||||
name="class"
|
||||
key="listener-class"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请填写Java类',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.class" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.listenerType === 'expressionListener'"
|
||||
label="表达式"
|
||||
name="expression"
|
||||
key="listener-expression"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请填写表达式',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.expression" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.listenerType === 'delegateExpressionListener'"
|
||||
label="代理表达式"
|
||||
name="delegateExpression"
|
||||
key="listener-delegate"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请填写代理表达式',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.delegateExpression" allow-clear />
|
||||
</FormItem>
|
||||
<template v-if="listenerForm.listenerType === 'scriptListener'">
|
||||
<FormItem
|
||||
label="脚本格式"
|
||||
name="scriptFormat"
|
||||
key="listener-script-format"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
trigger: ['blur', 'change'],
|
||||
message: '请填写脚本格式',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.scriptFormat" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="脚本类型"
|
||||
name="scriptType"
|
||||
key="listener-script-type"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
trigger: ['blur', 'change'],
|
||||
message: '请选择脚本类型',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Select v-model:value="listenerForm.scriptType">
|
||||
<SelectOption value="inlineScript">内联脚本</SelectOption>
|
||||
<SelectOption value="externalScript">外部脚本</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.scriptType === 'inlineScript'"
|
||||
label="脚本内容"
|
||||
name="value"
|
||||
key="listener-script"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
trigger: ['blur', 'change'],
|
||||
message: '请填写脚本内容',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.value" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.scriptType === 'externalScript'"
|
||||
label="资源地址"
|
||||
name="resource"
|
||||
key="listener-resource"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
trigger: ['blur', 'change'],
|
||||
message: '请填写资源地址',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.resource" allow-clear />
|
||||
</FormItem>
|
||||
</template>
|
||||
</Form>
|
||||
<Divider />
|
||||
<p class="listener-filed__title">
|
||||
<span><IconifyIcon icon="ep:menu" />注入字段:</span>
|
||||
<Button type="primary" @click="openListenerFieldForm(null, -1)">
|
||||
添加字段
|
||||
</Button>
|
||||
</p>
|
||||
<Table
|
||||
:data-source="fieldsListOfListener"
|
||||
size="small"
|
||||
:scroll="{ y: 240 }"
|
||||
:pagination="false"
|
||||
bordered
|
||||
style="flex: none"
|
||||
>
|
||||
<TableColumn title="序号" width="50px">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</TableColumn>
|
||||
<TableColumn title="字段名称" width="100px" data-index="name" />
|
||||
<TableColumn
|
||||
title="字段类型"
|
||||
width="80px"
|
||||
:custom-render="
|
||||
({ record }: any) =>
|
||||
fieldTypeObject[record.fieldType as keyof typeof fieldType]
|
||||
"
|
||||
/>
|
||||
<TableColumn
|
||||
title="字段值/表达式"
|
||||
width="100px"
|
||||
:custom-render="
|
||||
({ record }: any) => record.string || record.expression
|
||||
"
|
||||
/>
|
||||
<TableColumn title="操作" width="130px">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
@click="openListenerFieldForm(record, index)"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
danger
|
||||
@click="removeListenerField(index)"
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
|
||||
<div class="element-drawer__button">
|
||||
<Button @click="listenerFormModelVisible = false">取 消</Button>
|
||||
<Button type="primary" @click="saveListenerConfig">保 存</Button>
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
<!-- 注入字段 编辑/创建 部分 -->
|
||||
<Modal
|
||||
title="字段配置"
|
||||
v-model:open="listenerFieldFormModelVisible"
|
||||
width="600px"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="listenerFieldForm"
|
||||
ref="listenerFieldFormRef"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
style="height: 136px"
|
||||
>
|
||||
<FormItem
|
||||
label="字段名称:"
|
||||
name="name"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请填写字段名称',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerFieldForm.name" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="字段类型:"
|
||||
name="fieldType"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择字段类型',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Select v-model:value="listenerFieldForm.fieldType">
|
||||
<SelectOption
|
||||
v-for="i in Object.keys(fieldTypeObject)"
|
||||
:key="i"
|
||||
:value="i"
|
||||
>
|
||||
{{ fieldTypeObject[i as keyof typeof fieldType] }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerFieldForm.fieldType === 'string'"
|
||||
label="字段值:"
|
||||
name="string"
|
||||
key="field-string"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请填写字段值',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerFieldForm.string" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerFieldForm.fieldType === 'expression'"
|
||||
label="表达式:"
|
||||
name="expression"
|
||||
key="field-expression"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请填写表达式',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerFieldForm.expression" allow-clear />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button size="small" @click="listenerFieldFormModelVisible = false">
|
||||
取 消
|
||||
</Button>
|
||||
<Button size="small" type="primary" @click="saveListenerFiled">
|
||||
确 定
|
||||
</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<!-- 选择弹窗 -->
|
||||
<ProcessListenerDialog
|
||||
ref="processListenerDialogRef"
|
||||
@select="selectProcessListener"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,110 @@
|
||||
<!-- 执行器选择 -->
|
||||
<script setup lang="ts">
|
||||
import type { BpmProcessListenerApi } from '#/api/bpm/processListener';
|
||||
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
|
||||
import { Button, Modal, Pagination, Table } from 'ant-design-vue';
|
||||
|
||||
import { getProcessListenerPage } from '#/api/bpm/processListener';
|
||||
import { ContentWrap } from '#/components/content-wrap';
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
|
||||
/** BPM 流程 表单 */
|
||||
defineOptions({ name: 'ProcessListenerDialog' });
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success', 'select']);
|
||||
const dialogVisible = ref(false); // 弹窗的是否展示
|
||||
const loading = ref(true); // 列表的加载中
|
||||
const list = ref<BpmProcessListenerApi.ProcessListener[]>([]); // 列表的数据
|
||||
const total = ref(0); // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
type: '',
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
});
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string) => {
|
||||
queryParams.pageNo = 1;
|
||||
queryParams.type = type;
|
||||
await getList();
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getProcessListenerPage(queryParams);
|
||||
list.value = data.list;
|
||||
total.value = data.total;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 定义 success 事件,用于操作成功后的回调
|
||||
const select = async (row: BpmProcessListenerApi.ProcessListener) => {
|
||||
dialogVisible.value = false;
|
||||
// 发送操作成功的事件
|
||||
emit('select', row);
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<Modal
|
||||
title="请选择监听器"
|
||||
v-model:open="dialogVisible"
|
||||
width="1024px"
|
||||
:footer="null"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:loading="loading"
|
||||
:data-source="list"
|
||||
:pagination="false"
|
||||
:scroll="{ x: 'max-content' }"
|
||||
>
|
||||
<Table.Column title="名字" align="center" data-index="name" />
|
||||
<Table.Column title="类型" align="center" data-index="type">
|
||||
<template #default="{ record }">
|
||||
<DictTag
|
||||
:type="DICT_TYPE.BPM_PROCESS_LISTENER_TYPE"
|
||||
:value="record.type"
|
||||
/>
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column title="事件" align="center" data-index="event" />
|
||||
<Table.Column title="值类型" align="center" data-index="valueType">
|
||||
<template #default="{ record }">
|
||||
<DictTag
|
||||
:type="DICT_TYPE.BPM_PROCESS_LISTENER_VALUE_TYPE"
|
||||
:value="record.valueType"
|
||||
/>
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column title="值" align="center" data-index="value" />
|
||||
<Table.Column title="操作" align="center">
|
||||
<template #default="{ record }">
|
||||
<Button type="primary" @click="select(record)"> 选择 </Button>
|
||||
</template>
|
||||
</Table.Column>
|
||||
</Table>
|
||||
<!-- 分页 -->
|
||||
<div class="mt-4 flex justify-end">
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:current="queryParams.pageNo"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
show-size-changer
|
||||
@change="getList"
|
||||
/>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -0,0 +1,600 @@
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, ref, watch } from 'vue';
|
||||
|
||||
import { MenuOutlined, PlusOutlined, SelectOutlined } from '@vben/icons';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Drawer,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
Modal,
|
||||
Select,
|
||||
SelectOption,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import ProcessListenerDialog from '#/components/bpmn-process-designer/package/penal/listeners/ProcessListenerDialog.vue';
|
||||
|
||||
import { createListenerObject, updateElementExtensions } from '../../utils';
|
||||
import {
|
||||
eventType,
|
||||
fieldType,
|
||||
initListenerForm,
|
||||
initListenerForm2,
|
||||
initListenerType,
|
||||
listenerType,
|
||||
} from './utilSelf';
|
||||
|
||||
defineOptions({ name: 'UserTaskListeners' });
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Props {
|
||||
id?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
const prefix = inject<string>('prefix');
|
||||
const width = inject<number>('width');
|
||||
const elementListenersList = ref<any[]>([]);
|
||||
const listenerEventTypeObject = ref(eventType);
|
||||
const listenerTypeObject = ref(listenerType);
|
||||
const listenerFormModelVisible = ref(false);
|
||||
const listenerForm = ref<any>({});
|
||||
const fieldTypeObject = ref(fieldType);
|
||||
const fieldsListOfListener = ref<any[]>([]);
|
||||
const listenerFieldFormModelVisible = ref(false); // 监听器 注入字段表单弹窗 显示状态
|
||||
const editingListenerIndex = ref(-1); // 监听器所在下标,-1 为新增
|
||||
const editingListenerFieldIndex = ref<any>(-1); // 字段所在下标,-1 为新增
|
||||
const listenerFieldForm = ref<any>({}); // 监听器 注入字段 详情表单
|
||||
const bpmnElement = ref<any>();
|
||||
const bpmnElementListeners = ref<any[]>([]);
|
||||
const otherExtensionList = ref<any[]>([]);
|
||||
const listenerFormRef = ref<any>({});
|
||||
const listenerFieldFormRef = ref<any>({});
|
||||
|
||||
interface BpmnInstances {
|
||||
bpmnElement: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
bpmnInstances?: BpmnInstances;
|
||||
}
|
||||
}
|
||||
|
||||
const bpmnInstances = () => window.bpmnInstances;
|
||||
|
||||
const resetListenersList = () => {
|
||||
// console.log(
|
||||
// bpmnInstances().bpmnElement,
|
||||
// 'window.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElement',
|
||||
// );
|
||||
bpmnElement.value = bpmnInstances()?.bpmnElement;
|
||||
otherExtensionList.value = [];
|
||||
bpmnElementListeners.value =
|
||||
bpmnElement.value.businessObject?.extensionElements?.values.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:TaskListener`,
|
||||
) ?? [];
|
||||
elementListenersList.value = bpmnElementListeners.value.map((listener) =>
|
||||
initListenerType(listener),
|
||||
);
|
||||
};
|
||||
const openListenerForm = (listener: any, index?: number) => {
|
||||
if (listener) {
|
||||
listenerForm.value = initListenerForm(listener);
|
||||
editingListenerIndex.value = index || -1;
|
||||
} else {
|
||||
listenerForm.value = {};
|
||||
editingListenerIndex.value = -1; // 标记为新增
|
||||
}
|
||||
if (listener && listener.fields) {
|
||||
fieldsListOfListener.value = listener.fields.map((field: any) => ({
|
||||
...field,
|
||||
fieldType: field.string ? 'string' : 'expression',
|
||||
}));
|
||||
} else {
|
||||
fieldsListOfListener.value = [];
|
||||
listenerForm.value.fields = [];
|
||||
}
|
||||
// 打开侧边栏并清楚验证状态
|
||||
listenerFormModelVisible.value = true;
|
||||
nextTick(() => {
|
||||
if (listenerFormRef.value) listenerFormRef.value.clearValidate();
|
||||
});
|
||||
};
|
||||
// 移除监听器
|
||||
const removeListener = (_: any, index: number) => {
|
||||
// console.log(listener, 'listener');
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确认移除该监听器吗?',
|
||||
okText: '确 认',
|
||||
cancelText: '取 消',
|
||||
onOk() {
|
||||
bpmnElementListeners.value.splice(index, 1);
|
||||
elementListenersList.value.splice(index, 1);
|
||||
updateElementExtensions(bpmnElement.value, [
|
||||
...otherExtensionList.value,
|
||||
...bpmnElementListeners.value,
|
||||
]);
|
||||
},
|
||||
onCancel() {
|
||||
// console.info('操作取消');
|
||||
},
|
||||
});
|
||||
};
|
||||
// 保存监听器
|
||||
const saveListenerConfig = async () => {
|
||||
const validateStatus = await listenerFormRef.value.validate();
|
||||
if (!validateStatus) return; // 验证不通过直接返回
|
||||
const listenerObject = createListenerObject(listenerForm.value, true, prefix);
|
||||
if (editingListenerIndex.value === -1) {
|
||||
bpmnElementListeners.value.push(listenerObject);
|
||||
elementListenersList.value.push(listenerForm.value);
|
||||
} else {
|
||||
bpmnElementListeners.value.splice(
|
||||
editingListenerIndex.value,
|
||||
1,
|
||||
listenerObject,
|
||||
);
|
||||
elementListenersList.value.splice(
|
||||
editingListenerIndex.value,
|
||||
1,
|
||||
listenerForm.value,
|
||||
);
|
||||
}
|
||||
// 保存其他配置
|
||||
otherExtensionList.value =
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.filter(
|
||||
(ex: any) => ex.$type !== `${prefix}:TaskListener`,
|
||||
) ?? [];
|
||||
updateElementExtensions(bpmnElement.value, [
|
||||
...otherExtensionList.value,
|
||||
...bpmnElementListeners.value,
|
||||
]);
|
||||
// 4. 隐藏侧边栏
|
||||
listenerFormModelVisible.value = false;
|
||||
listenerForm.value = {};
|
||||
};
|
||||
|
||||
// 打开监听器字段编辑弹窗
|
||||
const openListenerFieldForm = (field: any, index?: number) => {
|
||||
listenerFieldForm.value = field ? cloneDeep(field) : {};
|
||||
editingListenerFieldIndex.value = field ? index : -1;
|
||||
listenerFieldFormModelVisible.value = true;
|
||||
nextTick(() => {
|
||||
if (listenerFieldFormRef.value) listenerFieldFormRef.value.clearValidate();
|
||||
});
|
||||
};
|
||||
// 保存监听器注入字段
|
||||
const saveListenerFiled = async () => {
|
||||
const validateStatus = await listenerFieldFormRef.value.validate();
|
||||
if (!validateStatus) return; // 验证不通过直接返回
|
||||
if (editingListenerFieldIndex.value === -1) {
|
||||
fieldsListOfListener.value.push(listenerFieldForm.value);
|
||||
listenerForm.value.fields.push(listenerFieldForm.value);
|
||||
} else {
|
||||
fieldsListOfListener.value.splice(
|
||||
editingListenerFieldIndex.value,
|
||||
1,
|
||||
listenerFieldForm.value,
|
||||
);
|
||||
listenerForm.value.fields.splice(
|
||||
editingListenerFieldIndex.value,
|
||||
1,
|
||||
listenerFieldForm.value,
|
||||
);
|
||||
}
|
||||
listenerFieldFormModelVisible.value = false;
|
||||
nextTick(() => {
|
||||
listenerFieldForm.value = {};
|
||||
});
|
||||
};
|
||||
// 移除监听器字段
|
||||
const removeListenerField = (_: any, index: number) => {
|
||||
// console.log(field, 'field');
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确认移除该字段吗?',
|
||||
okText: '确 认',
|
||||
cancelText: '取 消',
|
||||
onOk() {
|
||||
fieldsListOfListener.value.splice(index, 1);
|
||||
listenerForm.value.fields.splice(index, 1);
|
||||
},
|
||||
onCancel() {
|
||||
// console.info('操作取消');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 打开监听器弹窗
|
||||
const processListenerDialogRef = ref<any>();
|
||||
const openProcessListenerDialog = async () => {
|
||||
processListenerDialogRef.value.open('task');
|
||||
};
|
||||
const selectProcessListener = (listener: any) => {
|
||||
const listenerForm = initListenerForm2(listener);
|
||||
const listenerObject = createListenerObject(listenerForm, true, prefix);
|
||||
bpmnElementListeners.value.push(listenerObject);
|
||||
elementListenersList.value.push(listenerForm);
|
||||
|
||||
// 保存其他配置
|
||||
otherExtensionList.value =
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.filter(
|
||||
(ex: any) => ex.$type !== `${prefix}:TaskListener`,
|
||||
) ?? [];
|
||||
updateElementExtensions(
|
||||
bpmnElement.value,
|
||||
otherExtensionList.value?.concat(bpmnElementListeners.value),
|
||||
);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
val &&
|
||||
val.length > 0 &&
|
||||
nextTick(() => {
|
||||
resetListenersList();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Table :data="elementListenersList" size="small" bordered>
|
||||
<TableColumn title="序号" width="50px" type="index" />
|
||||
<TableColumn
|
||||
title="事件类型"
|
||||
width="80px"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
:custom-render="
|
||||
({ record }: any) =>
|
||||
listenerEventTypeObject[record.event as keyof typeof eventType]
|
||||
"
|
||||
/>
|
||||
<TableColumn
|
||||
title="事件id"
|
||||
width="80px"
|
||||
data-index="id"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn
|
||||
title="监听器类型"
|
||||
width="80px"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
:custom-render="
|
||||
({ record }: any) =>
|
||||
listenerTypeObject[record.listenerType as keyof typeof listenerType]
|
||||
"
|
||||
/>
|
||||
<TableColumn title="操作" width="90px">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
@click="openListenerForm(record, index)"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
danger
|
||||
@click="removeListener(record, index)"
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
<div class="element-drawer__button">
|
||||
<Button size="small" type="primary" @click="openListenerForm(null)">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
添加监听器
|
||||
</Button>
|
||||
<Button size="small" @click="openProcessListenerDialog">
|
||||
<template #icon><SelectOutlined /></template>
|
||||
选择监听器
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 监听器 编辑/创建 部分 -->
|
||||
<Drawer
|
||||
v-model:open="listenerFormModelVisible"
|
||||
title="任务监听器"
|
||||
:width="width"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="listenerForm"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
ref="listenerFormRef"
|
||||
>
|
||||
<FormItem
|
||||
label="事件类型"
|
||||
name="event"
|
||||
:rules="[{ required: true, message: '请选择事件类型' }]"
|
||||
>
|
||||
<Select v-model:value="listenerForm.event">
|
||||
<SelectOption
|
||||
v-for="i in Object.keys(listenerEventTypeObject)"
|
||||
:key="i"
|
||||
:value="i"
|
||||
>
|
||||
{{ listenerEventTypeObject[i as keyof typeof eventType] }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="监听器ID"
|
||||
name="id"
|
||||
:rules="[{ required: true, message: '请输入监听器ID' }]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.id" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="监听器类型"
|
||||
name="listenerType"
|
||||
:rules="[{ required: true, message: '请选择监听器类型' }]"
|
||||
>
|
||||
<Select v-model:value="listenerForm.listenerType">
|
||||
<SelectOption
|
||||
v-for="i in Object.keys(listenerTypeObject)"
|
||||
:key="i"
|
||||
:value="i"
|
||||
>
|
||||
{{ listenerTypeObject[i as keyof typeof listenerType] }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.listenerType === 'classListener'"
|
||||
label="Java类"
|
||||
name="class"
|
||||
key="listener-class"
|
||||
:rules="[{ required: true, message: '请输入Java类' }]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.class" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.listenerType === 'expressionListener'"
|
||||
label="表达式"
|
||||
name="expression"
|
||||
key="listener-expression"
|
||||
:rules="[{ required: true, message: '请输入表达式' }]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.expression" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.listenerType === 'delegateExpressionListener'"
|
||||
label="代理表达式"
|
||||
name="delegateExpression"
|
||||
key="listener-delegate"
|
||||
:rules="[{ required: true, message: '请输入代理表达式' }]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.delegateExpression" allow-clear />
|
||||
</FormItem>
|
||||
<template v-if="listenerForm.listenerType === 'scriptListener'">
|
||||
<FormItem
|
||||
label="脚本格式"
|
||||
name="scriptFormat"
|
||||
key="listener-script-format"
|
||||
:rules="[{ required: true, message: '请填写脚本格式' }]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.scriptFormat" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="脚本类型"
|
||||
name="scriptType"
|
||||
key="listener-script-type"
|
||||
:rules="[{ required: true, message: '请选择脚本类型' }]"
|
||||
>
|
||||
<Select v-model:value="listenerForm.scriptType">
|
||||
<SelectOption value="inlineScript">内联脚本</SelectOption>
|
||||
<SelectOption value="externalScript">外部脚本</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.scriptType === 'inlineScript'"
|
||||
label="脚本内容"
|
||||
name="value"
|
||||
key="listener-script"
|
||||
:rules="[{ required: true, message: '请填写脚本内容' }]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.value" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.scriptType === 'externalScript'"
|
||||
label="资源地址"
|
||||
name="resource"
|
||||
key="listener-resource"
|
||||
:rules="[{ required: true, message: '请填写资源地址' }]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.resource" allow-clear />
|
||||
</FormItem>
|
||||
</template>
|
||||
|
||||
<template v-if="listenerForm.event === 'timeout'">
|
||||
<FormItem
|
||||
label="定时器类型"
|
||||
name="eventDefinitionType"
|
||||
key="eventDefinitionType"
|
||||
>
|
||||
<Select v-model:value="listenerForm.eventDefinitionType">
|
||||
<SelectOption value="date">日期</SelectOption>
|
||||
<SelectOption value="duration">持续时长</SelectOption>
|
||||
<SelectOption value="cycle">循环</SelectOption>
|
||||
<SelectOption value="null">无</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="
|
||||
!!listenerForm.eventDefinitionType &&
|
||||
listenerForm.eventDefinitionType !== 'null'
|
||||
"
|
||||
label="定时器"
|
||||
name="eventTimeDefinitions"
|
||||
key="eventTimeDefinitions"
|
||||
:rules="[{ required: true, message: '请填写定时器配置' }]"
|
||||
>
|
||||
<Input
|
||||
v-model:value="listenerForm.eventTimeDefinitions"
|
||||
allow-clear
|
||||
/>
|
||||
</FormItem>
|
||||
</template>
|
||||
</Form>
|
||||
|
||||
<Divider />
|
||||
<p class="listener-filed__title">
|
||||
<span><MenuOutlined />注入字段:</span>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="openListenerFieldForm(null)"
|
||||
>
|
||||
添加字段
|
||||
</Button>
|
||||
</p>
|
||||
<Table
|
||||
:data="fieldsListOfListener"
|
||||
size="small"
|
||||
:scroll="{ y: 240 }"
|
||||
bordered
|
||||
style="flex: none"
|
||||
>
|
||||
<TableColumn title="序号" width="50px" type="index" />
|
||||
<TableColumn title="字段名称" width="100px" data-index="name" />
|
||||
<TableColumn
|
||||
title="字段类型"
|
||||
width="80px"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
:custom-render="
|
||||
({ record }: any) =>
|
||||
fieldTypeObject[record.fieldType as keyof typeof fieldType]
|
||||
"
|
||||
/>
|
||||
<TableColumn
|
||||
title="字段值/表达式"
|
||||
width="100px"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
:custom-render="
|
||||
({ record }: any) => record.string || record.expression
|
||||
"
|
||||
/>
|
||||
<TableColumn title="操作" width="100px">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
@click="openListenerFieldForm(record, index)"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
danger
|
||||
@click="removeListenerField(record, index)"
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
|
||||
<div class="element-drawer__button">
|
||||
<Button size="small" @click="listenerFormModelVisible = false">
|
||||
取 消
|
||||
</Button>
|
||||
<Button size="small" type="primary" @click="saveListenerConfig">
|
||||
保 存
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
<!-- 注入字段 编辑/创建 部分 -->
|
||||
<Modal
|
||||
title="字段配置"
|
||||
v-model:open="listenerFieldFormModelVisible"
|
||||
:width="600"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="listenerFieldForm"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
ref="listenerFieldFormRef"
|
||||
style="height: 136px"
|
||||
>
|
||||
<FormItem
|
||||
label="字段名称:"
|
||||
name="name"
|
||||
:rules="[{ required: true, message: '请输入字段名称' }]"
|
||||
>
|
||||
<Input v-model:value="listenerFieldForm.name" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="字段类型:"
|
||||
name="fieldType"
|
||||
:rules="[{ required: true, message: '请选择字段类型' }]"
|
||||
>
|
||||
<Select v-model:value="listenerFieldForm.fieldType">
|
||||
<SelectOption
|
||||
v-for="i in Object.keys(fieldTypeObject)"
|
||||
:key="i"
|
||||
:value="i"
|
||||
>
|
||||
{{ fieldTypeObject[i as keyof typeof fieldType] }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerFieldForm.fieldType === 'string'"
|
||||
label="字段值:"
|
||||
name="string"
|
||||
key="field-string"
|
||||
:rules="[{ required: true, message: '请输入字段值' }]"
|
||||
>
|
||||
<Input v-model:value="listenerFieldForm.string" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerFieldForm.fieldType === 'expression'"
|
||||
label="表达式:"
|
||||
name="expression"
|
||||
key="field-expression"
|
||||
:rules="[{ required: true, message: '请输入表达式' }]"
|
||||
>
|
||||
<Input v-model:value="listenerFieldForm.expression" allow-clear />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button size="small" @click="listenerFieldFormModelVisible = false">
|
||||
取 消
|
||||
</Button>
|
||||
<Button size="small" type="primary" @click="saveListenerFiled">
|
||||
确 定
|
||||
</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<!-- 选择弹窗 -->
|
||||
<ProcessListenerDialog
|
||||
ref="processListenerDialogRef"
|
||||
@select="selectProcessListener"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,98 @@
|
||||
// 初始化表单数据
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
export function initListenerForm(listener: any) {
|
||||
let self = {
|
||||
...listener,
|
||||
};
|
||||
if (listener.script) {
|
||||
self = {
|
||||
...listener,
|
||||
...listener.script,
|
||||
scriptType: listener.script.resource ? 'externalScript' : 'inlineScript',
|
||||
};
|
||||
}
|
||||
if (
|
||||
listener.event === 'timeout' &&
|
||||
listener.eventDefinitions &&
|
||||
listener.eventDefinitions.length > 0
|
||||
) {
|
||||
let k = '';
|
||||
for (const key in listener.eventDefinitions[0]) {
|
||||
// console.log(listener.eventDefinitions, key);
|
||||
if (key.includes('time')) {
|
||||
k = key;
|
||||
self.eventDefinitionType = key.replace('time', '').toLowerCase();
|
||||
}
|
||||
}
|
||||
// console.log(k);
|
||||
self.eventTimeDefinitions = listener.eventDefinitions[0][k].body;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
export function initListenerType(listener: any) {
|
||||
let listenerType;
|
||||
if (listener.class) listenerType = 'classListener';
|
||||
if (listener.expression) listenerType = 'expressionListener';
|
||||
if (listener.delegateExpression) listenerType = 'delegateExpressionListener';
|
||||
if (listener.script) listenerType = 'scriptListener';
|
||||
return {
|
||||
...cloneDeep(listener),
|
||||
...listener.script,
|
||||
listenerType,
|
||||
};
|
||||
}
|
||||
|
||||
/** 将 ProcessListenerDO 转换成 initListenerForm 想同的 Form 对象 */
|
||||
export function initListenerForm2(processListener: any) {
|
||||
switch (processListener.valueType) {
|
||||
case 'class': {
|
||||
return {
|
||||
listenerType: 'classListener',
|
||||
class: processListener.value,
|
||||
event: processListener.event,
|
||||
fields: [],
|
||||
};
|
||||
}
|
||||
case 'delegateExpression': {
|
||||
return {
|
||||
listenerType: 'delegateExpressionListener',
|
||||
delegateExpression: processListener.value,
|
||||
event: processListener.event,
|
||||
fields: [],
|
||||
};
|
||||
}
|
||||
case 'expression': {
|
||||
return {
|
||||
listenerType: 'expressionListener',
|
||||
expression: processListener.value,
|
||||
event: processListener.event,
|
||||
fields: [],
|
||||
};
|
||||
}
|
||||
// No default
|
||||
}
|
||||
throw new Error('未知的监听器类型');
|
||||
}
|
||||
|
||||
export const listenerType = {
|
||||
classListener: 'Java 类',
|
||||
expressionListener: '表达式',
|
||||
delegateExpressionListener: '代理表达式',
|
||||
scriptListener: '脚本',
|
||||
};
|
||||
|
||||
export const eventType = {
|
||||
create: '创建',
|
||||
assignment: '指派',
|
||||
complete: '完成',
|
||||
delete: '删除',
|
||||
update: '更新',
|
||||
timeout: '超时',
|
||||
};
|
||||
|
||||
export const fieldType = {
|
||||
string: '字符串',
|
||||
expression: '表达式',
|
||||
};
|
||||
@@ -0,0 +1,526 @@
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
InputNumber,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Select,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
APPROVE_METHODS,
|
||||
ApproveMethodType,
|
||||
} from '#/components/simple-process-design/consts';
|
||||
|
||||
defineOptions({ name: 'ElementMultiInstance' });
|
||||
|
||||
const props = defineProps({
|
||||
businessObject: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const prefix = inject<string>('prefix');
|
||||
const loopCharacteristics = ref('');
|
||||
// 默认配置,用来覆盖原始不存在的选项,避免报错
|
||||
const defaultLoopInstanceForm = ref({
|
||||
completionCondition: '',
|
||||
loopCardinality: '',
|
||||
extensionElements: [],
|
||||
asyncAfter: false,
|
||||
asyncBefore: false,
|
||||
exclusive: false,
|
||||
});
|
||||
interface LoopInstanceForm {
|
||||
completionCondition?: string;
|
||||
loopCardinality?: string;
|
||||
extensionElements?: any[];
|
||||
asyncAfter?: boolean;
|
||||
asyncBefore?: boolean;
|
||||
exclusive?: boolean;
|
||||
collection?: string;
|
||||
elementVariable?: string;
|
||||
timeCycle?: string;
|
||||
}
|
||||
|
||||
const loopInstanceForm = ref<LoopInstanceForm>({});
|
||||
const bpmnElement = ref<any>(null);
|
||||
const multiLoopInstance = ref<any>(null);
|
||||
declare global {
|
||||
interface Window {
|
||||
bpmnInstances?: () => any;
|
||||
}
|
||||
}
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const getElementLoop = (businessObject: any): void => {
|
||||
if (!businessObject.loopCharacteristics) {
|
||||
loopCharacteristics.value = 'Null';
|
||||
loopInstanceForm.value = {};
|
||||
return;
|
||||
}
|
||||
if (
|
||||
businessObject.loopCharacteristics.$type ===
|
||||
'bpmn:StandardLoopCharacteristics'
|
||||
) {
|
||||
loopCharacteristics.value = 'StandardLoop';
|
||||
loopInstanceForm.value = {};
|
||||
return;
|
||||
}
|
||||
loopCharacteristics.value = businessObject.loopCharacteristics.isSequential
|
||||
? 'SequentialMultiInstance'
|
||||
: 'ParallelMultiInstance';
|
||||
// 合并配置
|
||||
loopInstanceForm.value = {
|
||||
...defaultLoopInstanceForm.value,
|
||||
...businessObject.loopCharacteristics,
|
||||
completionCondition:
|
||||
businessObject.loopCharacteristics?.completionCondition?.body ?? '',
|
||||
loopCardinality:
|
||||
businessObject.loopCharacteristics?.loopCardinality?.body ?? '',
|
||||
};
|
||||
// 保留当前元素 businessObject 上的 loopCharacteristics 实例
|
||||
multiLoopInstance.value =
|
||||
bpmnInstances().bpmnElement.businessObject.loopCharacteristics;
|
||||
// 更新表单
|
||||
if (
|
||||
businessObject.loopCharacteristics.extensionElements &&
|
||||
businessObject.loopCharacteristics.extensionElements.values &&
|
||||
businessObject.loopCharacteristics.extensionElements.values.length > 0
|
||||
) {
|
||||
loopInstanceForm.value.timeCycle =
|
||||
businessObject.loopCharacteristics.extensionElements.values[0].body;
|
||||
}
|
||||
};
|
||||
|
||||
const changeLoopCharacteristicsType = (type: any): void => {
|
||||
// this.loopInstanceForm = { ...this.defaultLoopInstanceForm }; // 切换类型取消原表单配置
|
||||
// 取消多实例配置
|
||||
if (type === 'Null') {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
loopCharacteristics: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 配置循环
|
||||
if (type === 'StandardLoop') {
|
||||
const loopCharacteristicsObject = bpmnInstances().moddle.create(
|
||||
'bpmn:StandardLoopCharacteristics',
|
||||
);
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
loopCharacteristics: loopCharacteristicsObject,
|
||||
});
|
||||
multiLoopInstance.value = null;
|
||||
return;
|
||||
}
|
||||
// 时序
|
||||
multiLoopInstance.value =
|
||||
type === 'SequentialMultiInstance'
|
||||
? bpmnInstances().moddle.create('bpmn:MultiInstanceLoopCharacteristics', {
|
||||
isSequential: true,
|
||||
})
|
||||
: bpmnInstances().moddle.create('bpmn:MultiInstanceLoopCharacteristics', {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
collection: '${coll_userList}',
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
loopCharacteristics: toRaw(multiLoopInstance.value),
|
||||
});
|
||||
};
|
||||
|
||||
// 循环基数
|
||||
const updateLoopCardinality = (cardinality: string): void => {
|
||||
let loopCardinality = null;
|
||||
if (cardinality && cardinality.length > 0) {
|
||||
loopCardinality = bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||
body: cardinality,
|
||||
});
|
||||
}
|
||||
bpmnInstances().modeling.updateModdleProperties(
|
||||
toRaw(bpmnElement.value),
|
||||
multiLoopInstance.value,
|
||||
{
|
||||
loopCardinality,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// 完成条件
|
||||
const updateLoopCondition = (condition: string): void => {
|
||||
let completionCondition = null;
|
||||
if (condition && condition.length > 0) {
|
||||
completionCondition = bpmnInstances().moddle.create(
|
||||
'bpmn:FormalExpression',
|
||||
{
|
||||
body: condition,
|
||||
},
|
||||
);
|
||||
}
|
||||
bpmnInstances().modeling.updateModdleProperties(
|
||||
toRaw(bpmnElement.value),
|
||||
multiLoopInstance.value,
|
||||
{
|
||||
completionCondition,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// 重试周期
|
||||
const updateLoopTimeCycle = (timeCycle: string): void => {
|
||||
const extensionElements = bpmnInstances().moddle.create(
|
||||
'bpmn:ExtensionElements',
|
||||
{
|
||||
values: [
|
||||
bpmnInstances().moddle.create(`${prefix}:FailedJobRetryTimeCycle`, {
|
||||
body: timeCycle,
|
||||
}),
|
||||
],
|
||||
},
|
||||
);
|
||||
bpmnInstances().modeling.updateModdleProperties(
|
||||
toRaw(bpmnElement.value),
|
||||
multiLoopInstance.value,
|
||||
{
|
||||
extensionElements,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// 直接更新的基础信息
|
||||
const updateLoopBase = (): void => {
|
||||
bpmnInstances().modeling.updateModdleProperties(
|
||||
toRaw(bpmnElement.value),
|
||||
multiLoopInstance.value,
|
||||
{
|
||||
collection: loopInstanceForm.value.collection || null,
|
||||
elementVariable: loopInstanceForm.value.elementVariable || null,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// 各异步状态
|
||||
const updateLoopAsync = (key: any): void => {
|
||||
const { asyncBefore, asyncAfter } = loopInstanceForm.value;
|
||||
let asyncAttr = Object.create(null);
|
||||
if (!asyncBefore && !asyncAfter) {
|
||||
// this.$set(this.loopInstanceForm, "exclusive", false);
|
||||
loopInstanceForm.value.exclusive = false;
|
||||
asyncAttr = {
|
||||
asyncBefore: false,
|
||||
asyncAfter: false,
|
||||
exclusive: false,
|
||||
extensionElements: null,
|
||||
};
|
||||
} else {
|
||||
// @ts-ignore
|
||||
asyncAttr[key] = loopInstanceForm.value[key];
|
||||
}
|
||||
bpmnInstances().modeling.updateModdleProperties(
|
||||
toRaw(bpmnElement.value),
|
||||
multiLoopInstance.value,
|
||||
asyncAttr,
|
||||
);
|
||||
};
|
||||
|
||||
const changeConfig = (config: string): void => {
|
||||
switch (config) {
|
||||
case '会签': {
|
||||
changeLoopCharacteristicsType('ParallelMultiInstance');
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
updateLoopCondition('${ nrOfCompletedInstances >= nrOfInstances }');
|
||||
|
||||
break;
|
||||
}
|
||||
case '依次审批': {
|
||||
changeLoopCharacteristicsType('SequentialMultiInstance');
|
||||
updateLoopCardinality('1');
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
updateLoopCondition('${ nrOfCompletedInstances >= nrOfInstances }');
|
||||
|
||||
break;
|
||||
}
|
||||
case '或签': {
|
||||
changeLoopCharacteristicsType('ParallelMultiInstance');
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
updateLoopCondition('${ nrOfCompletedInstances > 0 }');
|
||||
|
||||
break;
|
||||
}
|
||||
// No default
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* -----新版本多实例-----
|
||||
*/
|
||||
const approveMethod = ref<ApproveMethodType | undefined>();
|
||||
const approveRatio = ref<number>(100);
|
||||
const otherExtensions = ref<any[]>([]);
|
||||
const getElementLoopNew = (): void => {
|
||||
if (props.type === 'UserTask') {
|
||||
const extensionElements =
|
||||
bpmnElement.value.businessObject?.extensionElements ??
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] });
|
||||
approveMethod.value = extensionElements.values.find(
|
||||
(ex: any) => ex.$type === `${prefix}:ApproveMethod`,
|
||||
)?.value;
|
||||
|
||||
otherExtensions.value =
|
||||
extensionElements.values.filter(
|
||||
(ex: any) => ex.$type !== `${prefix}:ApproveMethod`,
|
||||
) ?? [];
|
||||
|
||||
if (!approveMethod.value) {
|
||||
approveMethod.value = ApproveMethodType.SEQUENTIAL_APPROVE;
|
||||
updateLoopCharacteristics();
|
||||
}
|
||||
}
|
||||
};
|
||||
const onApproveMethodChange = (): void => {
|
||||
approveRatio.value = 100;
|
||||
updateLoopCharacteristics();
|
||||
};
|
||||
const onApproveRatioChange = (): void => {
|
||||
updateLoopCharacteristics();
|
||||
};
|
||||
const updateLoopCharacteristics = (): void => {
|
||||
// 根据ApproveMethod生成multiInstanceLoopCharacteristics节点
|
||||
if (approveMethod.value === ApproveMethodType.RANDOM_SELECT_ONE_APPROVE) {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
loopCharacteristics: null,
|
||||
});
|
||||
} else {
|
||||
if (approveMethod.value === ApproveMethodType.APPROVE_BY_RATIO) {
|
||||
multiLoopInstance.value = bpmnInstances().moddle.create(
|
||||
'bpmn:MultiInstanceLoopCharacteristics',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
{ isSequential: false, collection: '${coll_userList}' },
|
||||
);
|
||||
multiLoopInstance.value.completionCondition =
|
||||
bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||
body: `\${ nrOfCompletedInstances/nrOfInstances >= ${
|
||||
approveRatio.value / 100
|
||||
}}`,
|
||||
});
|
||||
}
|
||||
if (approveMethod.value === ApproveMethodType.ANY_APPROVE) {
|
||||
multiLoopInstance.value = bpmnInstances().moddle.create(
|
||||
'bpmn:MultiInstanceLoopCharacteristics',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
{ isSequential: false, collection: '${coll_userList}' },
|
||||
);
|
||||
multiLoopInstance.value.completionCondition =
|
||||
bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
body: '${ nrOfCompletedInstances > 0 }',
|
||||
});
|
||||
}
|
||||
if (approveMethod.value === ApproveMethodType.SEQUENTIAL_APPROVE) {
|
||||
multiLoopInstance.value = bpmnInstances().moddle.create(
|
||||
'bpmn:MultiInstanceLoopCharacteristics',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
{ isSequential: true, collection: '${coll_userList}' },
|
||||
);
|
||||
multiLoopInstance.value.loopCardinality = bpmnInstances().moddle.create(
|
||||
'bpmn:FormalExpression',
|
||||
{
|
||||
body: '1',
|
||||
},
|
||||
);
|
||||
multiLoopInstance.value.completionCondition =
|
||||
bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
body: '${ nrOfCompletedInstances >= nrOfInstances }',
|
||||
});
|
||||
}
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
loopCharacteristics: toRaw(multiLoopInstance.value),
|
||||
});
|
||||
}
|
||||
|
||||
// 添加ApproveMethod到ExtensionElements
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [
|
||||
...otherExtensions.value,
|
||||
bpmnInstances().moddle.create(`${prefix}:ApproveMethod`, {
|
||||
value: approveMethod.value,
|
||||
}),
|
||||
],
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions,
|
||||
});
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
multiLoopInstance.value = null;
|
||||
bpmnElement.value = null;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
if (val) {
|
||||
nextTick(() => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
// getElementLoop(val)
|
||||
getElementLoopNew();
|
||||
});
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<RadioGroup
|
||||
v-if="type === 'UserTask'"
|
||||
v-model:value="approveMethod"
|
||||
@change="onApproveMethodChange"
|
||||
>
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in APPROVE_METHODS" :key="index">
|
||||
<Radio :value="item.value">
|
||||
{{ item.label }}
|
||||
</Radio>
|
||||
<FormItem prop="approveRatio">
|
||||
<InputNumber
|
||||
v-model:value="approveRatio"
|
||||
:min="10"
|
||||
:max="100"
|
||||
:step="10"
|
||||
size="small"
|
||||
v-if="
|
||||
item.value === ApproveMethodType.APPROVE_BY_RATIO &&
|
||||
approveMethod === ApproveMethodType.APPROVE_BY_RATIO
|
||||
"
|
||||
@change="onApproveRatioChange"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<div v-else>除了UserTask以外节点的多实例待实现</div>
|
||||
<!-- 与Simple设计器配置合并,保留以前的代码 -->
|
||||
<Form :label-col="{ span: 6 }" style="display: none">
|
||||
<FormItem label="快捷配置">
|
||||
<Button size="small" @click="() => changeConfig('依次审批')">
|
||||
依次审批
|
||||
</Button>
|
||||
<Button size="small" @click="() => changeConfig('会签')">会签</Button>
|
||||
<Button size="small" @click="() => changeConfig('或签')">或签</Button>
|
||||
</FormItem>
|
||||
<FormItem label="会签类型">
|
||||
<Select
|
||||
v-model:value="loopCharacteristics"
|
||||
@change="changeLoopCharacteristicsType"
|
||||
>
|
||||
<Select.Option value="ParallelMultiInstance">
|
||||
并行多重事件
|
||||
</Select.Option>
|
||||
<Select.Option value="SequentialMultiInstance">
|
||||
时序多重事件
|
||||
</Select.Option>
|
||||
<Select.Option value="Null">无</Select.Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<template
|
||||
v-if="
|
||||
loopCharacteristics === 'ParallelMultiInstance' ||
|
||||
loopCharacteristics === 'SequentialMultiInstance'
|
||||
"
|
||||
>
|
||||
<FormItem label="循环数量" key="loopCardinality">
|
||||
<Input
|
||||
v-model:value="loopInstanceForm.loopCardinality"
|
||||
allow-clear
|
||||
@change="
|
||||
() =>
|
||||
updateLoopCardinality(loopInstanceForm.loopCardinality || '')
|
||||
"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="集合" key="collection" v-show="false">
|
||||
<Input
|
||||
v-model:value="loopInstanceForm.collection"
|
||||
allow-clear
|
||||
@change="() => updateLoopBase()"
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- add by 芋艿:由于「元素变量」暂时用不到,所以这里 display 为 none -->
|
||||
<FormItem label="元素变量" key="elementVariable" style="display: none">
|
||||
<Input
|
||||
v-model:value="loopInstanceForm.elementVariable"
|
||||
allow-clear
|
||||
@change="() => updateLoopBase()"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="完成条件" key="completionCondition">
|
||||
<Input
|
||||
v-model:value="loopInstanceForm.completionCondition"
|
||||
allow-clear
|
||||
@change="
|
||||
() =>
|
||||
updateLoopCondition(loopInstanceForm.completionCondition || '')
|
||||
"
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- add by 芋艿:由于「异步状态」暂时用不到,所以这里 display 为 none -->
|
||||
<FormItem label="异步状态" key="async" style="display: none">
|
||||
<Checkbox
|
||||
v-model:checked="loopInstanceForm.asyncBefore"
|
||||
@change="() => updateLoopAsync('asyncBefore')"
|
||||
>
|
||||
异步前
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
v-model:checked="loopInstanceForm.asyncAfter"
|
||||
@change="() => updateLoopAsync('asyncAfter')"
|
||||
>
|
||||
异步后
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
v-model:checked="loopInstanceForm.exclusive"
|
||||
v-if="loopInstanceForm.asyncAfter || loopInstanceForm.asyncBefore"
|
||||
@change="() => updateLoopAsync('exclusive')"
|
||||
>
|
||||
排除
|
||||
</Checkbox>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="重试周期"
|
||||
prop="timeCycle"
|
||||
v-if="loopInstanceForm.asyncAfter || loopInstanceForm.asyncBefore"
|
||||
key="timeCycle"
|
||||
>
|
||||
<Input
|
||||
v-model:value="loopInstanceForm.timeCycle"
|
||||
allow-clear
|
||||
@change="
|
||||
() => updateLoopTimeCycle(loopInstanceForm.timeCycle || '')
|
||||
"
|
||||
/>
|
||||
</FormItem>
|
||||
</template>
|
||||
</Form>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,74 @@
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { Input } from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'ElementOtherConfig' });
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const { Textarea } = Input;
|
||||
|
||||
const documentation = ref('');
|
||||
const bpmnElement = ref();
|
||||
|
||||
const bpmnInstances = () => (window as any).bpmnInstances;
|
||||
|
||||
const updateDocumentation = () => {
|
||||
(bpmnElement.value && bpmnElement.value.id === props.id) ||
|
||||
(bpmnElement.value = bpmnInstances().elementRegistry.get(props.id));
|
||||
const documentations = bpmnInstances().bpmnFactory.create(
|
||||
'bpmn:Documentation',
|
||||
{
|
||||
text: documentation.value,
|
||||
},
|
||||
);
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
documentation: [documentations],
|
||||
});
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
bpmnElement.value = null;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(id) => {
|
||||
if (id && id.length > 0) {
|
||||
nextTick(() => {
|
||||
const documentations =
|
||||
bpmnInstances().bpmnElement.businessObject?.documentation;
|
||||
documentation.value =
|
||||
documentations && documentations.length > 0
|
||||
? documentations[0].text
|
||||
: '';
|
||||
});
|
||||
} else {
|
||||
documentation.value = '';
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<div class="element-property input-property">
|
||||
<div class="element-property__label">元素文档:</div>
|
||||
<div class="element-property__value">
|
||||
<Textarea
|
||||
v-model:value="documentation"
|
||||
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||
@change="updateDocumentation"
|
||||
@blur="updateDocumentation"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,236 @@
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
Modal,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'ElementProperties' });
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const prefix = inject('prefix');
|
||||
// const width = inject('width')
|
||||
|
||||
const elementPropertyList = ref<Array<{ name: string; value: string }>>([]);
|
||||
const propertyForm = ref<{ name?: string; value?: string }>({});
|
||||
const editingPropertyIndex = ref(-1);
|
||||
const propertyFormModelVisible = ref(false);
|
||||
const bpmnElement = ref<any>();
|
||||
const otherExtensionList = ref<any[]>([]);
|
||||
const bpmnElementProperties = ref<any[]>([]);
|
||||
const bpmnElementPropertyList = ref<any[]>([]);
|
||||
const attributeFormRef = ref<any>();
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const resetAttributesList = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
otherExtensionList.value = []; // 其他扩展配置
|
||||
bpmnElementProperties.value =
|
||||
// bpmnElement.value.businessObject?.extensionElements?.filter((ex) => {
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.filter(
|
||||
(ex: any) => {
|
||||
if (ex.$type !== `${prefix}:Properties`) {
|
||||
otherExtensionList.value.push(ex);
|
||||
}
|
||||
return ex.$type === `${prefix}:Properties`;
|
||||
},
|
||||
) ?? [];
|
||||
|
||||
// 保存所有的 扩展属性字段
|
||||
bpmnElementPropertyList.value = bpmnElementProperties.value.flatMap(
|
||||
(current: any) => current.values,
|
||||
);
|
||||
// 复制 显示
|
||||
elementPropertyList.value = cloneDeep(bpmnElementPropertyList.value ?? []);
|
||||
};
|
||||
|
||||
const openAttributesForm = (
|
||||
attr: null | { name: string; value: string },
|
||||
index: number,
|
||||
) => {
|
||||
editingPropertyIndex.value = index;
|
||||
// @ts-ignore
|
||||
propertyForm.value = index === -1 ? {} : cloneDeep(attr);
|
||||
propertyFormModelVisible.value = true;
|
||||
nextTick(() => {
|
||||
if (attributeFormRef.value) attributeFormRef.value.clearValidate();
|
||||
});
|
||||
};
|
||||
|
||||
const removeAttributes = (
|
||||
_attr: { name: string; value: string },
|
||||
index: number,
|
||||
) => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确认移除该属性吗?',
|
||||
okText: '确 认',
|
||||
cancelText: '取 消',
|
||||
onOk() {
|
||||
elementPropertyList.value.splice(index, 1);
|
||||
bpmnElementPropertyList.value.splice(index, 1);
|
||||
// 新建一个属性字段的保存列表
|
||||
const propertiesObject = bpmnInstances().moddle.create(
|
||||
`${prefix}:Properties`,
|
||||
{
|
||||
values: bpmnElementPropertyList.value,
|
||||
},
|
||||
);
|
||||
updateElementExtensions(propertiesObject);
|
||||
resetAttributesList();
|
||||
},
|
||||
onCancel() {
|
||||
// console.info('操作取消');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const saveAttribute = () => {
|
||||
// console.log(propertyForm.value, 'propertyForm.value');
|
||||
const { name, value } = propertyForm.value;
|
||||
if (editingPropertyIndex.value === -1) {
|
||||
// 新建属性字段
|
||||
const newPropertyObject = bpmnInstances().moddle.create(
|
||||
`${prefix}:Property`,
|
||||
{
|
||||
name,
|
||||
value,
|
||||
},
|
||||
);
|
||||
// 新建一个属性字段的保存列表
|
||||
const propertiesObject = bpmnInstances().moddle.create(
|
||||
`${prefix}:Properties`,
|
||||
{
|
||||
values: [...bpmnElementPropertyList.value, newPropertyObject],
|
||||
},
|
||||
);
|
||||
updateElementExtensions(propertiesObject);
|
||||
} else {
|
||||
bpmnInstances().modeling.updateModdleProperties(
|
||||
toRaw(bpmnElement.value),
|
||||
toRaw(bpmnElementPropertyList.value)[toRaw(editingPropertyIndex.value)],
|
||||
{
|
||||
name,
|
||||
value,
|
||||
},
|
||||
);
|
||||
}
|
||||
propertyFormModelVisible.value = false;
|
||||
resetAttributesList();
|
||||
};
|
||||
|
||||
const updateElementExtensions = (properties: any) => {
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [...otherExtensionList.value, properties],
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions,
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
if (val) {
|
||||
val && val.length > 0 && resetAttributesList();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Table :data="elementPropertyList" :scroll="{ y: 240 }" bordered>
|
||||
<TableColumn title="序号" width="50">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</TableColumn>
|
||||
<TableColumn
|
||||
title="属性名"
|
||||
data-index="name"
|
||||
:min-width="100"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn
|
||||
title="属性值"
|
||||
data-index="value"
|
||||
:min-width="100"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn title="操作" width="110">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
type="link"
|
||||
@click="openAttributesForm(record, index)"
|
||||
size="small"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@click="removeAttributes(record, index)"
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
<div class="element-drawer__button">
|
||||
<Button type="primary" @click="openAttributesForm(null, -1)">
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:plus" />
|
||||
</template>
|
||||
添加属性
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
v-model:open="propertyFormModelVisible"
|
||||
title="属性配置"
|
||||
:width="600"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="propertyForm"
|
||||
ref="attributeFormRef"
|
||||
:label-col="{ span: 6 }"
|
||||
>
|
||||
<FormItem label="属性名:" name="name">
|
||||
<Input v-model:value="propertyForm.name" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem label="属性值:" name="value">
|
||||
<Input v-model:value="propertyForm.value" allow-clear />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button @click="propertyFormModelVisible = false">取 消</Button>
|
||||
<Button type="primary" @click="saveAttribute">确 定</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,178 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
message,
|
||||
Modal,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'SignalAndMassage' });
|
||||
const signalList = ref<any[]>([]);
|
||||
const messageList = ref<any[]>([]);
|
||||
const dialogVisible = ref(false);
|
||||
const modelType = ref('');
|
||||
const modelObjectForm = ref<any>({});
|
||||
const rootElements = ref();
|
||||
const messageIdMap = ref();
|
||||
const signalIdMap = ref();
|
||||
const modelConfig = computed(() => {
|
||||
return modelType.value === 'message'
|
||||
? { title: '创建消息', idLabel: '消息ID', nameLabel: '消息名称' }
|
||||
: { title: '创建信号', idLabel: '信号ID', nameLabel: '信号名称' };
|
||||
});
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const initDataList = () => {
|
||||
// console.log(window, 'window');
|
||||
rootElements.value = bpmnInstances().modeler.getDefinitions().rootElements;
|
||||
messageIdMap.value = {};
|
||||
signalIdMap.value = {};
|
||||
messageList.value = [];
|
||||
signalList.value = [];
|
||||
rootElements.value.forEach((el: any) => {
|
||||
if (el.$type === 'bpmn:Message') {
|
||||
messageIdMap.value[el.id] = true;
|
||||
messageList.value.push({ ...el });
|
||||
}
|
||||
if (el.$type === 'bpmn:Signal') {
|
||||
signalIdMap.value[el.id] = true;
|
||||
signalList.value.push({ ...el });
|
||||
}
|
||||
});
|
||||
};
|
||||
const openModel = (type: any) => {
|
||||
modelType.value = type;
|
||||
modelObjectForm.value = {};
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
const addNewObject = () => {
|
||||
if (modelType.value === 'message') {
|
||||
if (messageIdMap.value[modelObjectForm.value.id]) {
|
||||
message.error('该消息已存在,请修改id后重新保存');
|
||||
}
|
||||
const messageRef = bpmnInstances().moddle.create(
|
||||
'bpmn:Message',
|
||||
modelObjectForm.value,
|
||||
);
|
||||
rootElements.value.push(messageRef);
|
||||
} else {
|
||||
if (signalIdMap.value[modelObjectForm.value.id]) {
|
||||
message.error('该信号已存在,请修改id后重新保存');
|
||||
}
|
||||
const signalRef = bpmnInstances().moddle.create(
|
||||
'bpmn:Signal',
|
||||
modelObjectForm.value,
|
||||
);
|
||||
rootElements.value.push(signalRef);
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
initDataList();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initDataList();
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<div class="panel-tab__content--title">
|
||||
<span>
|
||||
<IconifyIcon icon="ep:menu" style="margin-right: 8px; color: #555" />
|
||||
消息列表
|
||||
</span>
|
||||
<Button type="primary" title="创建新消息" @click="openModel('message')">
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:plus" />
|
||||
</template>
|
||||
创建新消息
|
||||
</Button>
|
||||
</div>
|
||||
<Table :data-source="messageList" :bordered="true" :pagination="false">
|
||||
<TableColumn title="序号" width="60px">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</TableColumn>
|
||||
<TableColumn
|
||||
title="消息ID"
|
||||
data-index="id"
|
||||
:width="300"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn
|
||||
title="消息名称"
|
||||
data-index="name"
|
||||
:width="300"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
</Table>
|
||||
<div
|
||||
class="panel-tab__content--title"
|
||||
style="padding-top: 8px; margin-top: 8px; border-top: 1px solid #eee"
|
||||
>
|
||||
<span>
|
||||
<IconifyIcon icon="ep:menu" style="margin-right: 8px; color: #555">
|
||||
信号列表
|
||||
</IconifyIcon>
|
||||
</span>
|
||||
<Button type="primary" title="创建新信号" @click="openModel('signal')">
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:plus" />
|
||||
</template>
|
||||
创建新信号
|
||||
</Button>
|
||||
</div>
|
||||
<Table :data-source="signalList" :bordered="true" :pagination="false">
|
||||
<TableColumn title="序号" width="60px">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</TableColumn>
|
||||
<TableColumn
|
||||
title="信号ID"
|
||||
data-index="id"
|
||||
:width="300"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn
|
||||
title="信号名称"
|
||||
data-index="name"
|
||||
:width="300"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<Modal
|
||||
v-model:open="dialogVisible"
|
||||
:title="modelConfig.title"
|
||||
:mask-closable="false"
|
||||
width="400px"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="modelObjectForm"
|
||||
:label-col="{ span: 9 }"
|
||||
:wrapper-col="{ span: 15 }"
|
||||
>
|
||||
<FormItem :label="modelConfig.idLabel">
|
||||
<Input v-model:value="modelObjectForm.id" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem :label="modelConfig.nameLabel">
|
||||
<Input v-model:value="modelObjectForm.name" allow-clear />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button @click="dialogVisible = false">取 消</Button>
|
||||
<Button type="primary" @click="addNewObject">保 存</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,92 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { Checkbox, Form, FormItem } from 'ant-design-vue';
|
||||
|
||||
import { installedComponent } from './data';
|
||||
|
||||
defineOptions({ name: 'ElementTaskConfig' });
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const taskConfigForm = ref({
|
||||
asyncAfter: false,
|
||||
asyncBefore: false,
|
||||
exclusive: false,
|
||||
});
|
||||
const witchTaskComponent = ref();
|
||||
|
||||
const bpmnElement = ref();
|
||||
|
||||
const bpmnInstances = () => (window as any).bpmnInstances;
|
||||
const changeTaskAsync = () => {
|
||||
if (!taskConfigForm.value.asyncBefore && !taskConfigForm.value.asyncAfter) {
|
||||
taskConfigForm.value.exclusive = false;
|
||||
}
|
||||
bpmnInstances().modeling.updateProperties(bpmnInstances().bpmnElement, {
|
||||
...taskConfigForm.value,
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
() => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
taskConfigForm.value.asyncBefore =
|
||||
bpmnElement.value?.businessObject?.asyncBefore;
|
||||
taskConfigForm.value.asyncAfter =
|
||||
bpmnElement.value?.businessObject?.asyncAfter;
|
||||
taskConfigForm.value.exclusive =
|
||||
bpmnElement.value?.businessObject?.exclusive;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
watch(
|
||||
() => props.type,
|
||||
() => {
|
||||
if (props.type) {
|
||||
// @ts-ignore
|
||||
witchTaskComponent.value = installedComponent[props.type].component;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Form :label-col="{ span: 9 }" :wrapper-col="{ span: 15 }">
|
||||
<!-- add by 芋艿:由于「异步延续」暂时用不到,所以这里 display 为 none -->
|
||||
<FormItem label="异步延续" style="display: none">
|
||||
<Checkbox
|
||||
v-model:checked="taskConfigForm.asyncBefore"
|
||||
@change="changeTaskAsync"
|
||||
>
|
||||
异步前
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
v-model:checked="taskConfigForm.asyncAfter"
|
||||
@change="changeTaskAsync"
|
||||
>
|
||||
异步后
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
v-model:checked="taskConfigForm.exclusive"
|
||||
v-if="taskConfigForm.asyncAfter || taskConfigForm.asyncBefore"
|
||||
@change="changeTaskAsync"
|
||||
>
|
||||
排除
|
||||
</Checkbox>
|
||||
</FormItem>
|
||||
<component :is="witchTaskComponent" v-bind="$props" />
|
||||
</Form>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,40 @@
|
||||
import CallActivity from './task-components/CallActivity.vue';
|
||||
import ReceiveTask from './task-components/ReceiveTask.vue';
|
||||
import ScriptTask from './task-components/ScriptTask.vue';
|
||||
import ServiceTask from './task-components/ServiceTask.vue';
|
||||
import UserTask from './task-components/UserTask.vue';
|
||||
|
||||
export const installedComponent = {
|
||||
UserTask: {
|
||||
name: '用户任务',
|
||||
component: UserTask,
|
||||
},
|
||||
ServiceTask: {
|
||||
name: '服务任务',
|
||||
component: ServiceTask,
|
||||
},
|
||||
ScriptTask: {
|
||||
name: '脚本任务',
|
||||
component: ScriptTask,
|
||||
},
|
||||
ReceiveTask: {
|
||||
name: '接收任务',
|
||||
component: ReceiveTask,
|
||||
},
|
||||
CallActivity: {
|
||||
name: '调用活动',
|
||||
component: CallActivity,
|
||||
},
|
||||
};
|
||||
|
||||
export const getTaskCollapseItemName = (
|
||||
elementType: keyof typeof installedComponent,
|
||||
) => {
|
||||
return installedComponent[elementType].name;
|
||||
};
|
||||
|
||||
export const isTaskCollapseItemShow = (
|
||||
elementType: keyof typeof installedComponent,
|
||||
) => {
|
||||
return installedComponent[elementType];
|
||||
};
|
||||
@@ -0,0 +1,361 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, inject, nextTick, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { alert } from '@vben/common-ui';
|
||||
import { PlusOutlined } from '@vben/icons';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
Modal,
|
||||
Switch,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
interface FormData {
|
||||
processInstanceName: string;
|
||||
calledElement: string;
|
||||
inheritVariables: boolean;
|
||||
businessKey: string;
|
||||
inheritBusinessKey: boolean;
|
||||
calledElementType: string;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'CallActivity' });
|
||||
const props = defineProps({
|
||||
id: { type: String, default: '' },
|
||||
type: { type: String, default: '' },
|
||||
});
|
||||
const prefix = inject('prefix');
|
||||
|
||||
const formData = ref<FormData>({
|
||||
processInstanceName: '',
|
||||
calledElement: '',
|
||||
inheritVariables: false,
|
||||
businessKey: '',
|
||||
inheritBusinessKey: false,
|
||||
calledElementType: 'key',
|
||||
});
|
||||
const inVariableList = ref<any[]>([]);
|
||||
const outVariableList = ref<any[]>([]);
|
||||
const variableType = ref<string>(); // 参数类型
|
||||
const editingVariableIndex = ref<number>(-1); // 编辑参数下标
|
||||
const variableDialogVisible = ref<boolean>(false);
|
||||
const varialbeFormRef = ref<any>();
|
||||
const varialbeFormData = ref<{
|
||||
source: string;
|
||||
target: string;
|
||||
}>({
|
||||
source: '',
|
||||
target: '',
|
||||
});
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
const bpmnElement = ref<any>();
|
||||
const otherExtensionList = ref<any[]>([]);
|
||||
|
||||
const initCallActivity = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
// console.log(bpmnElement.value.businessObject, 'callActivity');
|
||||
|
||||
// 初始化所有配置项
|
||||
Object.keys(formData.value).forEach((key: string) => {
|
||||
// @ts-ignore
|
||||
formData.value[key] =
|
||||
bpmnElement.value.businessObject[key] ??
|
||||
formData.value[key as keyof FormData];
|
||||
});
|
||||
|
||||
otherExtensionList.value = []; // 其他扩展配置
|
||||
inVariableList.value.length = 0;
|
||||
outVariableList.value.length = 0;
|
||||
// 初始化输入参数
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.forEach(
|
||||
(ex: any) => {
|
||||
if (ex.$type === `${prefix}:In`) {
|
||||
inVariableList.value.push(ex);
|
||||
} else if (ex.$type === `${prefix}:Out`) {
|
||||
outVariableList.value.push(ex);
|
||||
} else {
|
||||
otherExtensionList.value.push(ex);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// 默认添加
|
||||
// bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
// calledElementType: 'key'
|
||||
// })
|
||||
};
|
||||
|
||||
const updateCallActivityAttr = (attr: keyof FormData) => {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
[attr]: formData.value[attr],
|
||||
});
|
||||
};
|
||||
|
||||
const openVariableForm = (type: string, data: any, index: number) => {
|
||||
editingVariableIndex.value = index;
|
||||
variableType.value = type;
|
||||
varialbeFormData.value = index === -1 ? {} : { ...data };
|
||||
variableDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const removeVariable = async (type: string, index: number) => {
|
||||
try {
|
||||
await alert('是否确认删除?');
|
||||
if (type === 'in') {
|
||||
inVariableList.value.splice(index, 1);
|
||||
}
|
||||
if (type === 'out') {
|
||||
outVariableList.value.splice(index, 1);
|
||||
}
|
||||
updateElementExtensions();
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const saveVariable = () => {
|
||||
if (editingVariableIndex.value === -1) {
|
||||
if (variableType.value === 'in') {
|
||||
inVariableList.value.push(
|
||||
bpmnInstances().moddle.create(`${prefix}:In`, {
|
||||
...varialbeFormData.value,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (variableType.value === 'out') {
|
||||
outVariableList.value.push(
|
||||
bpmnInstances().moddle.create(`${prefix}:Out`, {
|
||||
...varialbeFormData.value,
|
||||
}),
|
||||
);
|
||||
}
|
||||
updateElementExtensions();
|
||||
} else {
|
||||
if (variableType.value === 'in') {
|
||||
inVariableList.value[editingVariableIndex.value].source =
|
||||
varialbeFormData.value.source;
|
||||
inVariableList.value[editingVariableIndex.value].target =
|
||||
varialbeFormData.value.target;
|
||||
}
|
||||
if (variableType.value === 'out') {
|
||||
outVariableList.value[editingVariableIndex.value].source =
|
||||
varialbeFormData.value.source;
|
||||
outVariableList.value[editingVariableIndex.value].target =
|
||||
varialbeFormData.value.target;
|
||||
}
|
||||
}
|
||||
variableDialogVisible.value = false;
|
||||
};
|
||||
|
||||
const updateElementExtensions = () => {
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [
|
||||
...inVariableList.value,
|
||||
...outVariableList.value,
|
||||
...otherExtensionList.value,
|
||||
],
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions,
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
val &&
|
||||
val.length > 0 &&
|
||||
nextTick(() => {
|
||||
initCallActivity();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||
<FormItem label="实例名称">
|
||||
<Input
|
||||
v-model:value="formData.processInstanceName"
|
||||
allow-clear
|
||||
placeholder="请输入实例名称"
|
||||
@change="updateCallActivityAttr('processInstanceName')"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<!-- TODO 需要可选择已存在的流程 -->
|
||||
<FormItem label="被调用流程">
|
||||
<Input
|
||||
v-model:value="formData.calledElement"
|
||||
allow-clear
|
||||
placeholder="请输入被调用流程"
|
||||
@change="updateCallActivityAttr('calledElement')"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="继承变量">
|
||||
<Switch
|
||||
v-model:checked="formData.inheritVariables"
|
||||
@change="updateCallActivityAttr('inheritVariables')"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="继承业务键">
|
||||
<Switch
|
||||
v-model:checked="formData.inheritBusinessKey"
|
||||
@change="updateCallActivityAttr('inheritBusinessKey')"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem v-if="!formData.inheritBusinessKey" label="业务键表达式">
|
||||
<Input
|
||||
v-model:value="formData.businessKey"
|
||||
allow-clear
|
||||
placeholder="请输入业务键表达式"
|
||||
@change="updateCallActivityAttr('businessKey')"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<Divider />
|
||||
<div>
|
||||
<div class="mb-10px flex">
|
||||
<span>输入参数</span>
|
||||
<Button
|
||||
class="ml-auto"
|
||||
type="primary"
|
||||
:icon="h(PlusOutlined)"
|
||||
title="添加参数"
|
||||
size="small"
|
||||
@click="openVariableForm('in', null, -1)"
|
||||
/>
|
||||
</div>
|
||||
<Table
|
||||
:data-source="inVariableList"
|
||||
:scroll="{ y: 240 }"
|
||||
bordered
|
||||
:pagination="false"
|
||||
>
|
||||
<TableColumn
|
||||
title="源"
|
||||
data-index="source"
|
||||
:min-width="100"
|
||||
:ellipsis="true"
|
||||
/>
|
||||
<TableColumn
|
||||
title="目标"
|
||||
data-index="target"
|
||||
:min-width="100"
|
||||
:ellipsis="true"
|
||||
/>
|
||||
<TableColumn title="操作" :width="110">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
type="link"
|
||||
@click="openVariableForm('in', record, index)"
|
||||
size="small"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@click="removeVariable('in', index)"
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
<div>
|
||||
<div class="mb-10px flex">
|
||||
<span>输出参数</span>
|
||||
<Button
|
||||
class="ml-auto"
|
||||
type="primary"
|
||||
:icon="h(PlusOutlined)"
|
||||
title="添加参数"
|
||||
size="small"
|
||||
@click="openVariableForm('out', null, -1)"
|
||||
/>
|
||||
</div>
|
||||
<Table
|
||||
:data-source="outVariableList"
|
||||
:scroll="{ y: 240 }"
|
||||
bordered
|
||||
:pagination="false"
|
||||
>
|
||||
<TableColumn
|
||||
title="源"
|
||||
data-index="source"
|
||||
:min-width="100"
|
||||
:ellipsis="true"
|
||||
/>
|
||||
<TableColumn
|
||||
title="目标"
|
||||
data-index="target"
|
||||
:min-width="100"
|
||||
:ellipsis="true"
|
||||
/>
|
||||
<TableColumn title="操作" :width="110">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
type="link"
|
||||
@click="openVariableForm('out', record, index)"
|
||||
size="small"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@click="removeVariable('out', index)"
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<!-- 添加或修改参数 -->
|
||||
<Modal
|
||||
v-model:open="variableDialogVisible"
|
||||
title="参数配置"
|
||||
:width="600"
|
||||
:destroy-on-close="true"
|
||||
@ok="saveVariable"
|
||||
@cancel="variableDialogVisible = false"
|
||||
>
|
||||
<Form
|
||||
:model="varialbeFormData"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
ref="varialbeFormRef"
|
||||
>
|
||||
<FormItem label="源:" name="source">
|
||||
<Input v-model:value="varialbeFormData.source" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem label="目标:" name="target">
|
||||
<Input v-model:value="varialbeFormData.target" allow-clear />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,96 @@
|
||||
<!-- 表达式选择 -->
|
||||
<script setup lang="ts">
|
||||
import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression';
|
||||
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
import { CommonStatusEnum } from '@vben/constants';
|
||||
|
||||
import { Button, Modal, Pagination, Table, TableColumn } from 'ant-design-vue';
|
||||
|
||||
import { getProcessExpressionPage } from '#/api/bpm/processExpression';
|
||||
import { ContentWrap } from '#/components/content-wrap';
|
||||
|
||||
/** BPM 流程 表单 */
|
||||
defineOptions({ name: 'ProcessExpressionDialog' });
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['select']);
|
||||
const dialogVisible = ref(false); // 弹窗的是否展示
|
||||
const loading = ref(true); // 列表的加载中
|
||||
const list = ref<BpmProcessExpressionApi.ProcessExpression[]>([]); // 列表的数据
|
||||
const total = ref(0); // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
type: '',
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
});
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = (type: string) => {
|
||||
queryParams.pageNo = 1;
|
||||
queryParams.type = type;
|
||||
getList();
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getProcessExpressionPage(queryParams);
|
||||
list.value = data.list;
|
||||
total.value = data.total;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 定义 select 事件,用于操作成功后的回调
|
||||
const select = async (row: BpmProcessExpressionApi.ProcessExpression) => {
|
||||
dialogVisible.value = false;
|
||||
// 发送操作成功的事件
|
||||
emit('select', row);
|
||||
};
|
||||
|
||||
// const handleCancel = () => {
|
||||
// dialogVisible.value = false;
|
||||
// };
|
||||
</script>
|
||||
<template>
|
||||
<Modal
|
||||
title="请选择表达式"
|
||||
v-model:open="dialogVisible"
|
||||
width="1024px"
|
||||
:footer="null"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:loading="loading"
|
||||
:data-source="list"
|
||||
:pagination="false"
|
||||
:scroll="{ x: 'max-content' }"
|
||||
>
|
||||
<TableColumn title="名字" align="center" data-index="name" />
|
||||
<TableColumn title="表达式" align="center" data-index="expression" />
|
||||
<TableColumn title="操作" align="center">
|
||||
<template #default="{ record }">
|
||||
<Button type="primary" @click="select(record)"> 选择 </Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
<!-- 分页 -->
|
||||
<div class="mt-4 flex justify-end">
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:current="queryParams.pageNo"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
show-size-changer
|
||||
@change="getList"
|
||||
/>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -0,0 +1,161 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
h,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
ref,
|
||||
toRaw,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
import { PlusOutlined } from '@vben/icons';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
Input,
|
||||
message,
|
||||
Modal,
|
||||
Select,
|
||||
SelectOption,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'ReceiveTask' });
|
||||
const props = defineProps({
|
||||
id: { type: String, default: '' },
|
||||
type: { type: String, default: '' },
|
||||
});
|
||||
|
||||
const bindMessageId = ref('');
|
||||
const newMessageForm = ref<Record<string, any>>({});
|
||||
const messageMap = ref<Record<string, any>>({});
|
||||
const messageModelVisible = ref(false);
|
||||
const bpmnElement = ref<any>();
|
||||
const bpmnMessageRefsMap = ref<Record<string, any>>();
|
||||
const bpmnRootElements = ref<any>();
|
||||
|
||||
const bpmnInstances = () => (window as any).bpmnInstances;
|
||||
const getBindMessage = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
bindMessageId.value =
|
||||
bpmnElement.value.businessObject?.messageRef?.id || '-1';
|
||||
};
|
||||
const openMessageModel = () => {
|
||||
messageModelVisible.value = true;
|
||||
newMessageForm.value = {};
|
||||
};
|
||||
const createNewMessage = () => {
|
||||
if (messageMap.value[newMessageForm.value.id]) {
|
||||
message.error('该消息已存在,请修改id后重新保存');
|
||||
return;
|
||||
}
|
||||
const newMessage = bpmnInstances().moddle.create(
|
||||
'bpmn:Message',
|
||||
newMessageForm.value,
|
||||
);
|
||||
bpmnRootElements.value.push(newMessage);
|
||||
messageMap.value[newMessageForm.value.id] = newMessageForm.value.name;
|
||||
// @ts-ignore
|
||||
if (bpmnMessageRefsMap.value) {
|
||||
bpmnMessageRefsMap.value[newMessageForm.value.id] = newMessage;
|
||||
}
|
||||
messageModelVisible.value = false;
|
||||
};
|
||||
const updateTaskMessage = (messageId: string) => {
|
||||
if (messageId === '-1') {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
messageRef: null,
|
||||
});
|
||||
} else {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
messageRef: bpmnMessageRefsMap.value?.[messageId],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
bpmnMessageRefsMap.value = Object.create(null);
|
||||
bpmnRootElements.value =
|
||||
bpmnInstances().modeler.getDefinitions().rootElements;
|
||||
bpmnRootElements.value
|
||||
.filter((el: any) => el.$type === 'bpmn:Message')
|
||||
.forEach((m: any) => {
|
||||
// @ts-ignore
|
||||
if (bpmnMessageRefsMap.value) {
|
||||
bpmnMessageRefsMap.value[m.id] = m;
|
||||
}
|
||||
messageMap.value[m.id] = m.name;
|
||||
});
|
||||
messageMap.value['-1'] = '无';
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
bpmnElement.value = null;
|
||||
});
|
||||
watch(
|
||||
() => props.id,
|
||||
() => {
|
||||
// bpmnElement.value = bpmnInstances().bpmnElement
|
||||
nextTick(() => {
|
||||
getBindMessage();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="margin-top: 16px">
|
||||
<Form.Item label="消息实例">
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
"
|
||||
>
|
||||
<Select
|
||||
v-model:value="bindMessageId"
|
||||
@change="(value: any) => updateTaskMessage(value)"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="key in Object.keys(messageMap)"
|
||||
:value="key"
|
||||
:key="key"
|
||||
>
|
||||
{{ messageMap[key] }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
<Button
|
||||
type="primary"
|
||||
:icon="h(PlusOutlined)"
|
||||
style="margin-left: 8px"
|
||||
@click="openMessageModel"
|
||||
/>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Modal
|
||||
v-model:open="messageModelVisible"
|
||||
:mask-closable="false"
|
||||
title="创建新消息"
|
||||
width="400px"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form :model="newMessageForm" size="small" :label-col="{ span: 6 }">
|
||||
<Form.Item label="消息ID">
|
||||
<Input v-model:value="newMessageForm.id" allow-clear />
|
||||
</Form.Item>
|
||||
<Form.Item label="消息名称">
|
||||
<Input v-model:value="newMessageForm.name" allow-clear />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button size="small" type="primary" @click="createNewMessage">
|
||||
确 认
|
||||
</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,129 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
defineOptions,
|
||||
defineProps,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
ref,
|
||||
toRaw,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
import {
|
||||
FormItem,
|
||||
Input,
|
||||
Select,
|
||||
SelectOption,
|
||||
Textarea,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'ScriptTask' });
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const defaultTaskForm = ref({
|
||||
scriptFormat: '',
|
||||
script: '',
|
||||
resource: '',
|
||||
resultVariable: '',
|
||||
});
|
||||
const scriptTaskForm = ref<any>({});
|
||||
const bpmnElement = ref();
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const resetTaskForm = () => {
|
||||
for (const key in defaultTaskForm.value) {
|
||||
// @ts-ignore
|
||||
scriptTaskForm.value[key] =
|
||||
bpmnElement.value?.businessObject[
|
||||
key as keyof typeof defaultTaskForm.value
|
||||
] || defaultTaskForm.value[key as keyof typeof defaultTaskForm.value];
|
||||
}
|
||||
scriptTaskForm.value.scriptType = scriptTaskForm.value.script
|
||||
? 'inline'
|
||||
: 'external';
|
||||
};
|
||||
const updateElementTask = () => {
|
||||
const taskAttr = Object.create(null);
|
||||
taskAttr.scriptFormat = scriptTaskForm.value.scriptFormat || null;
|
||||
taskAttr.resultVariable = scriptTaskForm.value.resultVariable || null;
|
||||
if (scriptTaskForm.value.scriptType === 'inline') {
|
||||
taskAttr.script = scriptTaskForm.value.script || null;
|
||||
taskAttr.resource = null;
|
||||
} else {
|
||||
taskAttr.resource = scriptTaskForm.value.resource || null;
|
||||
taskAttr.script = null;
|
||||
}
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), taskAttr);
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
bpmnElement.value = null;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
() => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
nextTick(() => {
|
||||
resetTaskForm();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mt-4">
|
||||
<FormItem label="脚本格式">
|
||||
<Input
|
||||
v-model:value="scriptTaskForm.scriptFormat"
|
||||
allow-clear
|
||||
@input="updateElementTask()"
|
||||
@change="updateElementTask()"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="脚本类型">
|
||||
<Select v-model:value="scriptTaskForm.scriptType">
|
||||
<SelectOption value="inline">内联脚本</SelectOption>
|
||||
<SelectOption value="external">外部资源</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label="脚本" v-show="scriptTaskForm.scriptType === 'inline'">
|
||||
<Textarea
|
||||
v-model:value="scriptTaskForm.script"
|
||||
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||
allow-clear
|
||||
@input="updateElementTask()"
|
||||
@change="updateElementTask()"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="资源地址"
|
||||
v-show="scriptTaskForm.scriptType === 'external'"
|
||||
>
|
||||
<Input
|
||||
v-model:value="scriptTaskForm.resource"
|
||||
allow-clear
|
||||
@input="updateElementTask()"
|
||||
@change="updateElementTask()"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="结果变量">
|
||||
<Input
|
||||
v-model:value="scriptTaskForm.resultVariable"
|
||||
allow-clear
|
||||
@input="updateElementTask()"
|
||||
@change="updateElementTask()"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,111 @@
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { FormItem, Input, Select } from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'ServiceTask' });
|
||||
const props = defineProps({
|
||||
id: { type: String, default: '' },
|
||||
type: { type: String, default: '' },
|
||||
});
|
||||
|
||||
const defaultTaskForm = ref({
|
||||
executeType: '',
|
||||
class: '',
|
||||
expression: '',
|
||||
delegateExpression: '',
|
||||
});
|
||||
|
||||
const serviceTaskForm = ref<any>({});
|
||||
const bpmnElement = ref();
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const resetTaskForm = () => {
|
||||
for (const key in defaultTaskForm.value) {
|
||||
const value =
|
||||
// @ts-ignore
|
||||
bpmnElement.value?.businessObject[key] || defaultTaskForm.value[key];
|
||||
serviceTaskForm.value[key] = value;
|
||||
if (value) {
|
||||
serviceTaskForm.value.executeType = key;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateElementTask = () => {
|
||||
const taskAttr = Object.create(null);
|
||||
const type = serviceTaskForm.value.executeType;
|
||||
for (const key in serviceTaskForm.value) {
|
||||
if (key !== 'executeType' && key !== type) taskAttr[key] = null;
|
||||
}
|
||||
taskAttr[type] = serviceTaskForm.value[type] || '';
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), taskAttr);
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
bpmnElement.value = null;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
() => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
nextTick(() => {
|
||||
resetTaskForm();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<FormItem label="执行类型" key="executeType">
|
||||
<Select
|
||||
v-model:value="serviceTaskForm.executeType"
|
||||
:options="[
|
||||
{ label: 'Java类', value: 'class' },
|
||||
{ label: '表达式', value: 'expression' },
|
||||
{ label: '代理表达式', value: 'delegateExpression' },
|
||||
]"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="serviceTaskForm.executeType === 'class'"
|
||||
label="Java类"
|
||||
name="class"
|
||||
key="execute-class"
|
||||
>
|
||||
<Input
|
||||
v-model:value="serviceTaskForm.class"
|
||||
allow-clear
|
||||
@change="updateElementTask"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="serviceTaskForm.executeType === 'expression'"
|
||||
label="表达式"
|
||||
name="expression"
|
||||
key="execute-expression"
|
||||
>
|
||||
<Input
|
||||
v-model:value="serviceTaskForm.expression"
|
||||
allow-clear
|
||||
@change="updateElementTask"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="serviceTaskForm.executeType === 'delegateExpression'"
|
||||
label="代理表达式"
|
||||
name="delegateExpression"
|
||||
key="execute-delegate"
|
||||
>
|
||||
<Input
|
||||
v-model:value="serviceTaskForm.delegateExpression"
|
||||
allow-clear
|
||||
@change="updateElementTask"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,571 @@
|
||||
<script lang="ts" setup>
|
||||
import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression';
|
||||
import type { BpmUserGroupApi } from '#/api/bpm/userGroup';
|
||||
import type { SystemPostApi } from '#/api/system/post';
|
||||
import type { SystemRoleApi } from '#/api/system/role';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import {
|
||||
computed,
|
||||
h,
|
||||
inject,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
ref,
|
||||
toRaw,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
import { SelectOutlined } from '@vben/icons';
|
||||
import { handleTree } from '@vben/utils';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormItem,
|
||||
Select,
|
||||
SelectOption,
|
||||
Textarea,
|
||||
TreeSelect,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { getUserGroupSimpleList } from '#/api/bpm/userGroup';
|
||||
import { getSimpleDeptList } from '#/api/system/dept';
|
||||
import { getSimplePostList } from '#/api/system/post';
|
||||
import { getSimpleRoleList } from '#/api/system/role';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import {
|
||||
CANDIDATE_STRATEGY,
|
||||
CandidateStrategy,
|
||||
FieldPermissionType,
|
||||
MULTI_LEVEL_DEPT,
|
||||
} from '#/components/simple-process-design/consts';
|
||||
import { useFormFieldsPermission } from '#/components/simple-process-design/helpers';
|
||||
|
||||
import ProcessExpressionDialog from './ProcessExpressionDialog.vue';
|
||||
|
||||
defineOptions({ name: 'UserTask' });
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const prefix = inject('prefix');
|
||||
const userTaskForm = ref({
|
||||
candidateStrategy: undefined, // 分配规则
|
||||
candidateParam: [], // 分配选项
|
||||
skipExpression: '', // 跳过表达式
|
||||
});
|
||||
const bpmnElement = ref<any>();
|
||||
const bpmnInstances = () => (window as Record<string, any>)?.bpmnInstances;
|
||||
|
||||
const roleOptions = ref<SystemRoleApi.Role[]>([]); // 角色列表
|
||||
const deptTreeOptions = ref<any>(); // 部门树
|
||||
const postOptions = ref<SystemPostApi.Post[]>([]); // 岗位列表
|
||||
const userOptions = ref<SystemUserApi.User[]>([]); // 用户列表
|
||||
const userGroupOptions = ref<BpmUserGroupApi.UserGroup[]>([]); // 用户组列表
|
||||
const treeRef = ref<any>();
|
||||
|
||||
const { formFieldOptions } = useFormFieldsPermission(FieldPermissionType.READ);
|
||||
|
||||
// 定义 TreeSelect 的默认属性映射
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
};
|
||||
// 表单内用户字段选项, 必须是必填和用户选择器
|
||||
const userFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'UserSelect');
|
||||
});
|
||||
// 表单内部门字段选项, 必须是必填和部门选择器
|
||||
const deptFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'DeptSelect');
|
||||
});
|
||||
|
||||
const deptLevel = ref(1);
|
||||
const deptLevelLabel = computed(() => {
|
||||
let label = '部门负责人来源';
|
||||
if (
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
|
||||
) {
|
||||
label = `${label}(指定部门向上)`;
|
||||
} else if (
|
||||
userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER
|
||||
) {
|
||||
label = `${label}(表单内部门向上)`;
|
||||
} else {
|
||||
label = `${label}(发起人部门向上)`;
|
||||
}
|
||||
return label;
|
||||
});
|
||||
|
||||
const otherExtensions = ref<any>();
|
||||
|
||||
const resetTaskForm = () => {
|
||||
const businessObject = bpmnElement.value.businessObject;
|
||||
if (!businessObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extensionElements =
|
||||
businessObject?.extensionElements ??
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] });
|
||||
userTaskForm.value.candidateStrategy = extensionElements.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:CandidateStrategy`,
|
||||
)?.[0]?.value;
|
||||
const candidateParamStr = extensionElements.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:CandidateParam`,
|
||||
)?.[0]?.value;
|
||||
if (candidateParamStr && candidateParamStr.length > 0) {
|
||||
// eslint-disable-next-line unicorn/prefer-switch
|
||||
if (userTaskForm.value.candidateStrategy === CandidateStrategy.EXPRESSION) {
|
||||
// 特殊:流程表达式,只有一个 input 输入框
|
||||
// @ts-ignore
|
||||
userTaskForm.value.candidateParam = [candidateParamStr];
|
||||
} else if (
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
|
||||
) {
|
||||
// 特殊:多级不部门负责人,需要通过'|'分割
|
||||
userTaskForm.value.candidateParam = candidateParamStr
|
||||
.split('|')[0]
|
||||
.split(',')
|
||||
.map((item: any) => {
|
||||
// 如果数字超出了最大安全整数范围,则将其作为字符串处理
|
||||
const num = Number(item);
|
||||
return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER
|
||||
? item
|
||||
: num;
|
||||
});
|
||||
deptLevel.value = +candidateParamStr.split('|')[1];
|
||||
} else if (
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
|
||||
) {
|
||||
// @ts-ignore
|
||||
userTaskForm.value.candidateParam = +candidateParamStr;
|
||||
deptLevel.value = +candidateParamStr;
|
||||
} else if (
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.FORM_DEPT_LEADER
|
||||
) {
|
||||
userTaskForm.value.candidateParam = candidateParamStr.split('|')[0];
|
||||
deptLevel.value = +candidateParamStr.split('|')[1];
|
||||
} else {
|
||||
userTaskForm.value.candidateParam = candidateParamStr
|
||||
.split(',')
|
||||
.map((item: any) => {
|
||||
// 如果数字超出了最大安全整数范围,则将其作为字符串处理
|
||||
const num = Number(item);
|
||||
return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER
|
||||
? item
|
||||
: num;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
userTaskForm.value.candidateParam = [];
|
||||
}
|
||||
|
||||
otherExtensions.value =
|
||||
extensionElements.values?.filter(
|
||||
(ex: any) =>
|
||||
ex.$type !== `${prefix}:CandidateStrategy` &&
|
||||
ex.$type !== `${prefix}:CandidateParam`,
|
||||
) ?? [];
|
||||
|
||||
// 跳过表达式
|
||||
userTaskForm.value.skipExpression =
|
||||
businessObject.skipExpression === undefined
|
||||
? ''
|
||||
: businessObject.skipExpression;
|
||||
|
||||
// 改用通过extensionElements来存储数据
|
||||
|
||||
// if (businessObject.candidateStrategy != undefined) {
|
||||
// userTaskForm.value.candidateStrategy = parseInt(
|
||||
// businessObject.candidateStrategy,
|
||||
// ) as any;
|
||||
// } else {
|
||||
// userTaskForm.value.candidateStrategy = undefined;
|
||||
// }
|
||||
// if (
|
||||
// businessObject.candidateParam &&
|
||||
// businessObject.candidateParam.length > 0
|
||||
// ) {
|
||||
// if (userTaskForm.value.candidateStrategy === 60) {
|
||||
// // 特殊:流程表达式,只有一个 input 输入框
|
||||
// userTaskForm.value.candidateParam = [businessObject.candidateParam];
|
||||
// } else {
|
||||
// userTaskForm.value.candidateParam = businessObject.candidateParam
|
||||
// .split(',')
|
||||
// .map((item) => item);
|
||||
// }
|
||||
// } else {
|
||||
// userTaskForm.value.candidateParam = [];
|
||||
// }
|
||||
};
|
||||
|
||||
/** 更新 candidateStrategy 字段时,需要清空 candidateParam,并触发 bpmn 图更新 */
|
||||
const changeCandidateStrategy = () => {
|
||||
userTaskForm.value.candidateParam = [];
|
||||
deptLevel.value = 1;
|
||||
// 注释 by 芋艿:这个交互很多用户反馈费解,https://t.zsxq.com/xNmas 所以暂时屏蔽
|
||||
// if (userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_USER) {
|
||||
// // 特殊处理表单内用户字段,当只有发起人选项时应选中发起人
|
||||
// if (!userFieldOnFormOptions.value || userFieldOnFormOptions.value.length <= 1) {
|
||||
// userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER
|
||||
// }
|
||||
// }
|
||||
updateElementTask();
|
||||
};
|
||||
|
||||
/** 选中某个 options 时候,更新 bpmn 图 */
|
||||
const updateElementTask = () => {
|
||||
let candidateParam = Array.isArray(userTaskForm.value.candidateParam)
|
||||
? userTaskForm.value.candidateParam.join(',')
|
||||
: userTaskForm.value.candidateParam;
|
||||
|
||||
// 特殊处理多级部门情况
|
||||
if (
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||
userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER
|
||||
) {
|
||||
candidateParam += `|${deptLevel.value}`;
|
||||
}
|
||||
// 特殊处理发起人部门负责人、发起人连续部门负责人
|
||||
if (
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
|
||||
) {
|
||||
candidateParam = `${deptLevel.value}`;
|
||||
}
|
||||
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [
|
||||
...otherExtensions.value,
|
||||
bpmnInstances().moddle.create(`${prefix}:CandidateStrategy`, {
|
||||
value: userTaskForm.value.candidateStrategy,
|
||||
}),
|
||||
bpmnInstances().moddle.create(`${prefix}:CandidateParam`, {
|
||||
value: candidateParam,
|
||||
}),
|
||||
],
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions,
|
||||
});
|
||||
|
||||
// 改用通过extensionElements来存储数据
|
||||
// return;
|
||||
// bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
// candidateStrategy: userTaskForm.value.candidateStrategy,
|
||||
// candidateParam: userTaskForm.value.candidateParam.join(','),
|
||||
// });
|
||||
};
|
||||
|
||||
const updateSkipExpression = () => {
|
||||
if (
|
||||
userTaskForm.value.skipExpression &&
|
||||
userTaskForm.value.skipExpression !== ''
|
||||
) {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
skipExpression: userTaskForm.value.skipExpression,
|
||||
});
|
||||
} else {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
skipExpression: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 打开监听器弹窗
|
||||
const processExpressionDialogRef = ref<any>();
|
||||
const openProcessExpressionDialog = async () => {
|
||||
processExpressionDialogRef.value.open();
|
||||
};
|
||||
const selectProcessExpression = (
|
||||
expression: BpmProcessExpressionApi.ProcessExpression,
|
||||
) => {
|
||||
// @ts-ignore
|
||||
userTaskForm.value.candidateParam = [expression.expression];
|
||||
updateElementTask();
|
||||
};
|
||||
|
||||
const handleFormUserChange = (e: any) => {
|
||||
if (e === 'PROCESS_START_USER_ID') {
|
||||
userTaskForm.value.candidateParam = [];
|
||||
// @ts-ignore
|
||||
userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER;
|
||||
}
|
||||
updateElementTask();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
() => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
nextTick(() => {
|
||||
resetTaskForm();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
// 获得角色列表
|
||||
roleOptions.value = await getSimpleRoleList();
|
||||
// 获得部门列表
|
||||
const deptOptions = await getSimpleDeptList();
|
||||
deptTreeOptions.value = handleTree(deptOptions, 'id');
|
||||
// 获得岗位列表
|
||||
postOptions.value = await getSimplePostList();
|
||||
// 获得用户列表
|
||||
userOptions.value = await getSimpleUserList();
|
||||
// 获得用户组列表
|
||||
userGroupOptions.value = await getUserGroupSimpleList();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
bpmnElement.value = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||
<FormItem label="规则类型" name="candidateStrategy">
|
||||
<Select
|
||||
v-model:value="userTaskForm.candidateStrategy"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
@change="changeCandidateStrategy"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(dict, index) in CANDIDATE_STRATEGY"
|
||||
:key="index"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.ROLE"
|
||||
label="指定角色"
|
||||
name="candidateParam"
|
||||
>
|
||||
<Select
|
||||
v-model:value="userTaskForm.candidateParam"
|
||||
allow-clear
|
||||
mode="multiple"
|
||||
style="width: 100%"
|
||||
@change="updateElementTask"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in roleOptions"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="
|
||||
userTaskForm.candidateStrategy === CandidateStrategy.DEPT_MEMBER ||
|
||||
userTaskForm.candidateStrategy === CandidateStrategy.DEPT_LEADER ||
|
||||
userTaskForm.candidateStrategy ===
|
||||
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
|
||||
"
|
||||
label="指定部门"
|
||||
name="candidateParam"
|
||||
>
|
||||
<TreeSelect
|
||||
ref="treeRef"
|
||||
v-model:value="userTaskForm.candidateParam"
|
||||
:tree-data="deptTreeOptions"
|
||||
:field-names="defaultProps"
|
||||
placeholder="加载中,请稍后"
|
||||
multiple
|
||||
tree-checkable
|
||||
@change="updateElementTask"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.POST"
|
||||
label="指定岗位"
|
||||
name="candidateParam"
|
||||
>
|
||||
<Select
|
||||
v-model:value="userTaskForm.candidateParam"
|
||||
allow-clear
|
||||
mode="multiple"
|
||||
style="width: 100%"
|
||||
@change="updateElementTask"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in postOptions"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.USER"
|
||||
label="指定用户"
|
||||
name="candidateParam"
|
||||
>
|
||||
<Select
|
||||
v-model:value="userTaskForm.candidateParam"
|
||||
allow-clear
|
||||
mode="multiple"
|
||||
style="width: 100%"
|
||||
@change="updateElementTask"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.nickname }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.USER_GROUP"
|
||||
label="指定用户组"
|
||||
name="candidateParam"
|
||||
>
|
||||
<Select
|
||||
v-model:value="userTaskForm.candidateParam"
|
||||
allow-clear
|
||||
mode="multiple"
|
||||
style="width: 100%"
|
||||
@change="updateElementTask"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in userGroupOptions"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.FORM_USER"
|
||||
label="表单内用户字段"
|
||||
name="formUser"
|
||||
>
|
||||
<Select
|
||||
v-model:value="userTaskForm.candidateParam"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
@change="handleFormUserChange"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(item, idx) in userFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:value="item.field"
|
||||
:disabled="!item.required"
|
||||
>
|
||||
{{ item.title }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="
|
||||
userTaskForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER
|
||||
"
|
||||
label="表单内部门字段"
|
||||
name="formDept"
|
||||
>
|
||||
<Select
|
||||
v-model:value="userTaskForm.candidateParam"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
@change="updateElementTask"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(item, idx) in deptFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:value="item.field"
|
||||
:disabled="!item.required"
|
||||
>
|
||||
{{ item.title }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="
|
||||
userTaskForm.candidateStrategy ===
|
||||
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||
userTaskForm.candidateStrategy ===
|
||||
CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
userTaskForm.candidateStrategy ===
|
||||
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
|
||||
userTaskForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER
|
||||
"
|
||||
:label="deptLevelLabel!"
|
||||
name="deptLevel"
|
||||
>
|
||||
<Select v-model:value="deptLevel" allow-clear @change="updateElementTask">
|
||||
<SelectOption
|
||||
v-for="(item, index) in MULTI_LEVEL_DEPT"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.EXPRESSION"
|
||||
label="流程表达式"
|
||||
name="candidateParam"
|
||||
>
|
||||
<Textarea
|
||||
v-model:value="userTaskForm.candidateParam[0]"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
@change="updateElementTask"
|
||||
/>
|
||||
<Button
|
||||
class="!w-1/1 mt-5px"
|
||||
type="primary"
|
||||
:icon="h(SelectOutlined)"
|
||||
@click="openProcessExpressionDialog"
|
||||
>
|
||||
选择表达式
|
||||
</Button>
|
||||
<!-- 选择弹窗 -->
|
||||
<ProcessExpressionDialog
|
||||
ref="processExpressionDialogRef"
|
||||
@select="selectProcessExpression"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="跳过表达式" name="skipExpression">
|
||||
<Textarea
|
||||
v-model:value="userTaskForm.skipExpression"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
@change="updateSkipExpression"
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</template>
|
||||
@@ -0,0 +1,380 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
Input,
|
||||
InputNumber,
|
||||
Radio,
|
||||
Tabs,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
const tab = ref('cron');
|
||||
const cronStr = ref(props.value || '* * * * * ?');
|
||||
const fields = ref({
|
||||
second: '*',
|
||||
minute: '*',
|
||||
hour: '*',
|
||||
day: '*',
|
||||
month: '*',
|
||||
week: '?',
|
||||
year: '',
|
||||
});
|
||||
const cronFieldList = [
|
||||
{ key: 'second', label: '秒', min: 0, max: 59 },
|
||||
{ key: 'minute', label: '分', min: 0, max: 59 },
|
||||
{ key: 'hour', label: '时', min: 0, max: 23 },
|
||||
{ key: 'day', label: '天', min: 1, max: 31 },
|
||||
{ key: 'month', label: '月', min: 1, max: 12 },
|
||||
{ key: 'week', label: '周', min: 1, max: 7 },
|
||||
{ key: 'year', label: '年', min: 1970, max: 2099 },
|
||||
];
|
||||
const activeField = ref('second');
|
||||
const cronMode = ref({
|
||||
second: 'appoint',
|
||||
minute: 'every',
|
||||
hour: 'every',
|
||||
day: 'every',
|
||||
month: 'every',
|
||||
week: 'every',
|
||||
year: 'every',
|
||||
});
|
||||
const cronAppoint = ref({
|
||||
second: ['00', '01'],
|
||||
minute: [],
|
||||
hour: [],
|
||||
day: [],
|
||||
month: [],
|
||||
week: [],
|
||||
year: [],
|
||||
});
|
||||
const cronRange = ref({
|
||||
second: [0, 1],
|
||||
minute: [0, 1],
|
||||
hour: [0, 1],
|
||||
day: [1, 2],
|
||||
month: [1, 2],
|
||||
week: [1, 2],
|
||||
year: [1970, 1971],
|
||||
});
|
||||
const cronStep = ref({
|
||||
second: [1, 1],
|
||||
minute: [1, 1],
|
||||
hour: [1, 1],
|
||||
day: [1, 1],
|
||||
month: [1, 1],
|
||||
week: [1, 1],
|
||||
year: [1970, 1],
|
||||
});
|
||||
|
||||
function pad(n) {
|
||||
return n < 10 ? `0${n}` : `${n}`;
|
||||
}
|
||||
|
||||
watch(
|
||||
[fields, cronMode, cronAppoint, cronRange, cronStep],
|
||||
() => {
|
||||
// 组装cron表达式
|
||||
const arr = cronFieldList.map((f) => {
|
||||
if (cronMode.value[f.key] === 'every') return '*';
|
||||
if (cronMode.value[f.key] === 'appoint')
|
||||
return cronAppoint.value[f.key].join(',') || '*';
|
||||
if (cronMode.value[f.key] === 'range')
|
||||
return `${cronRange.value[f.key][0]}-${cronRange.value[f.key][1]}`;
|
||||
if (cronMode.value[f.key] === 'step')
|
||||
return `${cronStep.value[f.key][0]}/${cronStep.value[f.key][1]}`;
|
||||
return fields.value[f.key] || '*';
|
||||
});
|
||||
// week和year特殊处理
|
||||
arr[5] = arr[5] || '?';
|
||||
cronStr.value = arr.join(' ');
|
||||
if (tab.value === 'cron') emit('change', cronStr.value);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// 标准格式
|
||||
const isoStr = ref('');
|
||||
const repeat = ref(1);
|
||||
const isoDate = ref('');
|
||||
const isoDuration = ref('');
|
||||
function setDuration(type, val) {
|
||||
// 组装ISO 8601字符串
|
||||
let d = isoDuration.value;
|
||||
if (d.includes(type)) {
|
||||
d = d.replace(new RegExp(`\\d+${type}`), val + type);
|
||||
} else {
|
||||
d += val + type;
|
||||
}
|
||||
isoDuration.value = d;
|
||||
updateIsoStr();
|
||||
}
|
||||
function updateIsoStr() {
|
||||
let str = `R${repeat.value}`;
|
||||
if (isoDate.value)
|
||||
str += `/${
|
||||
typeof isoDate.value === 'string'
|
||||
? isoDate.value
|
||||
: new Date(isoDate.value).toISOString()
|
||||
}`;
|
||||
if (isoDuration.value) str += `/${isoDuration.value}`;
|
||||
isoStr.value = str;
|
||||
if (tab.value === 'iso') emit('change', isoStr.value);
|
||||
}
|
||||
watch([repeat, isoDate, isoDuration], updateIsoStr);
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
if (tab.value === 'cron') cronStr.value = val;
|
||||
if (tab.value === 'iso') isoStr.value = val;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<Tabs v-model:active-key="tab">
|
||||
<Tabs.TabPane key="cron" tab="CRON表达式">
|
||||
<div style="margin-bottom: 10px">
|
||||
<Input
|
||||
v-model:value="cronStr"
|
||||
readonly
|
||||
style="width: 400px; font-weight: bold"
|
||||
key="cronStr"
|
||||
/>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px; margin-bottom: 8px">
|
||||
<Input
|
||||
v-model:value="fields.second"
|
||||
placeholder="秒"
|
||||
style="width: 80px"
|
||||
key="second"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="fields.minute"
|
||||
placeholder="分"
|
||||
style="width: 80px"
|
||||
key="minute"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="fields.hour"
|
||||
placeholder="时"
|
||||
style="width: 80px"
|
||||
key="hour"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="fields.day"
|
||||
placeholder="天"
|
||||
style="width: 80px"
|
||||
key="day"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="fields.month"
|
||||
placeholder="月"
|
||||
style="width: 80px"
|
||||
key="month"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="fields.week"
|
||||
placeholder="周"
|
||||
style="width: 80px"
|
||||
key="week"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="fields.year"
|
||||
placeholder="年"
|
||||
style="width: 80px"
|
||||
key="year"
|
||||
/>
|
||||
</div>
|
||||
<Tabs
|
||||
v-model:active-key="activeField"
|
||||
type="card"
|
||||
style="margin-bottom: 8px"
|
||||
>
|
||||
<Tabs.TabPane v-for="f in cronFieldList" :key="f.key" :tab="f.label">
|
||||
<div style="margin-bottom: 8px">
|
||||
<Radio.Group
|
||||
v-model:value="cronMode[f.key]"
|
||||
:key="`radio-${f.key}`"
|
||||
>
|
||||
<Radio value="every" :key="`every-${f.key}`">
|
||||
每{{ f.label }}
|
||||
</Radio>
|
||||
<Radio value="range" :key="`range-${f.key}`">
|
||||
从
|
||||
<InputNumber
|
||||
v-model:value="cronRange[f.key][0]"
|
||||
:min="f.min"
|
||||
:max="f.max"
|
||||
size="small"
|
||||
style="width: 60px"
|
||||
:key="`range0-${f.key}`"
|
||||
/>
|
||||
到
|
||||
<InputNumber
|
||||
v-model:value="cronRange[f.key][1]"
|
||||
:min="f.min"
|
||||
:max="f.max"
|
||||
size="small"
|
||||
style="width: 60px"
|
||||
:key="`range1-${f.key}`"
|
||||
/>
|
||||
之间每{{ f.label }}
|
||||
</Radio>
|
||||
<Radio value="step" :key="`step-${f.key}`">
|
||||
从第
|
||||
<InputNumber
|
||||
v-model:value="cronStep[f.key][0]"
|
||||
:min="f.min"
|
||||
:max="f.max"
|
||||
size="small"
|
||||
style="width: 60px"
|
||||
:key="`step0-${f.key}`"
|
||||
/>
|
||||
开始每
|
||||
<InputNumber
|
||||
v-model:value="cronStep[f.key][1]"
|
||||
:min="1"
|
||||
:max="f.max"
|
||||
size="small"
|
||||
style="width: 60px"
|
||||
:key="`step1-${f.key}`"
|
||||
/>
|
||||
{{ f.label }}
|
||||
</Radio>
|
||||
<Radio value="appoint" :key="`appoint-${f.key}`"> 指定 </Radio>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
<div v-if="cronMode[f.key] === 'appoint'">
|
||||
<Checkbox.Group
|
||||
v-model:value="cronAppoint[f.key]"
|
||||
:key="`group-${f.key}`"
|
||||
>
|
||||
<Checkbox
|
||||
v-for="n in f.max + 1"
|
||||
:key="`cb-${f.key}-${n - 1}`"
|
||||
:value="pad(n - 1)"
|
||||
>
|
||||
{{ pad(n - 1) }}
|
||||
</Checkbox>
|
||||
</Checkbox.Group>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="iso" title="标准格式" tab="iso-tab">
|
||||
<div style="margin-bottom: 10px">
|
||||
<Input
|
||||
v-model:value="isoStr"
|
||||
placeholder="如R1/2025-05-21T21:59:54/P3DT30M30S"
|
||||
style="width: 400px; font-weight: bold"
|
||||
key="isoStr"
|
||||
/>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px">
|
||||
循环次数:<InputNumber
|
||||
v-model:value="repeat"
|
||||
:min="1"
|
||||
style="width: 100px"
|
||||
key="repeat"
|
||||
/>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px">
|
||||
日期时间:<DatePicker
|
||||
v-model:value="isoDate"
|
||||
show-time
|
||||
placeholder="选择日期时间"
|
||||
style="width: 200px"
|
||||
key="isoDate"
|
||||
/>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px">
|
||||
当前时长:<Input
|
||||
v-model:value="isoDuration"
|
||||
placeholder="如P3DT30M30S"
|
||||
style="width: 200px"
|
||||
key="isoDuration"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
秒:
|
||||
<Button
|
||||
v-for="s in [5, 10, 30, 50]"
|
||||
@click="setDuration('S', s)"
|
||||
:key="`sec-${s}`"
|
||||
>
|
||||
{{ s }}
|
||||
</Button>
|
||||
自定义
|
||||
</div>
|
||||
<div>
|
||||
分:
|
||||
<Button
|
||||
v-for="m in [5, 10, 30, 50]"
|
||||
@click="setDuration('M', m)"
|
||||
:key="`min-${m}`"
|
||||
>
|
||||
{{ m }}
|
||||
</Button>
|
||||
自定义
|
||||
</div>
|
||||
<div>
|
||||
小时:
|
||||
<Button
|
||||
v-for="h in [4, 8, 12, 24]"
|
||||
@click="setDuration('H', h)"
|
||||
:key="`hour-${h}`"
|
||||
>
|
||||
{{ h }}
|
||||
</Button>
|
||||
自定义
|
||||
</div>
|
||||
<div>
|
||||
天:
|
||||
<Button
|
||||
v-for="d in [1, 2, 3, 4]"
|
||||
@click="setDuration('D', d)"
|
||||
:key="`day-${d}`"
|
||||
>
|
||||
{{ d }}
|
||||
</Button>
|
||||
自定义
|
||||
</div>
|
||||
<div>
|
||||
月:
|
||||
<Button
|
||||
v-for="mo in [1, 2, 3, 4]"
|
||||
@click="setDuration('M', mo)"
|
||||
:key="`mon-${mo}`"
|
||||
>
|
||||
{{ mo }}
|
||||
</Button>
|
||||
自定义
|
||||
</div>
|
||||
<div>
|
||||
年:
|
||||
<Button
|
||||
v-for="y in [1, 2, 3, 4]"
|
||||
@click="setDuration('Y', y)"
|
||||
:key="`year-${y}`"
|
||||
>
|
||||
{{ y }}
|
||||
</Button>
|
||||
自定义
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</template>
|
||||
@@ -0,0 +1,99 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { Button, Input } from 'ant-design-vue';
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
const units = [
|
||||
{ key: 'Y', label: '年', presets: [1, 2, 3, 4] },
|
||||
{ key: 'M', label: '月', presets: [1, 2, 3, 4] },
|
||||
{ key: 'D', label: '天', presets: [1, 2, 3, 4] },
|
||||
{ key: 'H', label: '时', presets: [4, 8, 12, 24] },
|
||||
{ key: 'm', label: '分', presets: [5, 10, 30, 50] },
|
||||
{ key: 'S', label: '秒', presets: [5, 10, 30, 50] },
|
||||
];
|
||||
const custom = ref({ Y: '', M: '', D: '', H: '', m: '', S: '' });
|
||||
const isoString = ref('');
|
||||
|
||||
function setUnit(key, val) {
|
||||
if (!val || Number.isNaN(val)) {
|
||||
custom.value[key] = '';
|
||||
return;
|
||||
}
|
||||
custom.value[key] = val;
|
||||
updateIsoString();
|
||||
}
|
||||
|
||||
function updateIsoString() {
|
||||
let str = 'P';
|
||||
if (custom.value.Y) str += `${custom.value.Y}Y`;
|
||||
if (custom.value.M) str += `${custom.value.M}M`;
|
||||
if (custom.value.D) str += `${custom.value.D}D`;
|
||||
if (custom.value.H || custom.value.m || custom.value.S) str += 'T';
|
||||
if (custom.value.H) str += `${custom.value.H}H`;
|
||||
if (custom.value.m) str += `${custom.value.m}M`;
|
||||
if (custom.value.S) str += `${custom.value.S}S`;
|
||||
isoString.value = str === 'P' ? '' : str;
|
||||
emit('change', isoString.value);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
// 解析ISO 8601字符串到custom
|
||||
const match = val.match(
|
||||
/^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/,
|
||||
);
|
||||
if (match) {
|
||||
custom.value.Y = match[1] || '';
|
||||
custom.value.M = match[2] || '';
|
||||
custom.value.D = match[3] || '';
|
||||
custom.value.H = match[4] || '';
|
||||
custom.value.m = match[5] || '';
|
||||
custom.value.S = match[6] || '';
|
||||
updateIsoString();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 10px">
|
||||
当前选择:<Input
|
||||
v-model:value="isoString"
|
||||
readonly
|
||||
style="width: 300px"
|
||||
/>
|
||||
</div>
|
||||
<div v-for="unit in units" :key="unit.key" style="margin-bottom: 8px">
|
||||
<span>{{ unit.label }}:</span>
|
||||
<Button.Group>
|
||||
<Button
|
||||
v-for="val in unit.presets"
|
||||
:key="val"
|
||||
size="small"
|
||||
@click="setUnit(unit.key, val)"
|
||||
>
|
||||
{{ val }}
|
||||
</Button>
|
||||
<Input
|
||||
v-model:value="custom[unit.key]"
|
||||
size="small"
|
||||
style="width: 60px; margin-left: 8px"
|
||||
placeholder="自定义"
|
||||
@change="setUnit(unit.key, custom[unit.key])"
|
||||
/>
|
||||
</Button.Group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,357 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { computed, nextTick, onMounted, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
ExclamationCircleFilled,
|
||||
IconifyIcon,
|
||||
QuestionCircleFilled,
|
||||
} from '@vben/icons';
|
||||
|
||||
import { Button, DatePicker, Input, Modal, Tooltip } from 'ant-design-vue';
|
||||
|
||||
import CycleConfig from './CycleConfig.vue';
|
||||
import DurationConfig from './DurationConfig.vue';
|
||||
|
||||
const props = defineProps({
|
||||
businessObject: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const bpmnInstances = () => (window as any).bpmnInstances;
|
||||
const type: Ref<string> = ref('time');
|
||||
const condition: Ref<string> = ref('');
|
||||
const valid: Ref<boolean> = ref(true);
|
||||
const showDatePicker: Ref<boolean> = ref(false);
|
||||
const showDurationDialog: Ref<boolean> = ref(false);
|
||||
const showCycleDialog: Ref<boolean> = ref(false);
|
||||
const showHelp: Ref<boolean> = ref(false);
|
||||
const dateValue: Ref<Date | null> = ref(null);
|
||||
// const bpmnElement = ref(null);
|
||||
|
||||
const placeholder = computed<string>(() => {
|
||||
if (type.value === 'time') return '请输入时间';
|
||||
if (type.value === 'duration') return '请输入持续时长';
|
||||
if (type.value === 'cycle') return '请输入循环表达式';
|
||||
return '';
|
||||
});
|
||||
const helpText = computed<string>(() => {
|
||||
if (type.value === 'time') return '选择具体时间';
|
||||
if (type.value === 'duration') return 'ISO 8601格式,如PT1H';
|
||||
if (type.value === 'cycle') return 'CRON表达式或ISO 8601周期';
|
||||
return '';
|
||||
});
|
||||
const helpHtml = computed<string>(() => {
|
||||
if (type.value === 'duration') {
|
||||
return `指定定时器之前要等待多长时间。S表示秒,M表示分,D表示天;P表示时间段,T表示精确到时间的时间段。<br>
|
||||
时间格式依然为ISO 8601格式,一年两个月三天四小时五分六秒内,可以写成P1Y2M3DT4H5M6S。<br>
|
||||
P是开始标记,T是时间和日期分割标记,没有日期只有时间T是不能省去的,比如1小时执行一次应写成PT1H。`;
|
||||
}
|
||||
if (type.value === 'cycle') {
|
||||
return `支持CRON表达式(如0 0/30 * * * ?)或ISO 8601周期(如R3/PT10M)。`;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
// 初始化和监听
|
||||
function syncFromBusinessObject(): void {
|
||||
if (props.businessObject) {
|
||||
const timerDef = (props.businessObject.eventDefinitions || [])[0];
|
||||
if (timerDef) {
|
||||
if (timerDef.timeDate) {
|
||||
type.value = 'time';
|
||||
condition.value = timerDef.timeDate.body;
|
||||
} else if (timerDef.timeDuration) {
|
||||
type.value = 'duration';
|
||||
condition.value = timerDef.timeDuration.body;
|
||||
} else if (timerDef.timeCycle) {
|
||||
type.value = 'cycle';
|
||||
condition.value = timerDef.timeCycle.body;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onMounted(syncFromBusinessObject);
|
||||
|
||||
// 切换类型
|
||||
function setType(t: string) {
|
||||
type.value = t;
|
||||
condition.value = '';
|
||||
updateNode();
|
||||
}
|
||||
|
||||
// 输入校验
|
||||
watch([type, condition], () => {
|
||||
valid.value = validate();
|
||||
// updateNode() // 可以注释掉,避免频繁触发
|
||||
});
|
||||
|
||||
function validate(): boolean {
|
||||
if (type.value === 'time') {
|
||||
return !!condition.value && !Number.isNaN(Date.parse(condition.value));
|
||||
}
|
||||
if (type.value === 'duration') {
|
||||
return /^P.*$/.test(condition.value);
|
||||
}
|
||||
if (type.value === 'cycle') {
|
||||
return /^(?:[0-9*/?, ]+|R\d*\/P.*)$/.test(condition.value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 选择时间
|
||||
function onDateChange(val: any) {
|
||||
dateValue.value = val;
|
||||
}
|
||||
function onDateConfirm(): void {
|
||||
if (dateValue.value) {
|
||||
condition.value = new Date(dateValue.value).toISOString();
|
||||
showDatePicker.value = false;
|
||||
updateNode();
|
||||
}
|
||||
}
|
||||
|
||||
// 持续时长
|
||||
function onDurationChange(val: string) {
|
||||
condition.value = val;
|
||||
}
|
||||
function onDurationConfirm(): void {
|
||||
showDurationDialog.value = false;
|
||||
updateNode();
|
||||
}
|
||||
|
||||
// 循环
|
||||
function onCycleChange(val: string) {
|
||||
condition.value = val;
|
||||
}
|
||||
function onCycleConfirm(): void {
|
||||
showCycleDialog.value = false;
|
||||
updateNode();
|
||||
}
|
||||
|
||||
// 输入框聚焦时弹窗(可选)
|
||||
function handleInputFocus(): void {
|
||||
if (type.value === 'time') showDatePicker.value = true;
|
||||
if (type.value === 'duration') showDurationDialog.value = true;
|
||||
if (type.value === 'cycle') showCycleDialog.value = true;
|
||||
}
|
||||
|
||||
// 同步到节点
|
||||
function updateNode(): void {
|
||||
const moddle = (window.bpmnInstances as any)?.moddle;
|
||||
const modeling = (window.bpmnInstances as any)?.modeling;
|
||||
const elementRegistry = (window.bpmnInstances as any)?.elementRegistry;
|
||||
if (!moddle || !modeling || !elementRegistry) return;
|
||||
|
||||
// 获取元素
|
||||
if (!props.businessObject || !props.businessObject.id) return;
|
||||
const element = elementRegistry.get(props.businessObject.id);
|
||||
if (!element) return;
|
||||
|
||||
// 1. 复用原有 timerDef,或新建
|
||||
let timerDef =
|
||||
element.businessObject.eventDefinitions &&
|
||||
element.businessObject.eventDefinitions[0];
|
||||
if (!timerDef) {
|
||||
timerDef = bpmnInstances().bpmnFactory.create(
|
||||
'bpmn:TimerEventDefinition',
|
||||
{},
|
||||
);
|
||||
modeling.updateProperties(element, {
|
||||
eventDefinitions: [timerDef],
|
||||
});
|
||||
}
|
||||
|
||||
// 2. 清空原有
|
||||
delete timerDef.timeDate;
|
||||
delete timerDef.timeDuration;
|
||||
delete timerDef.timeCycle;
|
||||
|
||||
// 3. 设置新的
|
||||
if (type.value === 'time' && condition.value) {
|
||||
timerDef.timeDate = bpmnInstances().bpmnFactory.create(
|
||||
'bpmn:FormalExpression',
|
||||
{
|
||||
body: condition.value,
|
||||
},
|
||||
);
|
||||
} else if (type.value === 'duration' && condition.value) {
|
||||
timerDef.timeDuration = bpmnInstances().bpmnFactory.create(
|
||||
'bpmn:FormalExpression',
|
||||
{
|
||||
body: condition.value,
|
||||
},
|
||||
);
|
||||
} else if (type.value === 'cycle' && condition.value) {
|
||||
timerDef.timeCycle = bpmnInstances().bpmnFactory.create(
|
||||
'bpmn:FormalExpression',
|
||||
{
|
||||
body: condition.value,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
bpmnInstances().modeling.updateProperties(toRaw(element), {
|
||||
eventDefinitions: [timerDef],
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.businessObject,
|
||||
(val) => {
|
||||
if (val) {
|
||||
nextTick(() => {
|
||||
syncFromBusinessObject();
|
||||
});
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<div style="margin-top: 10px">
|
||||
<span>类型:</span>
|
||||
<Button.Group>
|
||||
<Button
|
||||
size="small"
|
||||
:type="type === 'time' ? 'primary' : 'default'"
|
||||
@click="setType('time')"
|
||||
>
|
||||
时间
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
:type="type === 'duration' ? 'primary' : 'default'"
|
||||
@click="setType('duration')"
|
||||
>
|
||||
持续
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
:type="type === 'cycle' ? 'primary' : 'default'"
|
||||
@click="setType('cycle')"
|
||||
>
|
||||
循环
|
||||
</Button>
|
||||
</Button.Group>
|
||||
<CheckCircleFilled v-if="valid" style="color: green; margin-left: 8px" />
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; margin-top: 10px">
|
||||
<span>条件:</span>
|
||||
<Input
|
||||
v-model:value="condition"
|
||||
:placeholder="placeholder"
|
||||
style="width: calc(100% - 100px)"
|
||||
:readonly="type !== 'duration' && type !== 'cycle'"
|
||||
@focus="handleInputFocus"
|
||||
@blur="updateNode"
|
||||
>
|
||||
<template #suffix>
|
||||
<Tooltip v-if="!valid" title="格式错误" placement="top">
|
||||
<ExclamationCircleFilled style="color: orange" />
|
||||
</Tooltip>
|
||||
<Tooltip :title="helpText" placement="top">
|
||||
<QuestionCircleFilled
|
||||
style="color: #409eff; cursor: pointer"
|
||||
@click="showHelp = true"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button
|
||||
v-if="type === 'time'"
|
||||
@click="showDatePicker = true"
|
||||
style="margin-left: 4px"
|
||||
shape="circle"
|
||||
size="small"
|
||||
>
|
||||
<IconifyIcon icon="ep:calendar" />
|
||||
</Button>
|
||||
<Button
|
||||
v-if="type === 'duration'"
|
||||
@click="showDurationDialog = true"
|
||||
style="margin-left: 4px"
|
||||
shape="circle"
|
||||
size="small"
|
||||
>
|
||||
<IconifyIcon icon="ep:timer" />
|
||||
</Button>
|
||||
<Button
|
||||
v-if="type === 'cycle'"
|
||||
@click="showCycleDialog = true"
|
||||
style="margin-left: 4px"
|
||||
shape="circle"
|
||||
size="small"
|
||||
>
|
||||
<IconifyIcon icon="ep:setting" />
|
||||
</Button>
|
||||
</template>
|
||||
</Input>
|
||||
</div>
|
||||
<!-- 时间选择器 -->
|
||||
<Modal
|
||||
v-model:open="showDatePicker"
|
||||
title="选择时间"
|
||||
width="400px"
|
||||
@cancel="showDatePicker = false"
|
||||
>
|
||||
<DatePicker
|
||||
v-model:value="dateValue"
|
||||
show-time
|
||||
placeholder="选择日期时间"
|
||||
style="width: 100%"
|
||||
@change="onDateChange"
|
||||
/>
|
||||
<template #footer>
|
||||
<Button @click="showDatePicker = false">取消</Button>
|
||||
<Button type="primary" @click="onDateConfirm">确定</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
<!-- 持续时长选择器 -->
|
||||
<Modal
|
||||
v-model:open="showDurationDialog"
|
||||
title="时间配置"
|
||||
width="600px"
|
||||
@cancel="showDurationDialog = false"
|
||||
>
|
||||
<DurationConfig :value="condition" @change="onDurationChange" />
|
||||
<template #footer>
|
||||
<Button @click="showDurationDialog = false">取消</Button>
|
||||
<Button type="primary" @click="onDurationConfirm">确定</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
<!-- 循环配置器 -->
|
||||
<Modal
|
||||
v-model:open="showCycleDialog"
|
||||
title="时间配置"
|
||||
width="800px"
|
||||
@cancel="showCycleDialog = false"
|
||||
>
|
||||
<CycleConfig :value="condition" @change="onCycleChange" />
|
||||
<template #footer>
|
||||
<Button @click="showCycleDialog = false">取消</Button>
|
||||
<Button type="primary" @click="onCycleConfirm">确定</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
<!-- 帮助说明 -->
|
||||
<Modal
|
||||
v-model:open="showHelp"
|
||||
title="格式说明"
|
||||
width="600px"
|
||||
@cancel="showHelp = false"
|
||||
>
|
||||
<div v-html="helpHtml"></div>
|
||||
<template #footer>
|
||||
<Button @click="showHelp = false">关闭</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 相关样式 */
|
||||
</style>
|
||||
@@ -0,0 +1,120 @@
|
||||
@use './process-designer';
|
||||
@use './process-panel';
|
||||
|
||||
$success-color: #4eb819;
|
||||
$primary-color: #409eff;
|
||||
$danger-color: #f56c6c;
|
||||
$cancel-color: #909399;
|
||||
|
||||
.process-viewer {
|
||||
position: relative;
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+')
|
||||
repeat !important;
|
||||
border: 1px solid #efefef;
|
||||
|
||||
.success-arrow {
|
||||
fill: $success-color;
|
||||
stroke: $success-color;
|
||||
}
|
||||
|
||||
.success-conditional {
|
||||
fill: white;
|
||||
stroke: $success-color;
|
||||
}
|
||||
|
||||
.success.djs-connection {
|
||||
.djs-visual path {
|
||||
stroke: $success-color !important;
|
||||
//marker-end: url(#sequenceflow-end-white-success)!important;
|
||||
}
|
||||
}
|
||||
|
||||
.success.djs-connection.condition-expression {
|
||||
.djs-visual path {
|
||||
//marker-start: url(#conditional-flow-marker-white-success)!important;
|
||||
}
|
||||
}
|
||||
|
||||
.success.djs-shape {
|
||||
.djs-visual rect {
|
||||
fill: $success-color !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
stroke: $success-color !important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $success-color !important;
|
||||
}
|
||||
|
||||
.djs-visual path:nth-child(2) {
|
||||
fill: $success-color !important;
|
||||
stroke: $success-color !important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
fill: $success-color !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
stroke: $success-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
.primary.djs-shape {
|
||||
.djs-visual rect {
|
||||
fill: $primary-color !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
stroke: $primary-color !important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $primary-color !important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
fill: $primary-color !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
stroke: $primary-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
.danger.djs-shape {
|
||||
.djs-visual rect {
|
||||
fill: $danger-color !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
stroke: $danger-color !important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $danger-color !important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
fill: $danger-color !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
stroke: $danger-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel.djs-shape {
|
||||
.djs-visual rect {
|
||||
fill: $cancel-color !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
stroke: $cancel-color !important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $cancel-color !important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
fill: $cancel-color !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
stroke: $cancel-color !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.process-viewer .djs-tooltip-container,
|
||||
.process-viewer .djs-overlay-container,
|
||||
.process-viewer .djs-palette {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
@use 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css';
|
||||
|
||||
// 边框被 token-simulation 样式覆盖了
|
||||
.djs-palette {
|
||||
background: var(--palette-background-color);
|
||||
border: solid 1px var(--palette-border-color) !important;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.my-process-designer {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.my-process-designer__header {
|
||||
width: 100%;
|
||||
min-height: 36px;
|
||||
|
||||
.el-button {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.el-button-group {
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.el-tooltip__popper {
|
||||
.el-button {
|
||||
width: 100%;
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.el-button:hover {
|
||||
color: #fff;
|
||||
background: rgb(64 158 255 / 80%);
|
||||
}
|
||||
}
|
||||
|
||||
.align {
|
||||
position: relative;
|
||||
|
||||
i {
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: '|';
|
||||
// transform: rotate(90deg) translate(200%, 60%);
|
||||
transform: rotate(180deg) translate(271%, -10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.align.align-left i {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.align.align-right i {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.align.align-top i {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.align.align-bottom i {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.align.align-center i {
|
||||
transform: rotate(0deg);
|
||||
|
||||
&::after {
|
||||
// transform: rotate(90deg) translate(0, 60%);
|
||||
transform: rotate(0deg) translate(-0%, -5%);
|
||||
}
|
||||
}
|
||||
|
||||
.align.align-middle i {
|
||||
transform: rotate(-90deg);
|
||||
|
||||
&::after {
|
||||
// transform: rotate(90deg) translate(0, 60%);
|
||||
transform: rotate(0deg) translate(0, -10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.my-process-designer__container {
|
||||
display: inline-flex;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
|
||||
.my-process-designer__canvas {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+')
|
||||
repeat !important;
|
||||
|
||||
div.toggle-mode {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.my-process-designer__property-panel {
|
||||
z-index: 10;
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
overflow-y: auto;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
// svg {
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// min-height: 100%;
|
||||
// overflow: hidden;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
//侧边栏配置
|
||||
// .djs-palette .two-column .open {
|
||||
.open {
|
||||
// .djs-palette.open {
|
||||
.djs-palette-entries {
|
||||
div[class^='bpmn-icon-']::before,
|
||||
div[class*='bpmn-icon-']::before {
|
||||
line-height: unset;
|
||||
}
|
||||
|
||||
div.entry {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.entry:hover {
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -10px;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: max-content;
|
||||
padding: 0 16px;
|
||||
overflow: hidden;
|
||||
font-size: 0.5em;
|
||||
font-variant: normal;
|
||||
vertical-align: text-bottom;
|
||||
text-transform: none;
|
||||
text-decoration: inherit;
|
||||
content: attr(title);
|
||||
background: #fafafa;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 6px #eee;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
height: 100%;
|
||||
max-height: calc(80vh - 32px);
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.hljs * {
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
.process-panel__container {
|
||||
box-sizing: border-box;
|
||||
max-height: 100%;
|
||||
padding: 0 8px;
|
||||
overflow-y: scroll;
|
||||
border-left: 1px solid #eee;
|
||||
box-shadow: 0 0 8px #ccc;
|
||||
}
|
||||
|
||||
.panel-tab__title {
|
||||
padding: 0 8px;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
line-height: 1.2em;
|
||||
|
||||
i {
|
||||
margin-right: 8px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-tab__content {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
border-top: 1px solid #eee;
|
||||
|
||||
.panel-tab__content--title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 8px;
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.element-property {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
margin: 8px 0;
|
||||
|
||||
.element-property__label {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 90px;
|
||||
padding-right: 12px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: 32px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.element-property__value {
|
||||
flex: 1;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
width: 100%;
|
||||
padding-bottom: 18px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.list-property {
|
||||
flex-direction: column;
|
||||
|
||||
.element-listener-item {
|
||||
display: inline-grid;
|
||||
grid-template-columns: 16px auto 32px 32px;
|
||||
grid-column-gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.element-listener-item + .element-listener-item {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.listener-filed__title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
|
||||
span {
|
||||
width: 200px;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.element-drawer__button {
|
||||
display: inline-flex;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.element-drawer__button > .el-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-collapse-item__content {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.el-input.is-disabled .el-input__inner {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.el-form-item.el-form-item--mini {
|
||||
margin-bottom: 0;
|
||||
|
||||
& + .el-form-item {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { toRaw } from 'vue';
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
// 创建监听器实例
|
||||
export function createListenerObject(options, isTask, prefix) {
|
||||
const listenerObj = Object.create(null);
|
||||
listenerObj.event = options.event;
|
||||
isTask && (listenerObj.id = options.id); // 任务监听器特有的 id 字段
|
||||
switch (options.listenerType) {
|
||||
case 'delegateExpressionListener': {
|
||||
listenerObj.delegateExpression = options.delegateExpression;
|
||||
break;
|
||||
}
|
||||
case 'expressionListener': {
|
||||
listenerObj.expression = options.expression;
|
||||
break;
|
||||
}
|
||||
case 'scriptListener': {
|
||||
listenerObj.script = createScriptObject(options, prefix);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
listenerObj.class = options.class;
|
||||
}
|
||||
}
|
||||
// 注入字段
|
||||
if (options.fields) {
|
||||
listenerObj.fields = options.fields.map((field) => {
|
||||
return createFieldObject(field, prefix);
|
||||
});
|
||||
}
|
||||
// 任务监听器的 定时器 设置
|
||||
if (isTask && options.event === 'timeout' && !!options.eventDefinitionType) {
|
||||
const timeDefinition = bpmnInstances().moddle.create(
|
||||
'bpmn:FormalExpression',
|
||||
{
|
||||
body: options.eventTimeDefinitions,
|
||||
},
|
||||
);
|
||||
const TimerEventDefinition = bpmnInstances().moddle.create(
|
||||
'bpmn:TimerEventDefinition',
|
||||
{
|
||||
id: `TimerEventDefinition_${uuid(8)}`,
|
||||
[`time${options.eventDefinitionType.replace(/^\S/, (s) => s.toUpperCase())}`]:
|
||||
timeDefinition,
|
||||
},
|
||||
);
|
||||
listenerObj.eventDefinitions = [TimerEventDefinition];
|
||||
}
|
||||
return bpmnInstances().moddle.create(
|
||||
`${prefix}:${isTask ? 'TaskListener' : 'ExecutionListener'}`,
|
||||
listenerObj,
|
||||
);
|
||||
}
|
||||
|
||||
// 创建 监听器的注入字段 实例
|
||||
export function createFieldObject(option, prefix) {
|
||||
const { name, fieldType, string, expression } = option;
|
||||
const fieldConfig =
|
||||
fieldType === 'string' ? { name, string } : { name, expression };
|
||||
return bpmnInstances().moddle.create(`${prefix}:Field`, fieldConfig);
|
||||
}
|
||||
|
||||
// 创建脚本实例
|
||||
export function createScriptObject(options, prefix) {
|
||||
const { scriptType, scriptFormat, value, resource } = options;
|
||||
const scriptConfig =
|
||||
scriptType === 'inlineScript'
|
||||
? { scriptFormat, value }
|
||||
: { scriptFormat, resource };
|
||||
return bpmnInstances().moddle.create(`${prefix}:Script`, scriptConfig);
|
||||
}
|
||||
|
||||
// 更新元素扩展属性
|
||||
export function updateElementExtensions(element, extensionList) {
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: extensionList,
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(element), {
|
||||
extensionElements: extensions,
|
||||
});
|
||||
}
|
||||
|
||||
// 创建一个id
|
||||
export function uuid(length = 8, chars?) {
|
||||
let result = '';
|
||||
const charsString =
|
||||
chars || '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
for (let i = length; i > 0; --i) {
|
||||
result += charsString[Math.floor(Math.random() * charsString.length)];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const hljs = require('highlight.js/lib/core');
|
||||
hljs.registerLanguage('xml', require('highlight.js/lib/languages/xml'));
|
||||
hljs.registerLanguage('json', require('highlight.js/lib/languages/json'));
|
||||
|
||||
module.exports = hljs;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user