|
|
@@ -0,0 +1,521 @@
|
|
|
+<!-- 转正申请详情 -->
|
|
|
+<template>
|
|
|
+ <main class="regularization-detail" v-loading="loading">
|
|
|
+ <!-- 员工信息 -->
|
|
|
+ <section class="detail-section">
|
|
|
+ <div class="info-card">
|
|
|
+ <div class="info-card__head">
|
|
|
+ <i class="el-icon-user-solid"></i>
|
|
|
+ <span>员工信息</span>
|
|
|
+ <span v-if="approvalStatusText" class="info-card__code">
|
|
|
+ <el-tag :type="approvalStatusTag" size="mini">{{ approvalStatusText }}</el-tag>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="info-card__body">
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-item__label">申请员工</span>
|
|
|
+ <span class="info-item__value">{{ employeeInfo.userName || '-' }}
|
|
|
+ <span v-if="employeeInfo.jobNumber" class="info-item__sub">({{ employeeInfo.jobNumber }})</span>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-item__label">手机号</span>
|
|
|
+ <span class="info-item__value">{{ employeeInfo.mobile || '-' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-item__label">所属机构</span>
|
|
|
+ <span class="info-item__value">{{ employeeInfo.groupName || '-' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-item__label">所属部门</span>
|
|
|
+ <span class="info-item__value">{{ employeeInfo.deptName || '-' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-item__label">岗位</span>
|
|
|
+ <span class="info-item__value">{{ employeeInfo.postName || employeeInfo.positionName || '-' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-item__label">直属上级</span>
|
|
|
+ <span class="info-item__value">{{ employeeInfo.managerName || '-' }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- 转正信息(表单可操作) -->
|
|
|
+ <section class="detail-section">
|
|
|
+ <div class="info-card">
|
|
|
+ <div class="info-card__head">
|
|
|
+ <i class="el-icon-date"></i>
|
|
|
+ <span>转正信息</span>
|
|
|
+ </div>
|
|
|
+ <el-form
|
|
|
+ ref="formRef"
|
|
|
+ class="regular-form"
|
|
|
+ :model="form"
|
|
|
+ :rules="formRules"
|
|
|
+ label-width="110px"
|
|
|
+ label-position="right"
|
|
|
+ >
|
|
|
+ <div class="form-row">
|
|
|
+ <el-form-item label="入职日期" prop="joinDate">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="form.joinDate"
|
|
|
+ type="date"
|
|
|
+ value-format="yyyy-MM-dd"
|
|
|
+ placeholder="请选择入职日期"
|
|
|
+ style="width: 100%"
|
|
|
+ :disabled="isReadonly"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="转正类型" prop="regularizationType">
|
|
|
+ <el-select
|
|
|
+ v-model="form.regularizationType"
|
|
|
+ placeholder="请选择转正类型"
|
|
|
+ style="width: 100%"
|
|
|
+ :disabled="isReadonly"
|
|
|
+ @change="onRegularizationTypeChange"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="opt in regularizationTypeOptions"
|
|
|
+ :key="opt.value"
|
|
|
+ :label="opt.label"
|
|
|
+ :value="opt.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-row">
|
|
|
+ <el-form-item label="计划转正日期" prop="plannedRegularDate">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="form.plannedRegularDate"
|
|
|
+ type="date"
|
|
|
+ value-format="yyyy-MM-dd"
|
|
|
+ placeholder="请选择计划转正日期"
|
|
|
+ style="width: 100%"
|
|
|
+ :disabled="isReadonly"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="实际生效日期" prop="effectiveDate">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="form.effectiveDate"
|
|
|
+ type="date"
|
|
|
+ value-format="yyyy-MM-dd"
|
|
|
+ placeholder="留空按计划日期生效"
|
|
|
+ style="width: 100%"
|
|
|
+ :disabled="isReadonly"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 延期转正专属字段 -->
|
|
|
+ <template v-if="form.regularizationType === 'EXTENDED'">
|
|
|
+ <div class="form-row form-row--extended">
|
|
|
+ <div class="extended-title">
|
|
|
+ <i class="el-icon-warning-outline"></i>
|
|
|
+ <span>延期转正信息</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="form-row">
|
|
|
+ <el-form-item label="延期天数" prop="extensionDays">
|
|
|
+ <el-input-number
|
|
|
+ v-model="form.extensionDays"
|
|
|
+ :min="1"
|
|
|
+ :max="365"
|
|
|
+ :step="1"
|
|
|
+ placeholder="请输入延期天数"
|
|
|
+ style="width: 100%"
|
|
|
+ :disabled="isReadonly"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="延期后试用期结束" prop="newProbationEndDate">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="form.newProbationEndDate"
|
|
|
+ type="date"
|
|
|
+ value-format="yyyy-MM-dd"
|
|
|
+ placeholder="选择延期后试用期结束日期"
|
|
|
+ style="width: 100%"
|
|
|
+ :disabled="isReadonly"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ <div class="form-row">
|
|
|
+ <el-form-item label="延期原因" prop="extensionReason">
|
|
|
+ <el-input
|
|
|
+ v-model="form.extensionReason"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="请填写延期转正的原因(必填)"
|
|
|
+ :disabled="isReadonly"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <div class="form-row">
|
|
|
+ <el-form-item label="签署方式">
|
|
|
+ <span class="plain-text">{{ signWayText }}</span>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="申请资料">
|
|
|
+ <fileMain
|
|
|
+ v-model="approvalFileIds"
|
|
|
+ type="view"
|
|
|
+ size="small"
|
|
|
+ :view-disabled="!approvalFileIds.length"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </main>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getRegularizationById } from '@/api/main';
|
|
|
+import { getEmployeeById } from '@/api/organization';
|
|
|
+
|
|
|
+const empty = () => ({
|
|
|
+ id: '',
|
|
|
+ userId: null,
|
|
|
+ joinDate: '',
|
|
|
+ plannedRegularDate: '',
|
|
|
+ effectiveDate: '',
|
|
|
+ regularizationType: '',
|
|
|
+ extensionDays: null,
|
|
|
+ extensionReason: '',
|
|
|
+ newProbationEndDate: '',
|
|
|
+ signWay: 'ONLINE',
|
|
|
+ approvalDocumentFileId: null,
|
|
|
+ userSummary: '',
|
|
|
+ managerEvaluation: '',
|
|
|
+ hrComment: '',
|
|
|
+ approvalStatus: '',
|
|
|
+ result: ''
|
|
|
+});
|
|
|
+
|
|
|
+const REGULARIZATION_TYPE_OPTIONS = [
|
|
|
+ { value: 'NORMAL', label: '按期转正' },
|
|
|
+ { value: 'EARLY', label: '提前转正' },
|
|
|
+ { value: 'EXTENDED', label: '延期转正' },
|
|
|
+ { value: 'FAILED', label: '转正不通过' }
|
|
|
+];
|
|
|
+
|
|
|
+const APPROVAL_STATUS_MAP = {
|
|
|
+ DRAFT: { text: '草稿', tag: 'info' },
|
|
|
+ APPROVING: { text: '审批中', tag: 'warning' },
|
|
|
+ APPROVED: { text: '已通过', tag: 'success' },
|
|
|
+ REJECTED: { text: '已驳回', tag: 'danger' },
|
|
|
+ COMPLETED: { text: '已完成', tag: 'success' }
|
|
|
+};
|
|
|
+
|
|
|
+/** 从多种字段名中取值 */
|
|
|
+function pick(obj, keys) {
|
|
|
+ if (obj == null || typeof obj !== 'object') return '';
|
|
|
+ for (let i = 0; i < keys.length; i++) {
|
|
|
+ const v = obj[keys[i]];
|
|
|
+ if (v !== undefined && v !== null && v !== '') return v;
|
|
|
+ }
|
|
|
+ return '';
|
|
|
+}
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'RegularizationHandlingDetail',
|
|
|
+ props: {
|
|
|
+ businessId: {
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ /** 是否只读(BPM 详情页一般只读) */
|
|
|
+ readonly: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ loading: false,
|
|
|
+ form: empty(),
|
|
|
+ employeeInfo: {},
|
|
|
+ approvalDocumentFileId: null,
|
|
|
+ approvalFileIds: [],
|
|
|
+ regularizationTypeOptions: REGULARIZATION_TYPE_OPTIONS
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ isReadonly() {
|
|
|
+ return this.readonly;
|
|
|
+ },
|
|
|
+ formRules() {
|
|
|
+ const base = {
|
|
|
+ regularizationType: [
|
|
|
+ { required: true, message: '请选择转正类型', trigger: 'change' }
|
|
|
+ ],
|
|
|
+ plannedRegularDate: [
|
|
|
+ { required: true, message: '请选择计划转正日期', trigger: 'change' }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ if (this.form.regularizationType === 'EXTENDED') {
|
|
|
+ base.extensionDays = [
|
|
|
+ { required: true, message: '请输入延期天数', trigger: 'blur' },
|
|
|
+ { type: 'number', min: 1, message: '延期天数必须大于 0', trigger: 'blur' }
|
|
|
+ ];
|
|
|
+ base.extensionReason = [
|
|
|
+ { required: true, message: '请填写延期原因', trigger: 'blur' }
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ return base;
|
|
|
+ },
|
|
|
+ approvalStatusText() {
|
|
|
+ const key = String(this.form.approvalStatus || '').toUpperCase();
|
|
|
+ return APPROVAL_STATUS_MAP[key]?.text || '';
|
|
|
+ },
|
|
|
+ approvalStatusTag() {
|
|
|
+ const key = String(this.form.approvalStatus || '').toUpperCase();
|
|
|
+ return APPROVAL_STATUS_MAP[key]?.tag || 'info';
|
|
|
+ },
|
|
|
+ signWayText() {
|
|
|
+ const map = { ONLINE: '线上签署', OFFLINE: '线下签署' };
|
|
|
+ return map[this.form.signWay] || this.form.signWay || '-' ;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ businessId: {
|
|
|
+ immediate: true,
|
|
|
+ handler(id) {
|
|
|
+ if (id) this.bootstrap(id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ onRegularizationTypeChange(val) {
|
|
|
+ // 切换为非延期转正时,清空延期相关字段
|
|
|
+ if (val !== 'EXTENDED') {
|
|
|
+ this.form.extensionDays = null;
|
|
|
+ this.form.extensionReason = '';
|
|
|
+ this.form.newProbationEndDate = '';
|
|
|
+ }
|
|
|
+ if (this.$refs.formRef) {
|
|
|
+ this.$nextTick(() => this.$refs.formRef.clearValidate());
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /** 将 swagger 字段 → 组件 form */
|
|
|
+ mapRegularization(data) {
|
|
|
+ if (!data || typeof data !== 'object') return empty();
|
|
|
+ return {
|
|
|
+ id: data.id ?? '',
|
|
|
+ userId: pick(data, ['userId']),
|
|
|
+ joinDate: this.toDateStr(pick(data, ['joinDate', 'joinDateTime', 'hireDate'])),
|
|
|
+ plannedRegularDate: this.toDateStr(pick(data, ['plannedRegularDate'])),
|
|
|
+ effectiveDate: this.toDateStr(pick(data, ['effectiveDate'])),
|
|
|
+ regularizationType: pick(data, ['regularizationType']),
|
|
|
+ extensionDays: pick(data, ['extensionDays']) ?? null,
|
|
|
+ extensionReason: pick(data, ['extensionReason']),
|
|
|
+ newProbationEndDate: this.toDateStr(pick(data, ['newProbationEndDate'])),
|
|
|
+ signWay: pick(data, ['signWay', 'signatureWay']) || 'ONLINE',
|
|
|
+ approvalDocumentFileId: pick(data, ['approvalDocumentFileId']),
|
|
|
+ userSummary: pick(data, ['userSummary']),
|
|
|
+ managerEvaluation: pick(data, ['managerEvaluation']),
|
|
|
+ hrComment: pick(data, ['hrComment']),
|
|
|
+ approvalStatus: pick(data, ['approvalStatus']),
|
|
|
+ result: pick(data, ['result'])
|
|
|
+ };
|
|
|
+ },
|
|
|
+ /** 员工信息:转正单接口优先,缺失则从 getEmployeeById 补
|
|
|
+ * 真实原始字段名对照 HR adaptEmployee:
|
|
|
+ * - 转正单 raw: employeeName / employeeNo / groupName / deptName / positionName / managerUserName
|
|
|
+ * - 员工档案 raw: name / jobNumber / groupName / deptName / postName / managerUserName / phone
|
|
|
+ */
|
|
|
+ mapEmployeeInfo(regularization, employee) {
|
|
|
+ const r = regularization || {};
|
|
|
+ const e = employee || {};
|
|
|
+ this.approvalDocumentFileId = pick(r, ['approvalDocumentFileId']);
|
|
|
+ return {
|
|
|
+ userId: r.userId || e.id,
|
|
|
+ // 申请员工
|
|
|
+ userName: r.employeeName || e.name,
|
|
|
+ // 工号
|
|
|
+ jobNumber: r.employeeNo || e.jobNumber,
|
|
|
+ // 手机号
|
|
|
+ mobile: e.phone || e.mobile,
|
|
|
+ // 所属机构
|
|
|
+ groupName: r.groupName || e.groupName,
|
|
|
+ // 所属部门
|
|
|
+ deptName: r.departmentName || r.deptName || e.deptName,
|
|
|
+ // 岗位(模板用 positionName 渲染)
|
|
|
+ positionName: r.positionName || e.postName,
|
|
|
+ // 岗位(兜底)
|
|
|
+ postName: e.postName,
|
|
|
+ // 直属上级
|
|
|
+ managerName: r.managerUserName || e.managerUserName
|
|
|
+ };
|
|
|
+ },
|
|
|
+ /** 后端日期 → yyyy-MM-dd */
|
|
|
+ toDateStr(val) {
|
|
|
+ if (!val) return '';
|
|
|
+ const s = String(val);
|
|
|
+ // 已经是 yyyy-MM-dd / yyyy-MM-dd HH:mm:ss
|
|
|
+ if (/^\d{4}-\d{2}-\d{2}/.test(s)) return s.slice(0, 10);
|
|
|
+ // 毫秒时间戳
|
|
|
+ if (/^\d{10,13}$/.test(s)) {
|
|
|
+ const d = new Date(Number(s));
|
|
|
+ if (!isNaN(d.getTime())) {
|
|
|
+ const pad = n => String(n).padStart(2, '0');
|
|
|
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return s.slice(0, 10);
|
|
|
+ },
|
|
|
+ async bootstrap(id) {
|
|
|
+ this.loading = true;
|
|
|
+ try {
|
|
|
+ const regularization = await getRegularizationById(id);
|
|
|
+ this.form = this.mapRegularization(regularization);
|
|
|
+
|
|
|
+ // 用转正单的 userId 补查员工档案,拿到完整的员工信息
|
|
|
+ let employee = null;
|
|
|
+ if (regularization?.userId) {
|
|
|
+ try {
|
|
|
+ employee = await getEmployeeById(regularization.userId);
|
|
|
+ if (process.env.NODE_ENV !== 'production') {
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log('[RegularizationDetail] 员工档案 raw employee:', employee);
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
+ console.log('[RegularizationDetail] 转正单 raw keys:', Object.keys(regularization));
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ if (process.env.NODE_ENV !== 'production') {
|
|
|
+ console.warn('[RegularizationDetail] 员工档案补查失败:', e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.employeeInfo = this.mapEmployeeInfo(regularization, employee);
|
|
|
+
|
|
|
+ // 转正单 swagger 只有单个 approvalDocumentFileId,统一转成数组给 fileMain
|
|
|
+ const fileId = this.approvalDocumentFileId;
|
|
|
+ this.approvalFileIds = fileId ? [fileId] : [];
|
|
|
+ } catch (error) {
|
|
|
+ this.form = empty();
|
|
|
+ this.employeeInfo = {};
|
|
|
+ if (process.env.NODE_ENV !== 'production') {
|
|
|
+ console.warn('[RegularizationDetail] 加载转正单失败:', error);
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ this.loading = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.regularization-detail {
|
|
|
+ padding: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-section {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.info-card {
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: #fff;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ &__head {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ padding: 10px 16px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ background: #f5f7fa;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+
|
|
|
+ i {
|
|
|
+ font-size: 16px;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &__code {
|
|
|
+ margin-left: auto;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: normal;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.info-card__body {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 12px 32px;
|
|
|
+ padding: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.info-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ min-width: 240px;
|
|
|
+
|
|
|
+ &__label {
|
|
|
+ flex-shrink: 0;
|
|
|
+ width: 90px;
|
|
|
+ color: #909399;
|
|
|
+ font-size: 13px;
|
|
|
+ text-align: right;
|
|
|
+ padding-right: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__value {
|
|
|
+ color: #303133;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__sub {
|
|
|
+ color: #909399;
|
|
|
+ font-weight: normal;
|
|
|
+ margin-left: 4px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.regular-form {
|
|
|
+ padding: 8px 16px 16px;
|
|
|
+
|
|
|
+ .form-row {
|
|
|
+ display: flex;
|
|
|
+ gap: 16px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ margin-bottom: 4px;
|
|
|
+
|
|
|
+ .el-form-item {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 320px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &--extended {
|
|
|
+ .extended-title {
|
|
|
+ width: 100%;
|
|
|
+ padding: 10px 0 4px;
|
|
|
+ color: #e6a23c;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ border-top: 1px dashed #ebeef5;
|
|
|
+ margin-top: 8px;
|
|
|
+
|
|
|
+ i {
|
|
|
+ margin-right: 6px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.plain-text {
|
|
|
+ color: #303133;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 32px;
|
|
|
+}
|
|
|
+</style>
|