hrContractDetail.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  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">{{
  6. detailRow.employeeName ? detailRow.employeeName.slice(0, 1) : '?'
  7. }}</span>
  8. <div class="cm-employee-info">
  9. <b
  10. >员工姓名:{{ detailRow.employeeName
  11. }}<span class="cm-employee-no"
  12. >员工工号:{{ detailRow.employeeNo }}</span
  13. ></b
  14. >
  15. </div>
  16. <div class="cm-detail-status">
  17. <small>合同状态</small>
  18. <span class="cm-status" :class="statusClass(detailRow.status)">{{
  19. detailRow.status
  20. }}</span>
  21. </div>
  22. </div>
  23. <div class="cm-create-section">
  24. <div class="cm-create-section-title">合同基本信息</div>
  25. <div class="cm-create-grid">
  26. <div class="cm-compare-item">
  27. <span class="cm-compare-label">合同编号:</span
  28. ><span class="cm-compare-value">{{ detailRow.contractNo }}</span>
  29. </div>
  30. <div class="cm-compare-item">
  31. <span class="cm-compare-label">合同类型:</span
  32. ><span class="cm-compare-value">{{ detailRow.contractType }}</span>
  33. </div>
  34. <div class="cm-compare-item">
  35. <span class="cm-compare-label">所属机构:</span
  36. ><span class="cm-compare-value">{{ detailRow.legalEntity }}</span>
  37. </div>
  38. <div class="cm-compare-item">
  39. <span class="cm-compare-label">所属部门:</span
  40. ><span class="cm-compare-value">{{ detailRow.department }}</span>
  41. </div>
  42. <div class="cm-compare-item">
  43. <span class="cm-compare-label">岗位名称:</span
  44. ><span class="cm-compare-value">{{ detailRow.positionName }}</span>
  45. </div>
  46. <div class="cm-compare-item">
  47. <span class="cm-compare-label">岗位层级:</span
  48. ><span class="cm-compare-value">{{
  49. detailRow.level || '-'
  50. }}</span>
  51. </div>
  52. <!-- <div class="cm-compare-item">
  53. <span class="cm-compare-label">直属上级:</span
  54. ><span class="cm-compare-value">{{
  55. detailRow.manager || '-'
  56. }}</span>
  57. </div> -->
  58. </div>
  59. </div>
  60. <div class="cm-create-section">
  61. <div class="cm-create-section-title">合同期限与薪资</div>
  62. <div class="cm-create-grid">
  63. <div class="cm-compare-item">
  64. <span class="cm-compare-label">合同起始:</span
  65. ><span class="cm-compare-value">{{
  66. detailRow.startDate || '-'
  67. }}</span>
  68. </div>
  69. <div class="cm-compare-item">
  70. <span class="cm-compare-label">合同终止:</span
  71. ><span class="cm-compare-value">{{
  72. detailRow.endDate || '长期'
  73. }}</span>
  74. </div>
  75. <div class="cm-compare-item">
  76. <span class="cm-compare-label">月薪资:</span
  77. ><span class="cm-compare-value is-highlight">{{
  78. detailRow.salary
  79. ? '¥ ' + Number(detailRow.salary).toLocaleString() + ' 元/月'
  80. : '待定'
  81. }}</span>
  82. </div>
  83. <div class="cm-compare-item">
  84. <span class="cm-compare-label">试用期至:</span
  85. ><span class="cm-compare-value">{{
  86. detailRow.probationEnd || '无试用期'
  87. }}</span>
  88. </div>
  89. </div>
  90. </div>
  91. <div class="cm-create-section">
  92. <div class="cm-create-section-title">签署信息</div>
  93. <div class="cm-create-grid">
  94. <div class="cm-compare-item">
  95. <span class="cm-compare-label">签署方式:</span
  96. ><span class="cm-compare-value">{{
  97. detailRow.signMethod || '-'
  98. }}</span>
  99. </div>
  100. <!-- <div class="cm-compare-item">
  101. <span class="cm-compare-label">签署状态:</span
  102. ><span class="cm-compare-value">{{
  103. detailRow.signingStatusLabel || '-'
  104. }}</span>
  105. </div> -->
  106. <div
  107. v-if="detailRow.flowSignStatus"
  108. class="cm-compare-item"
  109. >
  110. <span class="cm-compare-label">流程节点电子签章:</span
  111. ><span class="cm-compare-value">{{
  112. detailRow.flowSignStatusLabel || '-'
  113. }}</span>
  114. </div>
  115. <div
  116. class="cm-compare-item is-span-2"
  117. v-if="detailRow.flowSignNodes && detailRow.flowSignNodes.length"
  118. >
  119. <span class="cm-compare-label">签章节点:</span>
  120. <span class="cm-compare-value">
  121. {{
  122. detailRow.flowSignNodes
  123. .map(
  124. (n) =>
  125. (n.name || n.nodeName || n.taskName || '节点') +
  126. '(' +
  127. (n.statusLabel || '-') +
  128. ')'
  129. )
  130. .join('、')
  131. }}
  132. </span>
  133. </div>
  134. <div class="cm-compare-item">
  135. <span class="cm-compare-label">合同附件:</span
  136. ><span class="cm-compare-value">
  137. <fileMain
  138. v-if="
  139. detailRow.attachmentFileIds &&
  140. detailRow.attachmentFileIds.length
  141. "
  142. :value="detailRow.attachmentFileIds"
  143. type="view"
  144. size="small"
  145. />
  146. <span v-else class="cm-detail-muted">暂无附件</span>
  147. </span>
  148. </div>
  149. <div class="cm-compare-item">
  150. <span class="cm-compare-label">签订日期:</span
  151. ><span class="cm-compare-value">{{
  152. detailRow.signDate || detailRow.createdAt || '-'
  153. }}</span>
  154. </div>
  155. <div class="cm-compare-item">
  156. <span class="cm-compare-label">创建时间:</span
  157. ><span class="cm-compare-value">{{
  158. detailRow.createdAt || '-'
  159. }}</span>
  160. </div>
  161. </div>
  162. <div v-if="detailRow.remark" class="cm-detail-remark">
  163. <strong>备注:</strong>{{ detailRow.remark }}
  164. </div>
  165. </div>
  166. </div>
  167. <el-empty v-else-if="!loading" description="暂无合同详情" />
  168. </main>
  169. </template>
  170. <script>
  171. import {
  172. getContractById,
  173. adaptContractFlowSignNodes,
  174. contractSigningStatusLabel,
  175. contractStatusLabel,
  176. contractFlowSignStatusLabel,
  177. pickFlowSignStatus
  178. } from '@/api/hr';
  179. import { parseAttachmentIds } from '@/utils/attachment';
  180. const CONTRACT_TYPES = [
  181. { value: 'FIXED', label: '固定期限劳动合同' },
  182. { value: 'NON_FIXED', label: '无固定期限劳动合同' },
  183. { value: 'TASK', label: '以完成一定任务为期限' },
  184. { value: 'DISPATCH', label: '劳务派遣合同' },
  185. { value: 'INTERN', label: '实习协议' },
  186. { value: 'CONFIDENTIAL', label: '保密协议' },
  187. { value: 'NON_COMPETE', label: '竞业限制协议' },
  188. { value: 'CHANGE', label: '合同变更协议' },
  189. { value: 'TERMINATION', label: '合同终止协议' }
  190. ];
  191. function formatDate(value) {
  192. if (!value) return '';
  193. return String(value).slice(0, 10);
  194. }
  195. function formatDateTime(value) {
  196. if (!value) return '';
  197. return String(value).replace('T', ' ').slice(0, 16);
  198. }
  199. function typeLabel(value) {
  200. return CONTRACT_TYPES.find((t) => t.value === value)?.label || value || '';
  201. }
  202. function signMethodLabel(value) {
  203. if (value === 'ONLINE') return '线上';
  204. if (value === 'OFFLINE') return '线下';
  205. return value || '';
  206. }
  207. // 附件 ID 解析已统一由 src/utils/attachment.js 的 parseAttachmentIds 处理
  208. function adaptContract(item = {}) {
  209. const files = item.files || [];
  210. const startDate = formatDate(item.startDate);
  211. const endDate = formatDate(item.endDate);
  212. const termText =
  213. item.contractTerm ||
  214. (item.contractTermMonths != null && item.contractTermMonths !== ''
  215. ? `${item.contractTermMonths}个月`
  216. : '');
  217. return {
  218. ...item,
  219. employeeNo: item.employeeCode || item.employeeNo || '',
  220. employeeCode: item.employeeCode || item.employeeNo || '',
  221. legalEntity: item.legalEntityName || '',
  222. department: item.departmentName || '',
  223. contractTypeCode: item.contractType || '',
  224. contractType: typeLabel(item.contractType),
  225. signMethod: signMethodLabel(item.signingMethod),
  226. signingStatus: item.signingStatus || '',
  227. signingStatusLabel: contractSigningStatusLabel(item.signingStatus),
  228. flowSignStatus: pickFlowSignStatus(item),
  229. flowSignStatusLabel: contractFlowSignStatusLabel(pickFlowSignStatus(item)),
  230. flowSignNodes: adaptContractFlowSignNodes(item),
  231. status: contractStatusLabel(item.contractStatus),
  232. statusCode: item.contractStatus || '',
  233. salary: item.salaryStandard,
  234. signDate: formatDate(item.signingDate),
  235. startDate,
  236. endDate,
  237. contractTerm: termText,
  238. remainingDays:
  239. item.remainingDays === null ||
  240. item.remainingDays === undefined ||
  241. item.remainingDays === ''
  242. ? null
  243. : Number(item.remainingDays),
  244. probationMonths: item.probationMonths,
  245. createdAt: formatDateTime(item.createTime),
  246. probationEnd: formatDate(item.probationEndDate),
  247. attachmentFileIds: parseAttachmentIds(item),
  248. level: item.positionLevel || item.level || '',
  249. manager: item.managerName || item.manager || '',
  250. archived:
  251. item.contractStatus === 'ARCHIVED' ||
  252. item.archived === true ||
  253. item.archived === 1
  254. };
  255. }
  256. export default {
  257. name: 'HrContractDetail',
  258. props: {
  259. businessId: {
  260. default: ''
  261. }
  262. },
  263. data() {
  264. return {
  265. loading: false,
  266. detailRow: null
  267. };
  268. },
  269. watch: {
  270. businessId: {
  271. immediate: true,
  272. handler(id) {
  273. if (id) this.getInfo(id);
  274. }
  275. }
  276. },
  277. methods: {
  278. async getInfo(id) {
  279. if (!id) return;
  280. this.loading = true;
  281. try {
  282. const detail = await getContractById(id);
  283. this.detailRow = adaptContract(detail || {});
  284. } catch (e) {
  285. this.detailRow = null;
  286. this.$message.error(e.message || '合同详情加载失败');
  287. } finally {
  288. this.loading = false;
  289. }
  290. },
  291. statusClass(s) {
  292. const label = contractStatusLabel(s, s);
  293. const map = {
  294. 草稿: 'draft',
  295. 审批中: 'pending',
  296. 待签署: 'pending',
  297. 待签: 'pending',
  298. 签署中: 'signing',
  299. 已签待生效: 'pending',
  300. 待生效: 'pending',
  301. 生效: 'active',
  302. 生效中: 'active',
  303. 临期: 'expiring',
  304. 即将到期: 'expiring',
  305. 已变更: 'changed',
  306. 已续签: 'active',
  307. 已解除: 'terminated',
  308. 已终止: 'terminated',
  309. 已作废: 'expired',
  310. 已取消: 'expired',
  311. 已归档: 'archived',
  312. 试用期: 'trial',
  313. 已到期: 'expired',
  314. 已过期: 'expired'
  315. };
  316. return map[label] || map[s] || 'pending';
  317. }
  318. }
  319. };
  320. </script>
  321. <style lang="scss" scoped src="@/styles/views/hrContractManagement/index.scss"></style>