2025-04-21 19:05:00 +08:00
|
|
|
|
<script lang="ts" setup>
|
2025-05-07 16:36:17 +08:00
|
|
|
|
import type { ModelCategoryInfo } from '#/api/bpm/model';
|
2025-04-29 15:30:19 +08:00
|
|
|
|
|
|
|
|
|
|
import { onActivated, reactive, ref, useTemplateRef, watch } from 'vue';
|
|
|
|
|
|
|
2025-05-07 16:28:57 +08:00
|
|
|
|
import { Page, useVbenModal } from '@vben/common-ui';
|
2025-04-29 15:30:19 +08:00
|
|
|
|
import { Plus, Search, Settings } 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 {
|
|
|
|
|
|
getCategorySimpleList,
|
|
|
|
|
|
updateCategorySortBatch,
|
|
|
|
|
|
} from '#/api/bpm/category';
|
|
|
|
|
|
import { getModelList } from '#/api/bpm/model';
|
2025-05-16 14:05:52 +08:00
|
|
|
|
import { router } from '#/router';
|
2025-04-29 15:30:19 +08:00
|
|
|
|
|
2025-05-07 16:28:57 +08:00
|
|
|
|
// 流程分类对话框
|
|
|
|
|
|
import CategoryForm from '../category/modules/form.vue';
|
2025-04-29 15:30:19 +08:00
|
|
|
|
import CategoryDraggableModel from './modules/category-draggable-model.vue';
|
2025-05-07 16:28:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 新建流程分类对话框
|
|
|
|
|
|
const [CategoryFormModal, categoryFormModalApi] = useVbenModal({
|
|
|
|
|
|
connectedComponent: CategoryForm,
|
|
|
|
|
|
destroyOnClose: true,
|
|
|
|
|
|
});
|
2025-04-29 15:30:19 +08:00
|
|
|
|
// 模型列表加载状态
|
|
|
|
|
|
const modelListSpinning = refAutoReset(false, 3000);
|
|
|
|
|
|
// 保存排序状态
|
|
|
|
|
|
const saveSortLoading = ref(false);
|
|
|
|
|
|
// 按照 category 分组的数据
|
2025-05-07 16:36:17 +08:00
|
|
|
|
const categoryGroup = ref<ModelCategoryInfo[]>([]);
|
2025-04-29 15:30:19 +08:00
|
|
|
|
// 未排序前的原始数据
|
2025-05-07 16:36:17 +08:00
|
|
|
|
const originalData = ref<ModelCategoryInfo[]>([]);
|
2025-04-29 15:30:19 +08:00
|
|
|
|
// 可以排序元素的容器
|
|
|
|
|
|
const sortable = useTemplateRef<HTMLElement>('categoryGroupRef');
|
|
|
|
|
|
// 排序引用,以便后续启用或禁用排序
|
|
|
|
|
|
const sortableInstance = ref<any>(null);
|
|
|
|
|
|
// 分类排序状态
|
|
|
|
|
|
const isCategorySorting = ref(false);
|
|
|
|
|
|
// 查询参数
|
|
|
|
|
|
const queryParams = reactive({
|
|
|
|
|
|
name: '',
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 监听分类排序模式切换
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => isCategorySorting.value,
|
|
|
|
|
|
(newValue) => {
|
|
|
|
|
|
if (sortableInstance.value) {
|
|
|
|
|
|
if (newValue) {
|
|
|
|
|
|
// 启用排序功能
|
|
|
|
|
|
sortableInstance.value.option('disabled', false);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 禁用排序功能
|
|
|
|
|
|
sortableInstance.value.option('disabled', true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
/** 加载数据 */
|
|
|
|
|
|
const getList = async () => {
|
|
|
|
|
|
modelListSpinning.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const modelList = await getModelList(queryParams.name);
|
|
|
|
|
|
const categoryList = await getCategorySimpleList();
|
|
|
|
|
|
// 按照 category 聚合
|
|
|
|
|
|
categoryGroup.value = categoryList.map((category: any) => ({
|
|
|
|
|
|
...category,
|
|
|
|
|
|
modelList: modelList.filter(
|
|
|
|
|
|
(model: any) => model.categoryName === category.name,
|
|
|
|
|
|
),
|
|
|
|
|
|
}));
|
|
|
|
|
|
// 重置排序实例
|
|
|
|
|
|
sortableInstance.value = null;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
modelListSpinning.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-04-23 12:56:35 +08:00
|
|
|
|
|
2025-04-29 15:30:19 +08:00
|
|
|
|
/** 初始化 */
|
|
|
|
|
|
onActivated(() => {
|
|
|
|
|
|
getList();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/** 查询方法 */
|
|
|
|
|
|
const handleQuery = () => {
|
|
|
|
|
|
getList();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** 新增模型 */
|
|
|
|
|
|
const createModel = () => {
|
2025-05-16 14:05:52 +08:00
|
|
|
|
router.push({
|
|
|
|
|
|
name: 'BpmModelCreate',
|
|
|
|
|
|
});
|
2025-04-29 15:30:19 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** 处理下拉菜单命令 */
|
|
|
|
|
|
const handleCommand = (command: string) => {
|
|
|
|
|
|
if (command === 'handleCategoryAdd') {
|
2025-05-07 16:28:57 +08:00
|
|
|
|
// 打开新建流程分类弹窗
|
|
|
|
|
|
categoryFormModalApi.open();
|
2025-04-29 15:30:19 +08:00
|
|
|
|
} else if (command === 'handleCategorySort') {
|
|
|
|
|
|
originalData.value = cloneDeep(categoryGroup.value);
|
|
|
|
|
|
isCategorySorting.value = true;
|
|
|
|
|
|
// 如果排序实例不存在,则初始化
|
|
|
|
|
|
if (sortableInstance.value) {
|
|
|
|
|
|
// 已存在实例,则启用排序功能
|
|
|
|
|
|
sortableInstance.value.option('disabled', false);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
sortableInstance.value = useSortable(sortable, categoryGroup, {
|
|
|
|
|
|
disabled: false, // 启用排序
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** 取消分类排序 */
|
|
|
|
|
|
const handleCategorySortCancel = () => {
|
|
|
|
|
|
// 恢复初始数据
|
|
|
|
|
|
categoryGroup.value = cloneDeep(originalData.value);
|
|
|
|
|
|
isCategorySorting.value = false;
|
|
|
|
|
|
// 直接禁用排序功能
|
|
|
|
|
|
if (sortableInstance.value) {
|
|
|
|
|
|
sortableInstance.value.option('disabled', true);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** 提交分类排序 */
|
|
|
|
|
|
const handleCategorySortSubmit = async () => {
|
|
|
|
|
|
saveSortLoading.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 保存排序逻辑
|
|
|
|
|
|
const ids = categoryGroup.value.map((item: any) => item.id);
|
|
|
|
|
|
await updateCategorySortBatch(ids);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
saveSortLoading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
message.success('分类排序成功');
|
|
|
|
|
|
isCategorySorting.value = false;
|
|
|
|
|
|
// 刷新列表
|
|
|
|
|
|
await getList();
|
|
|
|
|
|
// 禁用排序功能
|
|
|
|
|
|
if (sortableInstance.value) {
|
|
|
|
|
|
sortableInstance.value.option('disabled', true);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-04-21 19:05:00 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2025-04-29 15:30:19 +08:00
|
|
|
|
<Page auto-content-height>
|
2025-05-25 08:46:09 +08:00
|
|
|
|
<!-- TODO @jaosn:没头像的图标,展示文字头像哈 @芋艿 好像已经展示了文字头像。是模型列表中吗? -->
|
2025-05-16 14:05:52 +08:00
|
|
|
|
<!-- 流程分类表单弹窗 -->
|
|
|
|
|
|
<CategoryFormModal @success="getList" />
|
2025-04-29 15:30:19 +08:00
|
|
|
|
<Card
|
|
|
|
|
|
:body-style="{ padding: '10px' }"
|
2025-05-26 19:05:45 +08:00
|
|
|
|
class="mb-4 h-[98%]"
|
2025-04-29 15:30:19 +08:00
|
|
|
|
v-spinning="modelListSpinning"
|
2025-04-23 12:56:35 +08:00
|
|
|
|
>
|
2025-05-06 14:39:03 +08:00
|
|
|
|
<div class="flex h-full items-center justify-between pl-5">
|
2025-04-29 15:30:19 +08:00
|
|
|
|
<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>
|
2025-05-07 16:28:57 +08:00
|
|
|
|
<Dropdown placement="bottomRight" arrow>
|
2025-04-29 15:30:19 +08:00
|
|
|
|
<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>
|
|
|
|
|
|
<Button @click="handleCategorySortCancel" class="mr-3">
|
|
|
|
|
|
取 消
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
:loading="saveSortLoading"
|
|
|
|
|
|
@click="handleCategorySortSubmit"
|
|
|
|
|
|
>
|
|
|
|
|
|
保存排序
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
<!-- 按照分类,展示其所属的模型列表 -->
|
|
|
|
|
|
<div class="px-5" ref="categoryGroupRef">
|
|
|
|
|
|
<CategoryDraggableModel
|
|
|
|
|
|
v-for="element in categoryGroup"
|
|
|
|
|
|
:class="isCategorySorting ? 'cursor-move' : ''"
|
|
|
|
|
|
:key="element.id"
|
|
|
|
|
|
:category-info="element"
|
|
|
|
|
|
:is-category-sorting="isCategorySorting"
|
|
|
|
|
|
@success="getList"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
2025-04-21 19:05:00 +08:00
|
|
|
|
</Page>
|
2025-04-23 12:56:35 +08:00
|
|
|
|
</template>
|