gaoluyang
2026-06-24 712aa51536236d43e87273e4ce45ac5691dffad8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
<script lang="ts" setup>
import type { ModelCategoryInfo } from '#/api/bpm/model';
 
import { onActivated, reactive, ref, useTemplateRef, watch } from 'vue';
 
import { Page, useVbenModal } from '..\..\..\packages\effects\common-ui\src';
import { IconifyIcon } from '..\..\..\packages\icons\src';
import { cloneDeep } from '..\..\..\packages\utils\src';
 
import { useSortable } from '@vueuse/integrations/useSortable';
import { Button, Card, Dropdown, Input, Menu, message } from 'ant-design-vue';
 
import {
  getCategorySimpleList,
  updateCategorySortBatch,
} from '#/api/bpm/category';
import { getModelList } from '#/api/bpm/model';
import { router } from '#/router';
 
import CategoryForm from '../category/modules/form.vue';
import CategoryDraggableModel from './modules/category-draggable-model.vue';
 
const [CategoryFormModal, categoryFormModalApi] = useVbenModal({
  connectedComponent: CategoryForm,
  destroyOnClose: true,
});
 
const modelListSpinning = ref(false); // 模型列表加载状态
 
const saveSortLoading = ref(false); // 保存排序状态
const categoryGroup = ref<ModelCategoryInfo[]>([]); // 按照 category 分组的数据
const originalData = ref<ModelCategoryInfo[]>([]); // 未排序前的原始数据
 
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);
      }
    }
  },
);
 
/** 加载数据 */
async function getList() {
  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;
  }
}
 
/** 初始化 */
onActivated(() => {
  getList();
});
 
/** 新增模型 */
function createModel() {
  router.push({
    name: 'BpmModelCreate',
  });
}
 
/** 处理下拉菜单命令 */
function handleCommand(command: string) {
  if (command === 'handleCategoryAdd') {
    // 打开新建流程分类弹窗
    categoryFormModalApi.open();
  } 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, // 启用排序
      });
    }
  }
}
 
/** 取消分类排序 */
function handleCategorySortCancel() {
  // 恢复初始数据
  categoryGroup.value = cloneDeep(originalData.value);
  isCategorySorting.value = false;
  // 直接禁用排序功能
  if (sortableInstance.value) {
    sortableInstance.value.option('disabled', true);
  }
}
 
/** 提交分类排序 */
async function handleCategorySortSubmit() {
  saveSortLoading.value = true;
  try {
    // 保存排序逻辑
    const ids = categoryGroup.value.map((item: any) => item.id);
    await updateCategorySortBatch(ids);
    message.success('分类排序成功');
  } finally {
    saveSortLoading.value = false;
  }
  isCategorySorting.value = false;
  // 刷新列表
  await getList();
  // 禁用排序功能
  if (sortableInstance.value) {
    sortableInstance.value.option('disabled', true);
  }
}
</script>
 
<template>
  <Page auto-content-height>
    <!-- 流程分类表单弹窗 -->
    <CategoryFormModal @success="getList" />
    <Card
      :body-style="{ padding: '10px' }"
      class="mb-4"
      title="流程模型"
      v-spinning="modelListSpinning"
    >
      <template #extra>
        <div v-if="!isCategorySorting">
          <Input
            v-model:value="queryParams.name"
            placeholder="搜索流程"
            allow-clear
            @press-enter="getList"
            class="!w-60"
          />
          <Button class="ml-2" type="primary" @click="createModel">
            <IconifyIcon icon="lucide:plus" /> 新建模型
          </Button>
          <Dropdown class="ml-2" placement="bottomRight" arrow>
            <Button>
              <template #icon>
                <div class="flex items-center justify-center">
                  <IconifyIcon icon="lucide:settings" />
                </div>
              </template>
            </Button>
            <template #overlay>
              <Menu @click="(e) => handleCommand(e.key as string)">
                <Menu.Item key="handleCategoryAdd">
                  <div class="flex items-center gap-1">
                    <IconifyIcon icon="lucide:plus" />
                    新建分类
                  </div>
                </Menu.Item>
                <Menu.Item key="handleCategorySort">
                  <div class="flex items-center gap-1">
                    <IconifyIcon icon="lucide:align-start-vertical" />
                    分类排序
                  </div>
                </Menu.Item>
              </Menu>
            </template>
          </Dropdown>
        </div>
        <div class="flex h-full items-center justify-between" v-else>
          <Button @click="handleCategorySortCancel" class="mr-3">
            取 消
          </Button>
          <Button
            type="primary"
            :loading="saveSortLoading"
            @click="handleCategorySortSubmit"
          >
            保存排序
          </Button>
        </div>
      </template>
 
      <!-- 按照分类,展示其所属的模型列表 -->
      <div class="px-3" ref="categoryGroupRef">
        <CategoryDraggableModel
          v-for="(element, index) in categoryGroup"
          :class="isCategorySorting ? 'cursor-move' : ''"
          :key="element.id"
          :category-info="element"
          :is-category-sorting="isCategorySorting"
          :is-first="index === 0"
          @success="getList"
        />
      </div>
    </Card>
  </Page>
</template>