Pre Merge pull request !115 from Jason/dev

pull/115/MERGE
Jason 2025-05-26 09:29:42 +00:00 committed by Gitee
commit eda64e1c84
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
9 changed files with 341 additions and 78 deletions

View File

@ -0,0 +1,284 @@
<script setup lang="ts">
import type { Ref } from 'vue';
import type { SimpleFlowNode } from '../../consts';
import type { SystemDeptApi } from '#/api/system/dept';
import type { SystemUserApi } from '#/api/system/user';
import { inject, ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import {
Divider,
Input,
Radio,
RadioGroup,
TabPane,
Tabs,
Tooltip,
TypographyText,
} from 'ant-design-vue';
import { BpmModelFormType } from '#/utils';
import {
FieldPermissionType,
NodeType,
START_USER_BUTTON_SETTING,
} from '../../consts';
import {
useFormFieldsPermission,
useNodeName,
useWatchNode,
} from '../../helpers';
defineOptions({ name: 'StartUserNodeConfig' });
const props = defineProps({
flowNode: {
type: Object as () => SimpleFlowNode,
required: true,
},
});
//
const startUserIds = inject<Ref<any[]>>('startUserIds');
//
const startDeptIds = inject<Ref<any[]>>('startDeptIds');
//
const userOptions = inject<Ref<SystemUserApi.User[]>>('userList');
//
const deptOptions = inject<Ref<SystemDeptApi.Dept[]>>('deptList');
//
const currentNode = useWatchNode(props);
//
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(
NodeType.COPY_TASK_NODE,
);
// Tab
const activeTabName = ref('user');
//
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } =
useFormFieldsPermission(FieldPermissionType.WRITE);
const getUserNicknames = (userIds: number[]): string => {
if (!userIds || userIds.length === 0) {
return '';
}
const nicknames: string[] = [];
userIds.forEach((userId) => {
const found = userOptions?.value.find((item) => item.id === userId);
if (found && found.nickname) {
nicknames.push(found.nickname);
}
});
return nicknames.join(',');
};
const getDeptNames = (deptIds: number[]): string => {
if (!deptIds || deptIds.length === 0) {
return '';
}
const deptNames: string[] = [];
deptIds.forEach((deptId) => {
const found = deptOptions?.value.find((item) => item.id === deptId);
if (found && found.name) {
deptNames.push(found.name);
}
});
return deptNames.join(',');
};
// 使 VbenDrawer
const [Drawer, drawerApi] = useVbenDrawer({
header: false,
closable: false,
onCancel() {
drawerApi.close();
},
onConfirm() {
saveConfig();
},
});
//
const saveConfig = async () => {
activeTabName.value = 'user';
currentNode.value.name = nodeName.value!;
currentNode.value.showText = '已设置';
//
currentNode.value.fieldsPermission = fieldsPermissionConfig.value;
//
currentNode.value.buttonsSetting = START_USER_BUTTON_SETTING;
drawerApi.close();
return true;
};
//
const showStartUserNodeConfig = (node: SimpleFlowNode) => {
nodeName.value = node.name;
//
getNodeConfigFormFields(node.fieldsPermission);
drawerApi.open();
};
/** 批量更新权限 */
const updatePermission = (type: string) => {
fieldsPermissionConfig.value.forEach((field) => {
if (type === 'READ') {
field.permission = FieldPermissionType.READ;
} else if (type === 'WRITE') {
field.permission = FieldPermissionType.WRITE;
} else {
field.permission = FieldPermissionType.NONE;
}
});
};
/**
* 暴露方法给父组件
*/
defineExpose({ showStartUserNodeConfig });
</script>
<template>
<Drawer>
<div class="config-header">
<!-- TODO v-mountedFocus 自动聚集 需要迁移一下 -->
<Input
v-if="showInput"
type="text"
class="config-editable-input"
@blur="blurEvent()"
v-model:value="nodeName"
:placeholder="nodeName"
/>
<div v-else class="node-name">
{{ nodeName }}
<IconifyIcon
class="ml-1"
icon="ep:edit-pen"
:size="16"
@click="clickIcon()"
/>
</div>
<Divider />
</div>
<Tabs v-model:active-key="activeTabName" type="card">
<TabPane tab="权限" key="user">
<TypographyText
v-if="
(!startUserIds || startUserIds.length === 0) &&
(!startDeptIds || startDeptIds.length === 0)
"
>
全部成员可以发起流程
</TypographyText>
<div v-else-if="startUserIds && startUserIds.length > 0">
<TypographyText v-if="startUserIds.length === 1">
{{ getUserNicknames(startUserIds) }} 可发起流程
</TypographyText>
<TypographyText v-else>
<Tooltip
class="box-item"
effect="dark"
placement="top"
:content="getUserNicknames(startUserIds)"
>
{{ getUserNicknames(startUserIds.slice(0, 2)) }}
{{ startUserIds.length }} 人可发起流程
</Tooltip>
</TypographyText>
</div>
<div v-else-if="startDeptIds && startDeptIds.length > 0">
<TypographyText v-if="startDeptIds.length === 1">
{{ getDeptNames(startDeptIds) }} 可发起流程
</TypographyText>
<TypographyText v-else>
<Tooltip
class="box-item"
effect="dark"
placement="top"
:content="getDeptNames(startDeptIds)"
>
{{ getDeptNames(startDeptIds.slice(0, 2)) }}
{{ startDeptIds.length }} 个部门可发起流程
</Tooltip>
</TypographyText>
</div>
</TabPane>
<TabPane
tab="表单字段权限"
key="fields"
v-if="formType === BpmModelFormType.NORMAL"
>
<div class="field-setting-pane">
<div class="field-setting-desc">字段权限</div>
<div class="field-permit-title">
<div class="setting-title-label first-title">字段名称</div>
<div class="other-titles">
<span
class="setting-title-label cursor-pointer"
@click="updatePermission('READ')"
>
只读
</span>
<span
class="setting-title-label cursor-pointer"
@click="updatePermission('WRITE')"
>
可编辑
</span>
<span
class="setting-title-label cursor-pointer"
@click="updatePermission('NONE')"
>
隐藏
</span>
</div>
</div>
<div
class="field-setting-item"
v-for="(item, index) in fieldsPermissionConfig"
:key="index"
>
<div class="field-setting-item-label">{{ item.title }}</div>
<RadioGroup
class="field-setting-item-group"
v-model:value="item.permission"
>
<div class="item-radio-wrap">
<Radio
:value="FieldPermissionType.READ"
size="large"
:label="FieldPermissionType.READ"
>
<span></span>
</Radio>
</div>
<div class="item-radio-wrap">
<Radio
:value="FieldPermissionType.WRITE"
size="large"
:label="FieldPermissionType.WRITE"
>
<span></span>
</Radio>
</div>
<div class="item-radio-wrap">
<Radio
:value="FieldPermissionType.NONE"
size="large"
:label="FieldPermissionType.NONE"
>
<span></span>
</Radio>
</div>
</RadioGroup>
</div>
</div>
</TabPane>
</Tabs>
</Drawer>
</template>
<style lang="scss" scoped></style>

View File

@ -11,6 +11,7 @@ import { Input } from 'ant-design-vue';
import { NODE_DEFAULT_TEXT, NodeType } from '../../consts';
import { useNodeName2, useTaskStatusClass, useWatchNode } from '../../helpers';
import StartUserNodeConfig from '../nodes-config/start-user-node-config.vue';
import NodeHandler from './node-handler.vue';
defineOptions({ name: 'StartUserNode' });
@ -55,8 +56,7 @@ const nodeClick = () => {
'TODO 编辑模式,打开节点配置、把当前节点传递给配置组件',
nodeSetting.value,
);
// nodeSetting.value.showStartUserNodeConfig(currentNode.value);
// nodeSetting.value.openDrawer();
nodeSetting.value.showStartUserNodeConfig(currentNode.value);
}
};
</script>
@ -108,12 +108,12 @@ const nodeClick = () => {
/>
</div>
</div>
<!-- TODO 发起人配置节点
<StartUserNodeConfig
<StartUserNodeConfig
v-if="!readonly && currentNode"
ref="nodeSetting"
:flow-node="currentNode"
/> -->
/>
<!-- 审批记录 TODO -->
</template>
<style lang="scss" scoped></style>

View File

@ -3,21 +3,19 @@ import type { Ref } from 'vue';
import type { SimpleFlowNode } from '../consts';
import type { BpmFormApi } from '#/api/bpm/form';
import type { BpmUserGroupApi } from '#/api/bpm/userGroup';
import type { SystemDeptApi } from '#/api/system/dept';
import type { SystemPostApi } from '#/api/system/post';
import type { SystemRoleApi } from '#/api/system/role';
import type { SystemUserApi } from '#/api/system/user';
import { inject, onMounted, provide, ref } from 'vue';
import { inject, onMounted, provide, ref, watch } from 'vue';
import { handleTree } from '@vben/utils';
import { Button, Modal } from 'ant-design-vue';
import { getFormDetail } from '#/api/bpm/form';
import { getModel } from '#/api/bpm/model';
import { getUserGroupSimpleList } from '#/api/bpm/userGroup';
import { getSimpleDeptList } from '#/api/system/dept';
import { getSimplePostList } from '#/api/system/post';
@ -33,21 +31,23 @@ defineOptions({
});
const props = defineProps({
modelId: {
type: String,
required: false,
default: undefined,
},
modelKey: {
type: String,
required: false,
default: undefined,
},
modelName: {
type: String,
required: false,
default: undefined,
},
// ID
modelFormId: {
type: Number,
required: false,
default: undefined,
},
//
modelFormType: {
type: Number,
required: false,
default: BpmModelFormType.NORMAL,
},
//
startUserIds: {
type: Array,
@ -66,7 +66,31 @@ const emits = defineEmits(['success']);
const processData = inject('processData') as Ref;
const loading = ref(false);
const formFields = ref<string[]>([]);
const formType = ref(20);
const formType = ref(props.modelFormType);
// modelFormType
watch(
() => props.modelFormType,
(newVal) => {
formType.value = newVal;
},
);
// modelFormId
watch(
() => props.modelFormId,
async (newVal) => {
if (newVal) {
const form = await getFormDetail(newVal);
formFields.value = form?.fields;
} else {
// modelFormId
formFields.value = [];
}
},
{ immediate: true },
);
const roleOptions = ref<SystemRoleApi.Role[]>([]); //
const postOptions = ref<SystemPostApi.Post[]>([]); //
const userOptions = ref<SystemUserApi.User[]>([]); //
@ -172,19 +196,6 @@ const validateNode = (
onMounted(async () => {
try {
loading.value = true;
//
if (props.modelId) {
const bpmnModel = await getModel(props.modelId);
if (bpmnModel) {
formType.value = bpmnModel.formType;
if (formType.value === BpmModelFormType.NORMAL && bpmnModel.formId) {
const bpmnForm = (await getFormDetail(
bpmnModel.formId,
)) as unknown as BpmFormApi.FormVO;
formFields.value = bpmnForm?.fields;
}
}
}
//
roleOptions.value = await getSimpleRoleList();
//

View File

@ -198,8 +198,8 @@ onMounted(() => {
});
</script>
<template>
<div class="simple-process-model-container position-relative h-full">
<div class="z-index-button-group absolute right-[0px] top-[0px] bg-[#fff]">
<div class="simple-process-model-container">
<div class="absolute right-[0px] top-[0px] bg-[#fff]">
<Row type="flex" justify="end">
<ButtonGroup key="scale-control">
<Button v-if="!readonly" @click="exportJson">
@ -247,6 +247,7 @@ onMounted(() => {
/>
</div>
</div>
<!-- TODO 这个好像暂时没有用到保存失败弹窗 -->
<Modal
v-model:open="errorDialogVisible"
title="保存失败"
@ -266,21 +267,4 @@ onMounted(() => {
</template>
</Modal>
</template>
<style lang="scss" scoped>
.simple-process-model-container {
width: 100%;
height: 100%;
overflow: auto;
user-select: none; //
}
.simple-process-model {
position: relative; //
min-width: 100%; // 100%
min-height: 100%; // 100%
}
.z-index-ButtonGroup {
z-index: 10;
}
</style>
<style lang="scss" scoped></style>

View File

@ -209,8 +209,8 @@
.simple-process-model-container {
width: 100%;
height: 100%;
padding-top: 32px;
overflow-x: auto;
background: url('./svg/simple-process-bg.svg') 0 0 repeat;
background-color: #fafafa;
.simple-process-model {
@ -219,6 +219,7 @@
align-items: center;
justify-content: center;
min-width: fit-content;
background: url('./svg/simple-process-bg.svg') 0 0 repeat;
transform: scale(1);
transform-origin: 50% 0 0;
transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);

View File

@ -26,6 +26,7 @@ import {
} from '#/api/bpm/model';
import { getSimpleDeptList } from '#/api/system/dept';
import { getSimpleUserList } from '#/api/system/user';
import { BpmAutoApproveType, BpmModelFormType, BpmModelType } from '#/utils';
import BasicInfo from './modules/basic-info.vue';
import FormDesign from './modules/form-design.vue';
@ -33,24 +34,6 @@ import ProcessDesign from './modules/process-design.vue';
defineOptions({ name: 'BpmModelCreate' });
// TODO apps 使 @utils/constant.ts @
// TODO @jason/Users/yunai/Java/yudao-ui-admin-vben-v5/apps/web-antd/src/utils/constants.ts apps
const BpmModelType = {
BPMN: 10, // BPMN
SIMPLE: 20, //
};
const BpmModelFormType = {
NORMAL: 10, //
CUSTOM: 20, //
};
const BpmAutoApproveType = {
NONE: 0, //
APPROVE_ALL: 1, //
APPROVE_SEQUENT: 2, //
};
//
type BpmProcessDefinitionType = Omit<
BpmProcessDefinitionApi.ProcessDefinitionVO,

View File

@ -55,9 +55,9 @@ defineExpose({
<template v-else>
<SimpleModelDesign
v-if="showDesigner"
:model-id="modelData.id"
:model-key="modelData.key"
:model-name="modelData.name"
:model-form-id="modelData.formId"
:model-form-type="modelData.formType"
:start-user-ids="modelData.startUserIds"
:start-dept-ids="modelData.startDeptIds"
@success="handleDesignSuccess"

View File

@ -7,8 +7,8 @@ import { SimpleProcessDesigner } from '#/components/simple-process-design';
defineOptions({ name: 'SimpleModelDesign' });
defineProps<{
modelId?: string;
modelKey?: string;
modelFormId?: number;
modelFormType?: number;
modelName?: string;
startDeptIds?: number[];
startUserIds?: number[];
@ -27,9 +27,9 @@ const handleSuccess = (data?: any) => {
<template>
<ContentWrap :body-style="{ padding: '20px 16px' }">
<SimpleProcessDesigner
:model-id="modelId"
:model-key="modelKey"
:model-form-id="modelFormId"
:model-name="modelName"
:model-form-type="modelFormType"
@success="handleSuccess"
:start-user-ids="startUserIds"
:start-dept-ids="startDeptIds"

View File

@ -162,7 +162,7 @@ const handleCategorySortSubmit = async () => {
<template>
<Page auto-content-height>
<!-- TODO @jaosn没头像的图标展示文字头像哈 -->
<!-- TODO @jaosn没头像的图标展示文字头像哈 @芋艿 好像已经展示了文字头像是模型列表中吗? -->
<!-- 流程分类表单弹窗 -->
<CategoryFormModal @success="getList" />
<Card