Merge branch 'dev' of https://gitee.com/yudaocode/yudao-ui-admin-vben into dev_xx
This commit is contained in:
@@ -23,6 +23,11 @@ interface DictTagProps {
|
||||
|
||||
const props = defineProps<DictTagProps>();
|
||||
|
||||
function isHexColor(color: string) {
|
||||
const reg = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
|
||||
return reg.test(color);
|
||||
}
|
||||
|
||||
/** 获取字典标签 */
|
||||
const dictTag = computed(() => {
|
||||
// 校验参数有效性
|
||||
@@ -66,7 +71,16 @@ const dictTag = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Tag v-if="dictTag" :color="dictTag.colorType">
|
||||
<Tag
|
||||
v-if="dictTag"
|
||||
:color="
|
||||
dictTag.colorType
|
||||
? dictTag.colorType
|
||||
: dictTag.cssClass && isHexColor(dictTag.cssClass)
|
||||
? dictTag.cssClass
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ dictTag.label }}
|
||||
</Tag>
|
||||
</template>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { isDocAlertEnable } from '@vben/hooks';
|
||||
import { openWindow } from '@vben/utils';
|
||||
|
||||
import { Alert, Typography } from 'ant-design-vue';
|
||||
|
||||
export interface DocAlertProps {
|
||||
/**
|
||||
* 文档标题
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* 文档 URL 地址
|
||||
*/
|
||||
url: string;
|
||||
}
|
||||
|
||||
const props = defineProps<DocAlertProps>();
|
||||
|
||||
/** 跳转 URL 链接 */
|
||||
const goToUrl = () => {
|
||||
openWindow(props.url);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Alert v-if="isDocAlertEnable()" type="info" show-icon class="mb-2 rounded">
|
||||
<template #message>
|
||||
<Typography.Link @click="goToUrl">
|
||||
【{{ title }}】文档地址:{{ url }}
|
||||
</Typography.Link>
|
||||
</template>
|
||||
</Alert>
|
||||
</template>
|
||||
@@ -1 +0,0 @@
|
||||
export { default as DocAlert } from './doc-alert.vue';
|
||||
@@ -1,3 +1,9 @@
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
|
||||
export const AsyncOperateLog = defineAsyncComponent(
|
||||
() => import('./operate-log.vue'),
|
||||
);
|
||||
|
||||
export { default as OperateLog } from './operate-log.vue';
|
||||
|
||||
export type { OperateLogProps } from './typing';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { OperateLogProps } from './typing';
|
||||
|
||||
import { Timeline } from 'ant-design-vue';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import { Tag, Timeline } from 'ant-design-vue';
|
||||
|
||||
import { DICT_TYPE, getDictLabel, getDictObj } from '#/utils';
|
||||
|
||||
@@ -38,8 +40,21 @@ function getUserTypeColor(userType: number) {
|
||||
:key="log.id"
|
||||
:color="getUserTypeColor(log.userType)"
|
||||
>
|
||||
<p>{{ log.createTime }}</p>
|
||||
<p>{{ getDictLabel(DICT_TYPE.USER_TYPE, log.userType)[0] }}</p>
|
||||
<template #dot>
|
||||
<p
|
||||
:style="{ backgroundColor: getUserTypeColor(log.userType) }"
|
||||
class="absolute left-[-5px] flex h-5 w-5 items-center justify-center rounded-full text-xs text-white"
|
||||
>
|
||||
{{ getDictLabel(DICT_TYPE.USER_TYPE, log.userType)[0] }}
|
||||
</p>
|
||||
</template>
|
||||
<p>{{ formatDateTime(log.createTime) }}</p>
|
||||
<p>
|
||||
<Tag :color="getUserTypeColor(log.userType)">
|
||||
{{ log.userName }}
|
||||
</Tag>
|
||||
{{ log.action }}
|
||||
</p>
|
||||
</Timeline.Item>
|
||||
</Timeline>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ref } from 'vue';
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { handleTree } from '@vben/utils';
|
||||
|
||||
import { Button, Card, Col, Row, Tree } from 'ant-design-vue';
|
||||
import { Card, Col, Row, Tree } from 'ant-design-vue';
|
||||
|
||||
import { getSimpleDeptList } from '#/api/system/dept';
|
||||
|
||||
@@ -41,24 +41,6 @@ const emit = defineEmits<{
|
||||
confirm: [deptList: SystemDeptApi.Dept[]];
|
||||
}>();
|
||||
|
||||
// 对话框配置
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
title: props.title,
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
resetData();
|
||||
return;
|
||||
}
|
||||
modalApi.setState({ loading: true });
|
||||
try {
|
||||
deptData.value = await getSimpleDeptList();
|
||||
deptTree.value = handleTree(deptData.value) as DataNode[];
|
||||
} finally {
|
||||
modalApi.setState({ loading: false });
|
||||
}
|
||||
},
|
||||
destroyOnClose: true,
|
||||
});
|
||||
type checkedKeys = number[] | { checked: number[]; halfChecked: number[] };
|
||||
// 部门树形结构
|
||||
const deptTree = ref<DataNode[]>([]);
|
||||
@@ -67,25 +49,56 @@ const selectedDeptIds = ref<checkedKeys>([]);
|
||||
// 部门数据
|
||||
const deptData = ref<SystemDeptApi.Dept[]>([]);
|
||||
|
||||
/** 打开对话框 */
|
||||
const open = async (selectedList?: SystemDeptApi.Dept[]) => {
|
||||
modalApi.open();
|
||||
// // 设置已选择的部门
|
||||
if (selectedList?.length) {
|
||||
const selectedIds = selectedList
|
||||
.map((dept) => dept.id)
|
||||
.filter((id): id is number => id !== undefined);
|
||||
selectedDeptIds.value = props.checkStrictly
|
||||
? {
|
||||
checked: selectedIds,
|
||||
halfChecked: [],
|
||||
}
|
||||
: selectedIds;
|
||||
}
|
||||
};
|
||||
// 对话框配置
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
// 获取选中的部门ID
|
||||
const selectedIds: number[] = Array.isArray(selectedDeptIds.value)
|
||||
? selectedDeptIds.value
|
||||
: selectedDeptIds.value.checked || [];
|
||||
const deptArray = deptData.value.filter((dept) =>
|
||||
selectedIds.includes(dept.id!),
|
||||
);
|
||||
emit('confirm', deptArray);
|
||||
// 关闭并提示
|
||||
await modalApi.close();
|
||||
},
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
deptTree.value = [];
|
||||
selectedDeptIds.value = [];
|
||||
return;
|
||||
}
|
||||
// 加载数据
|
||||
const data = modalApi.getData();
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
deptData.value = await getSimpleDeptList();
|
||||
deptTree.value = handleTree(deptData.value) as DataNode[];
|
||||
// // 设置已选择的部门
|
||||
if (data.selectedList?.length) {
|
||||
const selectedIds = data.selectedList
|
||||
.map((dept: SystemDeptApi.Dept) => dept.id)
|
||||
.filter((id: number) => id !== undefined);
|
||||
selectedDeptIds.value = props.checkStrictly
|
||||
? {
|
||||
checked: selectedIds,
|
||||
halfChecked: [],
|
||||
}
|
||||
: selectedIds;
|
||||
}
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
/** 处理选中状态变化 */
|
||||
const handleCheck = () => {
|
||||
function handleCheck() {
|
||||
if (!props.multiple) {
|
||||
// 单选模式下,只保留最后选择的节点
|
||||
if (Array.isArray(selectedDeptIds.value)) {
|
||||
@@ -106,37 +119,10 @@ const handleCheck = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 提交选择 */
|
||||
const handleConfirm = async () => {
|
||||
// 获取选中的部门ID
|
||||
const selectedIds: number[] = Array.isArray(selectedDeptIds.value)
|
||||
? selectedDeptIds.value
|
||||
: selectedDeptIds.value.checked || [];
|
||||
const deptArray = deptData.value.filter((dept) =>
|
||||
selectedIds.includes(dept.id!),
|
||||
);
|
||||
// 关闭并提示
|
||||
await modalApi.close();
|
||||
emit('confirm', deptArray);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
modalApi.close();
|
||||
};
|
||||
|
||||
/** 重置数据 */
|
||||
const resetData = () => {
|
||||
deptTree.value = [];
|
||||
selectedDeptIds.value = [];
|
||||
};
|
||||
|
||||
/** 提供 open 方法,用于打开对话框 */
|
||||
defineExpose({ open });
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<Modal>
|
||||
<Modal :title="title" key="dept-select-modal" class="w-[40%]">
|
||||
<Row class="h-full">
|
||||
<Col :span="24">
|
||||
<Card class="h-full">
|
||||
@@ -153,9 +139,5 @@ defineExpose({ open });
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<template #footer>
|
||||
<Button @click="handleCancel">{{ cancelText }}</Button>
|
||||
<Button type="primary" @click="handleConfirm">{{ confirmText }}</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
// TODO @芋艿:是否有更好的组织形式?!
|
||||
// TODO @xingyu:你感觉,这个放到每个 system、infra 模块下,然后新建一个 components,表示每个模块,有一些共享的组件?然后,全局只放通用的(无业务含义的),可以哇?
|
||||
import type { Key } from 'ant-design-vue/es/table/interface';
|
||||
|
||||
import type { SystemDeptApi } from '#/api/system/dept';
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
message,
|
||||
Pagination,
|
||||
Row,
|
||||
Spin,
|
||||
Transfer,
|
||||
Tree,
|
||||
} from 'ant-design-vue';
|
||||
@@ -35,7 +35,7 @@ interface DeptTreeNode {
|
||||
|
||||
defineOptions({ name: 'UserSelectModal' });
|
||||
|
||||
const props = withDefaults(
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
cancelText?: string;
|
||||
confirmText?: string;
|
||||
@@ -66,16 +66,66 @@ const expandedKeys = ref<Key[]>([]);
|
||||
const selectedDeptId = ref<number>();
|
||||
const deptSearchKeys = ref('');
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 用户数据管理
|
||||
const userList = ref<SystemUserApi.User[]>([]); // 存储所有已知用户
|
||||
const selectedUserIds = ref<string[]>([]);
|
||||
|
||||
// 弹窗配置
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
onCancel: handleCancel,
|
||||
onClosed: handleClosed,
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
resetData();
|
||||
return;
|
||||
}
|
||||
// 加载数据
|
||||
const data = modalApi.getData();
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
// 加载部门数据
|
||||
const deptData = await getSimpleDeptList();
|
||||
deptList.value = deptData;
|
||||
const treeData = handleTree(deptData);
|
||||
deptTree.value = treeData.map((node) => processDeptNode(node));
|
||||
expandedKeys.value = deptTree.value.map((node) => node.key);
|
||||
|
||||
// 加载初始用户数据
|
||||
await loadUserData(1, leftListState.value.pagination.pageSize);
|
||||
|
||||
// 设置已选用户
|
||||
if (data.userIds?.length) {
|
||||
selectedUserIds.value = data.userIds.map(String);
|
||||
// 加载已选用户的完整信息 TODO 目前接口暂不支持 多个用户ID 查询, 需要后端支持
|
||||
const { list } = await getUserPage({
|
||||
pageNo: 1,
|
||||
pageSize: 100, // 临时使用固定值确保能加载所有已选用户
|
||||
userIds: data.userIds,
|
||||
});
|
||||
// 使用 Map 来去重,以用户 ID 为 key
|
||||
const userMap = new Map(userList.value.map((user) => [user.id, user]));
|
||||
list.forEach((user) => {
|
||||
if (!userMap.has(user.id)) {
|
||||
userMap.set(user.id, user);
|
||||
}
|
||||
});
|
||||
userList.value = [...userMap.values()];
|
||||
updateRightListData();
|
||||
}
|
||||
|
||||
modalApi.open();
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
// 左侧列表状态
|
||||
const leftListState = ref({
|
||||
loading: false,
|
||||
searchValue: '',
|
||||
dataSource: [] as SystemUserApi.User[],
|
||||
pagination: {
|
||||
@@ -145,8 +195,7 @@ const filteredDeptTree = computed(() => {
|
||||
});
|
||||
|
||||
// 加载用户数据
|
||||
const loadUserData = async (pageNo: number, pageSize: number) => {
|
||||
leftListState.value.loading = true;
|
||||
async function loadUserData(pageNo: number, pageSize: number) {
|
||||
try {
|
||||
const { list, total } = await getUserPage({
|
||||
pageNo,
|
||||
@@ -168,12 +217,12 @@ const loadUserData = async (pageNo: number, pageSize: number) => {
|
||||
userList.value.push(...newUsers);
|
||||
}
|
||||
} finally {
|
||||
leftListState.value.loading = false;
|
||||
//
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 更新右侧列表数据
|
||||
const updateRightListData = () => {
|
||||
function updateRightListData() {
|
||||
// 使用 Set 来去重选中的用户ID
|
||||
const uniqueSelectedIds = new Set(selectedUserIds.value);
|
||||
|
||||
@@ -202,22 +251,22 @@ const updateRightListData = () => {
|
||||
const endIndex = startIndex + pageSize;
|
||||
|
||||
rightListState.value.dataSource = filteredUsers.slice(startIndex, endIndex);
|
||||
};
|
||||
}
|
||||
|
||||
// 处理左侧分页变化
|
||||
const handleLeftPaginationChange = async (page: number, pageSize: number) => {
|
||||
async function handleLeftPaginationChange(page: number, pageSize: number) {
|
||||
await loadUserData(page, pageSize);
|
||||
};
|
||||
}
|
||||
|
||||
// 处理右侧分页变化
|
||||
const handleRightPaginationChange = (page: number, pageSize: number) => {
|
||||
function handleRightPaginationChange(page: number, pageSize: number) {
|
||||
rightListState.value.pagination.current = page;
|
||||
rightListState.value.pagination.pageSize = pageSize;
|
||||
updateRightListData();
|
||||
};
|
||||
}
|
||||
|
||||
// 处理用户搜索
|
||||
const handleUserSearch = async (direction: string, value: string) => {
|
||||
async function handleUserSearch(direction: string, value: string) {
|
||||
if (direction === 'left') {
|
||||
leftListState.value.searchValue = value;
|
||||
leftListState.value.pagination.current = 1;
|
||||
@@ -227,18 +276,18 @@ const handleUserSearch = async (direction: string, value: string) => {
|
||||
rightListState.value.pagination.current = 1;
|
||||
updateRightListData();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 处理用户选择变化
|
||||
const handleUserChange = (targetKeys: string[]) => {
|
||||
function handleUserChange(targetKeys: string[]) {
|
||||
// 使用 Set 来去重选中的用户ID
|
||||
selectedUserIds.value = [...new Set(targetKeys)];
|
||||
emit('update:value', selectedUserIds.value.map(Number));
|
||||
updateRightListData();
|
||||
};
|
||||
}
|
||||
|
||||
// 重置数据
|
||||
const resetData = () => {
|
||||
function resetData() {
|
||||
userList.value = [];
|
||||
selectedUserIds.value = [];
|
||||
|
||||
@@ -249,7 +298,6 @@ const resetData = () => {
|
||||
selectedUserIds.value = [];
|
||||
|
||||
leftListState.value = {
|
||||
loading: false,
|
||||
searchValue: '',
|
||||
dataSource: [],
|
||||
pagination: {
|
||||
@@ -268,61 +316,20 @@ const resetData = () => {
|
||||
total: 0,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// 打开弹窗
|
||||
const open = async (userIds: string[]) => {
|
||||
resetData();
|
||||
loading.value = true;
|
||||
try {
|
||||
// 加载部门数据
|
||||
const deptData = await getSimpleDeptList();
|
||||
deptList.value = deptData;
|
||||
const treeData = handleTree(deptData);
|
||||
deptTree.value = treeData.map((node) => processDeptNode(node));
|
||||
expandedKeys.value = deptTree.value.map((node) => node.key);
|
||||
|
||||
// 加载初始用户数据
|
||||
await loadUserData(1, leftListState.value.pagination.pageSize);
|
||||
|
||||
// 设置已选用户
|
||||
if (userIds?.length) {
|
||||
selectedUserIds.value = userIds.map(String);
|
||||
// 加载已选用户的完整信息 TODO 目前接口暂不支持 多个用户ID 查询, 需要后端支持
|
||||
const { list } = await getUserPage({
|
||||
pageNo: 1,
|
||||
pageSize: 100, // 临时使用固定值确保能加载所有已选用户
|
||||
userIds,
|
||||
});
|
||||
// 使用 Map 来去重,以用户 ID 为 key
|
||||
const userMap = new Map(userList.value.map((user) => [user.id, user]));
|
||||
list.forEach((user) => {
|
||||
if (!userMap.has(user.id)) {
|
||||
userMap.set(user.id, user);
|
||||
}
|
||||
});
|
||||
userList.value = [...userMap.values()];
|
||||
updateRightListData();
|
||||
}
|
||||
|
||||
modalApi.open();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// TODO 后端接口目前仅支持 username 检索, 筛选条件需要跟后端请求参数保持一致。
|
||||
const filterOption = (inputValue: string, option: any) => {
|
||||
function filterOption(inputValue: string, option: any) {
|
||||
return option.username.toLowerCase().includes(inputValue.toLowerCase());
|
||||
};
|
||||
}
|
||||
|
||||
// 处理部门树展开/折叠
|
||||
const handleExpand = (keys: Key[]) => {
|
||||
function handleExpand(keys: Key[]) {
|
||||
expandedKeys.value = keys;
|
||||
};
|
||||
}
|
||||
|
||||
// 处理部门搜索
|
||||
const handleDeptSearch = (value: string) => {
|
||||
function handleDeptSearch(value: string) {
|
||||
deptSearchKeys.value = value;
|
||||
|
||||
// 如果有搜索结果,自动展开所有节点
|
||||
@@ -342,10 +349,10 @@ const handleDeptSearch = (value: string) => {
|
||||
// 清空搜索时,只展开第一级节点
|
||||
expandedKeys.value = deptTree.value.map((node) => node.key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 处理部门选择
|
||||
const handleDeptSelect = async (selectedKeys: Key[], _info: any) => {
|
||||
async function handleDeptSelect(selectedKeys: Key[], _info: any) {
|
||||
// 更新选中的部门ID
|
||||
const newDeptId =
|
||||
selectedKeys.length > 0 ? Number(selectedKeys[0]) : undefined;
|
||||
@@ -356,10 +363,10 @@ const handleDeptSelect = async (selectedKeys: Key[], _info: any) => {
|
||||
const { pageSize } = leftListState.value.pagination;
|
||||
leftListState.value.pagination.current = 1;
|
||||
await loadUserData(1, pageSize);
|
||||
};
|
||||
}
|
||||
|
||||
// 确认选择
|
||||
const handleConfirm = () => {
|
||||
function handleConfirm() {
|
||||
if (selectedUserIds.value.length === 0) {
|
||||
message.warning('请选择用户');
|
||||
return;
|
||||
@@ -371,115 +378,101 @@ const handleConfirm = () => {
|
||||
),
|
||||
);
|
||||
modalApi.close();
|
||||
};
|
||||
}
|
||||
|
||||
// 取消选择
|
||||
const handleCancel = () => {
|
||||
function handleCancel() {
|
||||
emit('cancel');
|
||||
modalApi.close();
|
||||
// 确保在动画结束后再重置数据
|
||||
setTimeout(() => {
|
||||
resetData();
|
||||
}, 300);
|
||||
};
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClosed = () => {
|
||||
function handleClosed() {
|
||||
emit('closed');
|
||||
resetData();
|
||||
};
|
||||
|
||||
// 弹窗配置
|
||||
const [ModalComponent, modalApi] = useVbenModal({
|
||||
title: props.title,
|
||||
onCancel: handleCancel,
|
||||
onClosed: handleClosed,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
}
|
||||
|
||||
// 递归处理部门树节点
|
||||
const processDeptNode = (node: any): DeptTreeNode => {
|
||||
function processDeptNode(node: any): DeptTreeNode {
|
||||
return {
|
||||
key: String(node.id),
|
||||
title: `${node.name} (${node.id})`,
|
||||
name: node.name,
|
||||
children: node.children?.map((child: any) => processDeptNode(child)),
|
||||
};
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ModalComponent class="w-[1000px]" key="user-select-modal">
|
||||
<Spin :spinning="loading">
|
||||
<Row :gutter="[16, 16]">
|
||||
<Col :span="6">
|
||||
<div class="h-[500px] overflow-auto rounded border">
|
||||
<div class="border-b p-2">
|
||||
<Input
|
||||
v-model:value="deptSearchKeys"
|
||||
placeholder="搜索部门"
|
||||
allow-clear
|
||||
@input="(e) => handleDeptSearch(e.target?.value ?? '')"
|
||||
/>
|
||||
</div>
|
||||
<Tree
|
||||
:tree-data="filteredDeptTree"
|
||||
:expanded-keys="expandedKeys"
|
||||
:selected-keys="selectedDeptId ? [String(selectedDeptId)] : []"
|
||||
@select="handleDeptSelect"
|
||||
@expand="handleExpand"
|
||||
<Modal class="w-[40%]" key="user-select-modal" :title="title">
|
||||
<Row :gutter="[16, 16]">
|
||||
<Col :span="6">
|
||||
<div class="h-[500px] overflow-auto rounded border">
|
||||
<div class="border-b p-2">
|
||||
<Input
|
||||
v-model:value="deptSearchKeys"
|
||||
placeholder="搜索部门"
|
||||
allow-clear
|
||||
@input="(e) => handleDeptSearch(e.target?.value ?? '')"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col :span="18">
|
||||
<Transfer
|
||||
:row-key="(record) => String(record.id)"
|
||||
:data-source="transferDataSource"
|
||||
v-model:target-keys="selectedUserIds"
|
||||
:titles="['未选', '已选']"
|
||||
:show-search="true"
|
||||
:show-select-all="true"
|
||||
:filter-option="filterOption"
|
||||
@change="handleUserChange"
|
||||
@search="handleUserSearch"
|
||||
>
|
||||
<template #render="item">
|
||||
<span>{{ item?.nickname }} ({{ item?.username }})</span>
|
||||
</template>
|
||||
<Tree
|
||||
:tree-data="filteredDeptTree"
|
||||
:expanded-keys="expandedKeys"
|
||||
:selected-keys="selectedDeptId ? [String(selectedDeptId)] : []"
|
||||
@select="handleDeptSelect"
|
||||
@expand="handleExpand"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col :span="18">
|
||||
<Transfer
|
||||
:row-key="(record) => String(record.id)"
|
||||
:data-source="transferDataSource"
|
||||
v-model:target-keys="selectedUserIds"
|
||||
:titles="['未选', '已选']"
|
||||
:show-search="true"
|
||||
:show-select-all="true"
|
||||
:filter-option="filterOption"
|
||||
@change="handleUserChange"
|
||||
@search="handleUserSearch"
|
||||
>
|
||||
<template #render="item">
|
||||
<span>{{ item?.nickname }} ({{ item?.username }})</span>
|
||||
</template>
|
||||
|
||||
<template #footer="{ direction }">
|
||||
<div v-if="direction === 'left'">
|
||||
<Pagination
|
||||
v-model:current="leftListState.pagination.current"
|
||||
v-model:page-size="leftListState.pagination.pageSize"
|
||||
:total="leftListState.pagination.total"
|
||||
:show-size-changer="true"
|
||||
:show-total="(total) => `共 ${total} 条`"
|
||||
size="small"
|
||||
@change="handleLeftPaginationChange"
|
||||
/>
|
||||
</div>
|
||||
<template #footer="{ direction }">
|
||||
<div v-if="direction === 'left'">
|
||||
<Pagination
|
||||
v-model:current="leftListState.pagination.current"
|
||||
v-model:page-size="leftListState.pagination.pageSize"
|
||||
:total="leftListState.pagination.total"
|
||||
:show-size-changer="true"
|
||||
:show-total="(total) => `共 ${total} 条`"
|
||||
size="small"
|
||||
@change="handleLeftPaginationChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="direction === 'right'">
|
||||
<Pagination
|
||||
v-model:current="rightListState.pagination.current"
|
||||
v-model:page-size="rightListState.pagination.pageSize"
|
||||
:total="rightListState.pagination.total"
|
||||
:show-size-changer="true"
|
||||
:show-total="(total) => `共 ${total} 条`"
|
||||
size="small"
|
||||
@change="handleRightPaginationChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Transfer>
|
||||
</Col>
|
||||
</Row>
|
||||
</Spin>
|
||||
<div v-if="direction === 'right'">
|
||||
<Pagination
|
||||
v-model:current="rightListState.pagination.current"
|
||||
v-model:page-size="rightListState.pagination.pageSize"
|
||||
:total="rightListState.pagination.total"
|
||||
:show-size-changer="true"
|
||||
:show-total="(total) => `共 ${total} 条`"
|
||||
size="small"
|
||||
@change="handleRightPaginationChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Transfer>
|
||||
</Col>
|
||||
</Row>
|
||||
<template #footer>
|
||||
<Button
|
||||
type="primary"
|
||||
@@ -490,7 +483,7 @@ defineExpose({
|
||||
</Button>
|
||||
<Button @click="handleCancel">{{ cancelText }}</Button>
|
||||
</template>
|
||||
</ModalComponent>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { SimpleFlowNode } from '../../consts';
|
||||
|
||||
import { ref, watch } from 'vue';
|
||||
import { nextTick, ref, watch } from 'vue';
|
||||
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
@@ -53,8 +53,6 @@ const condition = ref<any>({
|
||||
},
|
||||
});
|
||||
|
||||
// 显示名称输入框
|
||||
const showInput = ref(false);
|
||||
const conditionRef = ref();
|
||||
const fieldOptions = useFormFieldsAndStartUser(); // 流程表单字段和发起人字段
|
||||
|
||||
@@ -130,13 +128,24 @@ watch(
|
||||
currentNode.value = newValue;
|
||||
},
|
||||
);
|
||||
|
||||
// 显示名称输入框
|
||||
const showInput = ref(false);
|
||||
// 输入框的引用
|
||||
const inputRef = ref<HTMLInputElement | null>(null);
|
||||
// 监听 showInput 的变化,当变为 true 时自动聚焦
|
||||
watch(showInput, (value) => {
|
||||
if (value) {
|
||||
nextTick(() => {
|
||||
inputRef.value?.focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
function clickIcon() {
|
||||
showInput.value = true;
|
||||
}
|
||||
|
||||
// 输入框失去焦点
|
||||
function blurEvent() {
|
||||
// 修改节点名称
|
||||
function changeNodeName() {
|
||||
showInput.value = false;
|
||||
currentNode.value.name =
|
||||
currentNode.value.name ||
|
||||
@@ -153,10 +162,12 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
||||
<template #title>
|
||||
<div class="flex items-center">
|
||||
<Input
|
||||
ref="inputRef"
|
||||
v-if="showInput"
|
||||
type="text"
|
||||
class="mr-2 w-48"
|
||||
@blur="blurEvent()"
|
||||
@blur="changeNodeName()"
|
||||
@press-enter="changeNodeName()"
|
||||
v-model:value="currentNode.name"
|
||||
:placeholder="currentNode.name"
|
||||
/>
|
||||
@@ -166,7 +177,7 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
||||
@click="clickIcon()"
|
||||
>
|
||||
{{ currentNode.name }}
|
||||
<IconifyIcon class="ml-1" icon="ep:edit-pen" />
|
||||
<IconifyIcon class="ml-1" icon="lucide:edit-3" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -27,14 +27,13 @@ import {
|
||||
TreeSelect,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { BpmModelFormType } from '#/utils';
|
||||
import { BpmModelFormType, BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import {
|
||||
CANDIDATE_STRATEGY,
|
||||
CandidateStrategy,
|
||||
FieldPermissionType,
|
||||
MULTI_LEVEL_DEPT,
|
||||
NodeType,
|
||||
} from '../../consts';
|
||||
import {
|
||||
useFormFieldsPermission,
|
||||
@@ -76,9 +75,8 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
||||
const currentNode = useWatchNode(props);
|
||||
|
||||
// 节点名称
|
||||
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(
|
||||
NodeType.COPY_TASK_NODE,
|
||||
);
|
||||
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
|
||||
useNodeName(BpmNodeTypeEnum.COPY_TASK_NODE);
|
||||
|
||||
// 激活的 Tab 标签页
|
||||
const activeTabName = ref('user');
|
||||
@@ -137,7 +135,7 @@ const {
|
||||
getShowText,
|
||||
handleCandidateParam,
|
||||
parseCandidateParam,
|
||||
} = useNodeForm(NodeType.COPY_TASK_NODE);
|
||||
} = useNodeForm(BpmNodeTypeEnum.COPY_TASK_NODE);
|
||||
|
||||
const configForm = tempConfigForm as Ref<CopyTaskFormType>;
|
||||
// 抄送人策略, 去掉发起人自选 和 发起人自己
|
||||
@@ -214,15 +212,17 @@ defineExpose({ showCopyTaskNodeConfig }); // 暴露方法给父组件
|
||||
<div class="config-header">
|
||||
<Input
|
||||
v-if="showInput"
|
||||
ref="inputRef"
|
||||
type="text"
|
||||
class="config-editable-input"
|
||||
@blur="blurEvent()"
|
||||
@blur="changeNodeName()"
|
||||
@press-enter="changeNodeName()"
|
||||
v-model:value="nodeName"
|
||||
:placeholder="nodeName"
|
||||
/>
|
||||
<div v-else class="node-name">
|
||||
{{ nodeName }}
|
||||
<IconifyIcon class="ml-1" icon="ep:edit-pen" @click="clickIcon()" />
|
||||
<IconifyIcon class="ml-1" icon="lucide:edit-3" @click="clickIcon()" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -22,10 +22,11 @@ import {
|
||||
SelectOption,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import {
|
||||
DELAY_TYPE,
|
||||
DelayTypeEnum,
|
||||
NodeType,
|
||||
TIME_UNIT_TYPES,
|
||||
TimeUnitType,
|
||||
} from '../../consts';
|
||||
@@ -44,9 +45,8 @@ const props = defineProps({
|
||||
// 当前节点
|
||||
const currentNode = useWatchNode(props);
|
||||
// 节点名称
|
||||
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(
|
||||
NodeType.DELAY_TIMER_NODE,
|
||||
);
|
||||
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
|
||||
useNodeName(BpmNodeTypeEnum.DELAY_TIMER_NODE);
|
||||
// 抄送人表单配置
|
||||
const formRef = ref(); // 表单 Ref
|
||||
|
||||
@@ -157,9 +157,11 @@ defineExpose({ openDrawer }); // 暴露方法给父组件
|
||||
<div class="flex items-center">
|
||||
<Input
|
||||
v-if="showInput"
|
||||
ref="inputRef"
|
||||
type="text"
|
||||
class="mr-2 w-48"
|
||||
@blur="blurEvent()"
|
||||
@blur="changeNodeName()"
|
||||
@press-enter="changeNodeName()"
|
||||
v-model:value="nodeName"
|
||||
:placeholder="nodeName"
|
||||
/>
|
||||
@@ -169,7 +171,7 @@ defineExpose({ openDrawer }); // 暴露方法给父组件
|
||||
@click="clickIcon()"
|
||||
>
|
||||
{{ nodeName }}
|
||||
<IconifyIcon class="ml-1" icon="ep:edit-pen" :size="16" />
|
||||
<IconifyIcon class="ml-1" icon="lucide:edit-3" :size="16" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -33,6 +33,18 @@ const [Modal, modalApi] = useVbenModal({
|
||||
title: '条件配置',
|
||||
destroyOnClose: true,
|
||||
draggable: true,
|
||||
onOpenChange(isOpen) {
|
||||
if (isOpen) {
|
||||
// 获取传递的数据
|
||||
const conditionObj = modalApi.getData();
|
||||
if (conditionObj) {
|
||||
conditionData.value.conditionType = conditionObj.conditionType;
|
||||
conditionData.value.conditionExpression =
|
||||
conditionObj.conditionExpression;
|
||||
conditionData.value.conditionGroups = conditionObj.conditionGroups;
|
||||
}
|
||||
}
|
||||
},
|
||||
async onConfirm() {
|
||||
// 校验表单
|
||||
if (!conditionRef.value) return;
|
||||
@@ -50,17 +62,8 @@ const [Modal, modalApi] = useVbenModal({
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: jason open 在 useVbenModal 中 onOpenChange 方法
|
||||
function open(conditionObj: any | undefined) {
|
||||
if (conditionObj) {
|
||||
conditionData.value.conditionType = conditionObj.conditionType;
|
||||
conditionData.value.conditionExpression = conditionObj.conditionExpression;
|
||||
conditionData.value.conditionGroups = conditionObj.conditionGroups;
|
||||
}
|
||||
modalApi.open();
|
||||
}
|
||||
// TODO: jason 不需要暴露expose,直接使用modalApi.setData(formSetting).open()
|
||||
defineExpose({ open });
|
||||
// TODO xingyu 暴露 modalApi 给父组件是否合适? trigger-node-config.vue 会有多个 conditionDialog 实例
|
||||
defineExpose({ modalApi });
|
||||
</script>
|
||||
<template>
|
||||
<Modal class="w-1/2">
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
Tooltip,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { BpmModelFormType } from '#/utils/constants';
|
||||
import { BpmModelFormType } from '#/utils';
|
||||
|
||||
import {
|
||||
COMPARISON_OPERATORS,
|
||||
@@ -188,7 +188,7 @@ defineExpose({ validate });
|
||||
>
|
||||
<IconifyIcon
|
||||
color="#0089ff"
|
||||
icon="ep:circle-close-filled"
|
||||
icon="lucide:circle-x"
|
||||
class="size-4"
|
||||
@click="
|
||||
deleteConditionGroup(condition.conditionGroups.conditions, cIdx)
|
||||
|
||||
@@ -21,7 +21,9 @@ import {
|
||||
SelectOption,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { ConditionType, NodeType } from '../../consts';
|
||||
import { BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import { ConditionType } from '../../consts';
|
||||
import { useNodeName, useWatchNode } from '../../helpers';
|
||||
import Condition from './modules/condition.vue';
|
||||
|
||||
@@ -39,9 +41,8 @@ const processNodeTree = inject<Ref<SimpleFlowNode>>('processNodeTree');
|
||||
/** 当前节点 */
|
||||
const currentNode = useWatchNode(props);
|
||||
/** 节点名称 */
|
||||
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(
|
||||
NodeType.ROUTER_BRANCH_NODE,
|
||||
);
|
||||
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
|
||||
useNodeName(BpmNodeTypeEnum.ROUTER_BRANCH_NODE);
|
||||
const routerGroups = ref<RouterSetting[]>([]);
|
||||
const nodeOptions = ref<any[]>([]);
|
||||
const conditionRef = ref<any[]>([]);
|
||||
@@ -176,15 +177,15 @@ function getRouterNode(node: any) {
|
||||
while (true) {
|
||||
if (!node) break;
|
||||
if (
|
||||
node.type !== NodeType.ROUTER_BRANCH_NODE &&
|
||||
node.type !== NodeType.CONDITION_NODE
|
||||
node.type !== BpmNodeTypeEnum.ROUTER_BRANCH_NODE &&
|
||||
node.type !== BpmNodeTypeEnum.CONDITION_NODE
|
||||
) {
|
||||
nodeOptions.value.push({
|
||||
label: node.name,
|
||||
value: node.id,
|
||||
});
|
||||
}
|
||||
if (!node.childNode || node.type === NodeType.END_EVENT_NODE) {
|
||||
if (!node.childNode || node.type === BpmNodeTypeEnum.END_EVENT_NODE) {
|
||||
break;
|
||||
}
|
||||
if (node.conditionNodes && node.conditionNodes.length > 0) {
|
||||
@@ -199,14 +200,16 @@ function getRouterNode(node: any) {
|
||||
defineExpose({ openDrawer }); // 暴露方法给父组件
|
||||
</script>
|
||||
<template>
|
||||
<Drawer class="w-[630px]">
|
||||
<Drawer class="w-[40%]">
|
||||
<template #title>
|
||||
<div class="flex items-center">
|
||||
<Input
|
||||
ref="inputRef"
|
||||
v-if="showInput"
|
||||
type="text"
|
||||
class="mr-2 w-48"
|
||||
@blur="blurEvent()"
|
||||
@blur="changeNodeName()"
|
||||
@press-enter="changeNodeName()"
|
||||
v-model:value="nodeName"
|
||||
:placeholder="nodeName"
|
||||
/>
|
||||
@@ -216,7 +219,7 @@ defineExpose({ openDrawer }); // 暴露方法给父组件
|
||||
@click="clickIcon()"
|
||||
>
|
||||
{{ nodeName }}
|
||||
<IconifyIcon class="ml-1" icon="ep:edit-pen" />
|
||||
<IconifyIcon class="ml-1" icon="lucide:edit-3" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -263,7 +266,7 @@ defineExpose({ openDrawer }); // 暴露方法给父组件
|
||||
@click="deleteRouterGroup(index)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:close" />
|
||||
<IconifyIcon icon="lucide:x" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -284,7 +287,7 @@ defineExpose({ openDrawer }); // 暴露方法给父组件
|
||||
@click="addRouterGroup"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:setting" />
|
||||
<IconifyIcon icon="lucide:settings" />
|
||||
</template>
|
||||
新增路由分支
|
||||
</Button>
|
||||
|
||||
@@ -23,13 +23,9 @@ import {
|
||||
TypographyText,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { BpmModelFormType } from '#/utils';
|
||||
import { BpmModelFormType, BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import {
|
||||
FieldPermissionType,
|
||||
NodeType,
|
||||
START_USER_BUTTON_SETTING,
|
||||
} from '../../consts';
|
||||
import { FieldPermissionType, START_USER_BUTTON_SETTING } from '../../consts';
|
||||
import {
|
||||
useFormFieldsPermission,
|
||||
useNodeName,
|
||||
@@ -56,9 +52,8 @@ const deptOptions = inject<Ref<SystemDeptApi.Dept[]>>('deptList');
|
||||
// 当前节点
|
||||
const currentNode = useWatchNode(props);
|
||||
// 节点名称
|
||||
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(
|
||||
NodeType.COPY_TASK_NODE,
|
||||
);
|
||||
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
|
||||
useNodeName(BpmNodeTypeEnum.START_USER_NODE);
|
||||
// 激活的 Tab 标签页
|
||||
const activeTabName = ref('user');
|
||||
|
||||
@@ -149,12 +144,13 @@ defineExpose({ showStartUserNodeConfig });
|
||||
<Drawer>
|
||||
<template #title>
|
||||
<div class="config-header">
|
||||
<!-- TODO v-mountedFocus 自动聚集 需要迁移一下 -->
|
||||
<Input
|
||||
ref="inputRef"
|
||||
v-if="showInput"
|
||||
type="text"
|
||||
class="config-editable-input"
|
||||
@blur="blurEvent()"
|
||||
@blur="changeNodeName()"
|
||||
@press-enter="changeNodeName()"
|
||||
v-model:value="nodeName"
|
||||
:placeholder="nodeName"
|
||||
/>
|
||||
@@ -162,7 +158,7 @@ defineExpose({ showStartUserNodeConfig });
|
||||
{{ nodeName }}
|
||||
<IconifyIcon
|
||||
class="ml-1"
|
||||
icon="ep:edit-pen"
|
||||
icon="lucide:edit-3"
|
||||
:size="16"
|
||||
@click="clickIcon()"
|
||||
/>
|
||||
|
||||
@@ -29,9 +29,10 @@ import {
|
||||
Tag,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import {
|
||||
DEFAULT_CONDITION_GROUP_VALUE,
|
||||
NodeType,
|
||||
TRIGGER_TYPES,
|
||||
TriggerTypeEnum,
|
||||
} from '../../consts';
|
||||
@@ -71,9 +72,8 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
||||
// 当前节点
|
||||
const currentNode = useWatchNode(props);
|
||||
// 节点名称
|
||||
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(
|
||||
NodeType.TRIGGER_NODE,
|
||||
);
|
||||
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
|
||||
useNodeName(BpmNodeTypeEnum.TRIGGER_NODE);
|
||||
// 触发器表单配置
|
||||
const formRef = ref(); // 表单 Ref
|
||||
|
||||
@@ -200,8 +200,8 @@ function addFormSettingCondition(
|
||||
formSetting: FormTriggerSetting,
|
||||
) {
|
||||
const conditionDialog = proxy.$refs[`condition-${index}`][0];
|
||||
// TODO: jason Modal 使用 useVbenModal 初始化,弹出使用modalApi.setData(formSetting).open()
|
||||
conditionDialog.open(formSetting);
|
||||
// 使用modalApi来打开模态框并传递数据
|
||||
conditionDialog.modalApi.setData(formSetting).open();
|
||||
}
|
||||
|
||||
/** 删除条件配置 */
|
||||
@@ -215,7 +215,8 @@ function openFormSettingCondition(
|
||||
formSetting: FormTriggerSetting,
|
||||
) {
|
||||
const conditionDialog = proxy.$refs[`condition-${index}`][0];
|
||||
conditionDialog.open(formSetting);
|
||||
// 使用 modalApi 来打开模态框并传递数据
|
||||
conditionDialog.modalApi.setData(formSetting).open();
|
||||
}
|
||||
|
||||
/** 处理条件配置保存 */
|
||||
@@ -387,16 +388,18 @@ onMounted(() => {
|
||||
<template #title>
|
||||
<div class="config-header">
|
||||
<Input
|
||||
ref="inputRef"
|
||||
v-if="showInput"
|
||||
type="text"
|
||||
class="config-editable-input"
|
||||
@blur="blurEvent()"
|
||||
@blur="changeNodeName()"
|
||||
@press-enter="changeNodeName()"
|
||||
v-model:value="nodeName"
|
||||
:placeholder="nodeName"
|
||||
/>
|
||||
<div v-else class="node-name">
|
||||
{{ nodeName }}
|
||||
<IconifyIcon class="ml-1" icon="ep:edit-pen" @click="clickIcon()" />
|
||||
<IconifyIcon class="ml-1" icon="lucide:edit-3" @click="clickIcon()" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -453,7 +456,7 @@ onMounted(() => {
|
||||
@click="deleteFormSetting(index)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:close" />
|
||||
<IconifyIcon icon="lucide:x" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -483,7 +486,7 @@ onMounted(() => {
|
||||
@click="addFormSettingCondition(index, formSetting)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:link" />
|
||||
<IconifyIcon icon="lucide:link" />
|
||||
</template>
|
||||
添加条件
|
||||
</Button>
|
||||
@@ -558,7 +561,7 @@ onMounted(() => {
|
||||
@click="addFormFieldSetting(formSetting)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:memo" />
|
||||
<IconifyIcon icon="lucide:file-cog" />
|
||||
</template>
|
||||
添加修改字段
|
||||
</Button>
|
||||
@@ -576,7 +579,7 @@ onMounted(() => {
|
||||
@click="addFormSetting"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:setting" />
|
||||
<IconifyIcon icon="lucide:settings" />
|
||||
</template>
|
||||
添加设置
|
||||
</Button>
|
||||
@@ -601,7 +604,7 @@ onMounted(() => {
|
||||
@click="deleteFormSetting(index)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:close" />
|
||||
<IconifyIcon icon="lucide:x" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -632,7 +635,7 @@ onMounted(() => {
|
||||
@click="addFormSettingCondition(index, formSetting)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:link" />
|
||||
<IconifyIcon icon="lucide:link" />
|
||||
</template>
|
||||
添加条件
|
||||
</Button>
|
||||
@@ -670,7 +673,7 @@ onMounted(() => {
|
||||
@click="addFormSetting"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:setting" />
|
||||
<IconifyIcon icon="lucide:settings" />
|
||||
</template>
|
||||
添加设置
|
||||
</Button>
|
||||
|
||||
@@ -34,7 +34,11 @@ import {
|
||||
TypographyText,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { BpmModelFormType } from '#/utils';
|
||||
import {
|
||||
BpmModelFormType,
|
||||
BpmNodeTypeEnum,
|
||||
ProcessVariableEnum,
|
||||
} from '#/utils';
|
||||
|
||||
import {
|
||||
APPROVE_METHODS,
|
||||
@@ -49,9 +53,7 @@ import {
|
||||
DEFAULT_BUTTON_SETTING,
|
||||
FieldPermissionType,
|
||||
MULTI_LEVEL_DEPT,
|
||||
NodeType,
|
||||
OPERATION_BUTTON_NAME,
|
||||
ProcessVariableEnum,
|
||||
REJECT_HANDLER_TYPES,
|
||||
RejectHandlerType,
|
||||
TIME_UNIT_TYPES,
|
||||
@@ -112,9 +114,8 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
||||
});
|
||||
|
||||
// 节点名称配置
|
||||
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(
|
||||
NodeType.USER_TASK_NODE,
|
||||
);
|
||||
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
|
||||
useNodeName(BpmNodeTypeEnum.USER_TASK_NODE);
|
||||
|
||||
// 激活的 Tab 标签页
|
||||
const activeTabName = ref('user');
|
||||
@@ -245,7 +246,9 @@ const userTaskListenerRef = ref();
|
||||
|
||||
/** 节点类型名称 */
|
||||
const nodeTypeName = computed(() => {
|
||||
return currentNode.value.type === NodeType.TRANSACTOR_NODE ? '办理' : '审批';
|
||||
return currentNode.value.type === BpmNodeTypeEnum.TRANSACTOR_NODE
|
||||
? '办理'
|
||||
: '审批';
|
||||
});
|
||||
|
||||
/** 校验节点配置 */
|
||||
@@ -407,7 +410,7 @@ function showUserTaskNodeConfig(node: SimpleFlowNode) {
|
||||
// 3. 操作按钮设置
|
||||
buttonsSetting.value =
|
||||
cloneDeep(node.buttonsSetting) ||
|
||||
(node.type === NodeType.TRANSACTOR_NODE
|
||||
(node.type === BpmNodeTypeEnum.TRANSACTOR_NODE
|
||||
? TRANSACTOR_DEFAULT_BUTTON_SETTING
|
||||
: DEFAULT_BUTTON_SETTING);
|
||||
// 4. 表单字段权限配置
|
||||
@@ -582,20 +585,22 @@ onMounted(() => {
|
||||
<div class="config-header">
|
||||
<Input
|
||||
v-if="showInput"
|
||||
ref="inputRef"
|
||||
type="text"
|
||||
class="config-editable-input"
|
||||
@blur="blurEvent()"
|
||||
@blur="changeNodeName()"
|
||||
@press-enter="changeNodeName()"
|
||||
v-model:value="nodeName"
|
||||
:placeholder="nodeName"
|
||||
/>
|
||||
<div v-else class="node-name">
|
||||
{{ nodeName }}
|
||||
<IconifyIcon class="ml-1" icon="ep:edit-pen" @click="clickIcon()" />
|
||||
<IconifyIcon class="ml-1" icon="lucide:edit-3" @click="clickIcon()" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
v-if="currentNode.type === NodeType.USER_TASK_NODE"
|
||||
v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE"
|
||||
class="mb-3 flex items-center"
|
||||
>
|
||||
<span class="mr-3 text-[16px]">审批类型 :</span>
|
||||
@@ -860,7 +865,7 @@ onMounted(() => {
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
|
||||
<div v-if="currentNode.type === NodeType.USER_TASK_NODE">
|
||||
<div v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE">
|
||||
<Divider content-position="left">审批人拒绝时</Divider>
|
||||
<FormItem name="rejectHandlerType">
|
||||
<RadioGroup
|
||||
@@ -902,7 +907,7 @@ onMounted(() => {
|
||||
</FormItem>
|
||||
</div>
|
||||
|
||||
<div v-if="currentNode.type === NodeType.USER_TASK_NODE">
|
||||
<div v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE">
|
||||
<Divider content-position="left">审批人超时未处理时</Divider>
|
||||
<FormItem
|
||||
label="启用开关"
|
||||
@@ -1047,7 +1052,7 @@ onMounted(() => {
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<div v-if="currentNode.type === NodeType.USER_TASK_NODE">
|
||||
<div v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE">
|
||||
<Divider content-position="left">
|
||||
审批人与提交人为同一人时
|
||||
</Divider>
|
||||
@@ -1070,7 +1075,7 @@ onMounted(() => {
|
||||
</FormItem>
|
||||
</div>
|
||||
|
||||
<div v-if="currentNode.type === NodeType.USER_TASK_NODE">
|
||||
<div v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE">
|
||||
<Divider content-position="left">是否需要签名</Divider>
|
||||
<FormItem name="signEnable">
|
||||
<Switch
|
||||
@@ -1081,7 +1086,7 @@ onMounted(() => {
|
||||
</FormItem>
|
||||
</div>
|
||||
|
||||
<div v-if="currentNode.type === NodeType.USER_TASK_NODE">
|
||||
<div v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE">
|
||||
<Divider content-position="left">审批意见</Divider>
|
||||
<FormItem name="reasonRequire">
|
||||
<Switch
|
||||
@@ -1096,7 +1101,7 @@ onMounted(() => {
|
||||
</TabPane>
|
||||
<TabPane
|
||||
tab="操作按钮设置"
|
||||
v-if="currentNode.type === NodeType.USER_TASK_NODE"
|
||||
v-if="currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE"
|
||||
key="buttons"
|
||||
>
|
||||
<div class="p-1">
|
||||
@@ -1130,7 +1135,7 @@ onMounted(() => {
|
||||
<Button v-else text @click="changeBtnDisplayName(index)">
|
||||
<div class="flex items-center">
|
||||
{{ item.displayName }}
|
||||
<IconifyIcon icon="ep:edit" class="ml-2" />
|
||||
<IconifyIcon icon="lucide:edit" class="ml-2" />
|
||||
</div>
|
||||
</Button>
|
||||
</Col>
|
||||
|
||||
@@ -7,7 +7,9 @@ import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Input } from 'ant-design-vue';
|
||||
|
||||
import { NODE_DEFAULT_TEXT, NodeType } from '../../consts';
|
||||
import { BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import { NODE_DEFAULT_TEXT } from '../../consts';
|
||||
import { useNodeName2, useTaskStatusClass, useWatchNode } from '../../helpers';
|
||||
import CopyTaskNodeConfig from '../nodes-config/copy-task-node-config.vue';
|
||||
import NodeHandler from './node-handler.vue';
|
||||
@@ -30,9 +32,9 @@ const readonly = inject<Boolean>('readonly');
|
||||
// 监控节点的变化
|
||||
const currentNode = useWatchNode(props);
|
||||
// 节点名称编辑
|
||||
const { showInput, blurEvent, clickTitle } = useNodeName2(
|
||||
const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
|
||||
currentNode,
|
||||
NodeType.COPY_TASK_NODE,
|
||||
BpmNodeTypeEnum.COPY_TASK_NODE,
|
||||
);
|
||||
|
||||
const nodeSetting = ref();
|
||||
@@ -65,11 +67,13 @@ function deleteNode() {
|
||||
<span class="iconfont icon-copy"></span>
|
||||
</div>
|
||||
<Input
|
||||
ref="inputRef"
|
||||
v-if="!readonly && showInput"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
v-model="currentNode.name"
|
||||
@blur="changeNodeName()"
|
||||
@press-enter="changeNodeName()"
|
||||
v-model:value="currentNode.name"
|
||||
:placeholder="currentNode.name"
|
||||
/>
|
||||
<div v-else class="node-title" @click="clickTitle">
|
||||
@@ -85,15 +89,15 @@ function deleteNode() {
|
||||
{{ currentNode.showText }}
|
||||
</div>
|
||||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.COPY_TASK_NODE) }}
|
||||
{{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.COPY_TASK_NODE) }}
|
||||
</div>
|
||||
<IconifyIcon v-if="!readonly" icon="ep:arrow-right-bold" />
|
||||
<IconifyIcon v-if="!readonly" icon="lucide:chevron-right" />
|
||||
</div>
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon">
|
||||
<IconifyIcon
|
||||
color="#0089ff"
|
||||
icon="ep:circle-close-filled"
|
||||
icon="lucide:circle-x"
|
||||
:size="18"
|
||||
@click="deleteNode"
|
||||
/>
|
||||
|
||||
@@ -7,7 +7,9 @@ import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Input } from 'ant-design-vue';
|
||||
|
||||
import { NODE_DEFAULT_TEXT, NodeType } from '../../consts';
|
||||
import { BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import { NODE_DEFAULT_TEXT } from '../../consts';
|
||||
import { useNodeName2, useTaskStatusClass, useWatchNode } from '../../helpers';
|
||||
import DelayTimerNodeConfig from '../nodes-config/delay-timer-node-config.vue';
|
||||
import NodeHandler from './node-handler.vue';
|
||||
@@ -28,9 +30,9 @@ const readonly = inject<Boolean>('readonly');
|
||||
// 监控节点的变化
|
||||
const currentNode = useWatchNode(props);
|
||||
// 节点名称编辑
|
||||
const { showInput, blurEvent, clickTitle } = useNodeName2(
|
||||
const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
|
||||
currentNode,
|
||||
NodeType.DELAY_TIMER_NODE,
|
||||
BpmNodeTypeEnum.DELAY_TIMER_NODE,
|
||||
);
|
||||
|
||||
const nodeSetting = ref();
|
||||
@@ -62,11 +64,13 @@ function deleteNode() {
|
||||
<span class="iconfont icon-delay"></span>
|
||||
</div>
|
||||
<Input
|
||||
ref="inputRef"
|
||||
v-if="!readonly && showInput"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
v-model="currentNode.name"
|
||||
@blur="changeNodeName()"
|
||||
@press-enter="changeNodeName()"
|
||||
v-model:value="currentNode.name"
|
||||
:placeholder="currentNode.name"
|
||||
/>
|
||||
<div v-else class="node-title" @click="clickTitle">
|
||||
@@ -82,15 +86,15 @@ function deleteNode() {
|
||||
{{ currentNode.showText }}
|
||||
</div>
|
||||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.DELAY_TIMER_NODE) }}
|
||||
{{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.DELAY_TIMER_NODE) }}
|
||||
</div>
|
||||
<IconifyIcon v-if="!readonly" icon="ep:arrow-right-bold" />
|
||||
<IconifyIcon v-if="!readonly" icon="lucide:chevron-right" />
|
||||
</div>
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon">
|
||||
<IconifyIcon
|
||||
color="#0089ff"
|
||||
icon="ep:circle-close-filled"
|
||||
icon="lucide:circle-x"
|
||||
:size="18"
|
||||
@click="deleteNode"
|
||||
/>
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import type { SimpleFlowNode } from '../../consts';
|
||||
|
||||
import { getCurrentInstance, inject, ref, watch } from 'vue';
|
||||
import { getCurrentInstance, inject, nextTick, ref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { cloneDeep, buildShortUUID as generateUUID } from '@vben/utils';
|
||||
|
||||
import { Button, Input } from 'ant-design-vue';
|
||||
|
||||
import { BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import {
|
||||
ConditionType,
|
||||
DEFAULT_CONDITION_GROUP_VALUE,
|
||||
NODE_DEFAULT_TEXT,
|
||||
NodeType,
|
||||
} from '../../consts';
|
||||
import { getDefaultConditionNodeName, useTaskStatusClass } from '../../helpers';
|
||||
import ConditionNodeConfig from '../nodes-config/condition-node-config.vue';
|
||||
@@ -50,11 +51,30 @@ watch(
|
||||
currentNode.value = newValue;
|
||||
},
|
||||
);
|
||||
|
||||
// 条件节点名称输入框引用
|
||||
const inputRefs = ref<HTMLInputElement[]>([]);
|
||||
// 节点名称输入框显示状态
|
||||
const showInputs = ref<boolean[]>([]);
|
||||
|
||||
// 失去焦点
|
||||
function blurEvent(index: number) {
|
||||
// 监听显示状态变化
|
||||
watch(
|
||||
showInputs,
|
||||
(newValues) => {
|
||||
// 当状态为 true 时, 自动聚焦
|
||||
newValues.forEach((value, index) => {
|
||||
if (value) {
|
||||
// 当显示状态从 false 变为 true 时, 自动聚焦
|
||||
nextTick(() => {
|
||||
inputRefs.value[index]?.focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// 修改节点名称
|
||||
function changeNodeName(index: number) {
|
||||
showInputs.value[index] = false;
|
||||
const conditionNode = currentNode.value.conditionNodes?.at(
|
||||
index,
|
||||
@@ -90,7 +110,7 @@ function addCondition() {
|
||||
id: `Flow_${generateUUID()}`,
|
||||
name: `条件${len}`,
|
||||
showText: '',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
type: BpmNodeTypeEnum.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
conditionNodes: [],
|
||||
conditionSetting: {
|
||||
@@ -138,7 +158,7 @@ function recursiveFindParentNode(
|
||||
node: SimpleFlowNode,
|
||||
nodeType: number,
|
||||
) {
|
||||
if (!node || node.type === NodeType.START_USER_NODE) {
|
||||
if (!node || node.type === BpmNodeTypeEnum.START_USER_NODE) {
|
||||
return;
|
||||
}
|
||||
if (node.type === nodeType) {
|
||||
@@ -187,10 +207,16 @@ function recursiveFindParentNode(
|
||||
<div class="branch-node-title-container">
|
||||
<div v-if="!readonly && showInputs[index]">
|
||||
<Input
|
||||
:ref="
|
||||
(el) => {
|
||||
inputRefs[index] = el as HTMLInputElement;
|
||||
}
|
||||
"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent(index)"
|
||||
v-model="item.name"
|
||||
@blur="changeNodeName(index)"
|
||||
@press-enter="changeNodeName(index)"
|
||||
v-model:value="item.name"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="branch-title" @click="clickEvent(index)">
|
||||
@@ -210,7 +236,7 @@ function recursiveFindParentNode(
|
||||
{{ item.showText }}
|
||||
</div>
|
||||
<div class="branch-node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||
{{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.CONDITION_NODE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -222,7 +248,7 @@ function recursiveFindParentNode(
|
||||
<div class="toolbar-icon">
|
||||
<IconifyIcon
|
||||
color="#0089ff"
|
||||
icon="ep:circle-close-filled"
|
||||
icon="lucide:circle-x"
|
||||
:size="18"
|
||||
@click="deleteCondition(index)"
|
||||
/>
|
||||
@@ -237,7 +263,7 @@ function recursiveFindParentNode(
|
||||
"
|
||||
@click="moveNode(index, -1)"
|
||||
>
|
||||
<IconifyIcon icon="ep:arrow-left" />
|
||||
<IconifyIcon icon="lucide:chevron-left" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -249,7 +275,7 @@ function recursiveFindParentNode(
|
||||
"
|
||||
@click="moveNode(index, 1)"
|
||||
>
|
||||
<IconifyIcon icon="ep:arrow-right" />
|
||||
<IconifyIcon icon="lucide:chevron-right" />
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import type { SimpleFlowNode } from '../../consts';
|
||||
|
||||
import { getCurrentInstance, inject, ref, watch } from 'vue';
|
||||
import { getCurrentInstance, inject, nextTick, ref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { cloneDeep, buildShortUUID as generateUUID } from '@vben/utils';
|
||||
|
||||
import { Button, Input } from 'ant-design-vue';
|
||||
|
||||
import { BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import {
|
||||
ConditionType,
|
||||
DEFAULT_CONDITION_GROUP_VALUE,
|
||||
NODE_DEFAULT_TEXT,
|
||||
NodeType,
|
||||
} from '../../consts';
|
||||
import {
|
||||
getDefaultInclusiveConditionNodeName,
|
||||
@@ -56,10 +57,28 @@ watch(
|
||||
currentNode.value = newValue;
|
||||
},
|
||||
);
|
||||
|
||||
// 条件节点名称输入框引用
|
||||
const inputRefs = ref<HTMLInputElement[]>([]);
|
||||
// 节点名称输入框显示状态
|
||||
const showInputs = ref<boolean[]>([]);
|
||||
// 失去焦点
|
||||
function blurEvent(index: number) {
|
||||
// 监听显示状态变化
|
||||
watch(
|
||||
showInputs,
|
||||
(newValues) => {
|
||||
// 当状态为 true 时, 自动聚焦
|
||||
newValues.forEach((value, index) => {
|
||||
if (value) {
|
||||
// 当显示状态从 false 变为 true 时, 自动聚焦
|
||||
nextTick(() => {
|
||||
inputRefs.value[index]?.focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
// 修改节点名称
|
||||
function changeNodeName(index: number) {
|
||||
showInputs.value[index] = false;
|
||||
const conditionNode = currentNode.value.conditionNodes?.at(
|
||||
index,
|
||||
@@ -95,7 +114,7 @@ function addCondition() {
|
||||
id: `Flow_${generateUUID()}`,
|
||||
name: `包容条件${len}`,
|
||||
showText: '',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
type: BpmNodeTypeEnum.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
conditionNodes: [],
|
||||
conditionSetting: {
|
||||
@@ -143,7 +162,7 @@ function recursiveFindParentNode(
|
||||
node: SimpleFlowNode,
|
||||
nodeType: number,
|
||||
) {
|
||||
if (!node || node.type === NodeType.START_USER_NODE) {
|
||||
if (!node || node.type === BpmNodeTypeEnum.START_USER_NODE) {
|
||||
return;
|
||||
}
|
||||
if (node.type === nodeType) {
|
||||
@@ -191,10 +210,16 @@ function recursiveFindParentNode(
|
||||
<div class="branch-node-title-container">
|
||||
<div v-if="!readonly && showInputs[index]">
|
||||
<Input
|
||||
:ref="
|
||||
(el) => {
|
||||
inputRefs[index] = el as HTMLInputElement;
|
||||
}
|
||||
"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent(index)"
|
||||
v-model="item.name"
|
||||
@blur="changeNodeName(index)"
|
||||
@press-enter="changeNodeName(index)"
|
||||
v-model:value="item.name"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="branch-title" @click="clickEvent(index)">
|
||||
@@ -213,7 +238,7 @@ function recursiveFindParentNode(
|
||||
{{ item.showText }}
|
||||
</div>
|
||||
<div class="branch-node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||
{{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.CONDITION_NODE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -225,7 +250,7 @@ function recursiveFindParentNode(
|
||||
<div class="toolbar-icon">
|
||||
<IconifyIcon
|
||||
color="#0089ff"
|
||||
icon="ep:circle-close-filled"
|
||||
icon="lucide:circle-x"
|
||||
:size="18"
|
||||
@click="deleteCondition(index)"
|
||||
/>
|
||||
@@ -240,7 +265,7 @@ function recursiveFindParentNode(
|
||||
"
|
||||
@click="moveNode(index, -1)"
|
||||
>
|
||||
<IconifyIcon icon="ep:arrow-left" />
|
||||
<IconifyIcon icon="lucide:chevron-left" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -252,7 +277,7 @@ function recursiveFindParentNode(
|
||||
"
|
||||
@click="moveNode(index, 1)"
|
||||
>
|
||||
<IconifyIcon icon="ep:arrow-right" />
|
||||
<IconifyIcon icon="lucide:chevron-right" />
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler
|
||||
|
||||
@@ -8,6 +8,8 @@ import { cloneDeep, buildShortUUID as generateUUID } from '@vben/utils';
|
||||
|
||||
import { message, Popover } from 'ant-design-vue';
|
||||
|
||||
import { BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import {
|
||||
ApproveMethodType,
|
||||
AssignEmptyHandlerType,
|
||||
@@ -15,7 +17,6 @@ import {
|
||||
ConditionType,
|
||||
DEFAULT_CONDITION_GROUP_VALUE,
|
||||
NODE_DEFAULT_NAME,
|
||||
NodeType,
|
||||
RejectHandlerType,
|
||||
} from '../../consts';
|
||||
|
||||
@@ -41,17 +42,21 @@ const readonly = inject<Boolean>('readonly'); // 是否只读
|
||||
function addNode(type: number) {
|
||||
// 校验:条件分支、包容分支后面,不允许直接添加并行分支
|
||||
if (
|
||||
type === NodeType.PARALLEL_BRANCH_NODE &&
|
||||
[NodeType.CONDITION_BRANCH_NODE, NodeType.INCLUSIVE_BRANCH_NODE].includes(
|
||||
props.currentNode?.type,
|
||||
)
|
||||
type === BpmNodeTypeEnum.PARALLEL_BRANCH_NODE &&
|
||||
[
|
||||
BpmNodeTypeEnum.CONDITION_BRANCH_NODE,
|
||||
BpmNodeTypeEnum.INCLUSIVE_BRANCH_NODE,
|
||||
].includes(props.currentNode?.type)
|
||||
) {
|
||||
message.error('条件分支、包容分支后面,不允许直接添加并行分支');
|
||||
return;
|
||||
}
|
||||
|
||||
popoverShow.value = false;
|
||||
if (type === NodeType.USER_TASK_NODE || type === NodeType.TRANSACTOR_NODE) {
|
||||
if (
|
||||
type === BpmNodeTypeEnum.USER_TASK_NODE ||
|
||||
type === BpmNodeTypeEnum.TRANSACTOR_NODE
|
||||
) {
|
||||
const id = `Activity_${generateUUID()}`;
|
||||
const data: SimpleFlowNode = {
|
||||
id,
|
||||
@@ -83,20 +88,20 @@ function addNode(type: number) {
|
||||
};
|
||||
emits('update:childNode', data);
|
||||
}
|
||||
if (type === NodeType.COPY_TASK_NODE) {
|
||||
if (type === BpmNodeTypeEnum.COPY_TASK_NODE) {
|
||||
const data: SimpleFlowNode = {
|
||||
id: `Activity_${generateUUID()}`,
|
||||
name: NODE_DEFAULT_NAME.get(NodeType.COPY_TASK_NODE) as string,
|
||||
name: NODE_DEFAULT_NAME.get(BpmNodeTypeEnum.COPY_TASK_NODE) as string,
|
||||
showText: '',
|
||||
type: NodeType.COPY_TASK_NODE,
|
||||
type: BpmNodeTypeEnum.COPY_TASK_NODE,
|
||||
childNode: props.childNode,
|
||||
};
|
||||
emits('update:childNode', data);
|
||||
}
|
||||
if (type === NodeType.CONDITION_BRANCH_NODE) {
|
||||
if (type === BpmNodeTypeEnum.CONDITION_BRANCH_NODE) {
|
||||
const data: SimpleFlowNode = {
|
||||
name: '条件分支',
|
||||
type: NodeType.CONDITION_BRANCH_NODE,
|
||||
type: BpmNodeTypeEnum.CONDITION_BRANCH_NODE,
|
||||
id: `GateWay_${generateUUID()}`,
|
||||
childNode: props.childNode,
|
||||
conditionNodes: [
|
||||
@@ -104,7 +109,7 @@ function addNode(type: number) {
|
||||
id: `Flow_${generateUUID()}`,
|
||||
name: '条件1',
|
||||
showText: '',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
type: BpmNodeTypeEnum.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
conditionSetting: {
|
||||
defaultFlow: false,
|
||||
@@ -116,7 +121,7 @@ function addNode(type: number) {
|
||||
id: `Flow_${generateUUID()}`,
|
||||
name: '其它情况',
|
||||
showText: '未满足其它条件时,将进入此分支',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
type: BpmNodeTypeEnum.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
conditionSetting: {
|
||||
defaultFlow: true,
|
||||
@@ -126,10 +131,10 @@ function addNode(type: number) {
|
||||
};
|
||||
emits('update:childNode', data);
|
||||
}
|
||||
if (type === NodeType.PARALLEL_BRANCH_NODE) {
|
||||
if (type === BpmNodeTypeEnum.PARALLEL_BRANCH_NODE) {
|
||||
const data: SimpleFlowNode = {
|
||||
name: '并行分支',
|
||||
type: NodeType.PARALLEL_BRANCH_NODE,
|
||||
type: BpmNodeTypeEnum.PARALLEL_BRANCH_NODE,
|
||||
id: `GateWay_${generateUUID()}`,
|
||||
childNode: props.childNode,
|
||||
conditionNodes: [
|
||||
@@ -137,24 +142,24 @@ function addNode(type: number) {
|
||||
id: `Flow_${generateUUID()}`,
|
||||
name: '并行1',
|
||||
showText: '无需配置条件同时执行',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
type: BpmNodeTypeEnum.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
},
|
||||
{
|
||||
id: `Flow_${generateUUID()}`,
|
||||
name: '并行2',
|
||||
showText: '无需配置条件同时执行',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
type: BpmNodeTypeEnum.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
},
|
||||
],
|
||||
};
|
||||
emits('update:childNode', data);
|
||||
}
|
||||
if (type === NodeType.INCLUSIVE_BRANCH_NODE) {
|
||||
if (type === BpmNodeTypeEnum.INCLUSIVE_BRANCH_NODE) {
|
||||
const data: SimpleFlowNode = {
|
||||
name: '包容分支',
|
||||
type: NodeType.INCLUSIVE_BRANCH_NODE,
|
||||
type: BpmNodeTypeEnum.INCLUSIVE_BRANCH_NODE,
|
||||
id: `GateWay_${generateUUID()}`,
|
||||
childNode: props.childNode,
|
||||
conditionNodes: [
|
||||
@@ -162,7 +167,7 @@ function addNode(type: number) {
|
||||
id: `Flow_${generateUUID()}`,
|
||||
name: '包容条件1',
|
||||
showText: '',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
type: BpmNodeTypeEnum.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
conditionSetting: {
|
||||
defaultFlow: false,
|
||||
@@ -174,7 +179,7 @@ function addNode(type: number) {
|
||||
id: `Flow_${generateUUID()}`,
|
||||
name: '其它情况',
|
||||
showText: '未满足其它条件时,将进入此分支',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
type: BpmNodeTypeEnum.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
conditionSetting: {
|
||||
defaultFlow: true,
|
||||
@@ -184,42 +189,42 @@ function addNode(type: number) {
|
||||
};
|
||||
emits('update:childNode', data);
|
||||
}
|
||||
if (type === NodeType.DELAY_TIMER_NODE) {
|
||||
if (type === BpmNodeTypeEnum.DELAY_TIMER_NODE) {
|
||||
const data: SimpleFlowNode = {
|
||||
id: `Activity_${generateUUID()}`,
|
||||
name: NODE_DEFAULT_NAME.get(NodeType.DELAY_TIMER_NODE) as string,
|
||||
name: NODE_DEFAULT_NAME.get(BpmNodeTypeEnum.DELAY_TIMER_NODE) as string,
|
||||
showText: '',
|
||||
type: NodeType.DELAY_TIMER_NODE,
|
||||
type: BpmNodeTypeEnum.DELAY_TIMER_NODE,
|
||||
childNode: props.childNode,
|
||||
};
|
||||
emits('update:childNode', data);
|
||||
}
|
||||
if (type === NodeType.ROUTER_BRANCH_NODE) {
|
||||
if (type === BpmNodeTypeEnum.ROUTER_BRANCH_NODE) {
|
||||
const data: SimpleFlowNode = {
|
||||
id: `GateWay_${generateUUID()}`,
|
||||
name: NODE_DEFAULT_NAME.get(NodeType.ROUTER_BRANCH_NODE) as string,
|
||||
name: NODE_DEFAULT_NAME.get(BpmNodeTypeEnum.ROUTER_BRANCH_NODE) as string,
|
||||
showText: '',
|
||||
type: NodeType.ROUTER_BRANCH_NODE,
|
||||
type: BpmNodeTypeEnum.ROUTER_BRANCH_NODE,
|
||||
childNode: props.childNode,
|
||||
};
|
||||
emits('update:childNode', data);
|
||||
}
|
||||
if (type === NodeType.TRIGGER_NODE) {
|
||||
if (type === BpmNodeTypeEnum.TRIGGER_NODE) {
|
||||
const data: SimpleFlowNode = {
|
||||
id: `Activity_${generateUUID()}`,
|
||||
name: NODE_DEFAULT_NAME.get(NodeType.TRIGGER_NODE) as string,
|
||||
name: NODE_DEFAULT_NAME.get(BpmNodeTypeEnum.TRIGGER_NODE) as string,
|
||||
showText: '',
|
||||
type: NodeType.TRIGGER_NODE,
|
||||
type: BpmNodeTypeEnum.TRIGGER_NODE,
|
||||
childNode: props.childNode,
|
||||
};
|
||||
emits('update:childNode', data);
|
||||
}
|
||||
if (type === NodeType.CHILD_PROCESS_NODE) {
|
||||
if (type === BpmNodeTypeEnum.CHILD_PROCESS_NODE) {
|
||||
const data: SimpleFlowNode = {
|
||||
id: `Activity_${generateUUID()}`,
|
||||
name: NODE_DEFAULT_NAME.get(NodeType.CHILD_PROCESS_NODE) as string,
|
||||
name: NODE_DEFAULT_NAME.get(BpmNodeTypeEnum.CHILD_PROCESS_NODE) as string,
|
||||
showText: '',
|
||||
type: NodeType.CHILD_PROCESS_NODE,
|
||||
type: BpmNodeTypeEnum.CHILD_PROCESS_NODE,
|
||||
childNode: props.childNode,
|
||||
childProcessSetting: {
|
||||
calledProcessDefinitionKey: '',
|
||||
@@ -247,7 +252,10 @@ function addNode(type: number) {
|
||||
<Popover trigger="hover" placement="right" width="auto" v-if="!readonly">
|
||||
<template #content>
|
||||
<div class="handler-item-wrapper">
|
||||
<div class="handler-item" @click="addNode(NodeType.USER_TASK_NODE)">
|
||||
<div
|
||||
class="handler-item"
|
||||
@click="addNode(BpmNodeTypeEnum.USER_TASK_NODE)"
|
||||
>
|
||||
<div class="approve handler-item-icon">
|
||||
<span class="iconfont icon-approve icon-size"></span>
|
||||
</div>
|
||||
@@ -255,14 +263,17 @@ function addNode(type: number) {
|
||||
</div>
|
||||
<div
|
||||
class="handler-item"
|
||||
@click="addNode(NodeType.TRANSACTOR_NODE)"
|
||||
@click="addNode(BpmNodeTypeEnum.TRANSACTOR_NODE)"
|
||||
>
|
||||
<div class="transactor handler-item-icon">
|
||||
<span class="iconfont icon-transactor icon-size"></span>
|
||||
</div>
|
||||
<div class="handler-item-text">办理人</div>
|
||||
</div>
|
||||
<div class="handler-item" @click="addNode(NodeType.COPY_TASK_NODE)">
|
||||
<div
|
||||
class="handler-item"
|
||||
@click="addNode(BpmNodeTypeEnum.COPY_TASK_NODE)"
|
||||
>
|
||||
<div class="handler-item-icon copy">
|
||||
<span class="iconfont icon-size icon-copy"></span>
|
||||
</div>
|
||||
@@ -270,7 +281,7 @@ function addNode(type: number) {
|
||||
</div>
|
||||
<div
|
||||
class="handler-item"
|
||||
@click="addNode(NodeType.CONDITION_BRANCH_NODE)"
|
||||
@click="addNode(BpmNodeTypeEnum.CONDITION_BRANCH_NODE)"
|
||||
>
|
||||
<div class="handler-item-icon condition">
|
||||
<span class="iconfont icon-size icon-exclusive"></span>
|
||||
@@ -279,7 +290,7 @@ function addNode(type: number) {
|
||||
</div>
|
||||
<div
|
||||
class="handler-item"
|
||||
@click="addNode(NodeType.PARALLEL_BRANCH_NODE)"
|
||||
@click="addNode(BpmNodeTypeEnum.PARALLEL_BRANCH_NODE)"
|
||||
>
|
||||
<div class="handler-item-icon parallel">
|
||||
<span class="iconfont icon-size icon-parallel"></span>
|
||||
@@ -288,7 +299,7 @@ function addNode(type: number) {
|
||||
</div>
|
||||
<div
|
||||
class="handler-item"
|
||||
@click="addNode(NodeType.INCLUSIVE_BRANCH_NODE)"
|
||||
@click="addNode(BpmNodeTypeEnum.INCLUSIVE_BRANCH_NODE)"
|
||||
>
|
||||
<div class="handler-item-icon inclusive">
|
||||
<span class="iconfont icon-size icon-inclusive"></span>
|
||||
@@ -297,7 +308,7 @@ function addNode(type: number) {
|
||||
</div>
|
||||
<div
|
||||
class="handler-item"
|
||||
@click="addNode(NodeType.DELAY_TIMER_NODE)"
|
||||
@click="addNode(BpmNodeTypeEnum.DELAY_TIMER_NODE)"
|
||||
>
|
||||
<div class="handler-item-icon delay">
|
||||
<span class="iconfont icon-size icon-delay"></span>
|
||||
@@ -306,14 +317,17 @@ function addNode(type: number) {
|
||||
</div>
|
||||
<div
|
||||
class="handler-item"
|
||||
@click="addNode(NodeType.ROUTER_BRANCH_NODE)"
|
||||
@click="addNode(BpmNodeTypeEnum.ROUTER_BRANCH_NODE)"
|
||||
>
|
||||
<div class="handler-item-icon router">
|
||||
<span class="iconfont icon-size icon-router"></span>
|
||||
</div>
|
||||
<div class="handler-item-text">路由分支</div>
|
||||
</div>
|
||||
<div class="handler-item" @click="addNode(NodeType.TRIGGER_NODE)">
|
||||
<div
|
||||
class="handler-item"
|
||||
@click="addNode(BpmNodeTypeEnum.TRIGGER_NODE)"
|
||||
>
|
||||
<div class="handler-item-icon trigger">
|
||||
<span class="iconfont icon-size icon-trigger"></span>
|
||||
</div>
|
||||
@@ -321,7 +335,7 @@ function addNode(type: number) {
|
||||
</div>
|
||||
<div
|
||||
class="handler-item"
|
||||
@click="addNode(NodeType.CHILD_PROCESS_NODE)"
|
||||
@click="addNode(BpmNodeTypeEnum.CHILD_PROCESS_NODE)"
|
||||
>
|
||||
<div class="handler-item-icon child-process">
|
||||
<span class="iconfont icon-size icon-child-process"></span>
|
||||
@@ -330,7 +344,7 @@ function addNode(type: number) {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="add-icon"><IconifyIcon icon="ep:plus" /></div>
|
||||
<div class="add-icon"><IconifyIcon icon="lucide:plus" /></div>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { SimpleFlowNode } from '../../consts';
|
||||
|
||||
import { inject, ref, watch } from 'vue';
|
||||
import { inject, nextTick, ref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { buildShortUUID as generateUUID } from '@vben/utils';
|
||||
|
||||
import { Button, Input } from 'ant-design-vue';
|
||||
|
||||
import { NODE_DEFAULT_TEXT, NodeType } from '../../consts';
|
||||
import { BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import { NODE_DEFAULT_TEXT } from '../../consts';
|
||||
import { useTaskStatusClass } from '../../helpers';
|
||||
import ProcessNodeTree from '../process-node-tree.vue';
|
||||
import NodeHandler from './node-handler.vue';
|
||||
@@ -44,10 +46,28 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
// 条件节点名称输入框引用
|
||||
const inputRefs = ref<HTMLInputElement[]>([]);
|
||||
// 节点名称输入框显示状态
|
||||
const showInputs = ref<boolean[]>([]);
|
||||
|
||||
// 失去焦点
|
||||
function blurEvent(index: number) {
|
||||
// 监听显示状态变化
|
||||
watch(
|
||||
showInputs,
|
||||
(newValues) => {
|
||||
// 当输入框显示时, 自动聚焦
|
||||
newValues.forEach((value, index) => {
|
||||
if (value) {
|
||||
// 当显示状态从 false 变为 true 时, 自动聚焦
|
||||
nextTick(() => {
|
||||
inputRefs.value[index]?.focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
// 修改节点名称
|
||||
function changeNodeName(index: number) {
|
||||
showInputs.value[index] = false;
|
||||
const conditionNode = currentNode.value.conditionNodes?.at(
|
||||
index,
|
||||
@@ -70,7 +90,7 @@ function addCondition() {
|
||||
id: `Flow_${generateUUID()}`,
|
||||
name: `并行${len}`,
|
||||
showText: '无需配置条件同时执行',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
type: BpmNodeTypeEnum.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
conditionNodes: [],
|
||||
};
|
||||
@@ -97,7 +117,7 @@ function recursiveFindParentNode(
|
||||
node: SimpleFlowNode,
|
||||
nodeType: number,
|
||||
) {
|
||||
if (!node || node.type === NodeType.START_USER_NODE) {
|
||||
if (!node || node.type === BpmNodeTypeEnum.START_USER_NODE) {
|
||||
return;
|
||||
}
|
||||
if (node.type === nodeType) {
|
||||
@@ -148,10 +168,16 @@ function recursiveFindParentNode(
|
||||
<div class="branch-node-title-container">
|
||||
<div v-if="showInputs[index]">
|
||||
<Input
|
||||
:ref="
|
||||
(el) => {
|
||||
inputRefs[index] = el as HTMLInputElement;
|
||||
}
|
||||
"
|
||||
type="text"
|
||||
class="input-max-width editable-title-input"
|
||||
@blur="blurEvent(index)"
|
||||
v-model="item.name"
|
||||
@blur="changeNodeName(index)"
|
||||
@press-enter="changeNodeName(index)"
|
||||
v-model:value="item.name"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="branch-title" @click="clickEvent(index)">
|
||||
@@ -168,14 +194,14 @@ function recursiveFindParentNode(
|
||||
{{ item.showText }}
|
||||
</div>
|
||||
<div class="branch-node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||
{{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.CONDITION_NODE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon">
|
||||
<IconifyIcon
|
||||
color="#0089ff"
|
||||
icon="ep:circle-close-filled"
|
||||
icon="lucide:circle-x"
|
||||
@click="deleteCondition(index)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,9 @@ import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Input } from 'ant-design-vue';
|
||||
|
||||
import { NODE_DEFAULT_TEXT, NodeType } from '../../consts';
|
||||
import { BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import { NODE_DEFAULT_TEXT } from '../../consts';
|
||||
import { useNodeName2, useTaskStatusClass, useWatchNode } from '../../helpers';
|
||||
import RouterNodeConfig from '../nodes-config/router-node-config.vue';
|
||||
import NodeHandler from './node-handler.vue';
|
||||
@@ -31,9 +33,9 @@ const readonly = inject<Boolean>('readonly');
|
||||
// 监控节点的变化
|
||||
const currentNode = useWatchNode(props);
|
||||
// 节点名称编辑
|
||||
const { showInput, blurEvent, clickTitle } = useNodeName2(
|
||||
const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
|
||||
currentNode,
|
||||
NodeType.ROUTER_BRANCH_NODE,
|
||||
BpmNodeTypeEnum.ROUTER_BRANCH_NODE,
|
||||
);
|
||||
|
||||
const nodeSetting = ref();
|
||||
@@ -65,11 +67,13 @@ function deleteNode() {
|
||||
<span class="iconfont icon-router"></span>
|
||||
</div>
|
||||
<Input
|
||||
ref="inputRef"
|
||||
v-if="!readonly && showInput"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
v-model="currentNode.name"
|
||||
@blur="changeNodeName()"
|
||||
@press-enter="changeNodeName()"
|
||||
v-model:value="currentNode.name"
|
||||
:placeholder="currentNode.name"
|
||||
/>
|
||||
<div v-else class="node-title" @click="clickTitle">
|
||||
@@ -85,15 +89,15 @@ function deleteNode() {
|
||||
{{ currentNode.showText }}
|
||||
</div>
|
||||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.ROUTER_BRANCH_NODE) }}
|
||||
{{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.ROUTER_BRANCH_NODE) }}
|
||||
</div>
|
||||
<IconifyIcon v-if="!readonly" icon="ep:arrow-right-bold" />
|
||||
<IconifyIcon v-if="!readonly" icon="lucide:chevron-right" />
|
||||
</div>
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon">
|
||||
<IconifyIcon
|
||||
color="#0089ff"
|
||||
icon="ep:circle-close-filled"
|
||||
icon="lucide:circle-x"
|
||||
@click="deleteNode"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
// TODO @芋艿:后续是不是把业务组件,挪到每个模块里;待定;
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { SimpleFlowNode } from '../../consts';
|
||||
@@ -9,7 +10,9 @@ import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Input } from 'ant-design-vue';
|
||||
|
||||
import { NODE_DEFAULT_TEXT, NodeType } from '../../consts';
|
||||
import { BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import { NODE_DEFAULT_TEXT } from '../../consts';
|
||||
import { useNodeName2, useTaskStatusClass, useWatchNode } from '../../helpers';
|
||||
import StartUserNodeConfig from '../nodes-config/start-user-node-config.vue';
|
||||
import NodeHandler from './node-handler.vue';
|
||||
@@ -34,9 +37,9 @@ const tasks = inject<Ref<any[]>>('tasks', ref([]));
|
||||
// 监控节点变化
|
||||
const currentNode = useWatchNode(props);
|
||||
// 节点名称编辑
|
||||
const { showInput, blurEvent, clickTitle } = useNodeName2(
|
||||
const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
|
||||
currentNode,
|
||||
NodeType.START_USER_NODE,
|
||||
BpmNodeTypeEnum.START_USER_NODE,
|
||||
);
|
||||
|
||||
const nodeSetting = ref();
|
||||
@@ -78,10 +81,12 @@ function nodeClick() {
|
||||
<span class="iconfont icon-start-user"></span>
|
||||
</div>
|
||||
<Input
|
||||
ref="inputRef"
|
||||
v-if="!readonly && showInput"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
@blur="changeNodeName()"
|
||||
@press-enter="changeNodeName()"
|
||||
v-model:value="currentNode.name"
|
||||
:placeholder="currentNode.name"
|
||||
/>
|
||||
@@ -98,9 +103,9 @@ function nodeClick() {
|
||||
{{ currentNode.showText }}
|
||||
</div>
|
||||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.START_USER_NODE) }}
|
||||
{{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.START_USER_NODE) }}
|
||||
</div>
|
||||
<IconifyIcon icon="ep:arrow-right-bold" v-if="!readonly" />
|
||||
<IconifyIcon icon="lucide:chevron-right" v-if="!readonly" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||
|
||||
@@ -7,7 +7,9 @@ import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Input } from 'ant-design-vue';
|
||||
|
||||
import { NODE_DEFAULT_TEXT, NodeType } from '../../consts';
|
||||
import { BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import { NODE_DEFAULT_TEXT } from '../../consts';
|
||||
import { useNodeName2, useTaskStatusClass, useWatchNode } from '../../helpers';
|
||||
import TriggerNodeConfig from '../nodes-config/trigger-node-config.vue';
|
||||
import NodeHandler from './node-handler.vue';
|
||||
@@ -33,9 +35,9 @@ const readonly = inject<Boolean>('readonly');
|
||||
// 监控节点的变化
|
||||
const currentNode = useWatchNode(props);
|
||||
// 节点名称编辑
|
||||
const { showInput, blurEvent, clickTitle } = useNodeName2(
|
||||
const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
|
||||
currentNode,
|
||||
NodeType.TRIGGER_NODE,
|
||||
BpmNodeTypeEnum.TRIGGER_NODE,
|
||||
);
|
||||
|
||||
const nodeSetting = ref();
|
||||
@@ -67,11 +69,13 @@ function deleteNode() {
|
||||
<span class="iconfont icon-trigger"></span>
|
||||
</div>
|
||||
<Input
|
||||
ref="inputRef"
|
||||
v-if="!readonly && showInput"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
v-model="currentNode.name"
|
||||
@blur="changeNodeName()"
|
||||
@press-enter="changeNodeName()"
|
||||
v-model:value="currentNode.name"
|
||||
:placeholder="currentNode.name"
|
||||
/>
|
||||
<div v-else class="node-title" @click="clickTitle">
|
||||
@@ -87,15 +91,15 @@ function deleteNode() {
|
||||
{{ currentNode.showText }}
|
||||
</div>
|
||||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.TRIGGER_NODE) }}
|
||||
{{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.TRIGGER_NODE) }}
|
||||
</div>
|
||||
<IconifyIcon v-if="!readonly" icon="ep:arrow-right-bold" />
|
||||
<IconifyIcon v-if="!readonly" icon="lucide:chevron-right" />
|
||||
</div>
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon">
|
||||
<IconifyIcon
|
||||
color="#0089ff"
|
||||
icon="ep:circle-close-filled"
|
||||
icon="lucide:circle-x"
|
||||
:size="18"
|
||||
@click="deleteNode"
|
||||
/>
|
||||
|
||||
@@ -9,7 +9,9 @@ import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Input } from 'ant-design-vue';
|
||||
|
||||
import { NODE_DEFAULT_TEXT, NodeType } from '../../consts';
|
||||
import { BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import { NODE_DEFAULT_TEXT } from '../../consts';
|
||||
import { useNodeName2, useTaskStatusClass, useWatchNode } from '../../helpers';
|
||||
import UserTaskNodeConfig from '../nodes-config/user-task-node-config.vue';
|
||||
import NodeHandler from './node-handler.vue';
|
||||
@@ -24,7 +26,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const emits = defineEmits<{
|
||||
findParentNode: [nodeList: SimpleFlowNode[], nodeType: NodeType];
|
||||
findParentNode: [nodeList: SimpleFlowNode[], nodeType: BpmNodeTypeEnum];
|
||||
'update:flowNode': [node: SimpleFlowNode | undefined];
|
||||
}>();
|
||||
|
||||
@@ -34,9 +36,9 @@ const tasks = inject<Ref<any[]>>('tasks', ref([]));
|
||||
// 监控节点变化
|
||||
const currentNode = useWatchNode(props);
|
||||
// 节点名称编辑
|
||||
const { showInput, blurEvent, clickTitle } = useNodeName2(
|
||||
const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
|
||||
currentNode,
|
||||
NodeType.START_USER_NODE,
|
||||
BpmNodeTypeEnum.USER_TASK_NODE,
|
||||
);
|
||||
const nodeSetting = ref();
|
||||
|
||||
@@ -60,7 +62,7 @@ function findReturnTaskNodes(
|
||||
matchNodeList: SimpleFlowNode[], // 匹配的节点
|
||||
) {
|
||||
// 从父节点查找
|
||||
emits('findParentNode', matchNodeList, NodeType.USER_TASK_NODE);
|
||||
emits('findParentNode', matchNodeList, BpmNodeTypeEnum.USER_TASK_NODE);
|
||||
}
|
||||
|
||||
// const selectTasks = ref<any[] | undefined>([]); // 选中的任务数组
|
||||
@@ -77,19 +79,21 @@ function findReturnTaskNodes(
|
||||
>
|
||||
<div class="node-title-container">
|
||||
<div
|
||||
:class="`node-title-icon ${currentNode.type === NodeType.TRANSACTOR_NODE ? 'transactor-task' : 'user-task'}`"
|
||||
:class="`node-title-icon ${currentNode.type === BpmNodeTypeEnum.TRANSACTOR_NODE ? 'transactor-task' : 'user-task'}`"
|
||||
>
|
||||
<span
|
||||
:class="`iconfont ${currentNode.type === NodeType.TRANSACTOR_NODE ? 'icon-transactor' : 'icon-approve'}`"
|
||||
:class="`iconfont ${currentNode.type === BpmNodeTypeEnum.TRANSACTOR_NODE ? 'icon-transactor' : 'icon-approve'}`"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
<Input
|
||||
ref="inputRef"
|
||||
v-if="!readonly && showInput"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
v-model="currentNode.name"
|
||||
@blur="changeNodeName()"
|
||||
@press-enter="changeNodeName()"
|
||||
v-model:value="currentNode.name"
|
||||
:placeholder="currentNode.name"
|
||||
/>
|
||||
<div v-else class="node-title" @click="clickTitle">
|
||||
@@ -107,13 +111,13 @@ function findReturnTaskNodes(
|
||||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(currentNode.type) }}
|
||||
</div>
|
||||
<IconifyIcon icon="ep:arrow-right-bold" v-if="!readonly" />
|
||||
<IconifyIcon icon="lucide:chevron-right" v-if="!readonly" />
|
||||
</div>
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon">
|
||||
<IconifyIcon
|
||||
color="#0089ff"
|
||||
icon="ep:circle-close-filled"
|
||||
icon="lucide:circle-x"
|
||||
:size="18"
|
||||
@click="deleteNode"
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { SimpleFlowNode } from '../consts';
|
||||
|
||||
import { NodeType } from '../consts';
|
||||
import { BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import { useWatchNode } from '../helpers';
|
||||
import CopyTaskNode from './nodes/copy-task-node.vue';
|
||||
import DelayTimerNode from './nodes/delay-timer-node.vue';
|
||||
@@ -57,7 +58,7 @@ function recursiveFindParentNode(
|
||||
if (!findNode) {
|
||||
return;
|
||||
}
|
||||
if (findNode.type === NodeType.START_USER_NODE) {
|
||||
if (findNode.type === BpmNodeTypeEnum.START_USER_NODE) {
|
||||
nodeList.push(findNode);
|
||||
return;
|
||||
}
|
||||
@@ -71,15 +72,15 @@ function recursiveFindParentNode(
|
||||
<template>
|
||||
<!-- 发起人节点 -->
|
||||
<StartUserNode
|
||||
v-if="currentNode && currentNode.type === NodeType.START_USER_NODE"
|
||||
v-if="currentNode && currentNode.type === BpmNodeTypeEnum.START_USER_NODE"
|
||||
:flow-node="currentNode"
|
||||
/>
|
||||
<!-- 审批节点 -->
|
||||
<UserTaskNode
|
||||
v-if="
|
||||
currentNode &&
|
||||
(currentNode.type === NodeType.USER_TASK_NODE ||
|
||||
currentNode.type === NodeType.TRANSACTOR_NODE)
|
||||
(currentNode.type === BpmNodeTypeEnum.USER_TASK_NODE ||
|
||||
currentNode.type === BpmNodeTypeEnum.TRANSACTOR_NODE)
|
||||
"
|
||||
:flow-node="currentNode"
|
||||
@update:flow-node="handleModelValueUpdate"
|
||||
@@ -87,46 +88,54 @@ function recursiveFindParentNode(
|
||||
/>
|
||||
<!-- 抄送节点 -->
|
||||
<CopyTaskNode
|
||||
v-if="currentNode && currentNode.type === NodeType.COPY_TASK_NODE"
|
||||
v-if="currentNode && currentNode.type === BpmNodeTypeEnum.COPY_TASK_NODE"
|
||||
:flow-node="currentNode"
|
||||
@update:flow-node="handleModelValueUpdate"
|
||||
/>
|
||||
<!-- 条件节点 -->
|
||||
<ExclusiveNode
|
||||
v-if="currentNode && currentNode.type === NodeType.CONDITION_BRANCH_NODE"
|
||||
v-if="
|
||||
currentNode && currentNode.type === BpmNodeTypeEnum.CONDITION_BRANCH_NODE
|
||||
"
|
||||
:flow-node="currentNode"
|
||||
@update:model-value="handleModelValueUpdate"
|
||||
@find-parent-node="findParentNode"
|
||||
/>
|
||||
<!-- 并行节点 -->
|
||||
<ParallelNode
|
||||
v-if="currentNode && currentNode.type === NodeType.PARALLEL_BRANCH_NODE"
|
||||
v-if="
|
||||
currentNode && currentNode.type === BpmNodeTypeEnum.PARALLEL_BRANCH_NODE
|
||||
"
|
||||
:flow-node="currentNode"
|
||||
@update:model-value="handleModelValueUpdate"
|
||||
@find-parent-node="findParentNode"
|
||||
/>
|
||||
<!-- 包容分支节点 -->
|
||||
<InclusiveNode
|
||||
v-if="currentNode && currentNode.type === NodeType.INCLUSIVE_BRANCH_NODE"
|
||||
v-if="
|
||||
currentNode && currentNode.type === BpmNodeTypeEnum.INCLUSIVE_BRANCH_NODE
|
||||
"
|
||||
:flow-node="currentNode"
|
||||
@update:model-value="handleModelValueUpdate"
|
||||
@find-parent-node="findParentNode"
|
||||
/>
|
||||
<!-- 延迟器节点 -->
|
||||
<DelayTimerNode
|
||||
v-if="currentNode && currentNode.type === NodeType.DELAY_TIMER_NODE"
|
||||
v-if="currentNode && currentNode.type === BpmNodeTypeEnum.DELAY_TIMER_NODE"
|
||||
:flow-node="currentNode"
|
||||
@update:flow-node="handleModelValueUpdate"
|
||||
/>
|
||||
<!-- 路由分支节点 -->
|
||||
<RouterNode
|
||||
v-if="currentNode && currentNode.type === NodeType.ROUTER_BRANCH_NODE"
|
||||
v-if="
|
||||
currentNode && currentNode.type === BpmNodeTypeEnum.ROUTER_BRANCH_NODE
|
||||
"
|
||||
:flow-node="currentNode"
|
||||
@update:flow-node="handleModelValueUpdate"
|
||||
/>
|
||||
<!-- 触发器节点 -->
|
||||
<TriggerNode
|
||||
v-if="currentNode && currentNode.type === NodeType.TRIGGER_NODE"
|
||||
v-if="currentNode && currentNode.type === BpmNodeTypeEnum.TRIGGER_NODE"
|
||||
:flow-node="currentNode"
|
||||
@update:flow-node="handleModelValueUpdate"
|
||||
/>
|
||||
@@ -146,7 +155,7 @@ function recursiveFindParentNode(
|
||||
|
||||
<!-- 结束节点 -->
|
||||
<EndEventNode
|
||||
v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE"
|
||||
v-if="currentNode && currentNode.type === BpmNodeTypeEnum.END_EVENT_NODE"
|
||||
:flow-node="currentNode"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -22,9 +22,9 @@ 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 { BpmModelFormType } from '#/utils/constants';
|
||||
import { BpmModelFormType, BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import { NODE_DEFAULT_TEXT, NodeId, NodeType } from '../consts';
|
||||
import { NODE_DEFAULT_TEXT, NodeId } from '../consts';
|
||||
import SimpleProcessModel from './simple-process-model.vue';
|
||||
|
||||
defineOptions({
|
||||
@@ -97,7 +97,7 @@ const postOptions = ref<SystemPostApi.Post[]>([]); // 岗位列表
|
||||
const userOptions = ref<SystemUserApi.User[]>([]); // 用户列表
|
||||
const deptOptions = ref<SystemDeptApi.Dept[]>([]); // 部门列表
|
||||
const deptTreeOptions = ref();
|
||||
const userGroupOptions = ref<BpmUserGroupApi.UserGroupVO[]>([]); // 用户组列表
|
||||
const userGroupOptions = ref<BpmUserGroupApi.UserGroup[]>([]); // 用户组列表
|
||||
|
||||
provide('formFields', formFields);
|
||||
provide('formType', formType);
|
||||
@@ -124,13 +124,13 @@ function updateModel() {
|
||||
if (!processNodeTree.value) {
|
||||
processNodeTree.value = {
|
||||
name: '发起人',
|
||||
type: NodeType.START_USER_NODE,
|
||||
type: BpmNodeTypeEnum.START_USER_NODE,
|
||||
id: NodeId.START_USER_NODE_ID,
|
||||
showText: '默认配置',
|
||||
childNode: {
|
||||
id: NodeId.END_EVENT_NODE_ID,
|
||||
name: '结束',
|
||||
type: NodeType.END_EVENT_NODE,
|
||||
type: BpmNodeTypeEnum.END_EVENT_NODE,
|
||||
},
|
||||
};
|
||||
// 初始化时也触发一次保存
|
||||
@@ -162,14 +162,14 @@ function validateNode(
|
||||
) {
|
||||
if (node) {
|
||||
const { type, showText, conditionNodes } = node;
|
||||
if (type === NodeType.END_EVENT_NODE) {
|
||||
if (type === BpmNodeTypeEnum.END_EVENT_NODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
type === NodeType.CONDITION_BRANCH_NODE ||
|
||||
type === NodeType.PARALLEL_BRANCH_NODE ||
|
||||
type === NodeType.INCLUSIVE_BRANCH_NODE
|
||||
type === BpmNodeTypeEnum.CONDITION_BRANCH_NODE ||
|
||||
type === BpmNodeTypeEnum.PARALLEL_BRANCH_NODE ||
|
||||
type === BpmNodeTypeEnum.INCLUSIVE_BRANCH_NODE
|
||||
) {
|
||||
// 1. 分支节点, 先校验各个分支
|
||||
conditionNodes?.forEach((item) => {
|
||||
@@ -235,7 +235,7 @@ defineExpose({ validate });
|
||||
:readonly="false"
|
||||
@save="saveSimpleFlowModel"
|
||||
/>
|
||||
<ErrorModal title="流程设计校验不通过" class="w-[600px]">
|
||||
<ErrorModal title="流程设计校验不通过" class="w-[40%]">
|
||||
<div class="mb-2 text-base">以下节点配置不完善,请修改相关配置</div>
|
||||
<div
|
||||
class="mb-3 rounded-md bg-gray-100 p-2 text-sm"
|
||||
|
||||
@@ -8,7 +8,9 @@ import { downloadFileFromBlob, isString } from '@vben/utils';
|
||||
|
||||
import { Button, ButtonGroup, Modal, Row } from 'ant-design-vue';
|
||||
|
||||
import { NODE_DEFAULT_TEXT, NodeType } from '../consts';
|
||||
import { BpmNodeTypeEnum } from '#/utils';
|
||||
|
||||
import { NODE_DEFAULT_TEXT } from '../consts';
|
||||
import { useWatchNode } from '../helpers';
|
||||
import ProcessNodeTree from './process-node-tree.vue';
|
||||
|
||||
@@ -113,18 +115,18 @@ function validateNode(
|
||||
) {
|
||||
if (node) {
|
||||
const { type, showText, conditionNodes } = node;
|
||||
if (type === NodeType.END_EVENT_NODE) {
|
||||
if (type === BpmNodeTypeEnum.END_EVENT_NODE) {
|
||||
return;
|
||||
}
|
||||
if (type === NodeType.START_USER_NODE) {
|
||||
if (type === BpmNodeTypeEnum.START_USER_NODE) {
|
||||
// 发起人节点暂时不用校验,直接校验孩子节点
|
||||
validateNode(node.childNode, errorNodes);
|
||||
}
|
||||
|
||||
if (
|
||||
type === NodeType.USER_TASK_NODE ||
|
||||
type === NodeType.COPY_TASK_NODE ||
|
||||
type === NodeType.CONDITION_NODE
|
||||
type === BpmNodeTypeEnum.USER_TASK_NODE ||
|
||||
type === BpmNodeTypeEnum.COPY_TASK_NODE ||
|
||||
type === BpmNodeTypeEnum.CONDITION_NODE
|
||||
) {
|
||||
if (!showText) {
|
||||
errorNodes.push(node);
|
||||
@@ -133,9 +135,9 @@ function validateNode(
|
||||
}
|
||||
|
||||
if (
|
||||
type === NodeType.CONDITION_BRANCH_NODE ||
|
||||
type === NodeType.PARALLEL_BRANCH_NODE ||
|
||||
type === NodeType.INCLUSIVE_BRANCH_NODE
|
||||
type === BpmNodeTypeEnum.CONDITION_BRANCH_NODE ||
|
||||
type === BpmNodeTypeEnum.PARALLEL_BRANCH_NODE ||
|
||||
type === BpmNodeTypeEnum.INCLUSIVE_BRANCH_NODE
|
||||
) {
|
||||
// 分支节点
|
||||
// 1. 先校验各个分支
|
||||
@@ -203,10 +205,10 @@ onMounted(() => {
|
||||
<Row type="flex" justify="end">
|
||||
<ButtonGroup key="scale-control">
|
||||
<Button v-if="!readonly" @click="exportJson">
|
||||
<IconifyIcon icon="ep:download" /> 导出
|
||||
<IconifyIcon icon="lucide:download" /> 导出
|
||||
</Button>
|
||||
<Button v-if="!readonly" @click="importJson">
|
||||
<IconifyIcon icon="ep:upload" />导入
|
||||
<IconifyIcon icon="lucide:upload" />导入
|
||||
</Button>
|
||||
<!-- 用于打开本地文件-->
|
||||
<input
|
||||
@@ -219,14 +221,14 @@ onMounted(() => {
|
||||
@change="importLocalFile"
|
||||
/>
|
||||
<Button @click="processReZoom()">
|
||||
<IconifyIcon icon="tabler:relation-one-to-one" />
|
||||
<IconifyIcon icon="lucide:table-columns-split" />
|
||||
</Button>
|
||||
<Button :plain="true" @click="zoomOut()">
|
||||
<IconifyIcon icon="tabler:zoom-out" />
|
||||
<IconifyIcon icon="lucide:zoom-out" />
|
||||
</Button>
|
||||
<Button class="w-80px"> {{ scaleValue }}% </Button>
|
||||
<Button :plain="true" @click="zoomIn()">
|
||||
<IconifyIcon icon="tabler:zoom-in" />
|
||||
<IconifyIcon icon="lucide:zoom-in" />
|
||||
</Button>
|
||||
<Button @click="resetPosition">重置</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// TODO 芋艿 这些 常量是不是可以共享
|
||||
import { BpmNodeTypeEnum, BpmTaskStatusEnum } from '#/utils';
|
||||
|
||||
interface DictDataType {
|
||||
label: string;
|
||||
@@ -43,112 +43,6 @@ export enum ApproveMethodType {
|
||||
SEQUENTIAL_APPROVE = 4,
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务状态枚举
|
||||
*/
|
||||
export enum TaskStatusEnum {
|
||||
/**
|
||||
* 审批通过
|
||||
*/
|
||||
APPROVE = 2,
|
||||
|
||||
/**
|
||||
* 审批通过中
|
||||
*/
|
||||
APPROVING = 7,
|
||||
/**
|
||||
* 已取消
|
||||
*/
|
||||
CANCEL = 4,
|
||||
/**
|
||||
* 未开始
|
||||
*/
|
||||
NOT_START = -1,
|
||||
|
||||
/**
|
||||
* 审批不通过
|
||||
*/
|
||||
REJECT = 3,
|
||||
|
||||
/**
|
||||
* 已退回
|
||||
*/
|
||||
RETURN = 5,
|
||||
/**
|
||||
* 审批中
|
||||
*/
|
||||
RUNNING = 1,
|
||||
/**
|
||||
* 待审批
|
||||
*/
|
||||
WAIT = 0,
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点类型
|
||||
*/
|
||||
export enum NodeType {
|
||||
/**
|
||||
* 子流程节点
|
||||
*/
|
||||
CHILD_PROCESS_NODE = 20,
|
||||
/**
|
||||
* 条件分支节点 (对应排他网关)
|
||||
*/
|
||||
CONDITION_BRANCH_NODE = 51,
|
||||
/**
|
||||
* 条件节点
|
||||
*/
|
||||
CONDITION_NODE = 50,
|
||||
|
||||
/**
|
||||
* 抄送人节点
|
||||
*/
|
||||
COPY_TASK_NODE = 12,
|
||||
|
||||
/**
|
||||
* 延迟器节点
|
||||
*/
|
||||
DELAY_TIMER_NODE = 14,
|
||||
|
||||
/**
|
||||
* 结束节点
|
||||
*/
|
||||
END_EVENT_NODE = 1,
|
||||
|
||||
/**
|
||||
* 包容分支节点 (对应包容网关)
|
||||
*/
|
||||
INCLUSIVE_BRANCH_NODE = 53,
|
||||
|
||||
/**
|
||||
* 并行分支节点 (对应并行网关)
|
||||
*/
|
||||
PARALLEL_BRANCH_NODE = 52,
|
||||
|
||||
/**
|
||||
* 路由分支节点
|
||||
*/
|
||||
ROUTER_BRANCH_NODE = 54,
|
||||
/**
|
||||
* 发起人节点
|
||||
*/
|
||||
START_USER_NODE = 10,
|
||||
/**
|
||||
* 办理人节点
|
||||
*/
|
||||
TRANSACTOR_NODE = 13,
|
||||
|
||||
/**
|
||||
* 触发器节点
|
||||
*/
|
||||
TRIGGER_NODE = 15,
|
||||
/**
|
||||
* 审批人节点
|
||||
*/
|
||||
USER_TASK_NODE = 11,
|
||||
}
|
||||
|
||||
export enum NodeId {
|
||||
/**
|
||||
* 发起人节点 Id
|
||||
@@ -660,7 +554,7 @@ export type ChildProcessSetting = {
|
||||
*/
|
||||
export interface SimpleFlowNode {
|
||||
id: string;
|
||||
type: NodeType;
|
||||
type: BpmNodeTypeEnum;
|
||||
name: string;
|
||||
showText?: string;
|
||||
// 孩子节点
|
||||
@@ -698,7 +592,7 @@ export interface SimpleFlowNode {
|
||||
// 条件设置
|
||||
conditionSetting?: ConditionSetting;
|
||||
// 活动的状态,用于前端节点状态展示
|
||||
activityStatus?: TaskStatusEnum;
|
||||
activityStatus?: BpmTaskStatusEnum;
|
||||
// 延迟设置
|
||||
delaySetting?: DelaySetting;
|
||||
// 路由分支
|
||||
@@ -734,26 +628,26 @@ export const DEFAULT_CONDITION_GROUP_VALUE = {
|
||||
};
|
||||
|
||||
export const NODE_DEFAULT_TEXT = new Map<number, string>();
|
||||
NODE_DEFAULT_TEXT.set(NodeType.USER_TASK_NODE, '请配置审批人');
|
||||
NODE_DEFAULT_TEXT.set(NodeType.COPY_TASK_NODE, '请配置抄送人');
|
||||
NODE_DEFAULT_TEXT.set(NodeType.CONDITION_NODE, '请设置条件');
|
||||
NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人');
|
||||
NODE_DEFAULT_TEXT.set(NodeType.DELAY_TIMER_NODE, '请设置延迟器');
|
||||
NODE_DEFAULT_TEXT.set(NodeType.ROUTER_BRANCH_NODE, '请设置路由节点');
|
||||
NODE_DEFAULT_TEXT.set(NodeType.TRIGGER_NODE, '请设置触发器');
|
||||
NODE_DEFAULT_TEXT.set(NodeType.TRANSACTOR_NODE, '请设置办理人');
|
||||
NODE_DEFAULT_TEXT.set(NodeType.CHILD_PROCESS_NODE, '请设置子流程');
|
||||
NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.USER_TASK_NODE, '请配置审批人');
|
||||
NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.COPY_TASK_NODE, '请配置抄送人');
|
||||
NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.CONDITION_NODE, '请设置条件');
|
||||
NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.START_USER_NODE, '请设置发起人');
|
||||
NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.DELAY_TIMER_NODE, '请设置延迟器');
|
||||
NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.ROUTER_BRANCH_NODE, '请设置路由节点');
|
||||
NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.TRIGGER_NODE, '请设置触发器');
|
||||
NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.TRANSACTOR_NODE, '请设置办理人');
|
||||
NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.CHILD_PROCESS_NODE, '请设置子流程');
|
||||
|
||||
export const NODE_DEFAULT_NAME = new Map<number, string>();
|
||||
NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人');
|
||||
NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人');
|
||||
NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件');
|
||||
NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人');
|
||||
NODE_DEFAULT_NAME.set(NodeType.DELAY_TIMER_NODE, '延迟器');
|
||||
NODE_DEFAULT_NAME.set(NodeType.ROUTER_BRANCH_NODE, '路由分支');
|
||||
NODE_DEFAULT_NAME.set(NodeType.TRIGGER_NODE, '触发器');
|
||||
NODE_DEFAULT_NAME.set(NodeType.TRANSACTOR_NODE, '办理人');
|
||||
NODE_DEFAULT_NAME.set(NodeType.CHILD_PROCESS_NODE, '子流程');
|
||||
NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.USER_TASK_NODE, '审批人');
|
||||
NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.COPY_TASK_NODE, '抄送人');
|
||||
NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.CONDITION_NODE, '条件');
|
||||
NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.START_USER_NODE, '发起人');
|
||||
NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.DELAY_TIMER_NODE, '延迟器');
|
||||
NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.ROUTER_BRANCH_NODE, '路由分支');
|
||||
NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.TRIGGER_NODE, '触发器');
|
||||
NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.TRANSACTOR_NODE, '办理人');
|
||||
NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.CHILD_PROCESS_NODE, '子流程');
|
||||
|
||||
// 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序
|
||||
export const CANDIDATE_STRATEGY: DictDataType[] = [
|
||||
@@ -930,24 +824,6 @@ export const MULTI_LEVEL_DEPT: DictDataType[] = [
|
||||
{ label: '第 15 级部门', value: 15 },
|
||||
];
|
||||
|
||||
/**
|
||||
* 流程实例的变量枚举
|
||||
*/
|
||||
export enum ProcessVariableEnum {
|
||||
/**
|
||||
* 流程定义名称
|
||||
*/
|
||||
PROCESS_DEFINITION_NAME = 'PROCESS_DEFINITION_NAME',
|
||||
/**
|
||||
* 发起时间
|
||||
*/
|
||||
START_TIME = 'PROCESS_START_TIME',
|
||||
/**
|
||||
* 发起用户 ID
|
||||
*/
|
||||
START_USER_ID = 'PROCESS_START_USER_ID',
|
||||
}
|
||||
|
||||
export const DELAY_TYPE = [
|
||||
{ label: '固定时长', value: DelayTypeEnum.FIXED_TIME_DURATION },
|
||||
{ label: '固定日期', value: DelayTypeEnum.FIXED_DATE_TIME },
|
||||
|
||||
@@ -12,7 +12,13 @@ import type { SystemPostApi } from '#/api/system/post';
|
||||
import type { SystemRoleApi } from '#/api/system/role';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import { inject, ref, toRaw, unref, watch } from 'vue';
|
||||
import { inject, nextTick, ref, toRaw, unref, watch } from 'vue';
|
||||
|
||||
import {
|
||||
BpmNodeTypeEnum,
|
||||
BpmTaskStatusEnum,
|
||||
ProcessVariableEnum,
|
||||
} from '#/utils';
|
||||
|
||||
import {
|
||||
ApproveMethodType,
|
||||
@@ -23,10 +29,7 @@ import {
|
||||
ConditionType,
|
||||
FieldPermissionType,
|
||||
NODE_DEFAULT_NAME,
|
||||
NodeType,
|
||||
ProcessVariableEnum,
|
||||
RejectHandlerType,
|
||||
TaskStatusEnum,
|
||||
} from './consts';
|
||||
|
||||
export function useWatchNode(props: {
|
||||
@@ -252,12 +255,12 @@ export type CopyTaskFormType = {
|
||||
/**
|
||||
* @description 节点表单数据。 用于审批节点、抄送节点
|
||||
*/
|
||||
export function useNodeForm(nodeType: NodeType) {
|
||||
export function useNodeForm(nodeType: BpmNodeTypeEnum) {
|
||||
const roleOptions = inject<Ref<SystemRoleApi.Role[]>>('roleList', ref([])); // 角色列表
|
||||
const postOptions = inject<Ref<SystemPostApi.Post[]>>('postList', ref([])); // 岗位列表
|
||||
const userOptions = inject<Ref<SystemUserApi.User[]>>('userList', ref([])); // 用户列表
|
||||
const deptOptions = inject<Ref<SystemDeptApi.Dept[]>>('deptList', ref([])); // 部门列表
|
||||
const userGroupOptions = inject<Ref<BpmUserGroupApi.UserGroupVO[]>>(
|
||||
const userGroupOptions = inject<Ref<BpmUserGroupApi.UserGroup[]>>(
|
||||
'userGroupList',
|
||||
ref([]),
|
||||
); // 用户组列表
|
||||
@@ -269,8 +272,8 @@ export function useNodeForm(nodeType: NodeType) {
|
||||
const configForm = ref<any | CopyTaskFormType | UserTaskFormType>();
|
||||
|
||||
if (
|
||||
nodeType === NodeType.USER_TASK_NODE ||
|
||||
nodeType === NodeType.TRANSACTOR_NODE
|
||||
nodeType === BpmNodeTypeEnum.USER_TASK_NODE ||
|
||||
nodeType === BpmNodeTypeEnum.TRANSACTOR_NODE
|
||||
) {
|
||||
configForm.value = {
|
||||
candidateStrategy: CandidateStrategy.USER,
|
||||
@@ -614,37 +617,65 @@ export function useDrawer() {
|
||||
/**
|
||||
* @description 节点名称配置
|
||||
*/
|
||||
export function useNodeName(nodeType: NodeType) {
|
||||
export function useNodeName(nodeType: BpmNodeTypeEnum) {
|
||||
// 节点名称
|
||||
const nodeName = ref<string>();
|
||||
// 节点名称输入框
|
||||
const showInput = ref(false);
|
||||
// 输入框的引用
|
||||
const inputRef = ref<HTMLInputElement | null>(null);
|
||||
// 点击节点名称编辑图标
|
||||
function clickIcon() {
|
||||
showInput.value = true;
|
||||
}
|
||||
// 节点名称输入框失去焦点
|
||||
function blurEvent() {
|
||||
// 修改节点名称
|
||||
function changeNodeName() {
|
||||
showInput.value = false;
|
||||
nodeName.value =
|
||||
nodeName.value || (NODE_DEFAULT_NAME.get(nodeType) as string);
|
||||
}
|
||||
// 监听 showInput 的变化,当变为 true 时自动聚焦
|
||||
watch(showInput, (value) => {
|
||||
if (value) {
|
||||
nextTick(() => {
|
||||
inputRef.value?.focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
nodeName,
|
||||
showInput,
|
||||
inputRef,
|
||||
clickIcon,
|
||||
blurEvent,
|
||||
changeNodeName,
|
||||
};
|
||||
}
|
||||
|
||||
export function useNodeName2(node: Ref<SimpleFlowNode>, nodeType: NodeType) {
|
||||
export function useNodeName2(
|
||||
node: Ref<SimpleFlowNode>,
|
||||
nodeType: BpmNodeTypeEnum,
|
||||
) {
|
||||
// 显示节点名称输入框
|
||||
const showInput = ref(false);
|
||||
// 节点名称输入框失去焦点
|
||||
function blurEvent() {
|
||||
// 输入框的引用
|
||||
const inputRef = ref<HTMLInputElement | null>(null);
|
||||
|
||||
// 监听 showInput 的变化,当变为 true 时自动聚焦
|
||||
watch(showInput, (value) => {
|
||||
if (value) {
|
||||
nextTick(() => {
|
||||
inputRef.value?.focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 修改节点名称
|
||||
function changeNodeName() {
|
||||
showInput.value = false;
|
||||
node.value.name =
|
||||
node.value.name || (NODE_DEFAULT_NAME.get(nodeType) as string);
|
||||
console.warn('node.value.name===>', node.value.name);
|
||||
}
|
||||
// 点击节点标题进行输入
|
||||
function clickTitle() {
|
||||
@@ -652,8 +683,9 @@ export function useNodeName2(node: Ref<SimpleFlowNode>, nodeType: NodeType) {
|
||||
}
|
||||
return {
|
||||
showInput,
|
||||
inputRef,
|
||||
clickTitle,
|
||||
blurEvent,
|
||||
changeNodeName,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -661,21 +693,21 @@ export function useNodeName2(node: Ref<SimpleFlowNode>, nodeType: NodeType) {
|
||||
* @description 根据节点任务状态,获取节点任务状态样式
|
||||
*/
|
||||
export function useTaskStatusClass(
|
||||
taskStatus: TaskStatusEnum | undefined,
|
||||
taskStatus: BpmTaskStatusEnum | undefined,
|
||||
): string {
|
||||
if (!taskStatus) {
|
||||
return '';
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.APPROVE) {
|
||||
if (taskStatus === BpmTaskStatusEnum.APPROVE) {
|
||||
return 'status-pass';
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.RUNNING) {
|
||||
if (taskStatus === BpmTaskStatusEnum.RUNNING) {
|
||||
return 'status-running';
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.REJECT) {
|
||||
if (taskStatus === BpmTaskStatusEnum.REJECT) {
|
||||
return 'status-reject';
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.CANCEL) {
|
||||
if (taskStatus === BpmTaskStatusEnum.CANCEL) {
|
||||
return 'status-cancel';
|
||||
}
|
||||
return '';
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import './styles/simple-process-designer.scss';
|
||||
|
||||
export { default as HttpRequestSetting } from './components/nodes-config/modules/http-request-setting.vue';
|
||||
|
||||
export { default as SimpleProcessDesigner } from './components/simple-process-designer.vue';
|
||||
|
||||
export { parseFormFields } from './helpers';
|
||||
|
||||
2
apps/web-antd/src/components/summary-card/index.ts
Normal file
2
apps/web-antd/src/components/summary-card/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as SummaryCard } from './summary-card.vue';
|
||||
export type { SummaryCardProps } from './typing';
|
||||
57
apps/web-antd/src/components/summary-card/summary-card.vue
Normal file
57
apps/web-antd/src/components/summary-card/summary-card.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<script lang="ts" setup>
|
||||
import type { SummaryCardProps } from './typing';
|
||||
|
||||
import { CountTo } from '@vben/common-ui';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
|
||||
/** 统计卡片 */
|
||||
defineOptions({ name: 'SummaryCard' });
|
||||
|
||||
defineProps<SummaryCardProps>();
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-row items-center gap-3 rounded bg-[var(--el-bg-color-overlay)] p-4"
|
||||
>
|
||||
<div
|
||||
class="rounded-1 flex h-12 w-12 flex-shrink-0 items-center justify-center"
|
||||
:class="`${iconColor} ${iconBgColor}`"
|
||||
>
|
||||
<IconifyIcon v-if="icon" :icon="icon" class="!text-6" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="text-3.5">{{ title }}</span>
|
||||
<Tooltip :content="tooltip" placement="topLeft" v-if="tooltip">
|
||||
<IconifyIcon
|
||||
icon="lucide:circle-alert"
|
||||
class="item-center !text-3 flex"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="flex flex-row items-baseline gap-2">
|
||||
<div class="text-7">
|
||||
<CountTo
|
||||
:prefix="prefix"
|
||||
:end-val="value ?? 0"
|
||||
:decimals="decimals ?? 0"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
v-if="percent !== undefined"
|
||||
:class="Number(percent) > 0 ? 'text-red-500' : 'text-green-500'"
|
||||
>
|
||||
<span class="text-sm">{{ Math.abs(Number(percent)) }}%</span>
|
||||
<IconifyIcon
|
||||
:icon="
|
||||
Number(percent) > 0 ? 'lucide:chevron-up' : 'lucide:chevron-down'
|
||||
"
|
||||
class="!text-3 ml-0.5"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
11
apps/web-antd/src/components/summary-card/typing.ts
Normal file
11
apps/web-antd/src/components/summary-card/typing.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export interface SummaryCardProps {
|
||||
title: string;
|
||||
tooltip?: string;
|
||||
icon?: string;
|
||||
iconColor?: string;
|
||||
iconBgColor?: string;
|
||||
prefix?: string;
|
||||
value?: number;
|
||||
decimals?: number;
|
||||
percent?: number | string;
|
||||
}
|
||||
@@ -43,28 +43,27 @@ const { hasAccessByCodes } = useAccess();
|
||||
|
||||
function isIfShow(action: ActionItem): boolean {
|
||||
const ifShow = action.ifShow;
|
||||
|
||||
let isIfShow = true;
|
||||
|
||||
if (isBoolean(ifShow)) {
|
||||
isIfShow = ifShow;
|
||||
}
|
||||
if (isFunction(ifShow)) {
|
||||
isIfShow = ifShow(action);
|
||||
}
|
||||
if (isIfShow) {
|
||||
isIfShow =
|
||||
hasAccessByCodes(action.auth || []) || (action.auth || []).length === 0;
|
||||
}
|
||||
return isIfShow;
|
||||
}
|
||||
|
||||
const getActions = computed(() => {
|
||||
return (toRaw(props.actions) || [])
|
||||
.filter((action) => {
|
||||
return (
|
||||
(hasAccessByCodes(action.auth || []) ||
|
||||
(action.auth || []).length === 0) &&
|
||||
isIfShow(action)
|
||||
);
|
||||
const actions = toRaw(props.actions) || [];
|
||||
return actions
|
||||
.filter((action: ActionItem) => {
|
||||
return isIfShow(action);
|
||||
})
|
||||
.map((action) => {
|
||||
.map((action: ActionItem) => {
|
||||
const { popConfirm } = action;
|
||||
return {
|
||||
type: action.type || 'link',
|
||||
@@ -78,24 +77,21 @@ const getActions = computed(() => {
|
||||
});
|
||||
|
||||
const getDropdownList = computed((): any[] => {
|
||||
return (toRaw(props.dropDownActions) || [])
|
||||
.filter((action) => {
|
||||
return (
|
||||
(hasAccessByCodes(action.auth || []) ||
|
||||
(action.auth || []).length === 0) &&
|
||||
isIfShow(action)
|
||||
);
|
||||
const dropDownActions = toRaw(props.dropDownActions) || [];
|
||||
return dropDownActions
|
||||
.filter((action: ActionItem) => {
|
||||
return isIfShow(action);
|
||||
})
|
||||
.map((action, index) => {
|
||||
.map((action: ActionItem, index: number) => {
|
||||
const { label, popConfirm } = action;
|
||||
delete action.icon;
|
||||
return {
|
||||
...action,
|
||||
...popConfirm,
|
||||
onConfirm: popConfirm?.confirm,
|
||||
onCancel: popConfirm?.cancel,
|
||||
text: label,
|
||||
divider:
|
||||
index < props.dropDownActions.length - 1 ? props.divider : false,
|
||||
divider: index < dropDownActions.length - 1 ? props.divider : false,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -100,7 +100,6 @@ async function handleRemove(file: UploadFile) {
|
||||
}
|
||||
|
||||
async function beforeUpload(file: File) {
|
||||
// 使用现代的Blob.text()方法替代FileReader
|
||||
const fileContent = await file.text();
|
||||
emit('returnText', fileContent);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
// TODO @xingyu:这个组件,只有 pay 在用,和现有的 file-upload 和 image-upload 有点不一致。是不是可以考虑移除,只在 pay 那搞个复用的组件;
|
||||
import type { InputProps, TextAreaProps } from 'ant-design-vue';
|
||||
|
||||
import type { FileUploadProps } from './typing';
|
||||
|
||||
@@ -136,7 +136,7 @@ export function getUploadUrl(): string {
|
||||
* @param file 文件
|
||||
*/
|
||||
function createFile0(
|
||||
vo: InfraFileApi.FilePresignedUrlRespVO,
|
||||
vo: InfraFileApi.FilePresignedUrlResp,
|
||||
file: File,
|
||||
): InfraFileApi.File {
|
||||
const fileVO = {
|
||||
|
||||
Reference in New Issue
Block a user