hrRemoveContractDetail.vue 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <template>
  2. <main class="cm-detail-host" v-loading="loading">
  3. <div v-if="detailRow" class="cm-create-page cm-detail-page">
  4. <div class="cm-create-employee-card cm-detail-employee">
  5. <span class="cm-employee-avatar">{{ avatarText }}</span>
  6. <div class="cm-employee-info">
  7. <b
  8. >员工姓名:{{ detailRow.employeeName || '-'
  9. }}<span class="cm-employee-no"
  10. >合同编号:{{ detailRow.contractNo || '-' }}</span
  11. ></b
  12. >
  13. </div>
  14. </div>
  15. <div class="cm-create-section">
  16. <div class="cm-create-section-title">解除 / 终止信息</div>
  17. <div class="cm-create-grid">
  18. <div class="cm-compare-item">
  19. <span class="cm-compare-label">业务类型:</span
  20. ><span class="cm-compare-value">{{
  21. detailRow.terminationTypeText
  22. }}</span>
  23. </div>
  24. <div class="cm-compare-item">
  25. <span class="cm-compare-label">签署方式:</span
  26. ><span class="cm-compare-value">{{
  27. detailRow.signingMethodText
  28. }}</span>
  29. </div>
  30. <div class="cm-compare-item">
  31. <span class="cm-compare-label">原因编码:</span
  32. ><span class="cm-compare-value">{{ detailRow.reasonCodeText }}</span>
  33. </div>
  34. <div class="cm-compare-item">
  35. <span class="cm-compare-label">生效日期:</span
  36. ><span class="cm-compare-value">{{
  37. detailRow.effectiveDate || '-'
  38. }}</span>
  39. </div>
  40. <div class="cm-compare-item">
  41. <span class="cm-compare-label">最后工作日期:</span
  42. ><span class="cm-compare-value">{{
  43. detailRow.lastWorkingDate || '-'
  44. }}</span>
  45. </div>
  46. <div class="cm-compare-item">
  47. <span class="cm-compare-label">离职证明附件:</span
  48. ><span class="cm-compare-value">
  49. <fileMain
  50. v-if="
  51. detailRow.attachmentFileIds &&
  52. detailRow.attachmentFileIds.length
  53. "
  54. :value="detailRow.attachmentFileIds"
  55. type="view"
  56. size="small"
  57. />
  58. <span v-else class="cm-detail-muted">暂无附件</span>
  59. </span>
  60. </div>
  61. </div>
  62. <div v-if="detailRow.reason" class="cm-detail-remark">
  63. <strong>原因说明:</strong>{{ detailRow.reason }}
  64. </div>
  65. </div>
  66. </div>
  67. <el-empty v-else-if="!loading" description="暂无解除 / 终止详情" />
  68. </main>
  69. </template>
  70. <script>
  71. import { getContractById } from '@/api/hr';
  72. import { getByCode } from '@/api/system/dictionary-data';
  73. import { parseAttachmentIds } from '@/utils/attachment';
  74. /** 业务类型:接口参数为 TERMINATE / END(见劳动合同解除终止接口) */
  75. const TERMINATION_TYPE_LABELS = {
  76. TERMINATE: '解除',
  77. END: '终止'
  78. };
  79. /** 签署方式:ONLINE-审批待办附件盖章签字;OFFLINE-线下签署 */
  80. const SIGNING_METHOD_LABELS = {
  81. ONLINE: '线上',
  82. OFFLINE: '线下'
  83. };
  84. /** 原因编码兜底项:字典 hr_contract_terminate 拉取失败时使用 */
  85. const TERMINATE_REASON_FALLBACK = [
  86. { value: 'MUTUAL', label: '协商一致解除' },
  87. { value: 'EMPLOYEE', label: '员工提出解除' },
  88. { value: 'MISCONDUCT', label: '严重违纪' },
  89. { value: 'CONTRACT_EXPIRE', label: '合同到期终止' },
  90. { value: 'OTHER', label: '其他' }
  91. ];
  92. function formatDate(value) {
  93. if (!value) return '';
  94. return String(value).slice(0, 10);
  95. }
  96. /**
  97. * 解除/终止单的附件:文档里 terminations[] 给的是 attachmentIds;
  98. * 线下签署时创建入参传的是 offlineFileId,两种都兼容,避免附件丢失。
  99. */
  100. function pickTerminationFileIds(record = {}) {
  101. const ids = parseAttachmentIds(record);
  102. if (!ids.length && record.offlineFileId) ids.push(String(record.offlineFileId));
  103. return ids;
  104. }
  105. export default {
  106. name: 'HrRemoveContractDetail',
  107. props: {
  108. businessId: {
  109. default: ''
  110. }
  111. },
  112. data() {
  113. return {
  114. loading: false,
  115. detailRow: null,
  116. terminationReasons: TERMINATE_REASON_FALLBACK
  117. };
  118. },
  119. computed: {
  120. avatarText() {
  121. return this.detailRow?.employeeName
  122. ? this.detailRow.employeeName.slice(0, 1)
  123. : '?';
  124. }
  125. },
  126. watch: {
  127. businessId: {
  128. immediate: true,
  129. handler(id) {
  130. if (id) this.getInfo(id);
  131. }
  132. }
  133. },
  134. methods: {
  135. async getInfo(id) {
  136. if (!id) return;
  137. this.loading = true;
  138. // 原因编码字典单独拉取,失败不影响详情展示
  139. this.loadTerminationReasons();
  140. try {
  141. const detail = await getContractById(id);
  142. this.detailRow = this.adaptTermination(detail || {});
  143. } catch (e) {
  144. this.detailRow = null;
  145. this.$message.error(e.message || '解除/终止详情加载失败');
  146. } finally {
  147. this.loading = false;
  148. }
  149. },
  150. /**
  151. * 合同详情接口返回该合同全部解除/终止记录(按创建时间倒序)。
  152. * 审批单对应的是「最近一次解除终止单」terminationId,取不到时退回最新一条。
  153. */
  154. adaptTermination(contract = {}) {
  155. const list = Array.isArray(contract.terminations)
  156. ? contract.terminations
  157. : [];
  158. const record =
  159. list.find(
  160. (item) => String(item.id) === String(contract.terminationId)
  161. ) ||
  162. list[0] ||
  163. {};
  164. return {
  165. id: record.id ?? '',
  166. employeeName: contract.employeeName || '',
  167. contractNo: contract.contractNo || '',
  168. terminationType: record.terminationType || '',
  169. terminationTypeText:
  170. TERMINATION_TYPE_LABELS[record.terminationType] ||
  171. record.terminationType ||
  172. '-',
  173. signingMethod: record.signingMethod || '',
  174. signingMethodText:
  175. SIGNING_METHOD_LABELS[
  176. String(record.signingMethod || '').toUpperCase()
  177. ] ||
  178. record.signingMethodName ||
  179. record.signingMethod ||
  180. '-',
  181. reasonCode: record.reasonCode || '',
  182. reasonCodeText: this.reasonLabel(record.reasonCode),
  183. effectiveDate: formatDate(record.effectiveDate),
  184. lastWorkingDate: formatDate(record.lastWorkingDate),
  185. reason: record.reason || '',
  186. attachmentFileIds: pickTerminationFileIds(record)
  187. };
  188. },
  189. reasonLabel(code) {
  190. if (!code) return '-';
  191. const hit = this.terminationReasons.find((item) => item.value === code);
  192. return hit ? hit.label : code;
  193. },
  194. /** 原因编码字典:与 HR 侧同源 hr_contract_terminate */
  195. async loadTerminationReasons() {
  196. try {
  197. const res = await getByCode('hr_contract_terminate');
  198. const list = (res?.data || [])
  199. .map((item) => {
  200. const entry = Object.entries(item || {})[0] || [];
  201. return { value: entry[0], label: entry[1] };
  202. })
  203. .filter((item) => item.value);
  204. if (list.length) this.terminationReasons = list;
  205. } catch (e) {
  206. this.terminationReasons = TERMINATE_REASON_FALLBACK;
  207. }
  208. }
  209. }
  210. };
  211. </script>
  212. <style lang="scss" scoped src="@/styles/views/hrContractManagement/index.scss"></style>