refactor: bpm

pull/133/MERGE
xingyu4j 2025-06-06 20:45:45 +08:00
parent 7e8f2a1328
commit 2c3dd668e3
47 changed files with 1454 additions and 1898 deletions

View File

@ -1,13 +1,9 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmCategoryApi } from '#/api/bpm/category';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { z } from '#/adapter/form';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -106,9 +102,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = BpmCategoryApi.CategoryVO>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -146,29 +140,10 @@ export function useGridColumns<T = BpmCategoryApi.CategoryVO>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 180,
align: 'center',
width: 180,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '流程分类',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['bpm:category:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['bpm:category:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,16 +1,12 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmCategoryApi } from '#/api/bpm/category';
import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteCategory, getCategoryPage } from '#/api/bpm/category';
import { DocAlert } from '#/components/doc-alert';
import { $t } from '#/locales';
@ -22,12 +18,46 @@ const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建流程分类 */
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑流程分类 */
function handleEdit(row: BpmCategoryApi.CategoryVO) {
formModalApi.setData(row).open();
}
/** 删除流程分类 */
async function handleDelete(row: BpmCategoryApi.CategoryVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.code]),
key: 'action_key_msg',
});
try {
await deleteCategory(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.code]),
key: 'action_key_msg',
});
onRefresh();
} catch {
hideLoading();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -50,54 +80,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
} as VxeTableGridOptions<BpmCategoryApi.CategoryVO>,
});
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmCategoryApi.CategoryVO>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建流程分类 */
function onCreate() {
formModalApi.setData(null).open();
}
/** 编辑流程分类 */
function onEdit(row: BpmCategoryApi.CategoryVO) {
formModalApi.setData(row).open();
}
/** 删除流程分类 */
async function onDelete(row: BpmCategoryApi.CategoryVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.code]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteCategory(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
onRefresh();
} catch {
hideLoading();
}
}
</script>
<template>
@ -109,14 +91,41 @@ async function onDelete(row: BpmCategoryApi.CategoryVO) {
<FormModal @success="onRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['bpm:category:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['流程分类']) }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['流程分类']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['bpm:category:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['bpm:category:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['bpm:category:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -1,15 +1,9 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmFormApi } from '#/api/bpm/form';
import { useAccess } from '@vben/access';
import { $t } from '@vben/locales';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { z } from '#/adapter/form';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -68,9 +62,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = BpmFormApi.FormVO>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -103,41 +95,10 @@ export function useGridColumns<T = BpmFormApi.FormVO>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 200,
align: 'center',
width: 240,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '流程名称',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'copy',
text: $t('ui.actionTitle.copy'),
show: hasAccessByCodes(['bpm:form:update']),
},
{
code: 'edit',
text: $t('ui.actionTitle.edit'),
show: hasAccessByCodes(['bpm:form:update']),
},
{
code: 'detail',
text: $t('ui.actionTitle.detail'),
show: hasAccessByCodes(['bpm:form:query']),
},
{
code: 'delete',
text: $t('ui.actionTitle.delete'),
show: hasAccessByCodes(['bpm:form:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,36 +1,98 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmFormApi } from '#/api/bpm/form';
import { ref, watch } from 'vue';
import { watch } from 'vue';
import { useRoute } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { $t } from '@vben/locales';
import FormCreate from '@form-create/ant-design-vue';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteForm, getFormDetail, getFormPage } from '#/api/bpm/form';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteForm, getFormPage } from '#/api/bpm/form';
import { DocAlert } from '#/components/doc-alert';
import { router } from '#/router';
import { setConfAndFields2 } from '#/utils';
import { useGridColumns, useGridFormSchema } from './data';
import Detail from './modules/detail.vue';
defineOptions({ name: 'BpmForm' });
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 新增 */
function handleCreate() {
router.push({
name: 'BpmFormEditor',
query: {
type: 'create',
},
});
}
/** 编辑 */
function handleEdit(row: BpmFormApi.FormVO) {
router.push({
name: 'BpmFormEditor',
query: {
id: row.id,
type: 'edit',
},
});
}
/** 复制 */
function handleCopy(row: BpmFormApi.FormVO) {
router.push({
name: 'BpmFormEditor',
query: {
copyId: row.id,
type: 'copy',
},
});
}
/** 删除 */
async function handleDelete(row: BpmFormApi.FormVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
key: 'action_key_msg',
});
try {
await deleteForm(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} finally {
hideLoading();
}
}
async function handleDetail(row: BpmFormApi.FormVO) {
detailModalApi.setData(row).open();
}
/** 详情弹窗 */
const [DetailModal, detailModalApi] = useVbenModal({
connectedComponent: Detail,
destroyOnClose: true,
});
/** 检测路由参数 */
const route = useRoute();
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -57,101 +119,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
} as VxeTableGridOptions<BpmFormApi.FormVO>,
});
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<BpmFormApi.FormVO>) {
switch (code) {
case 'copy': {
onCopy(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
case 'detail': {
onDetail(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
/** 复制 */
function onCopy(row: BpmFormApi.FormVO) {
router.push({
name: 'BpmFormEditor',
query: {
copyId: row.id,
type: 'copy',
},
});
}
/** 删除 */
async function onDelete(row: BpmFormApi.FormVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteForm(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
onRefresh();
} finally {
hideLoading();
}
}
/** 详情 */
const formConfig = ref<any>({});
async function onDetail(row: BpmFormApi.FormVO) {
formConfig.value = await getFormDetail(row.id as number);
setConfAndFields2(
formConfig.value,
formConfig.value.conf,
formConfig.value.fields,
);
detailModalApi.open();
}
/** 编辑 */
function onEdit(row: BpmFormApi.FormVO) {
router.push({
name: 'BpmFormEditor',
query: {
id: row.id,
type: 'edit',
},
});
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 新增 */
function onCreate() {
router.push({
name: 'BpmFormEditor',
query: {
type: 'create',
},
});
}
/** 详情弹窗 */
const [DetailModal, detailModalApi] = useVbenModal({
destroyOnClose: true,
footer: false,
});
/** 检测路由参数 */
const route = useRoute();
watch(
() => route.query.refresh,
(val) => {
@ -171,25 +138,59 @@ watch(
url="https://doc.iocoder.cn/bpm/use-bpm-form/"
/>
</template>
<DetailModal />
<Grid table-title="">
<template #toolbar-tools>
<Button type="primary" @click="onCreate">
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['流程表单']) }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['流程表单']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['bpm:form:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('ui.actionTitle.copy'),
type: 'link',
icon: ACTION_ICON.COPY,
auth: ['bpm:form:update'],
onClick: handleCopy.bind(null, row),
},
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['bpm:form:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['bpm:form:query'],
onClick: handleDetail.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['bpm:form:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
<DetailModal
title="流程表单详情"
class="w-[800px]"
:body-style="{
maxHeight: '100px',
}"
>
<div class="mx-4">
<FormCreate :option="formConfig.option" :rule="formConfig.rule" />
</div>
</DetailModal>
</Page>
</template>

View File

@ -0,0 +1,50 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import FormCreate from '@form-create/ant-design-vue';
import { getFormDetail } from '#/api/bpm/form';
import { setConfAndFields2 } from '#/utils';
/** 详情 */
const formConfig = ref<any>({});
const [Modal, modalApi] = useVbenModal({
footer: false,
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
return;
}
//
const data = modalApi.getData();
if (!data || !data.id) {
return;
}
modalApi.lock();
try {
formConfig.value = await getFormDetail(data.id as number);
setConfAndFields2(
formConfig.value,
formConfig.value.conf,
formConfig.value.fields,
);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal
class="w-[600px]"
title="流程表单详情"
:body-style="{
maxHeight: '100px',
}"
>
<FormCreate :option="formConfig.option" :rule="formConfig.rule" />
</Modal>
</template>

View File

@ -106,7 +106,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal :title="getTitle" class="w-[600px]">
<Modal :title="getTitle" class="w-[40%]">
<Form class="mx-4" />
</Modal>
</template>

View File

@ -1,14 +1,10 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmCategoryApi } from '#/api/bpm/category';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { z } from '#/adapter/form';
import { getSimpleUserList } from '#/api/system/user';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -99,10 +95,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = BpmCategoryApi.CategoryVO>(
onActionClick: OnActionClickFn<T>,
getMemberNames: (userIds: number[]) => string,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -123,9 +116,7 @@ export function useGridColumns<T = BpmCategoryApi.CategoryVO>(
field: 'userIds',
title: '成员',
minWidth: 200,
formatter: ({ cellValue }) => {
return getMemberNames(cellValue);
},
slots: { default: 'userIds' },
},
{
field: 'status',
@ -143,29 +134,10 @@ export function useGridColumns<T = BpmCategoryApi.CategoryVO>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 180,
align: 'center',
width: 180,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '用户分组',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['bpm:user-group:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['bpm:user-group:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,19 +1,15 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmUserGroupApi } from '#/api/bpm/userGroup';
import type { SystemUserApi } from '#/api/system/user';
import { onMounted, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { message, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteUserGroup, getUserGroupPage } from '#/api/bpm/userGroup';
import { getSimpleUserList } from '#/api/system/user';
import { DocAlert } from '#/components/doc-alert';
@ -26,12 +22,53 @@ const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建用户分组 */
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑用户分组 */
function handleEdit(row: BpmUserGroupApi.UserGroupVO) {
formModalApi.setData(row).open();
}
/** 删除用户分组 */
async function handleDelete(row: BpmUserGroupApi.UserGroupVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
key: 'action_key_msg',
});
try {
await deleteUserGroup(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} catch {
hideLoading();
}
}
const userList = ref<SystemUserApi.User[]>([]);
/** 初始化 */
onMounted(async () => {
//
userList.value = await getSimpleUserList();
});
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick, getMemberNames),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -54,73 +91,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
} as VxeTableGridOptions<BpmUserGroupApi.UserGroupVO>,
});
/** 获取分组成员姓名 */
function getMemberNames(userIds: number[]) {
const userMap = new Map(
userList.value.map((user) => [user.id, user.nickname]),
);
return userIds
.map((userId) => userMap.get(userId))
.filter(Boolean)
.join('、');
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmUserGroupApi.UserGroupVO>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建用户分组 */
function onCreate() {
formModalApi.setData(null).open();
}
/** 编辑用户分组 */
function onEdit(row: BpmUserGroupApi.UserGroupVO) {
formModalApi.setData(row).open();
}
/** 删除用户分组 */
async function onDelete(row: BpmUserGroupApi.UserGroupVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteUserGroup(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
onRefresh();
} catch {
hideLoading();
}
}
//
const userList = ref<SystemUserApi.User[]>([]);
/** 初始化 */
onMounted(async () => {
//
userList.value = await getSimpleUserList();
});
</script>
<template>
@ -132,18 +102,46 @@ onMounted(async () => {
<FormModal @success="onRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['bpm:category:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['用户分组']) }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['用户分组']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['bpm:user-group:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #userIds-cell="{ row }">
<span>{{ row.nicknames }}</span>
<template #userIds="{ row }">
<Tag v-for="userId in row.userIds" :key="userId" color="blue">
{{ userList.find((u) => u.id === userId)?.nickname }}
</Tag>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['bpm:user-group:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['bpm:user-group:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -64,7 +64,6 @@ const [Modal, modalApi] = useVbenModal({
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
//
@ -85,7 +84,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal :title="getTitle">
<Modal :title="getTitle" class="w-[40%]">
<Form class="mx-4" />
</Modal>
</template>

View File

@ -60,24 +60,24 @@ const processDesignRef = ref<InstanceType<typeof ProcessDesign>>();
const extraSettingRef = ref<InstanceType<typeof ExtraSetting>>();
/** 步骤校验函数 */
const validateBasic = async () => {
async function validateBasic() {
await basicInfoRef.value?.validate();
};
}
/** 表单设计校验 */
const validateForm = async () => {
async function validateForm() {
await formDesignRef.value?.validate();
};
}
/** 流程设计校验 */
const validateProcess = async () => {
async function validateProcess() {
await processDesignRef.value?.validate();
};
}
/** 更多设置校验 */
const validateExtra = async () => {
async function validateExtra() {
await extraSettingRef.value?.validate();
};
}
const currentStep = ref(-1); // -1
@ -139,7 +139,7 @@ const deptList = ref<SystemDeptApi.Dept[]>([]);
/** 初始化数据 */
const actionType = route.params.type as string;
const initData = async () => {
async function initData() {
if (actionType === 'definition') {
//
const definitionId = route.params.id as string;
@ -200,7 +200,7 @@ const initData = async () => {
//
extraSettingRef.value?.initData();
};
}
/** 根据类型切换流程数据 */
watch(
@ -218,7 +218,7 @@ watch(
);
/** 校验所有步骤数据是否完整 */
const validateAllSteps = async () => {
async function validateAllSteps() {
//
try {
await validateBasic();
@ -254,10 +254,10 @@ const validateAllSteps = async () => {
}
return true;
};
}
/** 保存操作 */
const handleSave = async () => {
async function handleSave() {
try {
//
const result = await validateAllSteps();
@ -311,10 +311,10 @@ const handleSave = async () => {
console.error('保存失败:', error);
// message.warning(error.msg || '');
}
};
}
/** 发布操作 */
const handleDeploy = async () => {
async function handleDeploy() {
try {
//
if (!formData.value.id) {
@ -345,10 +345,10 @@ const handleDeploy = async () => {
console.error('发布失败:', error);
message.warning(error.message || '发布失败');
}
};
}
/** 步骤切换处理 */
const handleStepClick = async (index: number) => {
async function handleStepClick(index: number) {
try {
if (index !== 0) {
await validateBasic();
@ -370,17 +370,17 @@ const handleStepClick = async (index: number) => {
message.warning('请先完善当前步骤必填信息');
}
}
};
}
const tabs = useTabs();
/** 返回列表页 */
const handleBack = () => {
function handleBack() {
//
tabs.closeCurrentTab();
// 使 name 'name'+ menuId
router.push({ path: '/bpm/manager/model' });
};
}
/** 初始化 */
onMounted(async () => {
@ -398,11 +398,9 @@ onBeforeUnmount(() => {
<template>
<Page auto-content-height>
<div class="mx-auto">
<!-- 头部导航栏 -->
<div
class="absolute inset-x-0 top-0 z-10 flex h-12 items-center border-b bg-white px-5"
>
<!-- 主体内容 -->
<Card class="mb-4">
<template #title>
<!-- 左侧标题 -->
<div class="flex w-[200px] items-center overflow-hidden">
<ArrowLeft
@ -416,88 +414,82 @@ onBeforeUnmount(() => {
{{ formData.name || '创建流程' }}
</span>
</div>
<!-- 步骤条 -->
<div class="flex h-full flex-1 items-center justify-center">
<div class="flex h-full w-[400px] items-center justify-between">
</template>
<template #extra>
<Button
v-if="actionType === 'update'"
type="primary"
@click="handleDeploy"
>
</Button>
<Button type="primary" @click="handleSave">
<span v-if="actionType === 'definition'"> </span>
<span v-else> </span>
</Button>
</template>
<!-- 步骤条 -->
<div class="flex h-full flex-1 items-center justify-center">
<div class="flex h-full w-[400px] items-center justify-between">
<div
v-for="(step, index) in steps"
:key="index"
class="relative mx-[15px] flex h-full cursor-pointer items-center"
:class="[
currentStep === index
? 'border-b-2 border-solid border-blue-500 text-blue-500'
: 'text-gray-500',
]"
@click="handleStepClick(index)"
>
<div
v-for="(step, index) in steps"
:key="index"
class="relative mx-[15px] flex h-full cursor-pointer items-center"
class="mr-2 flex h-7 w-7 items-center justify-center rounded-full border-2 border-solid text-[15px]"
:class="[
currentStep === index
? 'border-b-2 border-solid border-blue-500 text-blue-500'
: 'text-gray-500',
? 'border-blue-500 bg-blue-500 text-white'
: 'border-gray-300 bg-white text-gray-500',
]"
@click="handleStepClick(index)"
>
<div
class="mr-2 flex h-7 w-7 items-center justify-center rounded-full border-2 border-solid text-[15px]"
:class="[
currentStep === index
? 'border-blue-500 bg-blue-500 text-white'
: 'border-gray-300 bg-white text-gray-500',
]"
>
{{ index + 1 }}
</div>
<span class="whitespace-nowrap text-base font-bold">{{
step.title
}}</span>
{{ index + 1 }}
</div>
<span class="whitespace-nowrap text-base font-bold">{{
step.title
}}</span>
</div>
</div>
<!-- 右侧按钮 -->
<div class="flex w-[200px] items-center justify-end gap-2">
<Button
v-if="actionType === 'update'"
type="primary"
@click="handleDeploy"
>
</Button>
<Button type="primary" @click="handleSave">
<span v-if="actionType === 'definition'"> </span>
<span v-else> </span>
</Button>
</div>
</div>
<!-- 主体内容 -->
<Card :body-style="{ padding: '10px' }" class="mb-4">
<div class="mt-[50px]">
<!-- 第一步基本信息 -->
<div v-if="currentStep === 0" class="mx-auto w-4/6">
<BasicInfo
v-model="formData"
:category-list="categoryList"
:user-list="userList"
:dept-list="deptList"
ref="basicInfoRef"
/>
</div>
<!-- 第二步表单设计 -->
<div v-if="currentStep === 1" class="mx-auto w-4/6">
<FormDesign
v-model="formData"
:form-list="formList"
ref="formDesignRef"
/>
</div>
<!-- 第三步流程设计 -->
<ProcessDesign
v-if="currentStep === 2"
<div class="mt-[50px]">
<!-- 第一步基本信息 -->
<div v-if="currentStep === 0" class="mx-auto w-4/6">
<BasicInfo
v-model="formData"
ref="processDesignRef"
:category-list="categoryList"
:user-list="userList"
:dept-list="deptList"
ref="basicInfoRef"
/>
<!-- 第四步更多设置 -->
<div v-if="currentStep === 3" class="mx-auto w-4/6">
<ExtraSetting v-model="formData" ref="extraSettingRef" />
</div>
</div>
</Card>
</div>
<!-- 第二步表单设计 -->
<div v-if="currentStep === 1" class="mx-auto w-4/6">
<FormDesign
v-model="formData"
:form-list="formList"
ref="formDesignRef"
/>
</div>
<!-- 第三步流程设计 -->
<ProcessDesign
v-if="currentStep === 2"
v-model="formData"
ref="processDesignRef"
/>
<!-- 第四步更多设置 -->
<div v-if="currentStep === 3" class="mx-auto w-4/6">
<ExtraSetting v-model="formData" ref="extraSettingRef" />
</div>
</div>
</Card>
</Page>
</template>

View File

@ -10,6 +10,7 @@ import type { SystemUserApi } from '#/api/system/user';
import { ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { CircleHelp, IconifyIcon, Plus, X } from '@vben/icons';
import {
@ -41,6 +42,16 @@ const props = defineProps({
},
});
const [UserSelectModalComp, userSelectModalApi] = useVbenModal({
connectedComponent: UserSelectModal,
destroyOnClose: true,
});
const [DeptSelectModalComp, deptSelectModalApi] = useVbenModal({
connectedComponent: DeptSelectModal,
destroyOnClose: true,
});
//
const formRef = ref();
@ -52,8 +63,6 @@ const selectedStartDepts = ref<SystemDeptApi.Dept[]>([]);
//
const selectedManagerUsers = ref<SystemUserApi.User[]>([]);
const userSelectFormRef = ref();
const deptSelectFormRef = ref();
const currentSelectType = ref<'manager' | 'start'>('start');
//
const selectedUsers = ref<number[]>();
@ -98,37 +107,37 @@ watch(
);
/** 打开发起人选择 */
const openStartUserSelect = () => {
function openStartUserSelect() {
currentSelectType.value = 'start';
selectedUsers.value = selectedStartUsers.value.map(
(user) => user.id,
) as number[];
userSelectFormRef.value.open(selectedUsers.value);
};
userSelectModalApi.setData({ userIds: selectedUsers.value }).open();
}
/** 打开部门选择 */
const openStartDeptSelect = () => {
deptSelectFormRef.value.open(selectedStartDepts.value);
};
function openStartDeptSelect() {
deptSelectModalApi.setData({ selectedList: selectedStartDepts.value }).open();
}
/** 处理部门选择确认 */
const handleDeptSelectConfirm = (depts: SystemDeptApi.Dept[]) => {
function handleDeptSelectConfirm(depts: SystemDeptApi.Dept[]) {
modelData.value = {
...modelData.value,
startDeptIds: depts.map((d) => d.id),
};
};
}
/** 打开管理员选择 */
const openManagerUserSelect = () => {
function openManagerUserSelect() {
currentSelectType.value = 'manager';
selectedUsers.value = selectedManagerUsers.value.map(
(user) => user.id,
) as number[];
userSelectFormRef.value.open(selectedUsers.value);
};
userSelectModalApi.setData({ userIds: selectedUsers.value }).open();
}
/** 处理用户选择确认 */
const handleUserSelectConfirm = (userList: SystemUserApi.User[]) => {
function handleUserSelectConfirm(userList: SystemUserApi.User[]) {
modelData.value =
currentSelectType.value === 'start'
? {
@ -139,20 +148,20 @@ const handleUserSelectConfirm = (userList: SystemUserApi.User[]) => {
...modelData.value,
managerUserIds: userList.map((u) => u.id),
};
};
}
/** 用户选择弹窗关闭 */
const handleUserSelectClosed = () => {
function handleUserSelectClosed() {
selectedUsers.value = [];
};
}
/** 用户选择弹窗取消 */
const handleUserSelectCancel = () => {
function handleUserSelectCancel() {
selectedUsers.value = [];
};
}
/** 处理发起人类型变化 */
const handleStartUserTypeChange = (value: SelectValue) => {
function handleStartUserTypeChange(value: SelectValue) {
const numValue = Number(value);
switch (numValue) {
case 0: {
@ -181,270 +190,266 @@ const handleStartUserTypeChange = (value: SelectValue) => {
break;
}
}
};
}
/** 移除发起人 */
const handleRemoveStartUser = (user: SystemUserApi.User) => {
function handleRemoveStartUser(user: SystemUserApi.User) {
modelData.value = {
...modelData.value,
startUserIds: modelData.value.startUserIds.filter(
(id: number) => id !== user.id,
),
};
};
}
/** 移除部门 */
const handleRemoveStartDept = (dept: SystemDeptApi.Dept) => {
function handleRemoveStartDept(dept: SystemDeptApi.Dept) {
modelData.value = {
...modelData.value,
startDeptIds: modelData.value.startDeptIds.filter(
(id: number) => id !== dept.id,
),
};
};
}
/** 移除管理员 */
const handleRemoveManagerUser = (user: SystemUserApi.User) => {
function handleRemoveManagerUser(user: SystemUserApi.User) {
modelData.value = {
...modelData.value,
managerUserIds: modelData.value.managerUserIds.filter(
(id: number) => id !== user.id,
),
};
};
}
/** 表单校验 */
const validate = async () => {
async function validate() {
await formRef.value?.validate();
};
}
defineExpose({ validate });
</script>
<template>
<Form
ref="formRef"
:model="modelData"
:rules="rules"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
class="mt-5"
>
<Form.Item label="流程标识" name="key" class="mb-5">
<div class="flex items-center">
<div>
<Form
ref="formRef"
:model="modelData"
:rules="rules"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
class="mt-5"
>
<Form.Item label="流程标识" name="key" class="mb-5">
<div class="flex items-center">
<Input
class="w-full"
v-model:value="modelData.key"
:disabled="!!modelData.id"
placeholder="请输入流程标识,以字母或下划线开头"
/>
<Tooltip
:title="
modelData.id ? '流程标识不可修改!' : '新建后,流程标识不可修改!'
"
placement="top"
>
<CircleHelp class="ml-1 size-5 text-gray-900" />
</Tooltip>
</div>
</Form.Item>
<Form.Item label="流程名称" name="name" class="mb-5">
<Input
class="w-full"
v-model:value="modelData.key"
v-model:value="modelData.name"
:disabled="!!modelData.id"
placeholder="请输入流程标识,以字母或下划线开头"
allow-clear
placeholder="请输入流程名称"
/>
<Tooltip
:title="
modelData.id ? '流程标识不可修改!' : '新建后,流程标识不可修改!'
"
placement="top"
</Form.Item>
<Form.Item label="流程分类" name="category" class="mb-5">
<Select
class="w-full"
v-model:value="modelData.category"
allow-clear
placeholder="请选择流程分类"
>
<CircleHelp class="ml-1 size-5 text-gray-900" />
</Tooltip>
</div>
</Form.Item>
<Form.Item label="流程名称" name="name" class="mb-5">
<Input
v-model:value="modelData.name"
:disabled="!!modelData.id"
allow-clear
placeholder="请输入流程名称"
/>
</Form.Item>
<Form.Item label="流程分类" name="category" class="mb-5">
<Select
class="w-full"
v-model:value="modelData.category"
allow-clear
placeholder="请选择流程分类"
>
<Select.Option
v-for="category in categoryList"
:key="category.code"
:value="category.code"
<Select.Option
v-for="category in categoryList"
:key="category.code"
:value="category.code"
>
{{ category.name }}
</Select.Option>
</Select>
</Form.Item>
<Form.Item label="流程图标" class="mb-5">
<ImageUpload v-model:value="modelData.icon" />
</Form.Item>
<Form.Item label="流程描述" name="description" class="mb-5">
<Input.TextArea v-model:value="modelData.description" allow-clear />
</Form.Item>
<Form.Item label="流程类型" name="type" class="mb-5">
<Radio.Group v-model:value="modelData.type">
<!-- TODO BPMN 流程类型需要整合暂时禁用 -->
<Radio
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
:key="dict.value"
:value="dict.value"
:disabled="dict.value === 10"
>
{{ dict.label }}
</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="是否可见" name="visible" class="mb-5">
<Radio.Group v-model:value="modelData.visible">
<Radio
v-for="(dict, index) in getBoolDictOptions(
DICT_TYPE.INFRA_BOOLEAN_STRING,
)"
:key="index"
:value="dict.value"
>
{{ dict.label }}
</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="谁可以发起" name="startUserType" class="mb-5">
<Select
v-model:value="modelData.startUserType"
placeholder="请选择谁可以发起"
@change="handleStartUserTypeChange"
>
{{ category.name }}
</Select.Option>
</Select>
</Form.Item>
<Form.Item label="流程图标" class="mb-5">
<ImageUpload v-model:value="modelData.icon" />
</Form.Item>
<Form.Item label="流程描述" name="description" class="mb-5">
<Input.TextArea v-model:value="modelData.description" allow-clear />
</Form.Item>
<Form.Item label="流程类型" name="type" class="mb-5">
<Radio.Group v-model:value="modelData.type">
<!-- TODO BPMN 流程类型需要整合暂时禁用 -->
<Radio
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
:key="dict.value"
:value="dict.value"
:disabled="dict.value === 10"
>
{{ dict.label }}
</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="是否可见" name="visible" class="mb-5">
<Radio.Group v-model:value="modelData.visible">
<Radio
v-for="(dict, index) in getBoolDictOptions(
DICT_TYPE.INFRA_BOOLEAN_STRING,
)"
:key="index"
:value="dict.value"
>
{{ dict.label }}
</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="谁可以发起" name="startUserType" class="mb-5">
<Select
v-model:value="modelData.startUserType"
placeholder="请选择谁可以发起"
@change="handleStartUserTypeChange"
>
<Select.Option :value="0">全员</Select.Option>
<Select.Option :value="1">指定人员</Select.Option>
<Select.Option :value="2">指定部门</Select.Option>
</Select>
<div
v-if="modelData.startUserType === 1"
class="mt-2 flex flex-wrap gap-2"
>
<Select.Option :value="0">全员</Select.Option>
<Select.Option :value="1">指定人员</Select.Option>
<Select.Option :value="2">指定部门</Select.Option>
</Select>
<div
v-for="user in selectedStartUsers"
:key="user.id"
class="relative flex h-9 items-center rounded-full bg-gray-100 pr-2"
v-if="modelData.startUserType === 1"
class="mt-2 flex flex-wrap gap-2"
>
<Avatar
class="m-1"
:size="28"
v-if="user.avatar"
:src="user.avatar"
/>
<Avatar class="m-1" :size="28" v-else>
{{ user.nickname?.substring(0, 1) }}
</Avatar>
{{ user.nickname }}
<X
class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500"
@click="handleRemoveStartUser(user)"
/>
<div
v-for="user in selectedStartUsers"
:key="user.id"
class="bg-destructive text-destructive-foreground hover:bg-destructive-hover relative flex h-9 items-center rounded-full pr-2"
>
<Avatar
class="m-1"
:size="28"
v-if="user.avatar"
:src="user.avatar"
/>
<Avatar class="m-1" :size="28" v-else>
{{ user.nickname?.substring(0, 1) }}
</Avatar>
{{ user.nickname }}
<X
class="ml-2 size-4 cursor-pointer"
@click="handleRemoveStartUser(user)"
/>
</div>
<Button
type="link"
@click="openStartUserSelect"
class="flex items-center"
>
<template #icon>
<IconifyIcon
icon="mdi:account-plus-outline"
class="size-[18px]"
/>
</template>
选择人员
</Button>
</div>
<Button
type="link"
@click="openStartUserSelect"
class="flex items-center"
>
<template #icon>
<IconifyIcon icon="mdi:account-plus-outline" class="size-[18px]" />
</template>
选择人员
</Button>
</div>
<div
v-if="modelData.startUserType === 2"
class="mt-2 flex flex-wrap gap-2"
>
<div
v-for="dept in selectedStartDepts"
:key="dept.id"
class="relative flex h-9 items-center rounded-full bg-gray-100 pr-2"
v-if="modelData.startUserType === 2"
class="mt-2 flex flex-wrap gap-2"
>
<IconifyIcon icon="ep:office-building" class="size-6 px-1" />
{{ dept.name }}
<X
class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500"
@click="handleRemoveStartDept(dept)"
/>
<div
v-for="dept in selectedStartDepts"
:key="dept.id"
class="bg-destructive text-destructive-foreground hover:bg-destructive-hover relative flex h-9 items-center rounded-full pr-2 shadow-sm"
>
<IconifyIcon icon="ep:office-building" class="size-6 px-1" />
{{ dept.name }}
<X
class="ml-2 size-4 cursor-pointer"
@click="handleRemoveStartDept(dept)"
/>
</div>
<Button
type="link"
@click="openStartDeptSelect"
class="flex items-center"
>
<template #icon>
<Plus class="size-[18px]" />
</template>
选择部门
</Button>
</div>
<Button
type="link"
@click="openStartDeptSelect"
class="flex items-center"
>
<template #icon>
<Plus class="size-[18px]" />
</template>
选择部门
</Button>
</div>
</Form.Item>
<Form.Item label="流程管理员" name="managerUserIds" class="mb-5">
<div class="flex flex-wrap gap-2">
<div
v-for="user in selectedManagerUsers"
:key="user.id"
class="relative flex h-9 items-center rounded-full bg-gray-100 pr-2"
>
<Avatar
class="m-1"
:size="28"
v-if="user.avatar"
:src="user.avatar"
/>
<Avatar class="m-1" :size="28" v-else>
{{ user.nickname?.substring(0, 1) }}
</Avatar>
{{ user.nickname }}
<X
class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500"
@click="handleRemoveManagerUser(user)"
/>
</Form.Item>
<Form.Item label="流程管理员" name="managerUserIds" class="mb-5">
<div class="flex flex-wrap gap-2">
<div
v-for="user in selectedManagerUsers"
:key="user.id"
class="bg-destructive text-destructive-foreground hover:bg-destructive-hover relative flex h-9 items-center rounded-full pr-2"
>
<Avatar
class="m-1"
:size="28"
v-if="user.avatar"
:src="user.avatar"
/>
<Avatar class="m-1" :size="28" v-else>
{{ user.nickname?.substring(0, 1) }}
</Avatar>
{{ user.nickname }}
<X
class="ml-2 size-4 cursor-pointer"
@click="handleRemoveManagerUser(user)"
/>
</div>
<Button
type="link"
@click="openManagerUserSelect"
class="flex items-center"
>
<template #icon>
<IconifyIcon
icon="mdi:account-plus-outline"
class="size-[18px]"
/>
</template>
选择人员
</Button>
</div>
<Button
type="link"
@click="openManagerUserSelect"
class="flex items-center"
>
<template #icon>
<IconifyIcon icon="mdi:account-plus-outline" class="size-[18px]" />
</template>
选择人员
</Button>
</div>
</Form.Item>
</Form>
</Form.Item>
</Form>
<!-- 用户选择弹窗 -->
<UserSelectModal
ref="userSelectFormRef"
v-model:value="selectedUsers"
:multiple="true"
title="选择用户"
@confirm="handleUserSelectConfirm"
@closed="handleUserSelectClosed"
@cancel="handleUserSelectCancel"
/>
<!-- 部门选择对话框 -->
<DeptSelectModal
ref="deptSelectFormRef"
title="发起人部门选择"
:check-strictly="true"
@confirm="handleDeptSelectConfirm"
/>
<!-- 用户选择弹窗 -->
<UserSelectModalComp
v-model:value="selectedUsers"
:multiple="true"
title="选择用户"
@confirm="handleUserSelectConfirm"
@closed="handleUserSelectClosed"
@cancel="handleUserSelectCancel"
/>
<!-- 部门选择对话框 -->
<DeptSelectModalComp
title="发起人部门选择"
:check-strictly="true"
@confirm="handleDeptSelectConfirm"
/>
</div>
</template>
<style lang="scss" scoped>
.bg-gray-100 {
background-color: #f5f7fa;
transition: all 0.3s;
&:hover {
background-color: #e6e8eb;
}
}
.upload-img-placeholder {
cursor: pointer;
background-color: #fafafa;
transition: all 0.3s;
&:hover {

View File

@ -91,9 +91,9 @@ const numberExample = computed(() => {
/** 是否开启流程前置通知 */
const processBeforeTriggerEnable = ref(false);
const handleProcessBeforeTriggerEnableChange = (
function handleProcessBeforeTriggerEnableChange(
val: boolean | number | string,
) => {
) {
modelData.value.processBeforeTriggerSetting = val
? {
url: '',
@ -102,13 +102,11 @@ const handleProcessBeforeTriggerEnableChange = (
response: [],
}
: null;
};
}
/** 是否开启流程后置通知 */
const processAfterTriggerEnable = ref(false);
const handleProcessAfterTriggerEnableChange = (
val: boolean | number | string,
) => {
function handleProcessAfterTriggerEnableChange(val: boolean | number | string) {
modelData.value.processAfterTriggerSetting = val
? {
url: '',
@ -117,13 +115,11 @@ const handleProcessAfterTriggerEnableChange = (
response: [],
}
: null;
};
}
/** 是否开启任务前置通知 */
const taskBeforeTriggerEnable = ref(false);
const handleTaskBeforeTriggerEnableChange = (
val: boolean | number | string,
) => {
function handleTaskBeforeTriggerEnableChange(val: boolean | number | string) {
modelData.value.taskBeforeTriggerSetting = val
? {
url: '',
@ -132,11 +128,11 @@ const handleTaskBeforeTriggerEnableChange = (
response: [],
}
: null;
};
}
/** 是否开启任务后置通知 */
const taskAfterTriggerEnable = ref(false);
const handleTaskAfterTriggerEnableChange = (val: boolean | number | string) => {
function handleTaskAfterTriggerEnableChange(val: boolean | number | string) {
modelData.value.taskAfterTriggerSetting = val
? {
url: '',
@ -145,7 +141,7 @@ const handleTaskAfterTriggerEnableChange = (val: boolean | number | string) => {
response: [],
}
: null;
};
}
/** 表单选项 */
const formField = ref<Array<{ field: string; title: string }>>([]);
@ -181,7 +177,7 @@ const formFieldOptions4Summary = computed(() => {
});
/** 兼容以前未配置更多设置的流程 */
const initData = () => {
function initData() {
if (!modelData.value.processIdRule) {
modelData.value.processIdRule = {
enable: false,
@ -218,7 +214,7 @@ const initData = () => {
if (modelData.value.taskAfterTriggerSetting) {
taskAfterTriggerEnable.value = true;
}
};
}
/** 监听表单 ID 变化,加载表单数据 */
watch(
@ -242,9 +238,9 @@ watch(
//
const formRef = ref();
/** 表单校验 */
const validate = async () => {
async function validate() {
await formRef.value?.validate();
};
}
defineExpose({ initData, validate });
</script>

View File

@ -80,9 +80,9 @@ const rules: Record<string, Rule[]> = {
};
/** 表单校验 */
const validate = async () => {
async function validate() {
await formRef.value?.validate();
};
}
defineExpose({ validate });
</script>

View File

@ -16,7 +16,7 @@ const processData = inject('processData') as Ref;
const simpleDesign = ref();
/** 表单校验 */
const validate = async () => {
async function validate() {
//
if (!processData.value) {
throw new Error('请设计流程');
@ -29,9 +29,9 @@ const validate = async () => {
}
}
return true;
};
}
/** 处理设计器保存成功 */
const handleDesignSuccess = async (data?: any) => {
async function handleDesignSuccess(data?: any) {
if (data) {
//
const newModelData = {
@ -44,7 +44,7 @@ const handleDesignSuccess = async (data?: any) => {
//
modelData.value = newModelData;
}
};
}
/** 是否显示设计器 */
const showDesigner = computed(() => {

View File

@ -18,15 +18,15 @@ const emit = defineEmits(['success']);
const designerRef = ref();
/** 保存成功回调 */
const handleSuccess = (data?: any) => {
function handleSuccess(data?: any) {
if (data) {
emit('success', data);
}
};
}
/** 设计器配置校验 */
const validateConfig = async () => {
async function validateConfig() {
return await designerRef.value.validate();
};
}
defineExpose({ validateConfig });
</script>
<template>

View File

@ -4,21 +4,12 @@ import type { ModelCategoryInfo } from '#/api/bpm/model';
import { onActivated, reactive, ref, useTemplateRef, watch } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Plus, Search, Settings } from '@vben/icons';
import { IconifyIcon } from '@vben/icons';
import { cloneDeep } from '@vben/utils';
import { refAutoReset } from '@vueuse/core';
import { useSortable } from '@vueuse/integrations/useSortable';
import {
Button,
Card,
Divider,
Dropdown,
Form,
Input,
Menu,
message,
} from 'ant-design-vue';
import { Button, Card, Dropdown, Input, Menu, message } from 'ant-design-vue';
import {
getCategorySimpleList,
@ -72,7 +63,7 @@ watch(
);
/** 加载数据 */
const getList = async () => {
async function getList() {
modelListSpinning.value = true;
try {
const modelList = await getModelList(queryParams.name);
@ -89,27 +80,22 @@ const getList = async () => {
} finally {
modelListSpinning.value = false;
}
};
}
/** 初始化 */
onActivated(() => {
getList();
});
/** 查询方法 */
const handleQuery = () => {
getList();
};
/** 新增模型 */
const createModel = () => {
function createModel() {
router.push({
name: 'BpmModelCreate',
});
};
}
/** 处理下拉菜单命令 */
const handleCommand = (command: string) => {
function handleCommand(command: string) {
if (command === 'handleCategoryAdd') {
//
categoryFormModalApi.open();
@ -126,10 +112,10 @@ const handleCommand = (command: string) => {
});
}
}
};
}
/** 取消分类排序 */
const handleCategorySortCancel = () => {
function handleCategorySortCancel() {
//
categoryGroup.value = cloneDeep(originalData.value);
isCategorySorting.value = false;
@ -137,10 +123,10 @@ const handleCategorySortCancel = () => {
if (sortableInstance.value) {
sortableInstance.value.option('disabled', true);
}
};
}
/** 提交分类排序 */
const handleCategorySortSubmit = async () => {
async function handleCategorySortSubmit() {
saveSortLoading.value = true;
try {
//
@ -157,76 +143,56 @@ const handleCategorySortSubmit = async () => {
if (sortableInstance.value) {
sortableInstance.value.option('disabled', true);
}
};
}
</script>
<template>
<Page auto-content-height>
<!-- TODO @jaosn没头像的图标展示文字头像哈 @芋艿 好像已经展示了文字头像是模型列表中吗? -->
<!-- 流程分类表单弹窗 -->
<CategoryFormModal @success="getList" />
<Card
:body-style="{ padding: '10px' }"
class="mb-4 h-[98%]"
title="流程模型"
v-spinning="modelListSpinning"
>
<template #extra>
<Input
v-model:value="queryParams.name"
placeholder="搜索流程"
allow-clear
@press-enter="getList"
class="!w-60"
/>
<Button type="primary" @click="createModel">
<IconifyIcon icon="lucide:plus" /> 新建模型
</Button>
<Dropdown placement="bottomRight" arrow>
<Button>
<template #icon>
<IconifyIcon icon="lucide:settings" />
</template>
</Button>
<template #overlay>
<Menu @click="(e) => handleCommand(e.key as string)">
<Menu.Item key="handleCategoryAdd">
<div class="flex items-center">
<IconifyIcon icon="lucide:plus" />
新建分类
</div>
</Menu.Item>
<Menu.Item key="handleCategorySort">
<div class="flex items-center">
<IconifyIcon icon="lucide:align-start-vertical" />
分类排序
</div>
</Menu.Item>
</Menu>
</template>
</Dropdown>
</template>
<div class="flex h-full items-center justify-between pl-5">
<span class="-mb-4 text-lg font-extrabold">流程模型</span>
<!-- 搜索工作栏 -->
<Form
v-if="!isCategorySorting"
class="-mb-4 mr-2.5 flex"
:model="queryParams"
layout="inline"
>
<Form.Item name="name" class="ml-auto">
<Input
v-model:value="queryParams.name"
placeholder="搜索流程"
allow-clear
@press-enter="handleQuery"
class="!w-60"
>
<template #prefix>
<Search class="mx-2.5" />
</template>
</Input>
</Form.Item>
<!-- 右上角新建模型更多操作 -->
<Form.Item>
<Button type="primary" @click="createModel">
<Plus class="size-5" /> 新建模型
</Button>
</Form.Item>
<Form.Item>
<Dropdown placement="bottomRight" arrow>
<Button>
<template #icon>
<Settings class="size-4" />
</template>
</Button>
<template #overlay>
<Menu @click="(e) => handleCommand(e.key as string)">
<Menu.Item key="handleCategoryAdd">
<div class="flex items-center">
<span
class="icon-[ant-design--plus-outlined] mr-1.5 text-[18px]"
></span>
新建分类
</div>
</Menu.Item>
<Menu.Item key="handleCategorySort">
<div class="flex items-center">
<span class="icon-[fa--sort-amount-desc] mr-1.5"></span>
分类排序
</div>
</Menu.Item>
</Menu>
</template>
</Dropdown>
</Form.Item>
</Form>
<div class="-mb-4 mr-6" v-else>
<div class="mb-4 mr-6" v-if="isCategorySorting">
<Button @click="handleCategorySortCancel" class="mr-3">
</Button>
@ -240,7 +206,6 @@ const handleCategorySortSubmit = async () => {
</div>
</div>
<Divider />
<!-- 按照分类展示其所属的模型列表 -->
<div class="px-5" ref="categoryGroupRef">
<CategoryDraggableModel

View File

@ -4,6 +4,7 @@ import type { BpmModelApi, ModelCategoryInfo } from '#/api/bpm/model';
import { computed, ref, watchEffect } from 'vue';
import { confirm, useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { cloneDeep, formatDateTime, isEqual } from '@vben/utils';
import { useDebounceFn } from '@vueuse/core';
@ -36,6 +37,12 @@ const props = defineProps<{
const emit = defineEmits(['success']);
//
const [CategoryRenameModal, categoryRenameModalApi] = useVbenModal({
connectedComponent: CategoryRenameForm,
destroyOnClose: true,
});
const isModelSorting = ref(false);
const originalData = ref<BpmModelApi.ModelVO[]>([]);
const modelList = ref<BpmModelApi.ModelVO[]>([]);
@ -98,7 +105,7 @@ const columns = [
];
/** 处理模型的排序 */
const handleModelSort = () => {
function handleModelSort() {
//
originalData.value = cloneDeep(props.categoryInfo.modelList);
//
@ -114,10 +121,10 @@ const handleModelSort = () => {
disabled: false, //
});
}
};
}
/** 处理模型的排序提交 */
const handleModelSortSubmit = async () => {
async function handleModelSortSubmit() {
try {
//
const ids = modelList.value.map((item) => item.id);
@ -129,10 +136,10 @@ const handleModelSortSubmit = async () => {
} catch (error) {
console.error('排序保存失败', error);
}
};
}
/** 处理模型的排序取消 */
const handleModelSortCancel = () => {
function handleModelSortCancel() {
//
modelList.value = cloneDeep(originalData.value);
isModelSorting.value = false;
@ -140,20 +147,20 @@ const handleModelSortCancel = () => {
if (sortableInstance.value) {
sortableInstance.value.option('disabled', true);
}
};
}
/** 处理下拉菜单命令 */
const handleCommand = (command: string) => {
function handleCommand(command: string) {
if (command === 'renameCategory') {
//
categoryRenameModalApi.setData(props.categoryInfo).open();
} else if (command === 'deleteCategory') {
handleDeleteCategory();
}
};
}
/** 删除流程分类 */
const handleDeleteCategory = async () => {
async function handleDeleteCategory() {
if (props.categoryInfo.modelList.length > 0) {
message.warning('该分类下仍有流程定义,不允许删除');
return;
@ -170,13 +177,13 @@ const handleDeleteCategory = async () => {
//
emit('success');
});
};
}
/** 处理表单详情点击 */
const handleFormDetail = (row: any) => {
function handleFormDetail(row: any) {
// TODO
console.warn('待实现', row);
};
}
/** 更新 modelList 模型列表 */
const updateModelList = useDebounceFn(() => {
@ -205,17 +212,11 @@ watchEffect(() => {
});
/** 自定义表格行渲染 */
const customRow = (_record: any) => {
function customRow(_record: any) {
return {
class: isModelSorting.value ? 'cursor-move' : '',
};
};
//
const [CategoryRenameModal, categoryRenameModalApi] = useVbenModal({
connectedComponent: CategoryRenameForm,
destroyOnClose: true,
});
}
//
const handleRenameSuccess = () => {
@ -268,7 +269,7 @@ const handleRenameSuccess = () => {
@click.stop="handleModelSort"
>
<template #icon>
<span class="icon-[fa--sort-amount-desc]"></span>
<IconifyIcon icon="lucide:align-start-vertical" />
</template>
排序
</Button>
@ -279,7 +280,7 @@ const handleRenameSuccess = () => {
class="flex items-center text-[14px]"
>
<template #icon>
<span class="icon-[ant-design--setting-outlined]"></span>
<IconifyIcon icon="lucide:settings" />
</template>
分类
</Button>

View File

@ -152,7 +152,7 @@ function onBack() {
// ============================== ==============================
/** 审批相关:获取审批详情 */
const getApprovalDetail = async () => {
async function getApprovalDetail() {
try {
const data = await getApprovalDetailApi({
processDefinitionId: processDefinitionId.value,
@ -188,13 +188,12 @@ const getApprovalDetail = async () => {
: [];
}
}
} finally {
}
};
} finally {}
}
/** 审批相关:选择发起人 */
const selectUserConfirm = (id: string, userList: any[]) => {
function selectUserConfirm(id: string, userList: any[]) {
startUserSelectAssignees.value[id] = userList?.map((item: any) => item.id);
};
}
/** 审批相关:预测流程节点会因为输入的参数值而产生新的预测结果值,所以需重新预测一次, formData.value可改成实际业务中的特定字段 */
watch(

View File

@ -1,18 +1,14 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmCategoryApi } from '#/api/bpm/category';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { DescriptionItemSchema } from '#/components/description';
import { h } from 'vue';
import { useAccess } from '@vben/access';
import dayjs from 'dayjs';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -118,9 +114,7 @@ export function GridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = BpmCategoryApi.CategoryVO>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -168,39 +162,11 @@ export function useGridColumns<T = BpmCategoryApi.CategoryVO>(
minWidth: 180,
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 180,
align: 'center',
width: 180,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '请假',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: '详情',
show: hasAccessByCodes(['bpm:oa-leave:query']),
},
{
code: 'progress',
text: '进度',
show: hasAccessByCodes(['bpm:oa-leave:query']),
},
{
code: 'cancel',
text: '取消',
show: (row: any) =>
row.status === 1 && hasAccessByCodes(['bpm:oa-leave:query']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -19,14 +19,14 @@ const detailData = ref<BpmOALeaveApi.LeaveVO>();
const { query } = useRoute();
const queryId = computed(() => query.id as string);
const getDetailData = async () => {
async function getDetailData() {
try {
datailLoading.value = true;
detailData.value = await getLeave(Number(props.id || queryId.value));
} finally {
datailLoading.value = false;
}
};
}
onMounted(() => {
getDetailData();

View File

@ -1,20 +1,14 @@
<script lang="ts" setup>
import type { PageParam } from '@vben/request';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmOALeaveApi } from '#/api/bpm/oa/leave';
import { h } from 'vue';
import { Page, prompt } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message, Textarea } from 'ant-design-vue';
import { message, Textarea } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getLeavePage } from '#/api/bpm/oa/leave';
import { cancelProcessInstanceByStartUser } from '#/api/bpm/processInstance';
import { DocAlert } from '#/components/doc-alert';
@ -27,12 +21,12 @@ const [Grid, gridApi] = useVbenVxeGrid({
schema: GridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }: PageParam, formValues: any) => {
query: async ({ page }, formValues) => {
return await getLeavePage({
pageNo: page.currentPage,
pageSize: page.pageSize,
@ -52,7 +46,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
});
/** 创建请假 */
function onCreate() {
function handleCreate() {
router.push({
name: 'OALeaveCreate',
query: {
@ -62,15 +56,15 @@ function onCreate() {
}
/** 查看请假详情 */
const onDetail = (row: BpmOALeaveApi.LeaveVO) => {
function handleDetail(row: BpmOALeaveApi.LeaveVO) {
router.push({
name: 'OALeaveDetail',
query: { id: row.id },
});
};
}
/** 取消请假 */
const onCancel = (row: BpmOALeaveApi.LeaveVO) => {
function handleCancel(row: BpmOALeaveApi.LeaveVO) {
prompt({
async beforeClose(scope) {
if (scope.isConfirm) {
@ -100,35 +94,14 @@ const onCancel = (row: BpmOALeaveApi.LeaveVO) => {
title: '取消流程',
modelPropName: 'value',
});
};
}
/** 审批进度 */
const onProgress = (row: BpmOALeaveApi.LeaveVO) => {
function handleProgress(row: BpmOALeaveApi.LeaveVO) {
router.push({
name: 'BpmProcessInstanceDetail',
query: { id: row.processInstanceId },
});
};
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmOALeaveApi.LeaveVO>) {
switch (code) {
case 'cancel': {
onCancel(row);
break;
}
case 'detail': {
onDetail(row);
break;
}
case 'progress': {
onProgress(row);
break;
}
}
}
/** 刷新表格 */
@ -146,25 +119,48 @@ function onRefresh() {
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['bpm:category:create']"
>
<Plus class="size-5" />
发起请假
</Button>
<TableAction
:actions="[
{
label: '发起请假',
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['bpm:oa-leave:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #userIds-cell="{ row }">
<span
v-for="(userId, index) in row.userIds"
:key="userId"
class="pr-5px"
>
{{ dataList.find((user) => user.id === userId)?.nickname }}
<span v-if="index < row.userIds.length - 1"></span>
</span>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['bpm:oa-leave:query'],
onClick: handleDetail.bind(null, row),
},
{
label: '审批进度',
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['bpm:oa-leave:query'],
onClick: handleProgress.bind(null, row),
},
{
label: '取消',
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['bpm:user-group:query'],
popConfirm: {
title: '取消流程',
confirm: handleCancel.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -1,14 +1,9 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { z } from '#/adapter/form';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -88,9 +83,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = BpmProcessExpressionApi.ProcessExpressionVO>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -124,29 +117,10 @@ export function useGridColumns<T = BpmProcessExpressionApi.ProcessExpressionVO>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 180,
align: 'center',
width: 180,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '流程表达式',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['bpm:process-expression:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['bpm:process-expression:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,16 +1,12 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression';
import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteProcessExpression,
getProcessExpressionPage,
@ -25,12 +21,46 @@ const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建流程表达式 */
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑流程表达式 */
function handleEdit(row: BpmProcessExpressionApi.ProcessExpressionVO) {
formModalApi.setData(row).open();
}
/** 删除流程表达式 */
async function handleDelete(row: BpmProcessExpressionApi.ProcessExpressionVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
key: 'action_key_msg',
});
try {
await deleteProcessExpression(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} finally {
hideLoading();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -53,55 +83,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
} as VxeTableGridOptions<BpmProcessExpressionApi.ProcessExpressionVO>,
});
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmProcessExpressionApi.ProcessExpressionVO>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建流程表达式 */
function onCreate() {
formModalApi.setData(null).open();
}
/** 编辑流程表达式 */
function onEdit(row: BpmProcessExpressionApi.ProcessExpressionVO) {
formModalApi.setData(row).open();
}
/** 删除流程表达式 */
async function onDelete(row: BpmProcessExpressionApi.ProcessExpressionVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteProcessExpression(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
onRefresh();
} finally {
hideLoading();
}
}
</script>
<template>
@ -115,14 +96,41 @@ async function onDelete(row: BpmProcessExpressionApi.ProcessExpressionVO) {
<FormModal @success="onRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['bpm:process-expression:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['流程表达式']) }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['流程表达式']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['bpm:process-expression:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['bpm:process-expression:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['bpm:process-expression:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -55,7 +55,6 @@ const [Modal, modalApi] = useVbenModal({
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
//
@ -77,7 +76,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal :title="getTitle" class="w-[600px]">
<Modal :title="getTitle" class="w-[40%]">
<Form class="mx-4" />
</Modal>
</template>

View File

@ -39,7 +39,7 @@ const processDefinitionList = ref<
>([]); //
// groupBy
const groupBy = (array: any[], key: string) => {
function groupBy(array: any[], key: string) {
const result: Record<string, any[]> = {};
for (const item of array) {
const groupKey = item[key];
@ -49,10 +49,10 @@ const groupBy = (array: any[], key: string) => {
result[groupKey].push(item);
}
return result;
};
}
/** 查询列表 */
const getList = async () => {
async function getList() {
loading.value = true;
try {
//
@ -79,20 +79,20 @@ const getList = async () => {
} finally {
loading.value = false;
}
};
}
/** 获取所有流程分类数据 */
const getCategoryList = async () => {
async function getCategoryList() {
try {
//
categoryList.value = await getCategorySimpleList();
} catch {
//
}
};
}
/** 获取所有流程定义数据 */
const handleGetProcessDefinitionList = async () => {
async function handleGetProcessDefinitionList() {
try {
//
processDefinitionList.value = await getProcessDefinitionList({
@ -108,7 +108,7 @@ const handleGetProcessDefinitionList = async () => {
} catch {
//
}
};
}
/** 用于存储搜索过滤后的流程定义 */
const filteredProcessDefinitionList = ref<
@ -116,7 +116,7 @@ const filteredProcessDefinitionList = ref<
>([]);
/** 搜索流程 */
const handleQuery = () => {
function handleQuery() {
if (searchName.value.trim()) {
//
isSearching.value = true;
@ -141,16 +141,16 @@ const handleQuery = () => {
isSearching.value = false;
filteredProcessDefinitionList.value = processDefinitionList.value;
}
};
}
/** 判断流程定义是否匹配搜索 */
const isDefinitionMatchSearch = (definition: any) => {
function isDefinitionMatchSearch(definition: any) {
if (!isSearching.value) return false;
return definition.name.toLowerCase().includes(searchName.value.toLowerCase());
};
}
/** 流程定义的分组 */
const processDefinitionGroup: any = computed(() => {
const processDefinitionGroup = computed(() => {
if (!processDefinitionList.value?.length) {
return {};
}
@ -172,26 +172,26 @@ const processDefinitionGroup: any = computed(() => {
});
/** 通过分类 code 获取对应的名称 */
const getCategoryName = (categoryCode: string) => {
function getCategoryName(categoryCode: string) {
return categoryList.value?.find((ctg: any) => ctg.code === categoryCode)
?.name;
};
}
// ========== ==========
const selectProcessDefinition = ref(); //
const processDefinitionDetailRef = ref();
/** 处理选择流程的按钮操作 */
const handleSelect = async (
async function handleSelect(
row: BpmProcessDefinitionApi.ProcessDefinitionVO,
formVariables?: any,
) => {
) {
//
selectProcessDefinition.value = row;
//
await nextTick();
processDefinitionDetailRef.value?.initProcessInfo(row, formVariables);
};
}
/** 过滤出有流程的分类列表。目的:只展示有流程的分类 */
const availableCategories = computed(() => {

View File

@ -91,7 +91,7 @@ const activityNodes = ref<ApprovalNodeInfo[]>([]);
const processInstanceStartLoading = ref(false);
/** 提交按钮 */
const submitForm = async () => {
async function submitForm() {
if (!fApi.value || !props.selectProcessDefinition) {
return;
}
@ -130,10 +130,10 @@ const submitForm = async () => {
} finally {
processInstanceStartLoading.value = false;
}
};
}
/** 设置表单信息、获取流程图数据 */
const initProcessInfo = async (row: any, formVariables?: any) => {
async function initProcessInfo(row: any, formVariables?: any) {
//
startUserSelectTasks.value = [];
startUserSelectAssignees.value = {};
@ -177,7 +177,7 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
});
// Tab
}
};
}
/** 预测流程节点会因为输入的参数值而产生新的预测结果值,所以需重新预测一次 */
watch(
@ -200,10 +200,10 @@ watch(
);
/** 获取审批详情 */
const getApprovalDetail = async (row: {
async function getApprovalDetail(row: {
id: string;
processVariablesStr: string;
}) => {
}) {
try {
const data = await getApprovalDetailApi({
processDefinitionId: row.id,
@ -246,12 +246,12 @@ const getApprovalDetail = async (row: {
message.error('获取审批详情失败');
console.error('获取审批详情失败:', error);
}
};
}
/**
* 设置表单权限
*/
const setFieldPermission = (field: string, permission: string) => {
function setFieldPermission(field: string, permission: string) {
if (permission === BpmFieldPermissionType.READ) {
// @ts-ignore
fApi.value?.disabled(true, field);
@ -264,18 +264,18 @@ const setFieldPermission = (field: string, permission: string) => {
// @ts-ignore
fApi.value?.hidden(true, field);
}
};
}
/** 取消发起审批 */
const handleCancel = () => {
function handleCancel() {
emit('cancel');
};
}
/** 选择发起人 */
const selectUserConfirm = (activityId: string, userList: any[]) => {
function selectUserConfirm(activityId: string, userList: any[]) {
if (!activityId || !Array.isArray(userList)) return;
startUserSelectAssignees.value[activityId] = userList.map((item) => item.id);
};
}
defineExpose({ initProcessInfo });
</script>
@ -293,7 +293,7 @@ defineExpose({ initProcessInfo });
<template #extra>
<Space wrap>
<Button plain type="default" @click="handleCancel">
<IconifyIcon icon="mdi:arrow-left" />&nbsp; 返回
<IconifyIcon icon="lucide:arrow-left" />&nbsp; 返回
</Button>
</Space>
</template>
@ -345,11 +345,11 @@ defineExpose({ initProcessInfo });
<template v-if="activeTab === 'form'">
<Space wrap class="flex w-full justify-center">
<Button plain type="primary" @click="submitForm">
<IconifyIcon icon="icon-park-outline:check" />
<IconifyIcon icon="lucide:check" />
发起
</Button>
<Button plain type="default" @click="handleCancel">
<IconifyIcon icon="icon-park-outline:close" />
<IconifyIcon icon="lucide:x" />
取消
</Button>
</Space>

View File

@ -1,19 +1,8 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { getCategorySimpleList } from '#/api/bpm/category';
import { $t } from '#/locales';
import {
BpmProcessInstanceStatus,
DICT_TYPE,
getDictOptions,
getRangePickerDefaultProps,
} from '#/utils';
const { hasAccessByCodes } = useAccess();
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
@ -88,9 +77,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = BpmProcessInstanceApi.ProcessInstanceVO>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'name',
@ -136,38 +123,11 @@ export function useGridColumns<T = BpmProcessInstanceApi.ProcessInstanceVO>(
minWidth: 180,
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 180,
align: 'center',
width: 180,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '流程名称',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: $t('ui.actionTitle.detail'),
show: hasAccessByCodes(['bpm:process-instance:query']),
},
{
code: 'cancel',
text: $t('ui.actionTitle.cancel'),
show: (row: BpmProcessInstanceApi.ProcessInstanceVO) => {
return (
row.status === BpmProcessInstanceStatus.RUNNING &&
hasAccessByCodes(['bpm:process-instance:cancel'])
);
},
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -175,7 +175,7 @@ async function getApprovalDetail() {
}
/** 获取流程模型视图*/
const getProcessModelView = async () => {
async function getProcessModelView() {
if (BpmModelType.BPMN === processDefinition.value?.modelType) {
// BPMN
processModelView.value = {
@ -186,14 +186,14 @@ const getProcessModelView = async () => {
if (data) {
processModelView.value = data;
}
};
}
//
const activityNodes = ref<BpmProcessInstanceApi.ApprovalNodeInfo[]>([]);
/**
* 设置表单权限
*/
const setFieldPermission = (field: string, permission: string) => {
function setFieldPermission(field: string, permission: string) {
if (permission === FieldPermissionType.READ) {
// @ts-ignore
fApi.value?.disabled(true, field);
@ -208,7 +208,7 @@ const setFieldPermission = (field: string, permission: string) => {
// @ts-ignore
fApi.value?.hidden(true, field);
}
};
}
/**
* 操作成功后刷新

View File

@ -7,6 +7,7 @@ import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
import { computed, reactive, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { useUserStore } from '@vben/stores';
import { isEmpty } from '@vben/utils';
@ -60,6 +61,17 @@ const props = defineProps<{
writableFields: string[]; //
}>(); //
const emit = defineEmits(['success']);
const [SignatureModal, signatureModalApi] = useVbenModal({
connectedComponent: Signature,
destroyOnClose: true,
});
/** 创建流程表达式 */
function openSignatureModal() {
signatureModalApi.setData(null).open();
}
const router = useRouter(); //
const userStore = useUserStore();
const userId = userStore.userInfo?.id;
@ -86,7 +98,6 @@ const nodeTypeName = ref('审批'); // 节点类型名称
//
const reasonRequire = ref();
const approveFormRef = ref<FormInstance>();
const signRef = ref();
const approveSignFormRef = ref();
const nextAssigneesActivityNode = ref<BpmProcessInstanceApi.ApprovalNodeInfo[]>(
[],
@ -235,7 +246,7 @@ watch(
);
/** 弹出气泡卡 */
const openPopover = async (type: string) => {
async function openPopover(type: string) {
if (type === 'approve') {
//
const valid = await validateNormalForm();
@ -258,19 +269,19 @@ const openPopover = async (type: string) => {
});
// await nextTick()
// formRef.value.resetFields()
};
}
/** 关闭气泡卡 */
const closePopover = (type: string, formRef: any | FormInstance) => {
function closePopover(type: string, formRef: any | FormInstance) {
if (formRef) {
formRef.resetFields();
}
if (popOverVisible.value[type]) popOverVisible.value[type] = false;
nextAssigneesActivityNode.value = [];
};
}
/** 流程通过时,根据表单变量查询新的流程节点,判断下一个节点类型是否为自选审批人 */
const initNextAssigneesFormField = async () => {
async function initNextAssigneesFormField() {
// ,
const variables = getUpdatedProcessInstanceVariables();
const data = await getNextApprovalNodes({
@ -293,14 +304,14 @@ const initNextAssigneesFormField = async () => {
}
});
}
};
}
/** 选择下一个节点的审批人 */
const selectNextAssigneesConfirm = (id: string, userList: any[]) => {
function selectNextAssigneesConfirm(id: string, userList: any[]) {
approveReasonForm.nextAssignees[id] = userList?.map((item: any) => item.id);
};
}
/** 审批通过时,校验每个自选审批人的节点是否都已配置了审批人 */
const validateNextAssignees = () => {
function validateNextAssignees() {
if (Object.keys(nextAssigneesActivityNode.value).length === 0) {
return true;
}
@ -312,13 +323,10 @@ const validateNextAssignees = () => {
}
}
return true;
};
}
/** 处理审批通过和不通过的操作 */
const handleAudit = async (
pass: boolean,
formRef: FormInstance | undefined,
) => {
async function handleAudit(pass: boolean, formRef: FormInstance | undefined) {
formLoading.value = true;
try {
//
@ -375,10 +383,10 @@ const handleAudit = async (
} finally {
formLoading.value = false;
}
};
}
/** 处理抄送 */
const handleCopy = async () => {
async function handleCopy() {
formLoading.value = true;
try {
// 1.
@ -397,10 +405,10 @@ const handleCopy = async () => {
} finally {
formLoading.value = false;
}
};
}
/** 处理转交 */
const handleTransfer = async () => {
async function handleTransfer() {
formLoading.value = true;
try {
// 1.1
@ -421,10 +429,10 @@ const handleTransfer = async () => {
} finally {
formLoading.value = false;
}
};
}
/** 处理委派 */
const handleDelegate = async () => {
async function handleDelegate() {
formLoading.value = true;
try {
// 1.1
@ -446,10 +454,10 @@ const handleDelegate = async () => {
} finally {
formLoading.value = false;
}
};
}
/** 处理加签 */
const handlerAddSign = async (type: string) => {
async function handlerAddSign(type: string) {
formLoading.value = true;
try {
// 1.1
@ -471,10 +479,10 @@ const handlerAddSign = async (type: string) => {
} finally {
formLoading.value = false;
}
};
}
/** 处理退回 */
const handleReturn = async () => {
async function handleReturn() {
formLoading.value = true;
try {
// 1.1
@ -496,10 +504,10 @@ const handleReturn = async () => {
} finally {
formLoading.value = false;
}
};
}
/** 处理取消 */
const handleCancel = async () => {
async function handleCancel() {
formLoading.value = true;
try {
// 1.1
@ -518,26 +526,26 @@ const handleCancel = async () => {
} finally {
formLoading.value = false;
}
};
}
/** 处理再次提交 */
const handleReCreate = async () => {
async function handleReCreate() {
//
await router.push({
path: '/bpm/task/create',
query: { processInstanceId: props.processInstance?.id },
});
// router.push('/bpm/task/my');
};
}
/** 获取减签人员标签 */
const getDeleteSignUserLabel = (task: any): string => {
function getDeleteSignUserLabel(task: any): string {
const deptName = task?.assigneeUser?.deptName || task?.ownerUser?.deptName;
const nickname = task?.assigneeUser?.nickname || task?.ownerUser?.nickname;
return `${nickname} ( 所属部门:${deptName} )`;
};
}
/** 处理减签 */
const handlerDeleteSign = async () => {
async function handlerDeleteSign() {
formLoading.value = true;
try {
// 1.1
@ -557,23 +565,23 @@ const handlerDeleteSign = async () => {
} finally {
formLoading.value = false;
}
};
}
/** 重新加载数据 */
const reload = () => {
function reload() {
emit('success');
};
}
/** 任务是否为处理中状态 */
const isHandleTaskStatus = () => {
function isHandleTaskStatus() {
let canHandle = false;
if (BpmTaskStatusEnum.RUNNING === runningTask.value?.status) {
canHandle = true;
}
return canHandle;
};
}
/** 流程状态是否为结束状态 */
const isEndProcessStatus = (status: number) => {
function isEndProcessStatus(status: number) {
let isEndStatus = false;
if (
BpmProcessInstanceStatus.APPROVE === status ||
@ -583,10 +591,10 @@ const isEndProcessStatus = (status: number) => {
isEndStatus = true;
}
return isEndStatus;
};
}
/** 是否显示按钮 */
const isShowButton = (btnType: BpmTaskOperationButtonTypeEnum): boolean => {
function isShowButton(btnType: BpmTaskOperationButtonTypeEnum): boolean {
let isShow = true;
if (
runningTask.value?.buttonsSetting &&
@ -595,10 +603,10 @@ const isShowButton = (btnType: BpmTaskOperationButtonTypeEnum): boolean => {
isShow = runningTask.value.buttonsSetting[btnType].enable;
}
return isShow;
};
}
/** 获取按钮的显示名称 */
const getButtonDisplayName = (btnType: BpmTaskOperationButtonTypeEnum) => {
function getButtonDisplayName(btnType: BpmTaskOperationButtonTypeEnum) {
let displayName = OPERATION_BUTTON_NAME.get(btnType);
if (
runningTask.value?.buttonsSetting &&
@ -607,9 +615,9 @@ const getButtonDisplayName = (btnType: BpmTaskOperationButtonTypeEnum) => {
displayName = runningTask.value.buttonsSetting[btnType].displayName;
}
return displayName;
};
}
const loadTodoTask = (task: any) => {
function loadTodoTask(task: any) {
approveForm.value = {};
runningTask.value = task;
approveFormFApi.value = {};
@ -629,10 +637,10 @@ const loadTodoTask = (task: any) => {
} else {
approveForm.value = {}; //
}
};
}
/** 校验流程表单 */
const validateNormalForm = async () => {
async function validateNormalForm() {
if (props.processDefinition?.formType === BpmModelFormType.NORMAL) {
let valid = true;
try {
@ -644,31 +652,31 @@ const validateNormalForm = async () => {
} else {
return true;
}
};
}
/** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */
const getUpdatedProcessInstanceVariables = () => {
function getUpdatedProcessInstanceVariables() {
const variables: any = {};
props.writableFields.forEach((field: string) => {
if (field && variables[field])
variables[field] = props.normalFormApi.getValue(field);
});
return variables;
};
}
/** 处理签名完成 */
const handleSignFinish = (url: string) => {
function handleSignFinish(url: string) {
approveReasonForm.signPicUrl = url;
approveFormRef.value?.validateFields(['signPicUrl']);
};
}
/** 处理弹窗可见性 */
const handlePopoverVisible = (visible: boolean) => {
function handlePopoverVisible(visible: boolean) {
if (!visible) {
//
popOverVisible.value.approve = true;
}
};
}
defineExpose({ loadTodoTask });
</script>
@ -745,7 +753,7 @@ defineExpose({ loadTodoTask });
name="signPicUrl"
ref="approveSignFormRef"
>
<Button @click="signRef.open()" type="primary">
<Button @click="openSignatureModal" type="primary">
{{ approveReasonForm.signPicUrl ? '重新签名' : '点击签名' }}
</Button>
@ -802,7 +810,7 @@ defineExpose({ loadTodoTask });
"
>
<Button ghost danger type="primary" @click="openPopover('reject')">
<IconifyIcon icon="icon-park-outline:close" />
<IconifyIcon icon="lucide:x" />
{{ getButtonDisplayName(BpmTaskOperationButtonTypeEnum.REJECT) }}
</Button>
<template #content>
@ -862,7 +870,7 @@ defineExpose({ loadTodoTask });
"
>
<Button type="dashed" @click="openPopover('copy')">
<IconifyIcon icon="icon-park-outline:copy" />
<IconifyIcon icon="lucide:copy" />
{{ getButtonDisplayName(BpmTaskOperationButtonTypeEnum.COPY) }}
</Button>
<template #content>
@ -1387,5 +1395,5 @@ defineExpose({ loadTodoTask });
</div>
<!-- 签名弹窗 -->
<Signature ref="signRef" @success="handleSignFinish" />
<SignatureModal @success="handleSignFinish" />
</template>

View File

@ -16,6 +16,8 @@ defineOptions({
const emits = defineEmits(['success']);
const signature = ref<InstanceType<typeof Vue3Signature>>();
const [Modal, modalApi] = useVbenModal({
title: '流程签名',
onOpenChange(visible) {
@ -23,42 +25,30 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close();
}
},
onConfirm: () => {
submit();
async onConfirm() {
message.success({
content: '签名上传中请稍等。。。',
});
const signFileUrl = await uploadFile({
file: download.base64ToFile(
signature?.value?.save('image/jpeg') || '',
'签名',
),
});
emits('success', signFileUrl);
modalApi.close();
},
});
const signature = ref<InstanceType<typeof Vue3Signature>>();
const open = async () => {
modalApi.open();
};
defineExpose({ open });
const submit = async () => {
message.success({
content: '签名上传中请稍等。。。',
});
const signFileUrl = await uploadFile({
file: download.base64ToFile(
signature?.value?.save('image/jpeg') || '',
'签名',
),
});
emits('success', signFileUrl);
modalApi.close();
};
</script>
<template>
<Modal class="h-[500px] w-[900px]">
<Modal class="h-[40%] w-[60%]">
<div class="mb-2 flex justify-end">
<Space>
<Tooltip title="撤销上一步操作">
<Button @click="signature?.undo()">
<template #icon>
<IconifyIcon icon="mi:undo" class="mb-[4px] size-[16px]" />
<IconifyIcon icon="lucide:undo" class="mb-[4px] size-[16px]" />
</template>
撤销
</Button>
@ -67,10 +57,7 @@ const submit = async () => {
<Tooltip title="清空画布">
<Button @click="signature?.clear()">
<template #icon>
<IconifyIcon
icon="mdi:delete-outline"
class="mb-[4px] size-[16px]"
/>
<IconifyIcon icon="lucide:trash" class="mb-[4px] size-[16px]" />
</template>
<span>清除</span>
</Button>

View File

@ -14,7 +14,7 @@ import { Button } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getTaskListByProcessInstanceId } from '#/api/bpm/task';
import { DICT_TYPE, formatPast2, setConfAndFields2 } from '#/utils';
import { DICT_TYPE, setConfAndFields2 } from '#/utils';
defineOptions({
name: 'BpmProcessInstanceTaskList',
@ -75,11 +75,7 @@ const columns = shallowRef([
field: 'durationInMillis',
title: '耗时',
minWidth: 180,
slots: {
default: ({ row }: { row: BpmTaskApi.TaskManagerVO }) => {
return formatPast2(row.durationInMillis);
},
},
formatter: 'formatPast2',
},
]);
@ -116,9 +112,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
/**
* 刷新表格数据
*/
const refresh = (): void => {
function refresh() {
gridApi.query();
};
}
//
interface TaskForm {

View File

@ -108,12 +108,12 @@ const nodeTypeSvgMap = {
const onlyStatusIconShow = [-1, 0, 1];
//
const getApprovalNodeTypeIcon = (nodeType: BpmNodeTypeEnum) => {
function getApprovalNodeTypeIcon(nodeType: BpmNodeTypeEnum) {
return nodeTypeSvgMap[nodeType]?.icon;
};
}
//
const getApprovalNodeIcon = (taskStatus: number, nodeType: BpmNodeTypeEnum) => {
function getApprovalNodeIcon(taskStatus: number, nodeType: BpmNodeTypeEnum) {
if (taskStatus === BpmTaskStatusEnum.NOT_START) {
return statusIconMap[taskStatus]?.icon || 'mdi:clock-outline';
}
@ -128,15 +128,15 @@ const getApprovalNodeIcon = (taskStatus: number, nodeType: BpmNodeTypeEnum) => {
return statusIconMap[taskStatus]?.icon || 'mdi:clock-outline';
}
return 'mdi:clock-outline';
};
}
//
const getApprovalNodeColor = (taskStatus: number) => {
function getApprovalNodeColor(taskStatus: number) {
return statusIconMap[taskStatus]?.color;
};
}
//
const getApprovalNodeTime = (node: BpmProcessInstanceApi.ApprovalNodeInfo) => {
function getApprovalNodeTime(node: BpmProcessInstanceApi.ApprovalNodeInfo) {
if (node.nodeType === BpmNodeTypeEnum.START_USER_NODE && node.startTime) {
return formatDateTime(node.startTime);
}
@ -147,7 +147,7 @@ const getApprovalNodeTime = (node: BpmProcessInstanceApi.ApprovalNodeInfo) => {
return formatDateTime(node.startTime);
}
return '';
};
}
//
const userSelectFormRef = ref();
@ -164,26 +164,26 @@ const handleSelectUser = (activityId: string, selectedList: any[]) => {
//
const selectedUsers = ref<number[]>([]);
const handleUserSelectConfirm = (userList: any[]) => {
function handleUserSelectConfirm(userList: any[]) {
customApproveUsers.value[selectedActivityNodeId.value] = userList || [];
emit('selectUserConfirm', selectedActivityNodeId.value, userList);
};
}
/** 跳转子流程 */
const handleChildProcess = (activity: any) => {
function handleChildProcess(activity: any) {
push({
name: 'BpmProcessInstanceDetail',
query: {
id: activity.processInstanceId,
},
});
};
}
//
const shouldShowCustomUserSelect = (
function shouldShowCustomUserSelect(
activity: BpmProcessInstanceApi.ApprovalNodeInfo,
) => {
) {
return (
isEmpty(activity.tasks) &&
isEmpty(activity.candidateUsers) &&
@ -192,27 +192,27 @@ const shouldShowCustomUserSelect = (
BpmCandidateStrategyEnum.APPROVE_USER_SELECT ===
activity.candidateStrategy)
);
};
}
//
const shouldShowApprovalReason = (task: any, nodeType: BpmNodeTypeEnum) => {
function shouldShowApprovalReason(task: any, nodeType: BpmNodeTypeEnum) {
return (
task.reason &&
[BpmNodeTypeEnum.END_EVENT_NODE, BpmNodeTypeEnum.USER_TASK_NODE].includes(
nodeType,
)
);
};
}
//
const handleUserSelectClosed = () => {
function handleUserSelectClosed() {
selectedUsers.value = [];
};
}
//
const handleUserSelectCancel = () => {
function handleUserSelectCancel() {
selectedUsers.value = [];
};
}
</script>
<template>

View File

@ -1,8 +1,5 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task';
import { h } from 'vue';
@ -11,7 +8,7 @@ import { Page, prompt } from '@vben/common-ui';
import { Button, message, Textarea } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
cancelProcessInstanceByStartUser,
getProcessInstanceMyPage,
@ -25,54 +22,21 @@ import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'BpmProcessInstanceMy' });
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getProcessInstanceMyPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
refresh: { code: 'query' },
search: true,
},
cellConfig: {
height: 64,
},
} as VxeTableGridOptions<BpmTaskApi.TaskVO>,
});
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<BpmTaskApi.TaskVO>) {
switch (code) {
case 'cancel': {
onCancel(row);
break;
}
case 'detail': {
onDetail(row);
break;
}
}
/** 查看流程实例 */
function handleDetail(row: BpmTaskApi.TaskVO) {
router.push({
name: 'BpmProcessInstanceDetail',
query: { id: row.id },
});
}
/** 取消流程实例 */
function onCancel(row: BpmTaskApi.TaskVO) {
function handleCancel(row: BpmTaskApi.TaskVO) {
prompt({
async beforeClose(scope) {
if (scope.isConfirm) {
@ -104,19 +68,37 @@ function onCancel(row: BpmTaskApi.TaskVO) {
});
}
/** 查看流程实例 */
function onDetail(row: BpmTaskApi.TaskVO) {
console.warn(row);
router.push({
name: 'BpmProcessInstanceDetail',
query: { id: row.id },
});
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getProcessInstanceMyPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
refresh: { code: 'query' },
search: true,
},
cellConfig: {
height: 64,
},
} as VxeTableGridOptions<BpmTaskApi.TaskVO>,
});
</script>
<template>
@ -126,7 +108,6 @@ function onRefresh() {
url="https://doc.iocoder.cn/bpm/process-instance"
/>
<FormModal @success="onRefresh" />
<Grid table-title="">
<!-- 摘要 -->
<template #slot-summary="{ row }">
@ -154,7 +135,7 @@ function onRefresh() {
<!-- 单人审批 -->
<template v-if="row.tasks.length === 1">
<span>
<Button type="link" @click="onDetail(row)">
<Button type="link" @click="handleDetail(row)">
{{ row.tasks[0].assigneeUser?.nickname }}
</Button>
({{ row.tasks[0].name }}) 审批中
@ -163,7 +144,7 @@ function onRefresh() {
<!-- 多人审批 -->
<template v-else>
<span>
<Button type="link" @click="onDetail(row)">
<Button type="link" @click="handleDetail(row)">
{{ row.tasks[0].assigneeUser?.nickname }}
</Button>
{{ row.tasks.length }} ({{ row.tasks[0].name }})审批中
@ -178,6 +159,28 @@ function onRefresh() {
/>
</template>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['bpm:process-instance:query'],
onClick: handleDetail.bind(null, row),
},
{
label: $t('ui.actionTitle.cancel'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
ifShow: row.status === BpmProcessInstanceStatus.RUNNING,
auth: ['bpm:process-instance:cancel'],
onClick: handleCancel.bind(null, row),
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -1,26 +1,14 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
import { h } from 'vue';
import { useAccess } from '@vben/access';
import { Button } from 'ant-design-vue';
import { getCategorySimpleList } from '#/api/bpm/category';
import { getSimpleUserList } from '#/api/system/user';
import { $t } from '#/locales';
import {
DICT_TYPE,
formatPast2,
getDictOptions,
getRangePickerDefaultProps,
} from '#/utils';
import { BpmProcessInstanceStatus } from '../../../../utils/constants';
const { hasAccessByCodes } = useAccess();
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
@ -95,8 +83,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = BpmProcessInstanceApi.ProcessInstanceVO>(
onActionClick: OnActionClickFn<T>,
export function useGridColumns(
onTaskClick: (task: BpmProcessInstanceApi.Task) => void,
): VxeTableGridOptions['columns'] {
return [
@ -153,13 +140,7 @@ export function useGridColumns<T = BpmProcessInstanceApi.ProcessInstanceVO>(
field: 'durationInMillis',
title: '流程耗时',
minWidth: 180,
slots: {
default: ({ row }) => {
return row.durationInMillis > 0
? formatPast2(row.durationInMillis)
: '-';
},
},
formatter: 'formatPast2',
},
// 当前审批任务 tasks
@ -192,36 +173,10 @@ export function useGridColumns<T = BpmProcessInstanceApi.ProcessInstanceVO>(
minWidth: 320,
},
{
field: 'operation',
title: '操作',
minWidth: 180,
align: 'center',
width: 180,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '流程分类',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: $t('ui.actionTitle.detail'),
show: hasAccessByCodes(['bpm:process-instance:query']),
},
{
code: 'cancel',
text: $t('ui.actionTitle.cancel'),
show: (row: BpmProcessInstanceApi.ProcessInstanceVO) => {
return (
row.status === BpmProcessInstanceStatus.RUNNING &&
hasAccessByCodes(['bpm:process-instance:cancel'])
);
},
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,8 +1,5 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
import { h } from 'vue';
@ -11,62 +8,22 @@ import { Page, prompt } from '@vben/common-ui';
import { message, Textarea } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
cancelProcessInstanceByAdmin,
getProcessInstanceManagerPage,
} from '#/api/bpm/processInstance';
import { DocAlert } from '#/components/doc-alert';
import { router } from '#/router';
import { BpmProcessInstanceStatus } from '#/utils';
import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'BpmProcessInstanceManager' });
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick, onTaskClick),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getProcessInstanceManagerPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<BpmProcessInstanceApi.ProcessInstanceVO>,
});
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmProcessInstanceApi.ProcessInstanceVO>) {
switch (code) {
case 'cancel': {
onCancel(row);
break;
}
case 'detail': {
onDetail(row);
break;
}
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 点击任务 */
@ -75,8 +32,17 @@ function onTaskClick(task: BpmProcessInstanceApi.Task) {
console.warn(task);
}
/** 查看流程实例 */
function handleDetail(row: BpmProcessInstanceApi.ProcessInstanceVO) {
console.warn(row);
router.push({
name: 'BpmProcessInstanceDetail',
query: { id: row.id },
});
}
/** 取消流程实例 */
function onCancel(row: BpmProcessInstanceApi.ProcessInstanceVO) {
function handleCancel(row: BpmProcessInstanceApi.ProcessInstanceVO) {
prompt({
async beforeClose(scope) {
if (scope.isConfirm) {
@ -110,19 +76,34 @@ function onCancel(row: BpmProcessInstanceApi.ProcessInstanceVO) {
.catch(() => {});
}
/** 查看流程实例 */
function onDetail(row: BpmProcessInstanceApi.ProcessInstanceVO) {
console.warn(row);
router.push({
name: 'BpmProcessInstanceDetail',
query: { id: row.id },
});
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onTaskClick),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getProcessInstanceManagerPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<BpmProcessInstanceApi.ProcessInstanceVO>,
});
</script>
<template>
@ -131,6 +112,29 @@ function onRefresh() {
<DocAlert title="工作流手册" url="https://doc.iocoder.cn/bpm" />
</template>
<Grid table-title="" />
<Grid table-title="">
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['bpm:process-instance:query'],
onClick: handleDetail.bind(null, row),
},
{
label: $t('ui.actionTitle.cancel'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
ifShow: row.status === BpmProcessInstanceStatus.RUNNING,
auth: ['bpm:process-instance:cancel'],
onClick: handleCancel.bind(null, row),
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -1,8 +1,5 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmCategoryApi } from '#/api/bpm/category';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { z } from '#/adapter/form';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
@ -27,8 +24,6 @@ export const EVENT_OPTIONS = [
{ label: 'timeout', value: 'timeout' },
];
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@ -80,6 +75,16 @@ export function useFormSchema(): VbenFormSchema[] {
allowClear: true,
},
rules: 'required',
dependencies: {
triggerFields: ['type'],
trigger: (values) => (values.event = undefined),
componentProps: (values) => ({
options:
values.type === 'execution'
? EVENT_EXECUTION_OPTIONS
: EVENT_OPTIONS,
}),
},
},
{
fieldName: 'valueType',
@ -96,9 +101,17 @@ export function useFormSchema(): VbenFormSchema[] {
},
{
fieldName: 'value',
label: '表达式',
label: '类路径|表达式',
component: 'Input',
rules: 'required',
dependencies: {
triggerFields: ['valueType'],
trigger: (values) => (values.value = undefined),
componentProps: (values) => ({
placeholder:
values.valueType === 'class' ? '请输入类路径' : '请输入表达式',
}),
},
},
];
}
@ -129,9 +142,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = BpmCategoryApi.CategoryVO>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@ -178,29 +189,11 @@ export function useGridColumns<T = BpmCategoryApi.CategoryVO>(
formatter: 'formatDateTime',
},
{
field: 'operation',
field: 'actions',
title: '操作',
minWidth: 180,
align: 'center',
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '流程监听器',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['bpm:process-listener:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['bpm:process-listener:delete']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,16 +1,12 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmProcessListenerApi } from '#/api/bpm/processListener';
import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteProcessListener,
getProcessListenerPage,
@ -25,12 +21,46 @@ const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建流程监听器 */
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑流程监听器 */
function handleEdit(row: BpmProcessListenerApi.ProcessListenerVO) {
formModalApi.setData(row).open();
}
/** 删除流程监听器 */
async function handleDelete(row: BpmProcessListenerApi.ProcessListenerVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
key: 'action_key_msg',
});
try {
await deleteProcessListener(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} catch {
hideLoading();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -53,57 +83,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
} as VxeTableGridOptions<BpmProcessListenerApi.ProcessListenerVO>,
});
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmProcessListenerApi.ProcessListenerVO>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建流程监听器 */
function onCreate() {
formModalApi.setData(null).open();
}
/** 编辑流程监听器 */
function onEdit(row: BpmProcessListenerApi.ProcessListenerVO) {
formModalApi.setData(row).open();
}
/** 删除流程监听器 */
async function onDelete(row: BpmProcessListenerApi.ProcessListenerVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteProcessListener(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_process_msg',
});
onRefresh();
} catch {
hideLoading();
}
}
</script>
<template>
@ -117,14 +96,41 @@ async function onDelete(row: BpmProcessListenerApi.ProcessListenerVO) {
<FormModal @success="onRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<Button
type="primary"
@click="onCreate"
v-access:code="['bpm:process-listener:create']"
>
<Plus class="size-5" />
{{ $t('ui.actionTitle.create', ['流程监听器']) }}
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['流程监听器']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['bpm:process-listener:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['bpm:process-listener:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['bpm:process-listener:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -15,7 +15,7 @@ import {
} from '#/api/bpm/processListener';
import { $t } from '#/locales';
import { EVENT_EXECUTION_OPTIONS, EVENT_OPTIONS, useFormSchema } from '../data';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<BpmProcessListenerApi.ProcessListenerVO>();
@ -31,7 +31,7 @@ const [Form, formApi] = useVbenForm({
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 110,
labelWidth: 100,
},
layout: 'horizontal',
schema: useFormSchema(),
@ -67,55 +67,11 @@ const [Modal, modalApi] = useVbenModal({
formData.value = undefined;
return;
}
//
formApi.updateSchema([
{
fieldName: 'type',
componentProps: {
onChange: (value: string) => {
formApi.setFieldValue('event', undefined);
formApi.updateSchema([
{
fieldName: 'event',
componentProps: {
options:
value === 'execution'
? EVENT_EXECUTION_OPTIONS
: EVENT_OPTIONS,
},
},
]);
},
},
},
{
fieldName: 'valueType',
componentProps: {
onChange: (value: string) => {
formApi.setFieldValue('value', undefined);
formApi.updateSchema([
{
fieldName: 'value',
label: value === 'class' ? '类路径' : '表达式',
componentProps: {
placeholder:
value === 'class' ? '请输入类路径' : '请输入表达式',
},
},
]);
},
},
},
]);
//
const data = modalApi.getData<BpmProcessListenerApi.ProcessListenerVO>();
if (!data || !data.id) {
return;
}
modalApi.lock();
try {
formData.value = await getProcessListener(data.id as number);
@ -129,7 +85,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal :title="getTitle" class="w-[600px]">
<Modal :title="getTitle" class="w-[40%]">
<Form class="mx-4" />
</Modal>
</template>

View File

@ -1,13 +1,8 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
@ -32,9 +27,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = BpmTaskApi.TaskVO>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'processInstanceName',
@ -46,8 +39,12 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
field: 'summary',
title: '摘要',
minWidth: 200,
slots: {
default: 'slot-summary',
formatter: ({ cellValue }) => {
return cellValue && cellValue.length > 0
? cellValue
.map((item: any) => `${item.key} : ${item.value}`)
.join('\n')
: '-';
},
},
{
@ -70,8 +67,8 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
field: 'createUser.nickname',
title: '抄送人',
minWidth: 180,
slots: {
default: 'slot-createUser',
formatter: ({ cellValue }) => {
return cellValue || '-';
},
},
{
@ -86,26 +83,10 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
formatter: 'formatDateTime',
},
{
field: 'operation',
title: '操作',
minWidth: 120,
align: 'center',
width: 120,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '流程名称',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: '详情',
show: hasAccessByCodes(['bpm:task:query']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,13 +1,10 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getProcessInstanceCopyPage } from '#/api/bpm/processInstance';
import { DocAlert } from '#/components/doc-alert';
import { router } from '#/router';
@ -16,12 +13,24 @@ import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'BpmCopyTask' });
const [Grid, gridApi] = useVbenVxeGrid({
/** 任务详情 */
function handleDetail(row: BpmProcessInstanceApi.CopyVO) {
const query = {
id: row.processInstanceId,
...(row.activityId && { activityId: row.activityId }),
};
router.push({
name: 'BpmProcessInstanceDetail',
query,
});
}
const [Grid] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -47,36 +56,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
} as VxeTableGridOptions<BpmProcessInstanceApi.CopyVO>,
});
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmProcessInstanceApi.CopyVO>) {
switch (code) {
case 'detail': {
onDetail(row);
break;
}
}
}
/** 任务详情 */
function onDetail(row: BpmProcessInstanceApi.CopyVO) {
const query = {
id: row.processInstanceId,
...(row.activityId && { activityId: row.activityId }),
};
router.push({
name: 'BpmProcessInstanceDetail',
query,
});
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
</script>
<template>
@ -88,27 +67,19 @@ function onRefresh() {
/>
</template>
<FormModal @success="onRefresh" />
<Grid table-title="">
<!-- 摘要 -->
<template #slot-summary="{ row }">
<div
class="flex flex-col py-2"
v-if="row.summary && row.summary.length > 0"
>
<div v-for="(item, index) in row.summary" :key="index">
<span class="text-gray-500">
{{ item.key }} : {{ item.value }}
</span>
</div>
</div>
<div v-else>-</div>
</template>
<!-- 抄送人 -->
<template #slot-createUser="{ row }">
<span class="text-gray-500">
{{ row.createUser.nickname || '系统' }}
</span>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['bpm:task:query'],
onClick: handleDetail.bind(null, row),
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -1,14 +1,8 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { getCategorySimpleList } from '#/api/bpm/category';
import {
DICT_TYPE,
formatPast2,
getDictOptions,
getRangePickerDefaultProps,
} from '#/utils';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
@ -69,9 +63,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = BpmTaskApi.TaskVO>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'processInstance.name',
@ -83,8 +75,12 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
field: 'processInstance.summary',
title: '摘要',
minWidth: 200,
slots: {
default: 'slot-summary',
formatter: ({ cellValue }) => {
return cellValue && cellValue.length > 0
? cellValue
.map((item: any) => `${item.key} : ${item.value}`)
.join('\n')
: '-';
},
},
{
@ -92,12 +88,6 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
title: '发起人',
minWidth: 120,
},
{
field: 'createTime',
title: '发起时间',
minWidth: 180,
formatter: 'formatDateTime',
},
{
field: 'name',
title: '当前任务',
@ -133,9 +123,7 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
field: 'durationInMillis',
title: '耗时',
minWidth: 180,
formatter: ({ cellValue }) => {
return `${formatPast2(cellValue)}`;
},
formatter: 'formatPast2',
},
{
field: 'processInstanceId',
@ -148,25 +136,10 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
minWidth: 280,
},
{
field: 'operation',
title: '操作',
minWidth: 120,
align: 'center',
width: 120,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '流程名称',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'history',
text: '历史',
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,13 +1,10 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task';
import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getTaskDonePage } from '#/api/bpm/task';
import { DocAlert } from '#/components/doc-alert';
import { router } from '#/router';
@ -16,12 +13,23 @@ import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'BpmDoneTask' });
const [Grid, gridApi] = useVbenVxeGrid({
/** 查看历史 */
function handleHistory(row: BpmTaskApi.TaskManagerVO) {
router.push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.processInstance.id,
taskId: row.id,
},
});
}
const [Grid] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -47,33 +55,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
} as VxeTableGridOptions<BpmTaskApi.TaskVO>,
});
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<BpmTaskApi.TaskVO>) {
switch (code) {
case 'history': {
onHistory(row);
break;
}
}
}
/** 查看历史 */
function onHistory(row: BpmTaskApi.TaskVO) {
console.warn(row);
router.push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.processInstance.id,
taskId: row.id,
},
});
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
</script>
<template>
@ -92,25 +73,17 @@ function onRefresh() {
</template>
<Grid table-title="">
<!-- 摘要 -->
<template #slot-summary="{ row }">
<div
class="flex flex-col py-2"
v-if="
row.processInstance.summary &&
row.processInstance.summary.length > 0
"
>
<div
v-for="(item, index) in row.processInstance.summary"
:key="index"
>
<span class="text-gray-500">
{{ item.key }} : {{ item.value }}
</span>
</div>
</div>
<div v-else>-</div>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '历史',
type: 'link',
icon: ACTION_ICON.VIEW,
onClick: handleHistory.bind(null, row),
},
]"
/>
</template>
</Grid>
</Page>

View File

@ -1,8 +1,7 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { DICT_TYPE, formatPast2, getRangePickerDefaultProps } from '#/utils';
import { DICT_TYPE, getRangePickerDefaultProps } from '#/utils';
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
@ -28,9 +27,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = BpmTaskApi.TaskVO>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'processInstance.name',
@ -89,9 +86,7 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
field: 'durationInMillis',
title: '耗时',
minWidth: 180,
formatter: ({ cellValue }) => {
return `${formatPast2(cellValue)}`;
},
formatter: 'formatPast2',
},
{
field: 'processInstanceId',
@ -104,25 +99,10 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
minWidth: 280,
},
{
field: 'operation',
title: '操作',
minWidth: 120,
align: 'center',
width: 120,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '流程名称',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'history',
text: '历史',
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,13 +1,10 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task';
import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getTaskManagerPage } from '#/api/bpm/task';
import { DocAlert } from '#/components/doc-alert';
import { router } from '#/router';
@ -16,12 +13,22 @@ import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'BpmManagerTask' });
/** 查看历史 */
function handleHistory(row: BpmTaskApi.TaskManagerVO) {
router.push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.processInstance.id,
},
});
}
const [Grid] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -47,30 +54,6 @@ const [Grid] = useVbenVxeGrid({
},
} as VxeTableGridOptions<BpmTaskApi.TaskManagerVO>,
});
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmTaskApi.TaskManagerVO>) {
switch (code) {
case 'history': {
onHistory(row);
break;
}
}
}
/** 查看历史 */
function onHistory(row: BpmTaskApi.TaskManagerVO) {
console.warn(row);
router.push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.processInstance.id,
},
});
}
</script>
<template>
@ -78,6 +61,20 @@ function onHistory(row: BpmTaskApi.TaskManagerVO) {
<template #doc>
<DocAlert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
</template>
<Grid table-title="" />
<Grid table-title="">
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '历史',
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['bpm:task:query'],
onClick: handleHistory.bind(null, row),
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -1,16 +1,9 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task';
import { h } from 'vue';
import { useAccess } from '@vben/access';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { getCategorySimpleList } from '#/api/bpm/category';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
@ -69,9 +62,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns<T = BpmTaskApi.TaskVO>(
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'processInstance.name',
@ -83,29 +74,12 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
field: 'processInstance.summary',
title: '摘要',
minWidth: 200,
slots: {
default: ({ row }) => {
const summary = row?.processInstance?.summary;
if (!summary || summary.length === 0) {
return '-';
}
return summary.map((item: any) => {
return h(
'div',
{
key: item.key,
},
h(
'span',
{
class: 'text-gray-500',
},
`${item.key} : ${item.value}`,
),
);
});
},
formatter: ({ cellValue }) => {
return cellValue && cellValue.length > 0
? cellValue
.map((item: any) => `${item.key} : ${item.value}`)
.join('\n')
: '-';
},
},
{
@ -113,12 +87,6 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
title: '发起人',
minWidth: 120,
},
{
field: 'createTime',
title: '发起时间',
minWidth: 180,
formatter: 'formatDateTime',
},
{
field: 'name',
title: '当前任务',
@ -141,26 +109,10 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
minWidth: 280,
},
{
field: 'operation',
title: '操作',
minWidth: 120,
align: 'center',
width: 120,
fixed: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: '流程名称',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'audit',
text: '办理',
show: hasAccessByCodes(['bpm:task:query']),
},
],
},
slots: { default: 'actions' },
},
];
}

View File

@ -1,13 +1,10 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task';
import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getTaskTodoPage } from '#/api/bpm/task';
import { DocAlert } from '#/components/doc-alert';
import { router } from '#/router';
@ -16,12 +13,24 @@ import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'BpmTodoTask' });
const [Grid, gridApi] = useVbenVxeGrid({
/** 办理任务 */
function handleAudit(row: BpmTaskApi.TaskVO) {
console.warn(row);
router.push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.processInstance.id,
taskId: row.id,
},
});
}
const [Grid] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(onActionClick),
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
@ -47,33 +56,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
} as VxeTableGridOptions<BpmTaskApi.TaskVO>,
});
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<BpmTaskApi.TaskVO>) {
switch (code) {
case 'audit': {
onAudit(row);
break;
}
}
}
/** 办理任务 */
function onAudit(row: BpmTaskApi.TaskVO) {
console.warn(row);
router.push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.processInstance.id,
taskId: row.id,
},
});
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
</script>
<template>
@ -90,6 +72,20 @@ function onRefresh() {
/>
<DocAlert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
</template>
<Grid table-title="" />
<Grid table-title="">
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '办理',
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['bpm:task:query'],
onClick: handleAudit.bind(null, row),
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -58,7 +58,6 @@ async function handleDelete(row: SystemTenantPackageApi.TenantPackage) {
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
// TODO @
},
gridOptions: {
columns: useGridColumns(),