tuqin
2 天以前 20ba9778ea1348f7719acd7abf7411f77056cd53
feat(bpm): 北方科技流程模型三项改造(对标中亿装配)

需求:协同办公-流程模型对齐中亿装配的实现。

改动(移植自 dev_新疆_中亿装配 的同名文件):
1. 四个步骤自由切换
handleStepClick 不再逐步骤校验,直接切换;
必填校验统一放到「保存/发布」时执行(validateAllSteps),
中间步骤可以随便点、随便返回,不受任何限制。

2. 流程管理员固定为超级管理员
- 进入页面即强制把 managerUserIds 回写为 [超级管理员, 当前操作人]
- 界面去掉「选择人员」按钮和移除(X),只读显示「超级管理员」
- 之所以同时写入当前操作人:后端 updateModel 是「先校验管理员、后写入」,
只存超管的话,换另一个账号再修改会报「你不是该流程的管理员」且无法自愈

3. 第三步流程设计:简易设计器始终显示
showDesigner 对 Simple 类型直接返回 true,不再依赖 key/name;
仅 BPMN 设计器仍要求先填流程标识与名称。
这样新建/修改模型时,即使没填基本信息,第三步也能看到设计器。
已修改3个文件
109 ■■■■■ 文件已修改
src/views/bpm/model/form/index.vue 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/bpm/model/form/modules/basic-info.vue 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/bpm/model/form/modules/process-design.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/bpm/model/form/index.vue
@@ -188,7 +188,7 @@
  } else {
    // 情况三:新增场景
    formData.value.startUserType = 0; // 全体
    formData.value.managerUserIds.push(userStore.userInfo?.id);
    // 流程管理员默认值稍后设置(需要先加载用户列表才能定位超级管理员)
  }
  // 获取表单列表
@@ -198,6 +198,24 @@
  userList.value = await getSimpleUserList();
  // 获取部门列表
  deptList.value = await getSimpleDeptList();
  // 流程管理员固定为超级管理员(客户要求,界面上不给选择)。
  // 同时把「当前操作人」一并写入:后端 updateModel 是「先校验管理员、后写入」,
  // 只写超管的话,换一个账号再修改就会报「你不是该流程的管理员」,且无法自愈。
  const adminUser = userList.value.find(
    (user: SystemUserApi.User) => user.username === 'admin',
  );
  const currentUserId = userStore.userInfo?.id;
  const managerIds: number[] = [];
  if (adminUser?.id) {
    managerIds.push(adminUser.id);
  }
  if (currentUserId && !managerIds.includes(currentUserId)) {
    managerIds.push(currentUserId);
  }
  if (managerIds.length > 0) {
    formData.value.managerUserIds = managerIds;
  }
  // 最终,设置 currentStep 切换到第一步
  currentStep.value = 0;
@@ -355,27 +373,9 @@
/** 步骤切换处理 */
async function handleStepClick(index: number) {
  try {
    if (index !== 0) {
      await validateBasic();
    }
    if (index !== 1) {
      await validateForm();
    }
    if (index !== 2) {
      await validateProcess();
    }
    if (index !== 3) {
      await validateExtra();
    }
    // 切换步骤
    currentStep.value = index;
  } catch (error) {
    console.error('步骤切换失败:', error);
    if (currentStep.value !== 2) {
      message.warning('请先完善当前步骤必填信息');
    }
  }
  // 步骤之间允许自由切换(含前进和后退),切换时不做校验;
  // 必填信息的校验统一在「保存」「发布」时执行(validateAllSteps),避免卡在中间步骤出不去
  currentStep.value = index;
}
/** 返回列表页 */
src/views/bpm/model/form/modules/basic-info.vue
@@ -14,6 +14,7 @@
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { IconifyIcon } from '@vben/icons';
import { useUserStore } from '@vben/stores';
import {
  Avatar,
@@ -56,6 +57,7 @@
const formRef = ref(); // 表单引用
const modelData = defineModel<any>(); // 创建本地数据副本
const userStore = useUserStore(); // 当前登录用户(写入流程管理员,避免换账号后无法再修改模型)
const selectedStartUsers = ref<SystemUserApi.User[]>([]); // 选中的发起人
const selectedStartDepts = ref<SystemDeptApi.Dept[]>([]); // 选中的发起部门
@@ -113,6 +115,28 @@
          newVal.managerUserIds.includes(user.id),
        ) as SystemUserApi.User[])
      : [];
    // 流程管理员固定为超级管理员:进入页面即强制回写,修改场景同样生效。
    // 注意:同时把「当前操作人」一并写入。因为后端 updateModel 是「先校验管理员、后写入」,
    // 一旦模型被保存成只包含超管,用其他账号再次修改就会报
    // 「操作流程失败,原因:你不是该流程(xxx)的管理员」,且无法自愈。
    const admin = props.userList.find(
      (user: SystemUserApi.User) => user.username === 'admin',
    );
    const currentUserId = userStore.userInfo?.id;
    const managerIds: number[] = [];
    if (admin) {
      managerIds.push(admin.id);
    }
    if (currentUserId && !managerIds.includes(currentUserId)) {
      managerIds.push(currentUserId);
    }
    if (managerIds.length > 0) {
      newVal.managerUserIds = managerIds;
      // 界面上仍然只展示「超级管理员」
      const displayUser =
        admin ?? props.userList.find((user) => user.id === managerIds[0]);
      selectedManagerUsers.value = displayUser ? [displayUser] : [];
    }
  },
  {
    immediate: true,
@@ -398,36 +422,10 @@
        </div>
      </Form.Item>
      <Form.Item label="流程管理员" name="managerUserIds">
        <div class="flex flex-wrap gap-1">
          <div
            v-for="user in selectedManagerUsers"
            :key="user.id"
            class="relative flex h-9 items-center rounded-full bg-gray-100 pr-2 hover:bg-gray-200 dark:border dark:border-gray-500 dark:bg-gray-700 dark:hover:bg-gray-600"
          >
            <Avatar class="m-1 size-7" v-if="user.avatar" :src="user.avatar" />
            <Avatar class="m-1 size-7" v-else>
              {{ user.nickname?.substring(0, 1) }}
            </Avatar>
            <span class="text-gray-700 dark:text-gray-200">
              {{ user.nickname }}
            </span>
            <IconifyIcon
              icon="lucide:x"
              class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500"
              @click="handleRemoveManagerUser(user)"
            />
          </div>
          <Button
            type="link"
            @click="openManagerUserSelect"
            class="flex items-center"
          >
            <template #icon>
              <IconifyIcon icon="lucide:user-plus" class="size-4" />
            </template>
            选择人员
          </Button>
        </div>
        <!-- 流程管理员固定为超级管理员,不允许选择 -->
        <span class="text-gray-700 dark:text-gray-200">
          {{ selectedManagerUsers[0]?.nickname || '超级管理员' }}
        </span>
      </Form.Item>
    </Form>
src/views/bpm/model/form/modules/process-design.vue
@@ -47,7 +47,12 @@
/** 是否显示设计器 */
const showDesigner = computed(() => {
  return Boolean(modelData.value?.key && modelData.value?.name);
  // 简易设计器(Simple 类型)始终显示,不依赖 key/name;
  // 只有 BPMN 设计器才要求先填写流程标识与名称
  if (modelData.value?.type === BpmModelType.BPMN) {
    return Boolean(modelData.value?.key && modelData.value?.name);
  }
  return true;
});
defineExpose({ validate });