| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123 |
- <template>
- <div class="ele-body" v-loading="loading">
- <el-card shadow="never">
- <el-form label-width="80px" class="ele-form-search" @keyup.enter.native="applyFilters" @submit.native.prevent>
- <el-row :gutter="15">
- <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
- <el-form-item label="离职单号">
- <el-input v-model.trim="draftFilters.resignationNo" clearable placeholder="请输入" />
- </el-form-item>
- </el-col>
- <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
- <el-form-item label="单据编码">
- <el-input v-model.trim="draftFilters.documentCode" clearable placeholder="请输入" />
- </el-form-item>
- </el-col>
- <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
- <el-form-item label="员工">
- <el-select v-model="draftFilters.userId" clearable filterable remote reserve-keyword
- :remote-method="searchFilterEmployees" :loading="filterEmployeeLoading" placeholder="可搜索员工姓名"
- style="width: 100%">
- <el-option v-for="item in filterEmployeeOptions" :key="item.id" :label="employeeLabel(item)"
- :value="item.id" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
- <el-form-item label="审批状态">
- <el-select v-model="draftFilters.approvalStatus" clearable placeholder="请选择" style="width: 100%">
- <el-option v-for="item in approvalStatusOptions" :key="item.value" :label="item.label"
- :value="item.value" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
- <el-form-item label="交接状态">
- <el-select v-model="draftFilters.handoverStatus" clearable placeholder="请选择" style="width: 100%">
- <el-option v-for="item in handoverStatusOptions" :key="item.value" :label="item.label"
- :value="item.value" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
- <el-form-item label="交接方式">
- <el-select v-model="draftFilters.handoverType" clearable placeholder="请选择" style="width: 100%">
- <el-option v-for="item in handoverTypeOptions" :key="item.value" :label="item.label"
- :value="item.value" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col v-bind="styleResponsive ? { lg: 12, md: 24 } : { span: 12 }">
- <el-form-item label="交接日期">
- <el-date-picker v-model="draftFilters.dateRange" type="daterange" value-format="yyyy-MM-dd"
- range-separator="至" start-placeholder="请选择" end-placeholder="请选择" style="width: 100%" />
- </el-form-item>
- </el-col>
- <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
- <div class="ele-form-actions">
- <el-button type="primary" icon="el-icon-search" class="ele-btn-icon" @click="applyFilters">
- 查询
- </el-button>
- <el-button type="primary" icon="el-icon-refresh" class="ele-btn-icon" @click="resetFilters">
- 重置
- </el-button>
- </div>
- </el-col>
- </el-row>
- </el-form>
- <ele-pro-table ref="table" row-key="id" :columns="columns" :datasource="fetchPage" :need-page="true"
- :page-size="20" :cache-key="$tableCacheKey('hr-resignation-handover')" class="resignation-table">
- <template v-slot:toolbar>
- <el-button v-if="$hasPermission('main:hrHandover:save')" type="primary" icon="el-icon-plus"
- class="ele-btn-icon" @click="openGenerate">
- 新建
- </el-button>
- <el-button v-if="$hasPermission('main:hrHandover:export')" icon="el-icon-download"
- class="ele-btn-icon" @click="exportData">
- 导出
- </el-button>
- </template>
- <!-- 点单据编码开详情,替代原操作列「查看」入口 -->
- <template v-slot:documentCode="{ row }">
- <el-link v-if="row.documentCode" type="primary" :underline="false" @click="openDetail(row)">
- {{ row.documentCode }}
- </el-link>
- <span v-else>-</span>
- </template>
- <template v-slot:person="{ row }">
- {{ row.employee || "-" }}
- </template>
- <template v-slot:handoverPerson="{ row }">
- {{ row.handoverUserName || "待指定" }}
- </template>
- <template v-slot:handoverDate="{ row }">
- {{ row.handoverDate || "-" }}
- </template>
- <template v-slot:handoverType="{ row }">
- <el-tag v-if="row.handoverTypeLabel" size="mini" type="info">
- {{ row.handoverTypeLabel }}
- </el-tag>
- <span v-else>-</span>
- </template>
- <!-- 审批状态:实心浅色 tag,表示流程审批结果 -->
- <template v-slot:approval="{ row }">
- <el-tag v-if="row.approvalStatusLabel" size="mini" effect="light" :type="approvalTagType(row.approvalStatus)">
- {{ row.approvalStatusLabel }}
- </el-tag>
- <span v-else>-</span>
- </template>
- <!-- 交接状态:描边 tag,表示交接办理进度,与审批状态的实心样式区分 -->
- <template v-slot:status="{ row }">
- <el-tag v-if="row.handoverStatusLabel" size="mini" effect="plain" class="handover-status-tag"
- :type="handoverStatusTagType(row.handoverStatus)">
- {{ row.handoverStatusLabel }}
- </el-tag>
- <span v-else>-</span>
- </template>
- <template v-slot:createTime="{ row }">
- <div v-if="row.createTime" class="datetime-cell">
- <span>{{ dateTimeDatePart(row.createTime) }}</span>
- <span>{{ dateTimeTimePart(row.createTime) }}</span>
- </div>
- <span v-else>-</span>
- </template>
- <template v-slot:updateTime="{ row }">
- <div v-if="row.updateTime" class="datetime-cell">
- <span>{{ dateTimeDatePart(row.updateTime) }}</span>
- <span>{{ dateTimeTimePart(row.updateTime) }}</span>
- </div>
- <span v-else>-</span>
- </template>
- <template v-slot:action="{ row }">
- <el-link v-if="canEdit(row) && $hasPermission('main:hrHandover:update')" type="primary"
- :underline="false" icon="el-icon-edit" @click="openEdit(row)">
- 编辑
- </el-link>
- <el-link v-if="canLaunch(row) && $hasPermission('main:hrHandover:submit')" type="primary"
- :underline="false" icon="el-icon-s-promotion" @click="launchApproval(row)">
- 提交
- </el-link>
- <el-link v-if="canShowApprovalRecord(row)" type="primary" :underline="false" icon="el-icon-tickets"
- @click="openApprovalRecord(row)">
- 审批记录
- </el-link>
- <el-link v-if="canDelete(row) && $hasPermission('main:hrHandover:delete')" type="primary"
- :underline="false" icon="el-icon-delete" :disabled="submitting" @click="removeRow(row)">
- 删除
- </el-link>
- <el-link v-if="canArchive(row) && $hasPermission('main:hrHandover:archive')" type="primary"
- :underline="false" icon="el-icon-archive" :disabled="submitting" @click="handleCommand('archive', row)">
- 归档
- </el-link>
- <!-- <el-link v-if="canForce(row)" type="danger" :underline="false" icon="el-icon-warning-outline"
- :disabled="submitting" @click="handleCommand('force', row)">
- 强制办结
- </el-link> -->
- </template>
- <template slot="empty">
- <el-empty :description="emptyText" />
- </template>
- </ele-pro-table>
- </el-card>
- <!-- 创建 / 编辑离职交接单:与离职申请页「发起交接单」共用同一抽屉组件 -->
- <HandoverGenerateDialog :visible.sync="generateVisible" :row="generateRow" @success="reloadTable" />
- <!-- 详情 -->
- <ele-modal title="离职交接单详情" :visible="detailVisible" width="900px" custom-class="resignation-detail-modal"
- :close-on-click-modal="false" append-to-body @update:visible="detailVisible = $event">
- <div v-if="detailRow" class="detail-modal-body" v-loading="detailLoading">
- <div class="detail-hero">
- <div class="detail-avatar">
- {{ (detailRow.employee || "?").slice(-1) }}
- </div>
- <div class="detail-hero-main">
- <h2>{{ detailRow.employee || detailRow.resignationNo || "-" }}</h2>
- <p>
- {{ detailRow.resignationNo || "-" }} ·
- {{ detailRow.department || "-" }}
- </p>
- </div>
- <span class="detail-status" :class="handoverClass(detailRow)">
- <i></i>{{ detailRow.handoverStatusLabel || "未开始" }}
- </span>
- </div>
- <div class="detail-stat-grid">
- <div>
- <span>单据编码</span>
- <strong>{{ detailRow.documentCode || "尚未生成" }}</strong>
- </div>
- <div>
- <span>交接方式</span>
- <strong>{{ detailRow.handoverTypeLabel || "-" }}</strong>
- </div>
- <div>
- <span>交接人</span>
- <strong>{{ detailRow.handoverUserName || "待指定" }}</strong>
- </div>
- <div>
- <span>交接日期</span>
- <strong>{{ detailRow.handoverDate || "-" }}</strong>
- </div>
- <div>
- <span>所属组织</span>
- <strong>{{ detailRow.deptName || detailRow.department || "-" }}</strong>
- </div>
- <div>
- <span>审批状态</span>
- <strong>{{ detailRow.approvalStatusLabel || "-" }}</strong>
- </div>
- <div>
- <span>交接进度</span>
- <strong>{{ detailRow.progress || 0 }}%</strong>
- </div>
- <div>
- <span>创建时间</span>
- <strong>{{ detailRow.createTime || "-" }}</strong>
- </div>
- </div>
- <div class="detail-section-card">
- <div class="detail-section-head">
- <h3>四类交接状态</h3>
- </div>
- <div class="category-status">
- <em>工作:{{ categoryLabel(detailRow.workStatus) }}</em>
- <em>资产:{{ categoryLabel(detailRow.assetStatus) }}</em>
- <em>财务:{{ categoryLabel(detailRow.financeStatus) }}</em>
- <em>HR:{{ categoryLabel(detailRow.hrStatus) }}</em>
- </div>
- </div>
- <div class="detail-section-card" style="margin-top: 14px">
- <div class="detail-section-head">
- <h3>资产归还清单</h3>
- <span>{{ detailReturnRows.length }} 项</span>
- </div>
- <el-table v-loading="detailAssetLoading" :data="detailReturnRows" size="mini" border class="detail-table"
- empty-text="暂无资产归还清单">
- <el-table-column type="index" label="序号" width="60" align="center" />
- <el-table-column label="物品名称" min-width="150">
- <template slot-scope="scope">
- {{ scope.row.itemName || "-" }}
- </template>
- </el-table-column>
- <el-table-column label="资产编码" min-width="140">
- <template slot-scope="scope">
- {{ scope.row.assetCode || "-" }}
- </template>
- </el-table-column>
- <el-table-column label="归还状态" width="110" align="center">
- <template slot-scope="scope">
- <el-tag v-if="!scope.row.inReturnList" size="mini" type="info">
- 未纳入清单
- </el-tag>
- <el-tag v-else size="mini" :type="scope.row.returned ? 'success' : 'warning'">
- {{
- scope.row.returnStatusLabel ||
- (scope.row.returnStatus === "RETURNED" ? "已归还" : "未归还")
- }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="归还时间" width="160">
- <template slot-scope="scope">
- {{ scope.row.returnedTime || "-" }}
- </template>
- </el-table-column>
- <el-table-column label="归还备注" min-width="150">
- <template slot-scope="scope">
- {{ scope.row.returnRemark || "-" }}
- </template>
- </el-table-column>
- </el-table>
- <p class="detail-description">
- {{
- detailAssetLoading
- ? "正在加载该员工入职物品领用记录…"
- : detailReturnRows.length
- ? `领用单号 ${detailReceiptCode || "-"};共 ${detailReturnRows.length} 项,其中 ${detailReturnRows.filter((row) =>
- row.inReturnList).length
- } 项已生成归还清单。`
- : "该员工暂无入职物品领用记录,且当前交接单没有归还清单"
- }}
- </p>
- </div>
- <div class="detail-section-card" style="margin-top: 14px">
- <div class="detail-section-head">
- <h3>交接事项</h3>
- <span>{{ (detailRow.items || []).length }} 项</span>
- </div>
- <el-progress :percentage="detailRow.progress || 0" :stroke-width="8" />
- <el-table :data="detailRow.items || []" size="mini" border class="detail-table" style="margin-top: 12px"
- empty-text="当前交接单没有交接事项">
- <el-table-column type="index" label="序号" width="60" align="center" />
- <el-table-column label="交接分类" width="100" align="center">
- <template slot-scope="scope">
- <el-tag size="mini" type="info">
- {{ scope.row.categoryLabel || scope.row.itemCategory || "-" }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="事项名称" min-width="140">
- <template slot-scope="scope">
- {{ scope.row.itemName || "-" }}
- </template>
- </el-table-column>
- <el-table-column label="事项说明" min-width="170">
- <template slot-scope="scope">
- {{ scope.row.itemContent || "-" }}
- </template>
- </el-table-column>
- <el-table-column label="接收人" width="110">
- <template slot-scope="scope">
- {{ receiverName(scope.row) || "-" }}
- </template>
- </el-table-column>
- <el-table-column label="状态" width="92" align="center">
- <template slot-scope="scope">
- <el-tag size="mini" :type="itemStatusTagType(scope.row.itemStatus)">
- {{ scope.row.itemStatusLabel || "待交接" }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="完成说明" min-width="140">
- <template slot-scope="scope">
- {{ scope.row.completionRemark || "-" }}
- </template>
- </el-table-column>
- <el-table-column label="操作" width="118" align="center">
- <template slot-scope="scope">
- <el-button v-if="canCompleteItem(scope.row) && $hasPermission('main:hrHandover:complete')"
- type="text" size="mini" :loading="acting" @click="completeItem(scope.row)">
- 完成
- </el-button>
- <el-button v-if="canRollbackItem(scope.row) && $hasPermission('main:hrHandover:rollback')"
- type="text" size="mini" :loading="acting" @click="rollbackItem(scope.row)">
- 回退
- </el-button>
- <span v-if="
- !canCompleteItem(scope.row) && !canRollbackItem(scope.row)
- " class="sub-text">
- -
- </span>
- </template>
- </el-table-column>
- </el-table>
- </div>
- <div v-if="detailRow.remark" class="detail-section-card" style="margin-top: 14px">
- <div class="detail-section-head">
- <h3>交接说明</h3>
- </div>
- <p class="detail-description">{{ detailRow.remark }}</p>
- </div>
- </div>
- <template v-slot:footer>
- <el-button @click="detailVisible = false">关闭</el-button>
- </template>
- </ele-modal>
- <!-- BPM 公共审批发起弹框 -->
- <processSubmitDialog :processSubmitDialogFlag.sync="processSubmitDialogFlag" v-if="processSubmitDialogFlag"
- ref="processSubmitDialogRef" @reload="onProcessSubmitReload" />
- <!-- 审批流程详情弹框 -->
- <BpmProcessDetailDialog ref="approvalRecordDialog" />
- </div>
- </template>
- <script>
- import {
- HANDOVER_APPROVAL_STATUS_OPTIONS,
- HANDOVER_APPROVAL_BUSINESS_KEY,
- HANDOVER_STATUS_LABELS,
- HANDOVER_STATUS_OPTIONS,
- HANDOVER_TYPE_OPTIONS,
- archiveResignation,
- canArchiveResignation,
- canCompleteHandoverItem,
- canDeleteHandover,
- canEditHandover,
- canForceCompleteResignation,
- canLaunchHandover,
- canRollbackHandoverItem,
- canShowHandoverApprovalRecord,
- completeResignationHandoverItem,
- composeAssetReturnRows,
- dateTimeDatePart,
- dateTimeTimePart,
- deleteHandover,
- forceCompleteResignation,
- getHandoverById,
- getHandoverPage,
- handoverDisplayStatus,
- rollbackHandoverItem,
- } from "@/api/hr/resignation";
- import {
- decorateReceiptDetailsForHandover,
- getLatestItemReceipt,
- } from "@/api/hr/onboardingItemReceipt";
- import { showRequestError } from "@/utils/message";
- import { getCurrentUser } from "@/utils/token-util";
- import {
- adaptEmployee,
- getEmployeeById,
- getUserPage,
- searchUsersByKeyword,
- } from "@/api/organization";
- import processSubmitDialog from "@/BIZComponents/processSubmitDialog/processSubmitDialog.vue";
- import BpmProcessDetailDialog from "@/views/bpm/processInstance/detailDialog.vue";
- import HandoverGenerateDialog from "@/views/hrManagement/resignationHandoverOrder/HandoverGenerateDialog.vue";
- import submitGuard from "@/mixins/submit-guard";
- /** 员工下拉统一结构:{ id, name, employeeNo, department } */
- function normalizeEmployee(raw) {
- const employee = adaptEmployee(raw);
- return {
- id: String(employee.id),
- name: employee.name || employee.employeeName || "",
- employeeNo: employee.employeeNo || employee.jobNumber || "",
- department: employee.deptName || employee.department || "",
- };
- }
- export default {
- name: "ResignationHandoverOrder",
- components: {
- processSubmitDialog,
- BpmProcessDetailDialog,
- HandoverGenerateDialog,
- },
- mixins: [submitGuard],
- data() {
- return {
- loading: false,
- acting: false,
- detailLoading: false,
- // 详情「资产归还清单」:最新领用物品与已生成归还清单合并后的行
- detailReturnRows: [],
- detailReceiptCode: "",
- detailAssetLoading: false,
- totalCount: 0,
- exportRows: [],
- draftFilters: {
- resignationNo: "",
- documentCode: "",
- userId: "",
- approvalStatus: "",
- handoverStatus: "",
- handoverType: "",
- dateRange: [],
- },
- filters: {
- resignationNo: "",
- documentCode: "",
- userId: "",
- approvalStatus: "",
- handoverStatus: "",
- handoverType: "",
- dateRange: [],
- },
- generateVisible: false,
- // 编辑中的交接单行;为空即创建(创建/编辑抽屉与离职申请页「发起交接单」共用组件)
- generateRow: null,
- // 详情接收人姓名回显用的员工缓存
- employeeOptions: [],
- filterEmployeeOptions: [],
- filterEmployeeLoading: false,
- detailVisible: false,
- detailRow: null,
- approvalStatusOptions: HANDOVER_APPROVAL_STATUS_OPTIONS,
- handoverStatusOptions: HANDOVER_STATUS_OPTIONS,
- handoverTypeOptions: HANDOVER_TYPE_OPTIONS,
- processSubmitDialogFlag: false,
- currentLaunchRow: null,
- };
- },
- computed: {
- styleResponsive() {
- return this.$store.state.theme?.styleResponsive !== false;
- },
- emptyText() {
- return "暂无交接单";
- },
- columns() {
- return [
- { type: "index", label: "序号", width: 60, align: "center" },
- {
- prop: "documentCode",
- label: "单据编码",
- minWidth: 250,
- align: "left",
- showOverflowTooltip: true,
- slot: "documentCode",
- },
- {
- prop: "resignationNo",
- label: "离职单号",
- minWidth: 250,
- align: "left",
- showOverflowTooltip: true,
- formatter: (_r, _c, value) => value || "-",
- },
- {
- prop: "employee",
- label: "员工",
- minWidth: 90,
- align: "left",
- slot: "person",
- },
- {
- /* 部门 / 机构只认交接单接口原始字段 groupName、deptName(adaptHandover ...raw 透传),
- 没返回就显示 -,不取员工档案等其他来源顶替 */
- prop: "groupName",
- label: "所属机构",
- minWidth: 180,
- align: "left",
- showOverflowTooltip: true,
- formatter: (_r, _c, value) => value || "-",
- },
- {
- prop: "deptName",
- label: "所属部门",
- minWidth: 150,
- align: "left",
- showOverflowTooltip: true,
- formatter: (_r, _c, value) => value || "-",
- },
- {
- prop: "handoverUserName",
- label: "交接人",
- width: 100,
- align: "left",
- slot: "handoverPerson",
- },
- {
- prop: "handoverDate",
- label: "交接日期",
- width: 110,
- align: "left",
- slot: "handoverDate",
- },
- {
- prop: "handoverType",
- label: "交接方式",
- width: 100,
- align: "left",
- slot: "handoverType",
- },
- {
- prop: "approvalStatus",
- label: "审批状态",
- width: 100,
- align: "left",
- slot: "approval",
- },
- {
- prop: "handoverStatus",
- label: "交接状态",
- width: 100,
- align: "left",
- slot: "status",
- },
- {
- prop: "createUserName",
- label: "创建人",
- width: 90,
- align: "left",
- formatter: (_r, _c, v) => v || "-",
- },
- {
- prop: "createTime",
- label: "创建时间",
- width: 112,
- align: "left",
- slot: "createTime",
- },
- {
- prop: "updateUserName",
- label: "修改人",
- width: 90,
- align: "left",
- formatter: (_r, _c, v) => v || "-",
- },
- {
- prop: "updateTime",
- label: "修改时间",
- width: 112,
- align: "left",
- slot: "updateTime",
- },
- {
- columnKey: "action",
- label: "操作",
- minWidth: 300,
- fixed: "right",
- align: "left",
- slot: "action",
- },
- ];
- },
- },
- methods: {
- canArchive: canArchiveResignation,
- canForce: canForceCompleteResignation,
- canCompleteItem: canCompleteHandoverItem,
- canRollbackItem: canRollbackHandoverItem,
- canDelete: canDeleteHandover,
- canEdit: canEditHandover,
- canLaunch: canLaunchHandover,
- canShowApprovalRecord: canShowHandoverApprovalRecord,
- dateTimeDatePart,
- dateTimeTimePart,
- handoverClass: handoverDisplayStatus,
- /** 员工下拉 label:姓名 · 工号 · 部门 */
- employeeLabel(item = {}) {
- const name = item.name || item.employeeName || "";
- const empNo = item.employeeNo ? " · " + item.employeeNo : "";
- const dept = item.department ? " · " + item.department : "";
- return name + empNo + dept;
- },
- categoryLabel(value) {
- return (
- HANDOVER_STATUS_LABELS[String(value || "").toUpperCase()] ||
- value ||
- "未开始"
- );
- },
- /** 交接明细状态 tag 颜色 */
- itemStatusTagType(code) {
- const key = String(code || "").toUpperCase();
- if (["COMPLETED", "DONE", "FINISHED"].includes(key)) return "success";
- if (["IN_PROGRESS", "PROCESSING"].includes(key)) return "warning";
- return "info";
- },
- /** 总体交接状态 tag 颜色 */
- handoverStatusTagType(code) {
- const key = String(code || "").toUpperCase();
- if (["COMPLETED", "FORCE_COMPLETED"].includes(key)) return "success";
- if (["IN_PROGRESS", "PROCESSING"].includes(key)) return "warning";
- if (["PENDING", "NOT_STARTED"].includes(key)) return "";
- return "info";
- },
- /** 审批状态 tag 颜色:已通过 success / 驳回、撤回、取消 danger / 审批中 warning / 草稿 info */
- approvalTagType(code) {
- const key = String(code || "").toLowerCase();
- if (["approved", "passed"].includes(key)) return "success";
- if (["rejected", "cancelled", "withdrawn"].includes(key)) return "danger";
- if (["pending", "approving", "waiting", "reviewing"].includes(key)) {
- return "warning";
- }
- return "info";
- },
- /** 详情里接收人姓名:优先取已加载的员工缓存 */
- receiverName(item = {}) {
- if (!item.receiverUserId) return "";
- const matched = this.employeeOptions.find(
- (employee) => String(employee.id) === String(item.receiverUserId),
- );
- return matched?.name || "";
- },
- queryParams(pageNum, size, extra = {}) {
- const range = this.filters.dateRange || [];
- return {
- pageNum,
- size,
- offset: (Number(pageNum) - 1) * Number(size),
- resignationNo: this.filters.resignationNo || undefined,
- documentCode: this.filters.documentCode || undefined,
- userId: this.filters.userId || undefined,
- approvalStatus: this.filters.approvalStatus || undefined,
- handoverStatus: this.filters.handoverStatus || undefined,
- handoverType: this.filters.handoverType || undefined,
- handoverDateStart: range[0],
- handoverDateEnd: range[1],
- sortName: "createTime",
- orderBy: "descending",
- ...extra,
- };
- },
- applyFilters() {
- this.filters = {
- resignationNo: this.draftFilters.resignationNo,
- documentCode: this.draftFilters.documentCode,
- userId: this.draftFilters.userId,
- approvalStatus: this.draftFilters.approvalStatus,
- handoverStatus: this.draftFilters.handoverStatus,
- handoverType: this.draftFilters.handoverType,
- dateRange: this.draftFilters.dateRange
- ? [...this.draftFilters.dateRange]
- : [],
- };
- this.reloadTable();
- },
- resetFilters() {
- const empty = {
- resignationNo: "",
- documentCode: "",
- userId: "",
- approvalStatus: "",
- handoverStatus: "",
- handoverType: "",
- dateRange: [],
- };
- this.draftFilters = { ...empty };
- this.filters = { ...empty };
- this.reloadTable();
- },
- reloadTable() {
- this.$nextTick(() => this.$refs.table?.reload({ page: 1 }));
- },
- refresh() {
- this.reloadTable();
- },
- async fetchPage({ page, limit }) {
- try {
- const pageNum = Number(page) || 1;
- const size = Number(limit) || 20;
- const data = await getHandoverPage(this.queryParams(pageNum, size));
- this.totalCount = Number(data?.count ?? 0);
- this.exportRows = data?.list || [];
- return { list: data?.list || [], count: this.totalCount };
- } catch (error) {
- showRequestError(error, "离职交接单列表加载失败");
- this.totalCount = 0;
- this.exportRows = [];
- return { list: [], count: 0 };
- }
- },
- /** 列表筛选用员工下拉(独立缓存,避免与弹框混用) */
- async searchFilterEmployees(keyword = "") {
- const text = String(keyword || "").trim();
- this.filterEmployeeLoading = true;
- try {
- const data = text
- ? await searchUsersByKeyword(text, { pageNum: 1, size: 50 })
- : await getUserPage({ pageNum: 1, size: 50 });
- const list = (Array.isArray(data?.list) ? data.list : []).map(
- normalizeEmployee,
- );
- const merged = new Map(
- this.filterEmployeeOptions.map((item) => [String(item.id), item]),
- );
- if (text) {
- const selectedId = String(this.draftFilters.userId || "");
- [...merged.keys()].forEach((key) => {
- if (key !== selectedId) merged.delete(key);
- });
- if (!list.length) {
- this.$message.warning(`未搜索到「${text}」匹配的员工`);
- }
- }
- list.forEach((item) => {
- if (item?.id) merged.set(String(item.id), item);
- });
- this.filterEmployeeOptions = [...merged.values()];
- } catch (error) {
- /* 忽略,保留已有选项 */
- } finally {
- this.filterEmployeeLoading = false;
- }
- },
- mergeEmployees(list = []) {
- const merged = new Map(
- this.employeeOptions.map((item) => [String(item.id), item]),
- );
- list.forEach((item) => {
- if (item?.id) merged.set(String(item.id), item);
- });
- this.employeeOptions = [...merged.values()];
- },
- /** 详情/编辑回显时补齐缺失的员工姓名(下拉需要 option 才能显示 label) */
- async ensureEmployees(userIds = []) {
- const missing = [...new Set(userIds.map(String))].filter(
- (id) =>
- id &&
- id !== "undefined" &&
- !this.employeeOptions.some((item) => String(item.id) === id),
- );
- if (!missing.length) return;
- const loaded = await Promise.all(
- missing.map(async (id) => {
- try {
- return normalizeEmployee(await getEmployeeById(id));
- } catch (error) {
- return null;
- }
- }),
- );
- this.mergeEmployees(loaded.filter(Boolean));
- },
- /** 创建:打开与离职申请页「发起交接单」共用的抽屉组件,由组件内部加载可选离职申请 */
- openGenerate() {
- this.generateRow = null;
- this.generateVisible = true;
- },
- /** 编辑:打开同一抽屉组件的编辑态,详情回填与保存均由组件承担 */
- openEdit(row) {
- if (!row?.id) return;
- this.generateRow = row;
- this.generateVisible = true;
- },
- async openDetail(row) {
- if (!row?.id) {
- this.$message.warning("缺少交接单主键,无法查看详情");
- return;
- }
- this.detailVisible = true;
- this.detailLoading = true;
- this.detailRow = row;
- this.detailReturnRows = [];
- this.detailReceiptCode = "";
- try {
- const detail = await getHandoverById(row.id);
- this.detailRow = detail;
- if (detail?.handoverUserId) this.ensureEmployees([detail.handoverUserId]);
- const receiverIds = (detail?.items || [])
- .map((item) => item.receiverUserId)
- .filter(Boolean);
- if (receiverIds.length) this.ensureEmployees(receiverIds);
- await this.loadDetailReturnRows(detail);
- } catch (error) {
- showRequestError(error, "交接单详情加载失败");
- } finally {
- this.detailLoading = false;
- }
- },
- /**
- * 详情「资产归还清单」与编辑态口径一致:
- * 详情返回的归还清单 + 该员工最新入职领用物品合并(GET /main/onboarding-item-receipts/latest/{employeeId}),
- * 保证清单尚未生成时也能看到领用物品行(标记「未纳入清单」)
- */
- async loadDetailReturnRows(detail) {
- const returnList = detail?.assetReturns || [];
- this.detailReturnRows = composeAssetReturnRows(returnList);
- this.detailReceiptCode = "";
- const employeeId = detail?.userId;
- if (!employeeId) return;
- this.detailAssetLoading = true;
- try {
- const receipt = await getLatestItemReceipt(employeeId);
- this.detailReceiptCode = receipt?.documentCode || "";
- this.detailReturnRows = composeAssetReturnRows(
- returnList,
- decorateReceiptDetailsForHandover(receipt),
- );
- } catch (error) {
- showRequestError(error, "入职物品领用记录加载失败");
- } finally {
- this.detailAssetLoading = false;
- }
- },
- /** 打开 BPM 提交审批弹框 */
- launchApproval(row) {
- if (!row?.id) {
- this.$message.warning("请先创建交接单后再提交审批");
- return;
- }
- if (!this.canLaunch(row)) {
- this.$message.warning("当前交接单不可提交审批");
- return;
- }
- this.currentLaunchRow = row;
- this.processSubmitDialogFlag = true;
- this.$nextTick(() => {
- const currentUser = getCurrentUser() || {};
- this.$refs.processSubmitDialogRef?.init({
- businessId: row.id,
- businessKey: HANDOVER_APPROVAL_BUSINESS_KEY,
- formCreateUserId:
- row.createUserId || currentUser.id || currentUser.userId || "",
- variables: {
- businessCode: row.documentCode || row.resignationNo || "",
- businessName: row.employee || row.userName || "",
- businessType: "离职交接单",
- groupId: row.groupId || row.deptId || "",
- groupName: row.deptName || row.company || "",
- },
- });
- });
- },
- /** 打开审批流程详情弹框:优先列表 row 上的实例 id,没有则拉详情 */
- async openApprovalRecord(row) {
- let id = row?.processInstanceId || row?.approvalInstanceId;
- if (!id && row?.id) {
- try {
- const detail = await getHandoverById(row.id);
- id = detail?.processInstanceId || detail?.approvalInstanceId;
- } catch (_) {
- // 详情拿不到也没关系,后面统一提示
- }
- }
- if (!id) {
- this.$message.warning("暂无审批记录(后端未返回流程实例 id)");
- return;
- }
- this.$refs.approvalRecordDialog?.open({ processInstanceId: id });
- },
- /** BPM 提交审批弹框回调:刷新列表 */
- onProcessSubmitReload() {
- this.currentLaunchRow = null;
- this.refresh();
- },
- async promptReason(title, required) {
- try {
- const { value } = await this.$prompt(
- required ? "请填写原因(必填)" : "可填写完成说明,也可留空",
- title,
- {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- inputPlaceholder: "原因 / 完成说明",
- inputValidator: required
- ? (val) => (String(val || "").trim() ? true : "请填写原因")
- : undefined,
- },
- );
- return String(value || "").trim();
- } catch (error) {
- return null;
- }
- },
- async handleCommand(command, row) {
- if (command === "archive") {
- if (!row?.resignationId) {
- this.$message.warning("缺少离职单主键,无法归档");
- return;
- }
- await this.runSubmit(
- () =>
- this.runAction("归档离职单", () =>
- archiveResignation(row.resignationId),
- ),
- { errorMessage: "归档离职单失败" },
- );
- return;
- }
- if (command === "force") {
- if (!row?.resignationId) {
- this.$message.warning("缺少离职单主键,无法强制办结");
- return;
- }
- await this.runSubmit(
- async () => {
- const reason = await this.promptReason("强制办结离职单", true);
- if (!reason) return;
- await this.runAction("强制办结离职单", () =>
- forceCompleteResignation(row.resignationId, { reason }),
- );
- },
- { errorMessage: "强制办结离职单失败" },
- );
- }
- },
- async removeRow(row) {
- await this.runSubmit(
- async () => {
- await this.$confirm(
- "确认删除该交接单?删除后离职单将恢复为未创建交接。",
- "删除交接单",
- { type: "warning" },
- );
- await this.runAction("删除离职交接单", () => deleteHandover(row.id));
- },
- { errorMessage: "删除离职交接单失败" },
- );
- },
- async runAction(name, task) {
- this.acting = true;
- try {
- await task();
- this.$message.success(`${name}成功`);
- this.reloadTable();
- if (this.detailVisible && this.detailRow?.id) {
- try {
- const detail = await getHandoverById(this.detailRow.id);
- this.detailRow = detail;
- await this.loadDetailReturnRows(detail);
- } catch (error) {
- this.detailVisible = false;
- }
- }
- } catch (error) {
- showRequestError(error, `${name}失败`);
- } finally {
- this.acting = false;
- }
- },
- async completeItem(item) {
- if (!this.detailRow?.resignationId || !item?.id) {
- this.$message.warning("缺少离职单或事项主键,无法完成");
- return;
- }
- const reason = await this.promptReason("完成交接事项", false);
- if (reason === null) return;
- await this.runAction("完成交接事项", () =>
- completeResignationHandoverItem(this.detailRow.resignationId, item.id, {
- reason,
- }),
- );
- },
- async rollbackItem(item) {
- if (!this.detailRow?.id || !item?.id) return;
- const reason = await this.promptReason("回退交接事项", true);
- if (!reason) return;
- await this.runAction("回退交接事项", () =>
- rollbackHandoverItem(this.detailRow.id, item.id, { reason }),
- );
- },
- exportData() {
- const header = [
- "单据编码",
- "离职单号",
- "员工",
- "所属部门",
- "交接人",
- "交接日期",
- "交接方式",
- "审批状态",
- "交接状态",
- "创建人",
- "创建时间",
- "修改人",
- "修改时间",
- ].join(",");
- const body = this.exportRows
- .map((row) =>
- [
- row.documentCode,
- row.resignationNo,
- row.employee,
- row.department,
- row.handoverUserName,
- row.handoverDate,
- row.handoverTypeLabel,
- row.approvalStatusLabel,
- row.handoverStatusLabel,
- row.createUserName,
- row.createTime,
- row.updateUserName,
- row.updateTime,
- ]
- .map((value) => JSON.stringify(value ?? ""))
- .join(","),
- )
- .join("\n");
- const blob = new Blob([`${header}\n${body}`], {
- type: "text/csv;charset=utf-8;",
- });
- const link = document.createElement("a");
- link.href = URL.createObjectURL(blob);
- link.download = "离职交接单.csv";
- link.click();
- URL.revokeObjectURL(link.href);
- },
- },
- };
- </script>
- <style lang="scss" src="@/styles/views/resignation/index.scss"></style>
- <style lang="scss">
- /* 创建 / 编辑交接单抽屉:抽屉 append-to-body,作用域失效,用 custom-class 穿透。
- 通用的 .handover-drawer 外壳样式在 @/styles/views/resignation/index.scss 中共享。 */
- .handover-edit-drawer .handover-items {
- width: 100%;
- }
- .handover-edit-drawer .handover-item-table {
- width: 100%;
- }
- .handover-edit-drawer .handover-item-table .el-form-item {
- margin-bottom: 0;
- }
- .handover-edit-drawer .apply-form .el-form-item {
- margin-bottom: 14px;
- }
- .handover-edit-drawer .form-section .el-form-item:last-child {
- margin-bottom: 0;
- }
- /* 详情弹框内的明细表格 */
- .resignation-detail-modal .detail-table th.el-table__cell {
- padding: 7px 0;
- background: #f7f9fc;
- }
- .resignation-detail-modal .detail-table td.el-table__cell {
- padding: 7px 0;
- }
- .resignation-detail-modal .detail-table {
- font-size: 14px;
- }
- /* .category-status 原本挂在 .resignation-page 下,详情弹框 apped-to-body 后需要补一份 */
- .resignation-detail-modal .category-status {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- }
- .resignation-detail-modal .category-status em {
- padding: 3px 9px;
- border-radius: 8px;
- color: #65758b;
- background: #f3f6fb;
- font-size: 14px;
- font-style: normal;
- }
- /* 交接状态 tag:描边 + 胶囊圆角,与审批状态的实心方角 tag 在填充和形状上双重区分 */
- .handover-status-tag {
- border-radius: 10px;
- font-weight: 600;
- }
- </style>
|