feat: 选人组件(未完成) 加签减签

This commit is contained in:
dap 2024-12-17 14:47:51 +08:00
parent 8b58440e00
commit 2e3d385747
9 changed files with 456 additions and 34 deletions

View File

@ -2,6 +2,8 @@ import type {
CompleteTaskReqData,
StartWorkFlowReqData,
TaskInfo,
TaskOperationData,
TaskOperationType,
} from './model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
@ -88,7 +90,7 @@ export function pageByTaskCopy(params?: PageQuery) {
* @returns info
*/
export function getTaskByTaskId(taskId: string) {
return requestClient.get<TaskInfo>(`/workflow/task/${taskId}`);
return requestClient.get<TaskInfo>(`/workflow/task/getTask/${taskId}`);
}
/**
@ -107,11 +109,14 @@ export function terminationTask(data: { taskId: string }) {
* @param taskOperationData
* @param taskOperation delegateTask transferTask addSignature reductionSignature
*/
export function taskOperation(taskOperationData: any, taskOperation: string) {
return requestClient.postWithMsg<void>('/workflow/task/taskOperation', {
...taskOperationData,
taskOperation,
});
export function taskOperation(
taskOperationData: TaskOperationData,
taskOperation: TaskOperationType,
) {
return requestClient.postWithMsg<void>(
`/workflow/task/taskOperation/${taskOperation}`,
taskOperationData,
);
}
/**

View File

@ -23,7 +23,7 @@ export interface TaskInfo {
assigneeNames: string;
processedBy: string;
type: string;
nodeRatio?: any;
nodeRatio?: string;
createBy: string;
createByName: string;
}
@ -52,3 +52,21 @@ export interface StartWorkFlowReqData {
*/
variables: Record<string, any>;
}
export interface TaskOperationData {
message?: string;
taskId: ID;
// 单个操作人
userId?: ID;
// 多个操作人
userIds?: IDS;
}
/**
* delegateTask transferTask addSignature reductionSignature
*/
export type TaskOperationType =
| 'addSignature'
| 'delegateTask'
| 'reductionSignature'
| 'transferTask';

View File

@ -10,6 +10,8 @@ import { getDeptTree } from '#/api/system/user';
defineOptions({ inheritAttrs: false });
withDefaults(defineProps<{ showSearch?: boolean }>(), { showSearch: true });
const emit = defineEmits<{
/**
* 点击刷新按钮的事件
@ -68,7 +70,10 @@ onMounted(loadTree);
class="bg-background flex h-full flex-col overflow-y-auto rounded-lg"
>
<!-- 固定在顶部 必须加上bg-background背景色 否则会产生'穿透'效果 -->
<div class="bg-background z-100 sticky left-0 top-0 p-[8px]">
<div
v-if="showSearch"
class="bg-background z-100 sticky left-0 top-0 p-[8px]"
>
<InputSearch
v-model:value="searchValue"
:placeholder="$t('pages.common.search')"

View File

@ -118,7 +118,6 @@ async function handleSubmit() {
return;
}
const data = cloneDeep(await formApi.getValues());
console.log(data);
const requestData = {
...omit(data, ['attachment']),
fileId: data.attachment.join(','),

View File

@ -1,4 +1,5 @@
<script setup lang="ts">
import type { User } from '#/api/core/user';
import type { FlowInfoResponse } from '#/api/workflow/instance/model';
import type { TaskInfo } from '#/api/workflow/task/model';
@ -6,11 +7,15 @@ import { computed, onUnmounted, ref, watch } from 'vue';
import { Fallback, useVbenModal, VbenAvatar } from '@vben/common-ui';
import { DictEnum } from '@vben/constants';
import { getPopupContainer } from '@vben/utils';
import { useEventListener } from '@vueuse/core';
import {
Card,
Divider,
Dropdown,
Menu,
MenuItem,
Modal,
Popconfirm,
Skeleton,
@ -20,10 +25,15 @@ import {
} from 'ant-design-vue';
import { flowInfo } from '#/api/workflow/instance';
import { terminationTask } from '#/api/workflow/task';
import {
getTaskByTaskId,
taskOperation,
terminationTask,
} from '#/api/workflow/task';
import { renderDict } from '#/utils/render';
import { approvalModal, approvalRejectionModal, ApprovalTimeline } from '.';
import userSelectModal from './user-select-modal.vue';
defineOptions({
name: 'ApprovalPanel',
@ -33,6 +43,20 @@ defineOptions({
// eslint-disable-next-line no-use-before-define
const props = defineProps<{ task?: TaskInfo; type: ApprovalType }>();
const currentTask = ref<TaskInfo>();
/**
* 是否显示 加签/减签操作
*/
const showMultiActions = computed(() => {
if (!currentTask.value) {
return false;
}
if (Number(currentTask.value.nodeRatio) > 0) {
return true;
}
return false;
});
/**
* myself 我发起的
* readonly 只读 只用于查看
@ -75,6 +99,9 @@ async function handleLoadInfo(task: TaskInfo | undefined) {
iframeLoaded.value = false;
const resp = await flowInfo(task.businessId);
currentFlowInfo.value = resp;
const taskResp = await getTaskByTaskId(props.task!.id);
currentTask.value = taskResp;
} catch (error) {
console.error(error);
} finally {
@ -129,6 +156,89 @@ function handleApproval() {
approvalModalApi.setData({ taskId: props.task?.id });
approvalModalApi.open();
}
/**
* TODO: 1提取公共函数 2原版是可以填写意见的(message参数)
*/
/**
* 委托
*/
const [DelegationModal, delegationModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleDelegation(userList: User[]) {
if (userList.length === 0) return;
const current = userList[0];
Modal.confirm({
title: '委托',
content: `确定委托给${current?.nickName}吗?`,
centered: true,
onOk: async () => {
await taskOperation(
{ taskId: props.task!.id, userId: current!.userId },
'delegateTask',
);
},
});
}
/**
* 转办
*/
const [TransferModal, transferModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleTransfer(userList: User[]) {
if (userList.length === 0) return;
const current = userList[0];
Modal.confirm({
title: '转办',
content: `确定转办给${current?.nickName}吗?`,
centered: true,
onOk: async () => {
await taskOperation(
{ taskId: props.task!.id, userId: current!.userId },
'transferTask',
);
},
});
}
const [AddSignatureModal, addSignatureModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleAddSignature(userList: User[]) {
if (userList.length === 0) return;
const userIds = userList.map((user) => user.userId);
Modal.confirm({
title: '提示',
content: '确认加签吗?',
centered: true,
onOk: async () => {
await taskOperation({ taskId: props.task!.id, userIds }, 'addSignature');
},
});
}
const [ReductionSignatureModal, reductionSignatureModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleReductionSignature(userList: User[]) {
if (userList.length === 0) return;
const userIds = userList.map((user) => user.userId);
Modal.confirm({
title: '提示',
content: '确认加签吗?',
centered: true,
onOk: async () => {
await taskOperation(
{ taskId: props.task!.id, userIds },
'reductionSignature',
);
},
});
}
</script>
<template>
@ -217,9 +327,45 @@ function handleApproval() {
<a-button danger type="primary" @click="handleRejection">
驳回
</a-button>
<a-button>其他</a-button>
<Dropdown
:get-popup-container="getPopupContainer"
placement="bottomRight"
>
<template #overlay>
<Menu>
<MenuItem key="1" @click="() => delegationModalApi.open()">
委托
</MenuItem>
<MenuItem key="2" @click="() => transferModalApi.open()">
转办
</MenuItem>
<MenuItem
v-if="showMultiActions"
key="3"
@click="() => addSignatureModalApi.open()"
>
加签
</MenuItem>
<MenuItem
v-if="showMultiActions"
key="4"
@click="() => reductionSignatureModalApi.open()"
>
减签
</MenuItem>
</Menu>
</template>
<a-button> 其他 </a-button>
</Dropdown>
<ApprovalModal />
<RejectionModal />
<DelegationModal mode="single" @finish="handleDelegation" />
<TransferModal mode="single" @finish="handleTransfer" />
<AddSignatureModal mode="multiple" @finish="handleAddSignature" />
<ReductionSignatureModal
mode="multiple"
@finish="handleReductionSignature"
/>
</Space>
</div>
</div>

View File

@ -0,0 +1,4 @@
<!--抄送组件-->
<script setup lang="ts"></script>
<template>dsadsa</template>

View File

@ -10,4 +10,7 @@ export { default as ApprovalPanel } from './approval-panel.vue';
*/
export { default as approvalRejectionModal } from './approval-rejection-modal.vue';
export { default as ApprovalTimeline } from './approval-timeline.vue';
/**
* /
*/
export { default as UserSelectModal } from './user-select-modal.vue';

View File

@ -1,45 +1,287 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import type { User } from '#/api';
import { useVbenModal } from '@vben/common-ui';
import { ref } from 'vue';
import { Transfer } from 'ant-design-vue';
import { useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { userList } from '#/api/system/user';
import DeptTree from '#/views/system/user/dept-tree.vue';
defineOptions({
name: 'UserSelectModal',
inheritAttrs: false,
});
const [BasicModal] = useVbenModal({
title: '选择',
class: 'w-[800px]',
const props = withDefaults(defineProps<{ mode?: 'multiple' | 'single' }>(), {
mode: 'multiple',
});
const targetKeys = ref<string[]>([]);
const emit = defineEmits<{
finish: [User[]];
}>();
const dataSource = ref<Awaited<ReturnType<typeof userList>>['rows']>([]);
onMounted(async () => {
const resp = await userList({ pageNum: 1, pageSize: 10 });
dataSource.value = resp.rows.map((item) => ({
...item,
userId: String(item.userId),
}));
const [BasicModal, modalApi] = useVbenModal({
title: '选择人员',
class: 'w-[1000px]',
fullscreenButton: false,
onConfirm: handleSubmit,
});
//
const selectDeptId = ref<string[]>([]);
const formOptions: VbenFormProps = {
schema: [
{
component: 'Input',
fieldName: 'userName',
label: '用户账号',
hideLabel: true,
componentProps: {
placeholder: '请输入账号',
},
},
],
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
wrapperClass: 'grid-cols-2',
handleReset: async () => {
selectDeptId.value = [];
// eslint-disable-next-line no-use-before-define
const { formApi, reload } = tableApi;
await formApi.resetForm();
const formValues = formApi.form.values;
formApi.setLatestSubmissionValues(formValues);
// eslint-disable-next-line no-use-before-define
await rightTableApi.grid.loadData([]);
await reload(formValues);
},
};
const gridOptions: VxeGridProps = {
checkboxConfig: {
//
reserve: true,
//
trigger: 'row',
},
radioConfig: {
trigger: 'row',
strict: true,
},
columns: [
{ type: props.mode === 'single' ? 'radio' : 'checkbox', width: 60 },
{
field: 'userName',
title: '名称',
width: 80,
},
{
field: 'nickName',
title: '昵称',
width: 140,
},
{
field: 'deptName',
title: '部门',
width: 120,
},
],
height: 'auto',
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
//
if (selectDeptId.value.length === 1) {
formValues.deptId = selectDeptId.value[0];
} else {
Reflect.deleteProperty(formValues, 'deptId');
}
return await userList({
pageNum: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
isHover: true,
keyField: 'userId',
},
toolbarConfig: {
//
custom: false,
//
zoom: false,
//
refresh: false,
},
};
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents: {
// radiocheckbox
checkboxChange: checkBoxEvent,
checkboxAll: checkBoxEvent,
radioChange: radioEvent,
},
});
function checkBoxEvent() {
if (props.mode !== 'multiple') {
return;
}
//
const records = tableApi.grid.getCheckboxRecords();
console.log(records);
// eslint-disable-next-line no-use-before-define
rightTableApi.grid.loadData(records);
}
function radioEvent() {
if (props.mode !== 'single') {
return;
}
//
const records = tableApi.grid.getRadioRecord();
// eslint-disable-next-line no-use-before-define
rightTableApi.grid.loadData([records]);
}
const rightGridOptions: VxeGridProps = {
checkboxConfig: {},
columns: [
{
field: 'nickName',
title: '昵称',
width: 200,
},
{
field: 'action',
title: '操作',
width: 120,
slots: { default: 'action' },
},
],
height: 'auto',
keepSource: true,
pagerConfig: {
enabled: false,
},
proxyConfig: {
enabled: false,
},
rowConfig: {
isHover: true,
keyField: 'userId',
},
toolbarConfig: {
//
custom: false,
//
zoom: false,
//
refresh: false,
},
};
const [RightBasicTable, rightTableApi] = useVbenVxeGrid({
gridOptions: rightGridOptions,
});
function handleRemoveItem(row: any) {
if (props.mode === 'multiple') {
tableApi.grid.setCheckboxRow(row, false);
}
if (props.mode === 'single') {
tableApi.grid.clearRadioRow();
}
rightTableApi.grid.remove(row);
}
function handleRemoveAll() {
if (props.mode === 'multiple') {
tableApi.grid.clearCheckboxRow();
}
if (props.mode === 'single') {
tableApi.grid.clearRadioRow();
}
rightTableApi.grid.loadData([]);
}
async function handleDeptQuery() {
await tableApi.reload();
//
const records = rightTableApi.grid.getData();
if (props.mode === 'multiple') {
tableApi?.grid.setCheckboxRow(records, true);
}
if (props.mode === 'single' && records.length === 1) {
tableApi.grid.setRadioRow(records[0]);
}
}
function handleSubmit() {
const records = rightTableApi.grid.getData();
console.log(records);
emit('finish', records);
modalApi.close();
}
</script>
<template>
<BasicModal>
<div class="min-h-[350px]">
<Transfer
v-model:target-keys="targetKeys"
:data-source="dataSource"
:pagination="true"
:render="(item) => item.nickName"
:row-key="(record) => record.userId"
class="h-full"
<div class="flex min-h-[600px]">
<DeptTree
v-model:select-dept-id="selectDeptId"
:show-search="false"
class="w-[230px]"
@reload="() => tableApi.reload()"
@select="handleDeptQuery"
/>
<div class="h-[600px] w-[420px]">
<BasicTable />
</div>
<div class="flex h-[600px] w-[360px] flex-col">
<div class="flex w-full px-4">
<div class="flex w-full items-center justify-between">
<div>已选中人员</div>
<div>
<a-button size="small" @click="handleRemoveAll">
清空选中
</a-button>
</div>
</div>
</div>
<RightBasicTable>
<template #action="{ row }">
<a-button size="small" @click="handleRemoveItem(row)">
移除
</a-button>
</template>
</RightBasicTable>
</div>
</div>
</BasicModal>
</template>
<style scoped>
:deep(div.vben-link) {
display: none;
}
:deep(.vxe-body--row) {
cursor: pointer;
}
</style>

View File

@ -168,6 +168,6 @@ function handleTest() {
</Space>
</template>
</BasicTable>
<UserSelectModal />
<UserSelectModal mode="single" />
</Page>
</template>