Pre Merge pull request !117 from Jason/dev
commit
fbfe1a01be
|
@ -12,10 +12,11 @@ import { useVbenDrawer } from '@vben/common-ui';
|
|||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import {
|
||||
Divider,
|
||||
Col,
|
||||
Input,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Row,
|
||||
TabPane,
|
||||
Tabs,
|
||||
Tooltip,
|
||||
|
@ -91,8 +92,8 @@ const getDeptNames = (deptIds: number[]): string => {
|
|||
|
||||
// 使用 VbenDrawer
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
header: false,
|
||||
closable: false,
|
||||
header: true,
|
||||
closable: true,
|
||||
onCancel() {
|
||||
drawerApi.close();
|
||||
},
|
||||
|
@ -142,28 +143,28 @@ defineExpose({ showStartUserNodeConfig });
|
|||
</script>
|
||||
<template>
|
||||
<Drawer>
|
||||
<div class="config-header">
|
||||
<!-- TODO v-mountedFocus 自动聚集 需要迁移一下 -->
|
||||
<Input
|
||||
v-if="showInput"
|
||||
type="text"
|
||||
class="config-editable-input"
|
||||
@blur="blurEvent()"
|
||||
v-model:value="nodeName"
|
||||
:placeholder="nodeName"
|
||||
/>
|
||||
<div v-else class="node-name">
|
||||
{{ nodeName }}
|
||||
<IconifyIcon
|
||||
class="ml-1"
|
||||
icon="ep:edit-pen"
|
||||
:size="16"
|
||||
@click="clickIcon()"
|
||||
<template #title>
|
||||
<div class="config-header">
|
||||
<!-- TODO v-mountedFocus 自动聚集 需要迁移一下 -->
|
||||
<Input
|
||||
v-if="showInput"
|
||||
type="text"
|
||||
class="config-editable-input"
|
||||
@blur="blurEvent()"
|
||||
v-model:value="nodeName"
|
||||
:placeholder="nodeName"
|
||||
/>
|
||||
<div v-else class="node-name">
|
||||
{{ nodeName }}
|
||||
<IconifyIcon
|
||||
class="ml-1"
|
||||
icon="ep:edit-pen"
|
||||
:size="16"
|
||||
@click="clickIcon()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Divider />
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<Tabs v-model:active-key="activeTabName" type="card">
|
||||
<TabPane tab="权限" key="user">
|
||||
<TypographyText
|
||||
|
@ -212,69 +213,76 @@ defineExpose({ showStartUserNodeConfig });
|
|||
key="fields"
|
||||
v-if="formType === BpmModelFormType.NORMAL"
|
||||
>
|
||||
<div class="field-setting-pane">
|
||||
<div class="field-setting-desc">字段权限</div>
|
||||
<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 fieldsPermissionConfig"
|
||||
: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"
|
||||
:label="FieldPermissionType.READ"
|
||||
>
|
||||
<span></span>
|
||||
</Radio>
|
||||
</div>
|
||||
<div class="item-radio-wrap">
|
||||
<Radio
|
||||
:value="FieldPermissionType.WRITE"
|
||||
size="large"
|
||||
:label="FieldPermissionType.WRITE"
|
||||
>
|
||||
<span></span>
|
||||
</Radio>
|
||||
</div>
|
||||
<div class="item-radio-wrap">
|
||||
<Radio
|
||||
:value="FieldPermissionType.NONE"
|
||||
size="large"
|
||||
:label="FieldPermissionType.NONE"
|
||||
>
|
||||
<span></span>
|
||||
</Radio>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<div class="p-1">
|
||||
<div class="mb-4 text-[16px] font-bold">字段权限</div>
|
||||
|
||||
<!-- 表头 -->
|
||||
<Row class="border border-gray-200 px-4 py-3">
|
||||
<Col :span="8" class="font-bold">字段名称</Col>
|
||||
<Col :span="16">
|
||||
<Row>
|
||||
<Col :span="8" class="flex items-center justify-center">
|
||||
<span
|
||||
class="cursor-pointer font-bold"
|
||||
@click="updatePermission('READ')"
|
||||
>
|
||||
只读
|
||||
</span>
|
||||
</Col>
|
||||
<Col :span="8" class="flex items-center justify-center">
|
||||
<span
|
||||
class="cursor-pointer font-bold"
|
||||
@click="updatePermission('WRITE')"
|
||||
>
|
||||
可编辑
|
||||
</span>
|
||||
</Col>
|
||||
<Col :span="8" class="flex items-center justify-center">
|
||||
<span
|
||||
class="cursor-pointer font-bold"
|
||||
@click="updatePermission('NONE')"
|
||||
>
|
||||
隐藏
|
||||
</span>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<!-- 表格内容 -->
|
||||
<div v-for="(item, index) in fieldsPermissionConfig" :key="index">
|
||||
<Row class="border border-t-0 border-gray-200 px-4 py-2">
|
||||
<Col :span="8" class="flex items-center truncate">
|
||||
{{ item.title }}
|
||||
</Col>
|
||||
<Col :span="16">
|
||||
<RadioGroup v-model:value="item.permission" class="w-full">
|
||||
<Row>
|
||||
<Col :span="8" class="flex items-center justify-center">
|
||||
<Radio
|
||||
:value="FieldPermissionType.READ"
|
||||
size="large"
|
||||
:label="FieldPermissionType.READ"
|
||||
/>
|
||||
</Col>
|
||||
<Col :span="8" class="flex items-center justify-center">
|
||||
<Radio
|
||||
:value="FieldPermissionType.WRITE"
|
||||
size="large"
|
||||
:label="FieldPermissionType.WRITE"
|
||||
/>
|
||||
</Col>
|
||||
<Col :span="8" class="flex items-center justify-center">
|
||||
<Radio
|
||||
:value="FieldPermissionType.NONE"
|
||||
size="large"
|
||||
:label="FieldPermissionType.NONE"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</RadioGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
</TabPane>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,48 @@
|
|||
import { APPROVE_TYPE, ApproveType, TimeUnitType } from '../../consts';
|
||||
|
||||
/** 获取条件节点默认的名称 */
|
||||
export const getDefaultConditionNodeName = (
|
||||
index: number,
|
||||
defaultFlow: boolean | undefined,
|
||||
): string => {
|
||||
if (defaultFlow) {
|
||||
return '其它情况';
|
||||
}
|
||||
return `条件${index + 1}`;
|
||||
};
|
||||
|
||||
/** 获取包容分支条件节点默认的名称 */
|
||||
export const getDefaultInclusiveConditionNodeName = (
|
||||
index: number,
|
||||
defaultFlow: boolean | undefined,
|
||||
): string => {
|
||||
if (defaultFlow) {
|
||||
return '其它情况';
|
||||
}
|
||||
return `包容条件${index + 1}`;
|
||||
};
|
||||
|
||||
/** 转换时间单位字符串为枚举值 */
|
||||
export const convertTimeUnit = (strTimeUnit: string) => {
|
||||
if (strTimeUnit === 'M') {
|
||||
return TimeUnitType.MINUTE;
|
||||
}
|
||||
if (strTimeUnit === 'H') {
|
||||
return TimeUnitType.HOUR;
|
||||
}
|
||||
if (strTimeUnit === 'D') {
|
||||
return TimeUnitType.DAY;
|
||||
}
|
||||
return TimeUnitType.HOUR;
|
||||
};
|
||||
|
||||
/** 根据审批类型获取对应的文本描述 */
|
||||
export const getApproveTypeText = (approveType: ApproveType): string => {
|
||||
let approveTypeText = '';
|
||||
APPROVE_TYPE.forEach((item) => {
|
||||
if (item.value === approveType) {
|
||||
approveTypeText = item.label;
|
||||
}
|
||||
});
|
||||
return approveTypeText;
|
||||
};
|
|
@ -0,0 +1,138 @@
|
|||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { SimpleFlowNode } from '../../consts';
|
||||
|
||||
import { inject, ref } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Input } from 'ant-design-vue';
|
||||
|
||||
import { NODE_DEFAULT_TEXT, NodeType } from '../../consts';
|
||||
import { useNodeName2, useTaskStatusClass, useWatchNode } from '../../helpers';
|
||||
import UserTaskNodeConfig from '../nodes-config/user-task-node-config.vue';
|
||||
import NodeHandler from './node-handler.vue';
|
||||
|
||||
defineOptions({ name: 'UserTaskNode' });
|
||||
|
||||
const props = defineProps({
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits<{
|
||||
findParentNode: [nodeList: SimpleFlowNode[], nodeType: NodeType];
|
||||
'update:flowNode': [node: SimpleFlowNode | undefined];
|
||||
}>();
|
||||
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly');
|
||||
const tasks = inject<Ref<any[]>>('tasks', ref([]));
|
||||
// 监控节点变化
|
||||
const currentNode = useWatchNode(props);
|
||||
// 节点名称编辑
|
||||
const { showInput, blurEvent, clickTitle } = useNodeName2(
|
||||
currentNode,
|
||||
NodeType.START_USER_NODE,
|
||||
);
|
||||
const nodeSetting = ref();
|
||||
|
||||
const nodeClick = () => {
|
||||
if (readonly) {
|
||||
if (tasks && tasks.value) {
|
||||
// 只读模式,弹窗显示任务信息 TODO 待实现
|
||||
console.warn('只读模式,弹窗显示任务信息待实现');
|
||||
}
|
||||
} else {
|
||||
// 编辑模式,打开节点配置、把当前节点传递给配置组件
|
||||
nodeSetting.value.showUserTaskNodeConfig(currentNode.value);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteNode = () => {
|
||||
emits('update:flowNode', currentNode.value.childNode);
|
||||
};
|
||||
// 查找可以驳回用户节点
|
||||
const findReturnTaskNodes = (
|
||||
matchNodeList: SimpleFlowNode[], // 匹配的节点
|
||||
) => {
|
||||
// 从父节点查找
|
||||
emits('findParentNode', matchNodeList, NodeType.USER_TASK_NODE);
|
||||
};
|
||||
|
||||
// const selectTasks = ref<any[] | undefined>([]); // 选中的任务数组
|
||||
</script>
|
||||
<template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !currentNode.showText },
|
||||
`${useTaskStatusClass(currentNode?.activityStatus)}`,
|
||||
]"
|
||||
>
|
||||
<div class="node-title-container">
|
||||
<div
|
||||
:class="`node-title-icon ${currentNode.type === NodeType.TRANSACTOR_NODE ? 'transactor-task' : 'user-task'}`"
|
||||
>
|
||||
<span
|
||||
:class="`iconfont ${currentNode.type === NodeType.TRANSACTOR_NODE ? 'icon-transactor' : 'icon-approve'}`"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
<Input
|
||||
v-if="!readonly && showInput"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
v-model="currentNode.name"
|
||||
:placeholder="currentNode.name"
|
||||
/>
|
||||
<div v-else class="node-title" @click="clickTitle">
|
||||
{{ currentNode.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-content" @click="nodeClick">
|
||||
<div
|
||||
class="node-text"
|
||||
:title="currentNode.showText"
|
||||
v-if="currentNode.showText"
|
||||
>
|
||||
{{ currentNode.showText }}
|
||||
</div>
|
||||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(currentNode.type) }}
|
||||
</div>
|
||||
<IconifyIcon icon="ep:arrow-right-bold" v-if="!readonly" />
|
||||
</div>
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon">
|
||||
<IconifyIcon
|
||||
color="#0089ff"
|
||||
icon="ep:circle-close-filled"
|
||||
:size="18"
|
||||
@click="deleteNode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<UserTaskNodeConfig
|
||||
v-if="currentNode"
|
||||
ref="nodeSetting"
|
||||
:flow-node="currentNode"
|
||||
@find-return-task-nodes="findReturnTaskNodes"
|
||||
/>
|
||||
<!-- TODO 审批记录 -->
|
||||
</template>
|
||||
<style lang="scss" scoped></style>
|
|
@ -5,6 +5,7 @@ import { NodeType } from '../consts';
|
|||
import { useWatchNode } from '../helpers';
|
||||
import EndEventNode from './nodes/end-event-node.vue';
|
||||
import StartUserNode from './nodes/start-user-node.vue';
|
||||
import UserTaskNode from './nodes/user-task-node.vue';
|
||||
|
||||
defineOptions({ name: 'ProcessNodeTree' });
|
||||
const props = defineProps({
|
||||
|
@ -29,16 +30,12 @@ const emits = defineEmits<{
|
|||
const currentNode = useWatchNode(props);
|
||||
|
||||
// 用于删除节点
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars, no-unused-vars
|
||||
|
||||
const handleModelValueUpdate = (updateValue: any) => {
|
||||
emits('update:flowNode', updateValue);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars, no-unused-vars
|
||||
const triggerFromParentNode = (
|
||||
nodeList: SimpleFlowNode[],
|
||||
nodeType: number,
|
||||
) => {
|
||||
const findParentNode = (nodeList: SimpleFlowNode[], nodeType: number) => {
|
||||
emits('recursiveFindParentNode', nodeList, props.parentNode, nodeType);
|
||||
};
|
||||
|
||||
|
@ -69,7 +66,7 @@ const recursiveFindParentNode = (
|
|||
:flow-node="currentNode"
|
||||
/>
|
||||
<!-- 审批节点 -->
|
||||
<!-- <UserTaskNode
|
||||
<UserTaskNode
|
||||
v-if="
|
||||
currentNode &&
|
||||
(currentNode.type === NodeType.USER_TASK_NODE ||
|
||||
|
@ -77,8 +74,8 @@ const recursiveFindParentNode = (
|
|||
"
|
||||
:flow-node="currentNode"
|
||||
@update:flow-node="handleModelValueUpdate"
|
||||
@find:parent-node="findFromParentNode"
|
||||
/> -->
|
||||
@find-parent-node="findParentNode"
|
||||
/>
|
||||
<!-- 抄送节点 -->
|
||||
<!-- <CopyTaskNode
|
||||
v-if="currentNode && currentNode.type === NodeType.COPY_TASK_NODE"
|
||||
|
|
|
@ -44,82 +44,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 表单字段权限
|
||||
.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: 110px;
|
||||
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: 110px;
|
||||
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: 110px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 节点连线气泡卡片样式
|
||||
.handler-item-wrapper {
|
||||
display: flex;
|
||||
|
|
Loading…
Reference in New Issue