yyb
14 小时以前 352f7bbb74f1b6c57b3d3e576849d0565932fbd4
src/views/officeProcessAutomation/HrManage/work-handover/index.vue
@@ -30,11 +30,11 @@
        <el-select v-model="searchForm.handoverType" placeholder="全部" clearable style="width: 140px">
          <el-option v-for="o in handoverTypeOptions" :key="o.value" :label="o.label" :value="o.value" />
        </el-select>
        <el-button type="primary" style="margin-left: 10px" @click="handleQuery">搜索</el-button>
        <el-button type="primary" style="margin-left: 10px" @click="onSearch">搜索</el-button>
        <el-button @click="resetSearch">重置</el-button>
      </div>
      <div>
        <el-button type="primary" @click="openFormDialog('add')">新增工作交接</el-button>
        <el-button type="primary" @click="openAddWithTemplate">新增工作交接</el-button>
      </div>
    </div>
    <div class="table_list">
@@ -45,164 +45,53 @@
        :page="page"
        :isSelection="false"
        :tableLoading="tableLoading"
        @pagination="pagination"
        @pagination="onPagination"
        :total="page.total"
      />
    </div>
    <!-- 新增 / 编辑 -->
    <el-dialog
      v-model="formDialog.visible"
      :title="formDialog.title"
      width="720px"
      append-to-body
      destroy-on-close
      class="work-handover-form-dialog"
      @closed="onFormClosed"
    >
      <el-form ref="formRef" :model="form" :rules="formRules" label-width="120px" class="work-handover-form">
        <el-row :gutter="24">
          <el-col :span="24">
            <el-form-item label="申请人" prop="applicantId">
              <el-select
                v-model="form.applicantId"
                filterable
                remote
                clearable
                reserve-keyword
                placeholder="请选择或搜索申请人"
                style="width: 100%"
                :remote-method="remoteSearchApplicantForm"
                :loading="applicantFormSearchLoading"
                @change="onApplicantChange"
              >
                <el-option
                  v-for="u in applicantFormOptions"
                  :key="u.userId"
                  :label="userSelectLabel(u)"
                  :value="u.userId"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="24">
          <el-col :span="12">
            <el-form-item label="离职日期" prop="leaveDate">
              <el-date-picker
                v-model="form.leaveDate"
                type="date"
                placeholder="请选择离职日期"
                format="YYYY-MM-DD"
                value-format="YYYY-MM-DD"
                style="width: 100%"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="交接状态" prop="handoverStatus">
              <el-select v-model="form.handoverStatus" placeholder="请选择交接状态" style="width: 100%">
                <el-option v-for="o in handoverStatusOptions" :key="o.value" :label="o.label" :value="o.value" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="24">
          <el-col :span="12">
            <el-form-item label="交接类型" prop="handoverType">
              <el-select v-model="form.handoverType" placeholder="请选择交接类型" style="width: 100%">
                <el-option v-for="o in handoverTypeOptions" :key="o.value" :label="o.label" :value="o.value" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="交接人" prop="handoverPersonId">
              <el-select
                v-model="form.handoverPersonId"
                filterable
                remote
                clearable
                reserve-keyword
                placeholder="请选择或搜索交接人"
                style="width: 100%"
                :remote-method="remoteSearchHandoverPerson"
                :loading="handoverPersonSearchLoading"
                @change="onHandoverPersonChange"
              >
                <el-option
                  v-for="u in handoverPersonOptions"
                  :key="u.userId"
                  :label="userSelectLabel(u)"
                  :value="u.userId"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="24">
          <el-col :span="12">
            <el-form-item label="审批方式" prop="approvalMode">
              <el-radio-group v-model="form.approvalMode">
                <el-radio value="parallel">与签</el-radio>
                <el-radio value="countersign">会签</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="24">
          <el-col :span="24">
            <el-form-item label="审批人" prop="approverIds">
              <el-tree-select
                v-model="form.approverIds"
                :data="approverTreeData"
                multiple
                collapse-tags
                collapse-tags-tooltip
                :max-collapse-tags="2"
                :render-after-expand="false"
                placeholder="请选择审批人(可多选)"
                style="width: 100%"
                :props="{ value: 'id', label: 'label', children: 'children', disabled: 'disabled' }"
                check-strictly
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确 定</el-button>
          <el-button @click="formDialog.visible = false">取 消</el-button>
        </div>
      </template>
    </el-dialog>
    <ApprovalInstanceSubmitDialog
      v-model="submitDialog.visible"
      :title="submitDialogTitle"
      :form="submitForm"
      :rules="submitFormRules"
      :fields="submitFormFields"
      :active-template="activeTemplate"
      :user-options="flowUserOptions"
      :is-edit="isSubmitEdit"
      :saving="submitSaving"
      :form-ref="submitFormRef"
      @submit="onSubmit"
    />
    <!-- 详情 -->
    <el-dialog v-model="detailDialog.visible" title="工作交接详情" width="560px" append-to-body>
      <el-descriptions :column="1" border>
        <el-descriptions-item label="申请人">{{ detailRow.applicantName }}</el-descriptions-item>
        <el-descriptions-item label="离职日期">{{ detailRow.leaveDate || "—" }}</el-descriptions-item>
        <el-descriptions-item label="交接状态">{{ handoverStatusLabel(detailRow.handoverStatus) }}</el-descriptions-item>
        <el-descriptions-item label="交接类型">{{ handoverTypeLabel(detailRow.handoverType) }}</el-descriptions-item>
        <el-descriptions-item label="交接人">{{ detailRow.handoverPersonName || "—" }}</el-descriptions-item>
        <el-descriptions-item label="审批结果">{{ approvalResultLabel(detailRow.approvalResult) }}</el-descriptions-item>
        <el-descriptions-item label="审批方式">{{ approvalModeLabel(detailRow.approvalMode) }}</el-descriptions-item>
        <el-descriptions-item label="审批人">{{ detailRow.approverNames || "—" }}</el-descriptions-item>
      </el-descriptions>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="detailDialog.visible = false">关 闭</el-button>
        </div>
      </template>
    </el-dialog>
    <ApprovalTemplateBindDialog
      v-model:visible="templateBindVisible"
      :module-key="APPROVAL_MODULE_KEYS.WORK_HANDOVER"
      skip-form-confirm
      @confirm="onTemplateBound"
      @closed="onTemplateBindClosed"
    />
    <ApprovalInstanceDetailDialog
      v-model="detailDialog.visible"
      title="工作交接详情"
      :row="detailRow"
      @edit="openEditFromDetail"
    />
  </div>
</template>
<script setup>
import { deptTreeSelect, userListNoPageByTenantId } from "@/api/system/user.js";
import { computed, getCurrentInstance, nextTick, onMounted, reactive, ref, watch } from "vue";
const { proxy } = getCurrentInstance();
import { userListNoPageByTenantId } from "@/api/system/user.js";
import { ElMessage } from "element-plus";
import { onMounted, reactive, ref } from "vue";
import ApprovalInstanceDetailDialog from "../../ApproveManage/approve-shared/components/ApprovalInstanceDetailDialog.vue";
import ApprovalInstanceSubmitDialog from "../../ApproveManage/approve-shared/components/ApprovalInstanceSubmitDialog.vue";
import ApprovalTemplateBindDialog from "../../ApproveManage/approve-shared/components/ApprovalTemplateBindDialog.vue";
import { buildInstanceTableColumns } from "../../ApproveManage/approve-shared/approvalInstanceFormConfigTable.js";
import { APPROVAL_MODULE_KEYS } from "../../ApproveManage/approve-shared/approvalModuleRegistry.js";
import { useApprovalInstanceModule } from "../../ApproveManage/approve-shared/useApprovalInstanceModule.js";
import { useFlowUserOptions } from "../../ApproveManage/approve-shared/useFlowUserOptions.js";
const handoverStatusOptions = [
  { value: "in_progress", label: "进行中" },
@@ -215,30 +104,61 @@
  { value: "transfer", label: "调岗交接" },
];
function handoverStatusLabel(v) {
  return handoverStatusOptions.find((o) => o.value === v)?.label || "—";
}
function handoverTypeLabel(v) {
  return handoverTypeOptions.find((o) => o.value === v)?.label || "—";
}
/** 与后端约定字段(本地占位,后期接口对齐) */
const createEmptyForm = () => ({
  id: undefined,
const searchForm = reactive({
  applicantId: "",
  applicantName: "",
  leaveDate: "",
  handoverStatus: "in_progress",
  handoverType: "resignation",
  handoverPersonId: "",
  handoverPersonName: "",
  approvalMode: "parallel",
  approverIds: [],
  approverNames: "",
  handoverStatus: "",
  handoverType: "",
});
const mod = useApprovalInstanceModule({
  moduleKey: APPROVAL_MODULE_KEYS.WORK_HANDOVER,
});
const {
  tableData,
  tableLoading,
  page,
  detailDialog,
  detailRow,
  submitDialog,
  submitForm,
  submitFormRef,
  submitSaving,
  isSubmitEdit,
  activeTemplate,
  submitFormFields,
  submitFormRules,
  submitDialogTitle,
  templateBindVisible,
  handleQuery,
  initModuleList,
  pagination,
  openAddWithTemplate,
  onTemplateBound,
  onTemplateBindClosed,
  openEditFromDetail,
  submitInstanceForm,
  buildTableActions,
} = mod;
const { flowUserOptions, loadFlowUsers } = useFlowUserOptions();
const allUsersCache = ref([]);
const applicantSearchOptions = ref([]);
const applicantSearchLoading = ref(false);
function unwrapArray(payload) {
  if (Array.isArray(payload)) return payload;
  if (payload && Array.isArray(payload.data)) return payload.data;
  if (payload && Array.isArray(payload.rows)) return payload.rows;
  return [];
}
function isActiveUser(u) {
  if (u.delFlag === "2" || u.delFlag === 2) return false;
  if (u.status == null) return true;
  return String(u.status) === "0";
}
function userSelectLabel(u) {
  const nick = u.nickName || "";
@@ -247,21 +167,18 @@
  return nick || name || `用户${u.userId ?? u.id ?? ""}`;
}
function userById(id) {
  if (id == null || id === "") return undefined;
  return allUsersCache.value.find((u) => String(u.userId ?? u.id) === String(id));
}
function filterUsersByQuery(query) {
  const list = allUsersCache.value.filter((u) => isActiveUser(u));
  const q = (query || "").trim().toLowerCase();
  if (!q) return [...list];
  return list.filter((u) => {
    const nick = (u.nickName || "").toLowerCase();
    const uname = (u.userName || "").toLowerCase();
    const phone = (u.phonenumber || u.phone || "").toString();
    return nick.includes(q) || uname.includes(q) || phone.includes(q);
  });
  if (!q) return list.slice(0, 50);
  return list
    .filter((u) => {
      const nick = (u.nickName || "").toLowerCase();
      const name = (u.userName || "").toLowerCase();
      const id = String(u.userId ?? u.id ?? "");
      return nick.includes(q) || name.includes(q) || id.includes(q);
    })
    .slice(0, 50);
}
async function loadUserPool() {
@@ -273,513 +190,44 @@
  }
}
const applicantSearchLoading = ref(false);
const applicantSearchOptions = ref([]);
async function remoteSearchApplicant(query) {
  applicantSearchLoading.value = true;
  try {
    if (!allUsersCache.value.length) {
      await loadUserPool();
    }
    if (!allUsersCache.value.length) await loadUserPool();
    applicantSearchOptions.value = filterUsersByQuery(query);
  } finally {
    applicantSearchLoading.value = false;
  }
}
const applicantFormSearchLoading = ref(false);
const applicantFormOptions = ref([]);
const tableColumn = buildInstanceTableColumns(tableData, buildTableActions);
async function remoteSearchApplicantForm(query) {
  applicantFormSearchLoading.value = true;
  try {
    if (!allUsersCache.value.length) {
      await loadUserPool();
    }
    applicantFormOptions.value = filterUsersByQuery(query);
  } finally {
    applicantFormSearchLoading.value = false;
  }
}
function onApplicantChange(uid) {
  const u = userById(uid);
  form.applicantName = u ? u.nickName || u.userName || "" : "";
}
const handoverPersonSearchLoading = ref(false);
const handoverPersonOptions = ref([]);
async function remoteSearchHandoverPerson(query) {
  handoverPersonSearchLoading.value = true;
  try {
    if (!allUsersCache.value.length) {
      await loadUserPool();
    }
    handoverPersonOptions.value = filterUsersByQuery(query);
  } finally {
    handoverPersonSearchLoading.value = false;
  }
}
function onHandoverPersonChange(uid) {
  const u = userById(uid);
  form.handoverPersonName = u ? u.nickName || u.userName || "" : "";
}
const approverTreeData = ref([]);
const approverLabelMap = ref({});
function unwrapArray(payload) {
  if (Array.isArray(payload)) return payload;
  if (payload && Array.isArray(payload.data)) return payload.data;
  if (payload && Array.isArray(payload.rows)) return payload.rows;
  return [];
}
function filterDisabledDept(deptList) {
  if (!Array.isArray(deptList)) return [];
  return deptList.filter((dept) => {
    if (dept.disabled) return false;
    if (dept.children?.length) {
      dept.children = filterDisabledDept(dept.children);
    }
    return true;
  });
}
function getUserDeptId(u) {
  return u.deptId ?? u.sysDeptId ?? u.dept?.deptId ?? u.dept?.id ?? u.dept_id;
}
function getDeptNodeKey(node) {
  const k = node?.id ?? node?.value ?? node?.deptId;
  if (k == null || k === "") return null;
  return k;
}
function isActiveUser(u) {
  if (u.delFlag === "2" || u.delFlag === 2) return false;
  if (u.status == null) return true;
  return String(u.status) === "0";
}
function userToTreeLeaf(u) {
  return {
    id: String(u.userId ?? u.id),
    label: u.nickName || u.userName || `用户${u.userId ?? u.id}`,
  };
}
function buildUsersByDeptId(users) {
  const map = new Map();
  const unassigned = [];
  for (const u of users) {
    if (!isActiveUser(u)) continue;
    const did = getUserDeptId(u);
    if (did == null || did === "" || did === 0 || did === "0") {
      unassigned.push(u);
      continue;
    }
    const k = String(did);
    if (!map.has(k)) map.set(k, []);
    map.get(k).push(u);
  }
  return { map, unassigned };
}
function collectUserLabels(nodes, map) {
  (nodes || []).forEach((n) => {
    if (n.children?.length) {
      collectUserLabels(n.children, map);
    } else if (n.id != null && !String(n.id).startsWith("dept_")) {
      map[String(n.id)] = n.label;
    }
  });
}
function mergeDeptTreeWithUsers(nodes, usersByDept) {
  if (!Array.isArray(nodes)) return [];
  const out = [];
  for (const node of nodes) {
    const deptIdRaw = getDeptNodeKey(node);
    if (deptIdRaw == null) continue;
    const sub = mergeDeptTreeWithUsers(node.children || [], usersByDept);
    const usersHere = usersByDept.get(String(deptIdRaw)) || [];
    const userChildren = usersHere.map(userToTreeLeaf);
    const children = [...sub, ...userChildren];
    if (!children.length) continue;
    out.push({
      id: `dept_${deptIdRaw}`,
      label: node.label ?? node.deptName ?? "部门",
      disabled: true,
      children,
    });
  }
  return out;
}
function buildFlatApproverTree(users) {
  const list = users.filter(isActiveUser).map(userToTreeLeaf);
  if (!list.length) return [];
  return [
    {
      id: "dept_all_users",
      label: "系统用户",
      disabled: true,
      children: list,
    },
  ];
}
async function loadApproverTree() {
  try {
    const needFetchUsers = !allUsersCache.value.length;
    const [deptRes, userRes] = await Promise.all([
      deptTreeSelect(),
      needFetchUsers ? userListNoPageByTenantId() : Promise.resolve(null),
    ]);
    let rawTree = unwrapArray(deptRes);
    rawTree = rawTree.length ? JSON.parse(JSON.stringify(rawTree)) : [];
    let deptTree = filterDisabledDept(JSON.parse(JSON.stringify(rawTree)));
    if (!deptTree.length && rawTree.length) {
      deptTree = JSON.parse(JSON.stringify(rawTree));
    }
    let users = needFetchUsers ? unwrapArray(userRes) : [...allUsersCache.value];
    if (needFetchUsers && users.length) {
      allUsersCache.value = users;
    }
    const { map: usersByDept, unassigned } = buildUsersByDeptId(users);
    let merged = mergeDeptTreeWithUsers(deptTree, usersByDept);
    if (unassigned.length) {
      merged.push({
        id: "dept_unassigned",
        label: "未分配部门",
        disabled: true,
        children: unassigned.map(userToTreeLeaf),
      });
    }
    if (!merged.length && users.length) {
      merged = buildFlatApproverTree(users);
    }
    approverTreeData.value = merged;
    const map = {};
    collectUserLabels(merged, map);
    approverLabelMap.value = map;
  } catch {
    approverTreeData.value = [];
    approverLabelMap.value = {};
    proxy?.$modal?.msgWarning?.("审批人数据加载失败,请检查网络或稍后重试");
  }
}
function resolveApproverNames(ids) {
  if (!ids?.length) return "";
  const map = approverLabelMap.value;
  return ids.map((id) => map[String(id)] || id).join("、");
}
function approvalModeLabel(mode) {
  if (mode === "countersign") return "会签";
  return "与签";
}
function approvalResultLabel(v) {
  if (v === "approved") return "已通过";
  if (v === "rejected") return "已驳回";
  if (v === "cancelled") return "已撤销";
  return "待审批";
}
function handoverStatusTagType(v) {
  if (v === "completed") return "success";
  if (v === "returned") return "danger";
  return "warning";
}
function handoverTypeTagType(v) {
  return v === "transfer" ? "info" : "";
}
/** 本地模拟列表数据 */
const allRows = ref([
  {
    id: "1",
    applicantId: "1001",
    applicantName: "周明",
    leaveDate: "2026-05-28",
    handoverStatus: "in_progress",
    handoverType: "resignation",
    handoverPersonId: "1003",
    handoverPersonName: "王强",
    approvalResult: "pending",
    approvalMode: "parallel",
    approverIds: [],
    approverNames: "",
  },
  {
    id: "2",
    applicantId: "1002",
    applicantName: "吴芳",
    leaveDate: "2026-05-15",
    handoverStatus: "completed",
    handoverType: "transfer",
    handoverPersonId: "1004",
    handoverPersonName: "赵敏",
    approvalResult: "approved",
    approvalMode: "countersign",
    approverIds: [],
    approverNames: "张三、李四",
  },
  {
    id: "3",
    applicantId: "1005",
    applicantName: "陈浩",
    leaveDate: "2026-04-20",
    handoverStatus: "returned",
    handoverType: "resignation",
    handoverPersonId: "1006",
    handoverPersonName: "刘洋",
    approvalResult: "rejected",
    approvalMode: "parallel",
    approverIds: [],
    approverNames: "李四",
  },
]);
const searchForm = reactive({
  applicantId: "",
  handoverStatus: "",
  handoverType: "",
});
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 10,
  total: 0,
});
const filteredList = computed(() => {
  let list = [...allRows.value];
  if (searchForm.applicantId) {
    list = list.filter((r) => String(r.applicantId) === String(searchForm.applicantId));
  }
  if (searchForm.handoverStatus) {
    list = list.filter((r) => r.handoverStatus === searchForm.handoverStatus);
  }
  if (searchForm.handoverType) {
    list = list.filter((r) => r.handoverType === searchForm.handoverType);
  }
  return list.sort((a, b) => (a.leaveDate < b.leaveDate ? 1 : -1));
});
watch(
  filteredList,
  (list) => {
    page.total = list.length;
    const maxPage = Math.max(1, Math.ceil(list.length / page.size) || 1);
    if (page.current > maxPage) {
      page.current = maxPage;
    }
  },
  { immediate: true }
);
const tableData = computed(() => {
  const list = filteredList.value;
  const start = (page.current - 1) * page.size;
  return list.slice(start, start + page.size);
});
const tableColumn = ref([
  { label: "申请人", prop: "applicantName", minWidth: 100 },
  { label: "离职日期", prop: "leaveDate", width: 120 },
  {
    label: "交接状态",
    prop: "handoverStatus",
    width: 110,
    dataType: "tag",
    formatData: (v) => handoverStatusLabel(v),
    formatType: (v) => handoverStatusTagType(v),
  },
  {
    label: "交接类型",
    prop: "handoverType",
    width: 110,
    dataType: "tag",
    formatData: (v) => handoverTypeLabel(v),
    formatType: (v) => handoverTypeTagType(v),
  },
  { label: "交接人", prop: "handoverPersonName", minWidth: 100 },
  {
    label: "审批结果",
    prop: "approvalResult",
    width: 110,
    dataType: "tag",
    formatData: (v) => approvalResultLabel(v),
    formatType: (v) => {
      if (v === "approved") return "success";
      if (v === "rejected") return "danger";
      if (v === "cancelled") return "info";
      return "warning";
    },
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 200,
    operation: [
      { name: "编辑", type: "text", clickFun: (row) => openFormDialog("edit", row) },
      { name: "详情", type: "text", clickFun: (row) => openDetail(row) },
    ],
  },
]);
const formDialog = reactive({
  visible: false,
  title: "",
  mode: "add",
});
const formRef = ref();
const form = reactive(createEmptyForm());
const formRules = {
  applicantId: [{ required: true, message: "请选择申请人", trigger: "change" }],
  leaveDate: [{ required: true, message: "请选择离职日期", trigger: "change" }],
  handoverStatus: [{ required: true, message: "请选择交接状态", trigger: "change" }],
  handoverType: [{ required: true, message: "请选择交接类型", trigger: "change" }],
  handoverPersonId: [{ required: true, message: "请选择交接人", trigger: "change" }],
  approvalMode: [{ required: true, message: "请选择审批方式", trigger: "change" }],
  approverIds: [{ type: "array", required: true, message: "请选择审批人", trigger: "change" }],
};
const detailDialog = reactive({ visible: false });
const detailRow = ref({});
function handleQuery() {
  page.current = 1;
  tableLoading.value = true;
  setTimeout(() => {
    tableLoading.value = false;
  }, 150);
function onSearch() {
  handleQuery(searchForm);
}
async function resetSearch() {
  searchForm.applicantId = "";
  searchForm.handoverStatus = "";
  searchForm.handoverType = "";
  handleQuery();
  onSearch();
  await remoteSearchApplicant("");
}
function pagination(obj) {
  page.current = obj.page;
  page.size = obj.limit;
function onPagination(obj) {
  pagination(obj, searchForm);
}
function openDetail(row) {
  detailRow.value = { ...row };
  detailDialog.visible = true;
}
function ensureUserInOptions(optionsRef, row, idKey, nameKey) {
  const id = row?.[idKey];
  if (id == null || id === "") return;
  const sid = String(id);
  if (!optionsRef.value.some((u) => String(u.userId ?? u.id) === sid)) {
    optionsRef.value = [
      {
        userId: id,
        nickName: row[nameKey],
        userName: row.applicantUserName,
      },
      ...optionsRef.value,
    ];
  }
}
async function openFormDialog(mode, row) {
  formDialog.mode = mode;
  formDialog.title = mode === "add" ? "新增工作交接" : "编辑工作交接";
  loadApproverTree();
  Object.assign(form, createEmptyForm());
  await Promise.all([remoteSearchApplicantForm(""), remoteSearchHandoverPerson("")]);
  if (mode === "edit" && row) {
    ensureUserInOptions(applicantFormOptions, row, "applicantId", "applicantName");
    ensureUserInOptions(handoverPersonOptions, row, "handoverPersonId", "handoverPersonName");
    Object.assign(form, {
      id: row.id,
      applicantId: row.applicantId,
      applicantName: row.applicantName,
      leaveDate: row.leaveDate,
      handoverStatus: row.handoverStatus,
      handoverType: row.handoverType,
      handoverPersonId: row.handoverPersonId,
      handoverPersonName: row.handoverPersonName,
      approvalMode: row.approvalMode,
      approverIds: (row.approverIds || []).map((id) => String(id)),
      approverNames: row.approverNames,
    });
  }
  formDialog.visible = true;
  nextTick(() => formRef.value?.clearValidate?.());
}
function onFormClosed() {
  formRef.value?.resetFields?.();
}
async function submitForm() {
  try {
    await formRef.value?.validate?.();
  } catch {
    return;
  }
  form.approverNames = resolveApproverNames(form.approverIds);
  const payload = {
    applicantId: form.applicantId,
    applicantName: form.applicantName,
    leaveDate: form.leaveDate,
    handoverStatus: form.handoverStatus,
    handoverType: form.handoverType,
    handoverPersonId: form.handoverPersonId,
    handoverPersonName: form.handoverPersonName,
    approvalMode: form.approvalMode,
    approverIds: [...form.approverIds],
    approverNames: form.approverNames,
  };
  if (formDialog.mode === "add") {
    const id = `local_${Date.now()}`;
    allRows.value.unshift({
      id,
      ...payload,
      approvalResult: "pending",
    });
    proxy?.$modal?.msgSuccess?.("新增成功(本地模拟)");
  } else {
    const idx = allRows.value.findIndex((r) => r.id === form.id);
    const prev = idx !== -1 ? allRows.value[idx] : {};
    if (idx !== -1) {
      allRows.value[idx] = {
        ...prev,
        id: form.id,
        ...payload,
      };
    }
    proxy?.$modal?.msgSuccess?.("保存成功(本地模拟)");
  }
  formDialog.visible = false;
  handleQuery();
async function onSubmit() {
  const ok = await submitInstanceForm({ skipValidate: true });
  if (ok) ElMessage.success(isSubmitEdit.value ? "修改成功" : "提交成功");
}
onMounted(async () => {
  await loadUserPool();
  loadApproverTree();
  loadFlowUsers();
  await remoteSearchApplicant("");
  await initModuleList(searchForm);
});
</script>
@@ -797,14 +245,5 @@
.search_title {
  font-size: 14px;
  color: var(--el-text-color-regular);
}
.work-handover-form :deep(.el-row) {
  margin-bottom: 0;
}
.work-handover-form :deep(.el-form-item) {
  margin-bottom: 18px;
}
.work-handover-form-dialog :deep(.el-dialog__body) {
  padding-top: 12px;
}
</style>