Pre Merge pull request !117 from Jason/dev

pull/117/MERGE
Jason 2025-05-27 14:15:43 +00:00 committed by Gitee
commit fbfe1a01be
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
6 changed files with 1538 additions and 171 deletions

View File

@ -12,10 +12,11 @@ import { useVbenDrawer } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { import {
Divider, Col,
Input, Input,
Radio, Radio,
RadioGroup, RadioGroup,
Row,
TabPane, TabPane,
Tabs, Tabs,
Tooltip, Tooltip,
@ -91,8 +92,8 @@ const getDeptNames = (deptIds: number[]): string => {
// 使 VbenDrawer // 使 VbenDrawer
const [Drawer, drawerApi] = useVbenDrawer({ const [Drawer, drawerApi] = useVbenDrawer({
header: false, header: true,
closable: false, closable: true,
onCancel() { onCancel() {
drawerApi.close(); drawerApi.close();
}, },
@ -142,28 +143,28 @@ defineExpose({ showStartUserNodeConfig });
</script> </script>
<template> <template>
<Drawer> <Drawer>
<div class="config-header"> <template #title>
<!-- TODO v-mountedFocus 自动聚集 需要迁移一下 --> <div class="config-header">
<Input <!-- TODO v-mountedFocus 自动聚集 需要迁移一下 -->
v-if="showInput" <Input
type="text" v-if="showInput"
class="config-editable-input" type="text"
@blur="blurEvent()" class="config-editable-input"
v-model:value="nodeName" @blur="blurEvent()"
:placeholder="nodeName" 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 v-else class="node-name">
{{ nodeName }}
<IconifyIcon
class="ml-1"
icon="ep:edit-pen"
:size="16"
@click="clickIcon()"
/>
</div>
</div> </div>
<Divider /> </template>
</div>
<Tabs v-model:active-key="activeTabName" type="card"> <Tabs v-model:active-key="activeTabName" type="card">
<TabPane tab="权限" key="user"> <TabPane tab="权限" key="user">
<TypographyText <TypographyText
@ -212,69 +213,76 @@ defineExpose({ showStartUserNodeConfig });
key="fields" key="fields"
v-if="formType === BpmModelFormType.NORMAL" v-if="formType === BpmModelFormType.NORMAL"
> >
<div class="field-setting-pane"> <div class="p-1">
<div class="field-setting-desc">字段权限</div> <div class="mb-4 text-[16px] font-bold">字段权限</div>
<div class="field-permit-title">
<div class="setting-title-label first-title">字段名称</div> <!-- 表头 -->
<div class="other-titles"> <Row class="border border-gray-200 px-4 py-3">
<span <Col :span="8" class="font-bold">字段名称</Col>
class="setting-title-label cursor-pointer" <Col :span="16">
@click="updatePermission('READ')" <Row>
> <Col :span="8" class="flex items-center justify-center">
只读 <span
</span> class="cursor-pointer font-bold"
<span @click="updatePermission('READ')"
class="setting-title-label cursor-pointer" >
@click="updatePermission('WRITE')" 只读
> </span>
可编辑 </Col>
</span> <Col :span="8" class="flex items-center justify-center">
<span <span
class="setting-title-label cursor-pointer" class="cursor-pointer font-bold"
@click="updatePermission('NONE')" @click="updatePermission('WRITE')"
> >
隐藏 可编辑
</span> </span>
</div> </Col>
</div> <Col :span="8" class="flex items-center justify-center">
<div <span
class="field-setting-item" class="cursor-pointer font-bold"
v-for="(item, index) in fieldsPermissionConfig" @click="updatePermission('NONE')"
:key="index" >
> 隐藏
<div class="field-setting-item-label">{{ item.title }}</div> </span>
<RadioGroup </Col>
class="field-setting-item-group" </Row>
v-model:value="item.permission" </Col>
> </Row>
<div class="item-radio-wrap">
<Radio <!-- 表格内容 -->
:value="FieldPermissionType.READ" <div v-for="(item, index) in fieldsPermissionConfig" :key="index">
size="large" <Row class="border border-t-0 border-gray-200 px-4 py-2">
:label="FieldPermissionType.READ" <Col :span="8" class="flex items-center truncate">
> {{ item.title }}
<span></span> </Col>
</Radio> <Col :span="16">
</div> <RadioGroup v-model:value="item.permission" class="w-full">
<div class="item-radio-wrap"> <Row>
<Radio <Col :span="8" class="flex items-center justify-center">
:value="FieldPermissionType.WRITE" <Radio
size="large" :value="FieldPermissionType.READ"
:label="FieldPermissionType.WRITE" size="large"
> :label="FieldPermissionType.READ"
<span></span> />
</Radio> </Col>
</div> <Col :span="8" class="flex items-center justify-center">
<div class="item-radio-wrap"> <Radio
<Radio :value="FieldPermissionType.WRITE"
:value="FieldPermissionType.NONE" size="large"
size="large" :label="FieldPermissionType.WRITE"
:label="FieldPermissionType.NONE" />
> </Col>
<span></span> <Col :span="8" class="flex items-center justify-center">
</Radio> <Radio
</div> :value="FieldPermissionType.NONE"
</RadioGroup> size="large"
:label="FieldPermissionType.NONE"
/>
</Col>
</Row>
</RadioGroup>
</Col>
</Row>
</div> </div>
</div> </div>
</TabPane> </TabPane>

View File

@ -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;
};

View File

@ -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>

View File

@ -5,6 +5,7 @@ import { NodeType } from '../consts';
import { useWatchNode } from '../helpers'; import { useWatchNode } from '../helpers';
import EndEventNode from './nodes/end-event-node.vue'; import EndEventNode from './nodes/end-event-node.vue';
import StartUserNode from './nodes/start-user-node.vue'; import StartUserNode from './nodes/start-user-node.vue';
import UserTaskNode from './nodes/user-task-node.vue';
defineOptions({ name: 'ProcessNodeTree' }); defineOptions({ name: 'ProcessNodeTree' });
const props = defineProps({ const props = defineProps({
@ -29,16 +30,12 @@ const emits = defineEmits<{
const currentNode = useWatchNode(props); const currentNode = useWatchNode(props);
// //
// eslint-disable-next-line unused-imports/no-unused-vars, no-unused-vars
const handleModelValueUpdate = (updateValue: any) => { const handleModelValueUpdate = (updateValue: any) => {
emits('update:flowNode', updateValue); emits('update:flowNode', updateValue);
}; };
// eslint-disable-next-line unused-imports/no-unused-vars, no-unused-vars const findParentNode = (nodeList: SimpleFlowNode[], nodeType: number) => {
const triggerFromParentNode = (
nodeList: SimpleFlowNode[],
nodeType: number,
) => {
emits('recursiveFindParentNode', nodeList, props.parentNode, nodeType); emits('recursiveFindParentNode', nodeList, props.parentNode, nodeType);
}; };
@ -69,7 +66,7 @@ const recursiveFindParentNode = (
:flow-node="currentNode" :flow-node="currentNode"
/> />
<!-- 审批节点 --> <!-- 审批节点 -->
<!-- <UserTaskNode <UserTaskNode
v-if=" v-if="
currentNode && currentNode &&
(currentNode.type === NodeType.USER_TASK_NODE || (currentNode.type === NodeType.USER_TASK_NODE ||
@ -77,8 +74,8 @@ const recursiveFindParentNode = (
" "
:flow-node="currentNode" :flow-node="currentNode"
@update:flow-node="handleModelValueUpdate" @update:flow-node="handleModelValueUpdate"
@find:parent-node="findFromParentNode" @find-parent-node="findParentNode"
/> --> />
<!-- 抄送节点 --> <!-- 抄送节点 -->
<!-- <CopyTaskNode <!-- <CopyTaskNode
v-if="currentNode && currentNode.type === NodeType.COPY_TASK_NODE" v-if="currentNode && currentNode.type === NodeType.COPY_TASK_NODE"

View File

@ -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 { .handler-item-wrapper {
display: flex; display: flex;