|
|
@@ -0,0 +1,342 @@
|
|
|
+<template>
|
|
|
+ <main class="cm-detail-host" v-loading="loading">
|
|
|
+ <div v-if="detailRow" class="cm-create-page cm-detail-page">
|
|
|
+ <div class="cm-create-employee-card cm-detail-employee">
|
|
|
+ <span class="cm-employee-avatar">{{
|
|
|
+ detailRow.employeeName ? detailRow.employeeName.slice(0, 1) : '?'
|
|
|
+ }}</span>
|
|
|
+ <div class="cm-employee-info">
|
|
|
+ <b
|
|
|
+ >员工姓名:{{ detailRow.employeeName
|
|
|
+ }}<span class="cm-employee-no"
|
|
|
+ >员工工号:{{ detailRow.employeeNo }}</span
|
|
|
+ ></b
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div class="cm-detail-status">
|
|
|
+ <small>合同状态</small>
|
|
|
+ <span class="cm-status" :class="statusClass(detailRow.status)">{{
|
|
|
+ detailRow.status
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="cm-create-section">
|
|
|
+ <div class="cm-create-section-title">合同基本信息</div>
|
|
|
+ <div class="cm-create-grid">
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">合同编号:</span
|
|
|
+ ><span class="cm-compare-value">{{ detailRow.contractNo }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">合同类型:</span
|
|
|
+ ><span class="cm-compare-value">{{ detailRow.contractType }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">所属机构:</span
|
|
|
+ ><span class="cm-compare-value">{{ detailRow.legalEntity }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">所属部门:</span
|
|
|
+ ><span class="cm-compare-value">{{ detailRow.department }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">岗位名称:</span
|
|
|
+ ><span class="cm-compare-value">{{ detailRow.positionName }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">岗位层级:</span
|
|
|
+ ><span class="cm-compare-value">{{
|
|
|
+ detailRow.level || '-'
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">直属上级:</span
|
|
|
+ ><span class="cm-compare-value">{{
|
|
|
+ detailRow.manager || '-'
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="cm-create-section">
|
|
|
+ <div class="cm-create-section-title">合同期限与薪资</div>
|
|
|
+ <div class="cm-create-grid">
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">合同起始:</span
|
|
|
+ ><span class="cm-compare-value">{{
|
|
|
+ detailRow.startDate || '-'
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">合同终止:</span
|
|
|
+ ><span class="cm-compare-value">{{
|
|
|
+ detailRow.endDate || '长期'
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">月薪资:</span
|
|
|
+ ><span class="cm-compare-value is-highlight">{{
|
|
|
+ detailRow.salary
|
|
|
+ ? '¥ ' + Number(detailRow.salary).toLocaleString() + ' 元/月'
|
|
|
+ : '待定'
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">试用期至:</span
|
|
|
+ ><span class="cm-compare-value">{{
|
|
|
+ detailRow.probationEnd || '无试用期'
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="cm-create-section">
|
|
|
+ <div class="cm-create-section-title">签署信息</div>
|
|
|
+ <div class="cm-create-grid">
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">签署方式:</span
|
|
|
+ ><span class="cm-compare-value">{{
|
|
|
+ detailRow.signMethod || '-'
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">签署状态:</span
|
|
|
+ ><span class="cm-compare-value">{{
|
|
|
+ detailRow.signingStatusLabel || '-'
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-if="detailRow.flowSignStatus"
|
|
|
+ class="cm-compare-item"
|
|
|
+ >
|
|
|
+ <span class="cm-compare-label">流程节点电子签章:</span
|
|
|
+ ><span class="cm-compare-value">{{
|
|
|
+ detailRow.flowSignStatusLabel || '-'
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="cm-compare-item is-span-2"
|
|
|
+ v-if="detailRow.flowSignNodes && detailRow.flowSignNodes.length"
|
|
|
+ >
|
|
|
+ <span class="cm-compare-label">签章节点:</span>
|
|
|
+ <span class="cm-compare-value">
|
|
|
+ {{
|
|
|
+ detailRow.flowSignNodes
|
|
|
+ .map(
|
|
|
+ (n) =>
|
|
|
+ (n.name || n.nodeName || n.taskName || '节点') +
|
|
|
+ '(' +
|
|
|
+ (n.statusLabel || '-') +
|
|
|
+ ')'
|
|
|
+ )
|
|
|
+ .join('、')
|
|
|
+ }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">合同附件:</span
|
|
|
+ ><span class="cm-compare-value">
|
|
|
+ <fileMain
|
|
|
+ v-if="
|
|
|
+ detailRow.attachmentFileIds &&
|
|
|
+ detailRow.attachmentFileIds.length
|
|
|
+ "
|
|
|
+ :value="detailRow.attachmentFileIds"
|
|
|
+ type="view"
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span v-else class="cm-detail-muted">暂无附件</span>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">签订日期:</span
|
|
|
+ ><span class="cm-compare-value">{{
|
|
|
+ detailRow.signDate || detailRow.createdAt || '-'
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="cm-compare-item">
|
|
|
+ <span class="cm-compare-label">创建时间:</span
|
|
|
+ ><span class="cm-compare-value">{{
|
|
|
+ detailRow.createdAt || '-'
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-if="detailRow.remark" class="cm-detail-remark">
|
|
|
+ <strong>备注:</strong>{{ detailRow.remark }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-empty v-else-if="!loading" description="暂无合同详情" />
|
|
|
+ </main>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import {
|
|
|
+ getContractById,
|
|
|
+ adaptContractFlowSignNodes,
|
|
|
+ contractSigningStatusLabel,
|
|
|
+ contractStatusLabel,
|
|
|
+ contractFlowSignStatusLabel,
|
|
|
+ pickFlowSignStatus
|
|
|
+} from '@/api/hr';
|
|
|
+
|
|
|
+const CONTRACT_TYPES = [
|
|
|
+ { value: 'FIXED', label: '固定期限劳动合同' },
|
|
|
+ { value: 'NON_FIXED', label: '无固定期限劳动合同' },
|
|
|
+ { value: 'TASK', label: '以完成一定任务为期限' },
|
|
|
+ { value: 'DISPATCH', label: '劳务派遣合同' },
|
|
|
+ { value: 'INTERN', label: '实习协议' },
|
|
|
+ { value: 'CONFIDENTIAL', label: '保密协议' },
|
|
|
+ { value: 'NON_COMPETE', label: '竞业限制协议' },
|
|
|
+ { value: 'CHANGE', label: '合同变更协议' },
|
|
|
+ { value: 'TERMINATION', label: '合同终止协议' }
|
|
|
+];
|
|
|
+
|
|
|
+function formatDate(value) {
|
|
|
+ if (!value) return '';
|
|
|
+ return String(value).slice(0, 10);
|
|
|
+}
|
|
|
+
|
|
|
+function formatDateTime(value) {
|
|
|
+ if (!value) return '';
|
|
|
+ return String(value).replace('T', ' ').slice(0, 16);
|
|
|
+}
|
|
|
+
|
|
|
+function typeLabel(value) {
|
|
|
+ return CONTRACT_TYPES.find((t) => t.value === value)?.label || value || '';
|
|
|
+}
|
|
|
+
|
|
|
+function signMethodLabel(value) {
|
|
|
+ if (value === 'ONLINE') return '线上';
|
|
|
+ if (value === 'OFFLINE') return '线下';
|
|
|
+ return value || '';
|
|
|
+}
|
|
|
+
|
|
|
+function collectAttachmentFileIds(files) {
|
|
|
+ const list = (Array.isArray(files) ? files : []).filter(Boolean);
|
|
|
+ const attach = list.filter((item) =>
|
|
|
+ ['ATTACHMENT', 'SCAN', 'ARCHIVE'].includes(item.filePurpose)
|
|
|
+ );
|
|
|
+ const source = attach.length ? attach : list;
|
|
|
+ return source
|
|
|
+ .map((item) => item.fileId || item.id)
|
|
|
+ .filter((id) => id != null && id !== '');
|
|
|
+}
|
|
|
+
|
|
|
+function adaptContract(item = {}) {
|
|
|
+ const files = item.files || [];
|
|
|
+ const startDate = formatDate(item.startDate);
|
|
|
+ const endDate = formatDate(item.endDate);
|
|
|
+ const termText =
|
|
|
+ item.contractTerm ||
|
|
|
+ (item.contractTermMonths != null && item.contractTermMonths !== ''
|
|
|
+ ? `${item.contractTermMonths}个月`
|
|
|
+ : '');
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ employeeNo: item.employeeCode || item.employeeNo || '',
|
|
|
+ employeeCode: item.employeeCode || item.employeeNo || '',
|
|
|
+ legalEntity: item.legalEntityName || '',
|
|
|
+ department: item.departmentName || '',
|
|
|
+ contractTypeCode: item.contractType || '',
|
|
|
+ contractType: typeLabel(item.contractType),
|
|
|
+ signMethod: signMethodLabel(item.signingMethod),
|
|
|
+ signingStatus: item.signingStatus || '',
|
|
|
+ signingStatusLabel: contractSigningStatusLabel(item.signingStatus),
|
|
|
+ flowSignStatus: pickFlowSignStatus(item),
|
|
|
+ flowSignStatusLabel: contractFlowSignStatusLabel(pickFlowSignStatus(item)),
|
|
|
+ flowSignNodes: adaptContractFlowSignNodes(item),
|
|
|
+ status: contractStatusLabel(item.contractStatus),
|
|
|
+ statusCode: item.contractStatus || '',
|
|
|
+ salary: item.salaryStandard,
|
|
|
+ signDate: formatDate(item.signingDate),
|
|
|
+ startDate,
|
|
|
+ endDate,
|
|
|
+ contractTerm: termText,
|
|
|
+ remainingDays:
|
|
|
+ item.remainingDays === null ||
|
|
|
+ item.remainingDays === undefined ||
|
|
|
+ item.remainingDays === ''
|
|
|
+ ? null
|
|
|
+ : Number(item.remainingDays),
|
|
|
+ probationMonths: item.probationMonths,
|
|
|
+ createdAt: formatDateTime(item.createTime),
|
|
|
+ probationEnd: formatDate(item.probationEndDate),
|
|
|
+ attachmentFileIds: collectAttachmentFileIds(files),
|
|
|
+ level: item.positionLevel || item.level || '',
|
|
|
+ manager: item.managerName || item.manager || '',
|
|
|
+ archived:
|
|
|
+ item.contractStatus === 'ARCHIVED' ||
|
|
|
+ item.archived === true ||
|
|
|
+ item.archived === 1
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'HrContractDetail',
|
|
|
+ props: {
|
|
|
+ businessId: {
|
|
|
+ default: ''
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ loading: false,
|
|
|
+ detailRow: null
|
|
|
+ };
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ businessId: {
|
|
|
+ immediate: true,
|
|
|
+ handler(id) {
|
|
|
+ if (id) this.getInfo(id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ async getInfo(id) {
|
|
|
+ if (!id) return;
|
|
|
+ this.loading = true;
|
|
|
+ try {
|
|
|
+ const detail = await getContractById(id);
|
|
|
+ this.detailRow = adaptContract(detail || {});
|
|
|
+ } catch (e) {
|
|
|
+ this.detailRow = null;
|
|
|
+ this.$message.error(e.message || '合同详情加载失败');
|
|
|
+ } finally {
|
|
|
+ this.loading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ statusClass(s) {
|
|
|
+ const label = contractStatusLabel(s, s);
|
|
|
+ const map = {
|
|
|
+ 草稿: 'draft',
|
|
|
+ 审批中: 'pending',
|
|
|
+ 待签署: 'pending',
|
|
|
+ 待签: 'pending',
|
|
|
+ 签署中: 'signing',
|
|
|
+ 已签待生效: 'pending',
|
|
|
+ 待生效: 'pending',
|
|
|
+ 生效: 'active',
|
|
|
+ 生效中: 'active',
|
|
|
+ 临期: 'expiring',
|
|
|
+ 即将到期: 'expiring',
|
|
|
+ 已变更: 'changed',
|
|
|
+ 已续签: 'active',
|
|
|
+ 已解除: 'terminated',
|
|
|
+ 已终止: 'terminated',
|
|
|
+ 已作废: 'expired',
|
|
|
+ 已取消: 'expired',
|
|
|
+ 已归档: 'archived',
|
|
|
+ 试用期: 'trial',
|
|
|
+ 已到期: 'expired',
|
|
|
+ 已过期: 'expired'
|
|
|
+ };
|
|
|
+ return map[label] || map[s] || 'pending';
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped src="@/styles/views/hrContractManagement/index.scss"></style>
|