!141 Merge remote-tracking branch 'yudao/dev' into dev

Merge pull request !141 from Jason/dev
pull/142/MERGE
xingyu 2025-06-14 03:36:05 +00:00 committed by Gitee
commit 7f72a398b2
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
4 changed files with 306 additions and 278 deletions

View File

@ -33,6 +33,18 @@ const [Modal, modalApi] = useVbenModal({
title: '条件配置',
destroyOnClose: true,
draggable: true,
onOpenChange(isOpen) {
if (isOpen) {
//
const conditionObj = modalApi.getData();
if (conditionObj) {
conditionData.value.conditionType = conditionObj.conditionType;
conditionData.value.conditionExpression =
conditionObj.conditionExpression;
conditionData.value.conditionGroups = conditionObj.conditionGroups;
}
}
},
async onConfirm() {
//
if (!conditionRef.value) return;
@ -50,17 +62,8 @@ const [Modal, modalApi] = useVbenModal({
},
});
// TODO: jason open useVbenModal onOpenChange
function open(conditionObj: any | undefined) {
if (conditionObj) {
conditionData.value.conditionType = conditionObj.conditionType;
conditionData.value.conditionExpression = conditionObj.conditionExpression;
conditionData.value.conditionGroups = conditionObj.conditionGroups;
}
modalApi.open();
}
// TODO: jason expose使modalApi.setData(formSetting).open()
defineExpose({ open });
// TODO xingyu modalApi ? trigger-node-config.vue conditionDialog
defineExpose({ modalApi });
</script>
<template>
<Modal class="w-1/2">

View File

@ -200,8 +200,8 @@ function addFormSettingCondition(
formSetting: FormTriggerSetting,
) {
const conditionDialog = proxy.$refs[`condition-${index}`][0];
// TODO: jason Modal 使 useVbenModal 使modalApi.setData(formSetting).open()
conditionDialog.open(formSetting);
// 使modalApi
conditionDialog.modalApi.setData(formSetting).open();
}
/** 删除条件配置 */
@ -215,7 +215,8 @@ function openFormSettingCondition(
formSetting: FormTriggerSetting,
) {
const conditionDialog = proxy.$refs[`condition-${index}`][0];
conditionDialog.open(formSetting);
// 使 modalApi
conditionDialog.modalApi.setData(formSetting).open();
}
/** 处理条件配置保存 */

View File

@ -1,10 +1,11 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmModelApi, ModelCategoryInfo } from '#/api/bpm/model';
import { computed, ref, watchEffect } from 'vue';
import { useRouter } from 'vue-router';
import { confirm, EllipsisText, useVbenModal } from '@vben/common-ui';
import { confirm, useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { useUserStore } from '@vben/stores';
import { cloneDeep, formatDateTime, isEqual } from '@vben/utils';
@ -18,11 +19,11 @@ import {
Dropdown,
Menu,
message,
Table,
Tag,
Tooltip,
} from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteCategory } from '#/api/bpm/category';
import {
cleanModel,
@ -31,14 +32,14 @@ import {
updateModelSortBatch,
updateModelState,
} from '#/api/bpm/model';
import { DictTag } from '#/components/dict-tag';
import { $t } from '#/locales';
import { BpmModelFormType, DICT_TYPE } from '#/utils';
import { BpmModelFormType } from '#/utils';
//
import CategoryRenameForm from '../../category/modules/rename-form.vue';
// FormCreate
import FormCreateDetail from '../../form/modules/detail.vue';
import { useGridColumns } from './data';
const props = defineProps<{
categoryInfo: ModelCategoryInfo;
@ -67,66 +68,41 @@ const isModelSorting = ref(false);
const originalData = ref<BpmModelApi.ModelVO[]>([]);
const modelList = ref<BpmModelApi.ModelVO[]>([]);
const isExpand = ref(false);
const tableRef = ref();
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useGridColumns(),
pagerConfig: {
enabled: false,
},
data: modelList.value,
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
enabled: false, //
},
} as VxeTableGridOptions,
});
// 便
const sortableInstance = ref<any>(null);
/** 解决 v-model 问题,使用计算属性 */
const expandKeys = computed(() => (isExpand.value ? ['1'] : []));
//
const columns = [
{
title: '流程名',
dataIndex: 'name',
key: 'name',
align: 'left' as const,
width: 250,
},
{
title: '可见范围',
dataIndex: 'startUserIds',
key: 'startUserIds',
align: 'center' as const,
ellipsis: true,
width: 150,
},
{
title: '流程类型',
dataIndex: 'type',
key: 'type',
align: 'center' as const,
ellipsis: true,
width: 150,
},
{
title: '表单信息',
dataIndex: 'formType',
key: 'formType',
align: 'center' as const,
ellipsis: true,
width: 150,
},
{
title: '最后发布',
dataIndex: 'deploymentTime',
key: 'deploymentTime',
align: 'center' as const,
width: 250,
},
{
title: '操作',
key: 'operation',
align: 'center' as const,
fixed: 'right' as const,
width: 150,
},
];
/** 处理模型的排序 */
function handleModelSort() {
//
//
if (props.categoryInfo.modelList && props.categoryInfo.modelList.length > 0) {
originalData.value = cloneDeep(props.categoryInfo.modelList);
modelList.value = cloneDeep(props.categoryInfo.modelList);
}
//
gridApi.setGridOptions({
data: modelList.value,
});
//
isExpand.value = true;
isModelSorting.value = true;
@ -135,9 +111,28 @@ function handleModelSort() {
//
sortableInstance.value.option('disabled', false);
} else {
const sortableClass = `.category-${props.categoryInfo.id} .ant-table .ant-table-tbody`;
sortableInstance.value = useSortable(sortableClass, modelList, {
disabled: false, //
const sortableClass = `.category-${props.categoryInfo.id} .vxe-table .vxe-table--body-wrapper:not(.fixed-right--wrapper) .vxe-table--body tbody`;
// 使
modelList.value = cloneDeep(props.categoryInfo.modelList);
//
gridApi.setGridOptions({
data: modelList.value,
});
sortableInstance.value = useSortable(sortableClass, modelList.value, {
draggable: '.vxe-body--row',
animation: 150,
handle: '.drag-handle',
disabled: false,
onEnd: ({ newDraggableIndex, oldDraggableIndex }) => {
if (oldDraggableIndex !== newDraggableIndex) {
modelList.value.splice(
newDraggableIndex ?? 0,
0,
modelList.value.splice(oldDraggableIndex ?? 0, 1)[0]!,
);
}
},
});
}
}
@ -145,9 +140,26 @@ function handleModelSort() {
/** 处理模型的排序提交 */
async function handleModelSortSubmit() {
try {
//
if (!modelList.value || modelList.value.length === 0) {
message.error('排序数据异常,请重试');
return;
}
//
const ids = modelList.value.map((item) => item.id);
// ID
if (ids.length === props.categoryInfo.modelList.length) {
// 使
await updateModelSortBatch(ids);
} else {
console.warn('排序数据不完整,尝试使用原始数据');
// 使
const originalIds = props.categoryInfo.modelList.map((item) => item.id);
await updateModelSortBatch(originalIds);
}
//
isModelSorting.value = false;
message.success('排序模型成功');
@ -160,12 +172,18 @@ async function handleModelSortSubmit() {
/** 处理模型的排序取消 */
function handleModelSortCancel() {
//
if (originalData.value && originalData.value.length > 0) {
modelList.value = cloneDeep(originalData.value);
isModelSorting.value = false;
//
gridApi.setGridOptions({
data: modelList.value,
});
}
//
if (sortableInstance.value) {
sortableInstance.value.option('disabled', true);
}
isModelSorting.value = false;
}
/** 处理下拉菜单命令 */
@ -353,6 +371,10 @@ const updateModelList = useDebounceFn(() => {
isModelSorting.value = false;
//
sortableInstance.value = null;
//
gridApi.setGridOptions({
data: modelList.value,
});
}
}, 100);
@ -367,13 +389,6 @@ watchEffect(() => {
}
});
/** 自定义表格行渲染 */
function customRow(_record: any) {
return {
class: isModelSorting.value ? 'cursor-move' : '',
};
}
//
const handleRenameSuccess = () => {
emit('success');
@ -476,129 +491,104 @@ const handleRenameSuccess = () => {
class="border-0 bg-transparent p-0"
v-show="isExpand"
>
<Table
<Grid
v-if="modelList && modelList.length > 0"
:class="`category-${categoryInfo.id}`"
ref="tableRef"
:data-source="modelList"
:columns="columns"
:pagination="false"
:custom-row="customRow"
:scroll="{ x: '100%' }"
row-key="id"
>
<template #bodyCell="{ column, record }">
<!-- 流程名 -->
<template v-if="column.key === 'name'">
<template #name="{ row }">
<div class="flex items-center">
<Tooltip v-if="isModelSorting" title="拖动排序">
<Tooltip
v-if="isModelSorting"
title="拖动排序"
placement="left"
>
<span
class="icon-[ic--round-drag-indicator] mr-2.5 cursor-move text-2xl text-gray-500"
class="icon-[ic--round-drag-indicator] drag-handle mr-2.5 cursor-move text-2xl text-gray-500"
></span>
</Tooltip>
<div
v-if="!record.icon"
class="mr-2.5 flex h-9 w-9 items-center justify-center rounded bg-blue-500 text-white"
v-if="!row.icon"
class="mr-2.5 flex h-9 w-9 flex-shrink-0 items-center justify-center rounded bg-blue-500 text-white"
>
<span style="font-size: 12px">
{{ record.name.substring(0, 2) }}
{{ row.name.substring(0, 2) }}
</span>
</div>
<img
v-else
:src="record.icon"
class="mr-2.5 h-9 w-9 rounded"
:src="row.icon"
class="mr-2.5 h-9 w-9 flex-shrink-0 rounded"
alt="图标"
/>
<EllipsisText :max-width="160" :tooltip-when-ellipsis="true">
{{ record.name }}
<div class="min-w-0">
<EllipsisText :max-width="100" :tooltip-when-ellipsis="true">
{{ row.name }}
</EllipsisText>
</div>
</div>
</template>
<!-- 可见范围列-->
<template v-else-if="column.key === 'startUserIds'">
<span
v-if="
!record.startUsers?.length && !record.startDepts?.length
"
>
<template #startUserIds="{ row }">
<span v-if="!row.startUsers?.length && !row.startDepts?.length">
全部可见
</span>
<span v-else-if="record.startUsers?.length === 1">
{{ record.startUsers[0].nickname }}
<span v-else-if="row.startUsers?.length === 1">
{{ row.startUsers[0].nickname }}
</span>
<span v-else-if="record.startDepts?.length === 1">
{{ record.startDepts[0].name }}
<span v-else-if="row.startDepts?.length === 1">
{{ row.startDepts[0].name }}
</span>
<span v-else-if="record.startDepts?.length > 1">
<span v-else-if="row.startDepts?.length > 1">
<Tooltip
placement="top"
:title="
record.startDepts.map((dept: any) => dept.name).join('、')
row.startDepts.map((dept: any) => dept.name).join('、')
"
>
{{ record.startDepts[0].name }}
{{ record.startDepts.length }} 个部门可见
{{ row.startDepts[0].name }}
{{ row.startDepts.length }} 个部门可见
</Tooltip>
</span>
<span v-else-if="record.startUsers?.length > 1">
<span v-else-if="row.startUsers?.length > 1">
<Tooltip
placement="top"
:title="
record.startUsers
.map((user: any) => user.nickname)
.join('、')
row.startUsers.map((user: any) => user.nickname).join('、')
"
>
{{ record.startUsers[0].nickname }}
{{ record.startUsers.length }} 人可见
{{ row.startUsers[0].nickname }}
{{ row.startUsers.length }} 人可见
</Tooltip>
</span>
</template>
<!-- 流程类型列 -->
<template v-else-if="column.key === 'type'">
<!-- <DictTag :value="record.type" :type="DICT_TYPE.BPM_MODEL_TYPE" /> -->
<!-- <Tag>{{ record.type }}</Tag> -->
<DictTag
:type="DICT_TYPE.BPM_MODEL_TYPE"
:value="record.type"
/>
</template>
<!-- 表单信息列 -->
<template v-else-if="column.key === 'formType'">
<!-- TODO BpmModelFormType.NORMAL -->
<template #formInfo="{ row }">
<Button
v-if="record.formType === BpmModelFormType.NORMAL"
v-if="row.formType === BpmModelFormType.NORMAL"
type="link"
@click="handleFormDetail(record)"
@click="handleFormDetail(row)"
>
{{ record.formName }}
{{ row.formName }}
</Button>
<!-- TODO BpmModelFormType.CUSTOM -->
<Button
v-else-if="record.formType === BpmModelFormType.CUSTOM"
v-else-if="row.formType === BpmModelFormType.CUSTOM"
type="link"
@click="handleFormDetail(record)"
@click="handleFormDetail(row)"
>
{{ record.formCustomCreatePath }}
{{ row.formCustomCreatePath }}
</Button>
<span v-else></span>
</template>
<!-- 最后发布列 -->
<template v-else-if="column.key === 'deploymentTime'">
<template #deploymentTime="{ row }">
<div class="flex items-center justify-center">
<span v-if="record.processDefinition" class="w-[150px]">
{{
formatDateTime(record.processDefinition.deploymentTime)
}}
<span v-if="row.processDefinition" class="w-[150px]">
{{ formatDateTime(row.processDefinition.deploymentTime) }}
</span>
<Tag v-if="record.processDefinition">
v{{ record.processDefinition.version }}
<Tag v-if="row.processDefinition">
v{{ row.processDefinition.version }}
</Tag>
<Tag v-else color="warning">未部署</Tag>
<Tag
v-if="record.processDefinition?.suspensionState === 2"
v-if="row.processDefinition?.suspensionState === 2"
color="warning"
class="ml-[10px]"
>
@ -606,16 +596,15 @@ const handleRenameSuccess = () => {
</Tag>
</div>
</template>
<!-- 操作列 -->
<template v-else-if="column.key === 'operation'">
<template #actions="{ row }">
<div class="flex items-center space-x-0">
<!-- TODO 权限校验-->
<Button
type="link"
size="small"
class="px-1"
@click="modelOperation('update', record.id)"
:disabled="!isManagerUser(record)"
@click="modelOperation('update', row.id)"
:disabled="!isManagerUser(row)"
>
修改
</Button>
@ -623,8 +612,8 @@ const handleRenameSuccess = () => {
type="link"
size="small"
class="px-1"
@click="handleDeploy(record)"
:disabled="!isManagerUser(record)"
@click="handleDeploy(row)"
:disabled="!isManagerUser(row)"
>
发布
</Button>
@ -632,9 +621,7 @@ const handleRenameSuccess = () => {
<Button type="link" size="small" class="px-1">更多</Button>
<template #overlay>
<Menu
@click="
(e) => handleModelCommand(e.key as string, record)
"
@click="(e) => handleModelCommand(e.key as string, row)"
>
<Menu.Item key="handleCopy"> 复制 </Menu.Item>
<Menu.Item key="handleDefinitionList"> 历史 </Menu.Item>
@ -648,11 +635,11 @@ const handleRenameSuccess = () => {
</Menu.Item> -->
<Menu.Item
key="handleChangeState"
v-if="record.processDefinition"
:disabled="!isManagerUser(record)"
v-if="row.processDefinition"
:disabled="!isManagerUser(row)"
>
{{
record.processDefinition.suspensionState === 1
row.processDefinition.suspensionState === 1
? '停用'
: '启用'
}}
@ -660,14 +647,14 @@ const handleRenameSuccess = () => {
<Menu.Item
danger
key="handleClean"
:disabled="!isManagerUser(record)"
:disabled="!isManagerUser(row)"
>
清理
</Menu.Item>
<Menu.Item
danger
key="handleDelete"
:disabled="!isManagerUser(record)"
:disabled="!isManagerUser(row)"
>
删除
</Menu.Item>
@ -676,8 +663,7 @@ const handleRenameSuccess = () => {
</Dropdown>
</div>
</template>
</template>
</Table>
</Grid>
</Collapse.Panel>
</Collapse>
</Card>
@ -691,22 +677,11 @@ const handleRenameSuccess = () => {
<style lang="scss" scoped>
.category-draggable-model {
// ant-table-tbody
:deep(.ant-table-tbody > tr > td) {
overflow: hidden;
border-bottom: none;
}
// ant-collapse-header
:deep(.ant-collapse-header) {
padding: 0;
}
//
:deep(.ant-table-tbody) {
transform: translateZ(0);
will-change: transform;
}
//
:deep(.ant-collapse-content-box) {
padding: 0;

View File

@ -0,0 +1,49 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmModelApi } from '#/api/bpm/model';
import { DICT_TYPE } from '#/utils';
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<BpmModelApi.ModelVO>['columns'] {
return [
{
field: 'name',
title: '流程名称',
width: 250,
slots: { default: 'name' },
},
{
field: 'startUserIds',
title: '可见范围',
width: 150,
slots: { default: 'startUserIds' },
},
{
field: 'type',
title: '流程类型',
width: 150,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.BPM_MODEL_TYPE },
},
},
{
field: 'formType',
title: '表单信息',
width: 150,
slots: { default: 'formInfo' },
},
{
field: 'deploymentTime',
title: '最后发布',
width: 260,
slots: { default: 'deploymentTime' },
},
{
title: '操作',
width: 150,
fixed: 'right',
slots: { default: 'actions' },
},
];
}