| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940 |
- <template>
- <view class="page-no-tab" :class="{ 'readonly-mode': isReadonly }">
- <uni-nav-bar fixed="true" statusBar="true" left-icon="back" :title="navTitle" background-color="#157A2C"
- color="#fff" @clickLeft="backToList"></uni-nav-bar>
- <!-- 详情态:统一申请视图(编号 / 状态 / 当前节点 / 提交时间) -->
- <view v-if="showDetailHeader" class="detail-head card section">
- <view class="detail-head-top">
- <text class="detail-title">{{ detailTitle || meta.title }}</text>
- <text class="status-tag" :class="'status-' + detailStatusClass">{{ detailStatusLabel }}</text>
- </view>
- <view class="detail-row">
- <text class="detail-label">申请编号</text>
- <text class="detail-value">{{ detailNo }}</text>
- </view>
- <view class="detail-row">
- <text class="detail-label">申请人</text>
- <text class="detail-value">{{ detailApplicant || form.name || '—' }}</text>
- </view>
- <view class="detail-row">
- <text class="detail-label">提交时间</text>
- <text class="detail-value">{{ detailCreatedAt || '—' }}</text>
- </view>
- <view v-if="detailNode" class="detail-row">
- <text class="detail-label">当前节点</text>
- <text class="detail-value">{{ detailNode }}</text>
- </view>
- </view>
- <view v-if="essRow.maskedSummary && isReadonly" class="masked-card card section">
- <text class="masked-label">申请摘要</text>
- <text class="masked-text">{{ essRow.maskedSummary }}</text>
- </view>
- <!-- ============ 员工信息(字段与 PC 端「新建转正申请」一致)============ -->
- <view class="form-card card section">
- <view class="section-head">
- <view class="section-title"><text>员工信息</text></view>
- </view>
- <hr-picker-row v-bind="pickerRowMetrics" label="申请员工" required :value="form.userId"
- :display="form.userId ? employeeText : ''" :options="employeeOptions" placeholder="当前登录人"
- :disabled="lockedEmployee" @change="onEmployeePick" />
- <view class="divider"></view>
- <view class="field">
- <text class="label required">手机号</text>
- <input class="input-flex" type="number" maxlength="11" :disabled="formDisabled" :value="form.phone"
- placeholder="请输入 11 位手机号码" @input="onPhoneInput" />
- </view>
- <view class="divider"></view>
- <hr-picker-row v-bind="pickerRowMetrics" label="所属机构" required :value="form.groupId" :display="form.company"
- :options="legalOptions.map((item) => ({ label: item.name, value: item.id }))" hint="机构数据加载中"
- @change="onCascadeLegalChange" />
- <view class="divider"></view>
- <hr-picker-row v-bind="pickerRowMetrics" mode="tree" label="所属部门" required :value="form.deptId"
- :display="form.department" :tree="cascadeDeptTree" :disabled="!form.groupId" :hint="deptPlaceholder"
- @change="onCascadeDeptPick" />
- <view class="divider"></view>
- <hr-picker-row v-bind="pickerRowMetrics" label="岗位序列" required :value="form.positionSequenceId"
- :display="form.positionSequence"
- :options="sequenceOptions.map((item) => ({ label: item.name, value: item.id }))"
- :disabled="!form.deptId" :hint="sequencePlaceholder" @change="onCascadeSequenceChange" />
- <view class="divider"></view>
- <hr-picker-row v-bind="pickerRowMetrics" label="岗位名称" required :value="form.positionId"
- :display="form.position"
- :options="cascadePositionOptions.map((item) => ({ label: item.name, value: item.id }))"
- :disabled="!form.positionSequenceId" :hint="positionPlaceholder" @change="onCascadePositionChange" />
- <view class="divider"></view>
- <view class="field">
- <text class="label required">岗位性质</text>
- <text class="value readonly-text">{{ form.positionTypeName || "选择岗位名称后自动带出" }}</text>
- </view>
- <view class="divider"></view>
- <hr-picker-row v-bind="pickerRowMetrics" label="岗位层级" required :value="form.positionLevelId"
- :display="form.positionLevel"
- :options="levelOptions.map((item) => ({ label: item.name, value: item.id }))"
- :disabled="!form.positionId" :hint="levelPlaceholder" @change="onCascadeLevelChange" />
- <view class="divider"></view>
- <!-- 直属上级:与「个人档案」同款选人弹窗(整行可点,右侧 › 靠右;只读态不显示 ›) -->
- <view class="field press" @click="openManagerPicker">
- <text class="label">直属上级</text>
- <text :class="form.managerUserName ? 'value' : 'placeholder'">{{ form.managerUserName || "请选择" }}</text>
- <text v-if="!isReadonly" class="arrow">›</text>
- </view>
- </view>
- <!-- ============ 转正信息 ============ -->
- <view class="form-card card section">
- <view class="section-head">
- <view class="section-title"><text>转正信息</text></view>
- </view>
- <view class="field">
- <text class="label">入职日期</text>
- <text class="value readonly-text">{{ form.hireDate || "未填写" }}</text>
- </view>
- <view class="divider"></view>
- <hr-picker-row v-bind="pickerRowMetrics" label="转正类型" required :value="form.regularizationType"
- :display="typeLabel" :options="typeOptions" :disabled="formDisabled" @change="onTypeChange" />
- <view class="divider"></view>
- <view class="field">
- <text class="label required">计划转正日期</text>
- <picker class="field-picker" :disabled="formDisabled" mode="date" :value="form.plannedRegularDate"
- @change="onDateChange('plannedRegularDate', $event)">
- <text :class="form.plannedRegularDate ? 'value' : 'placeholder'">
- {{ form.plannedRegularDate || "请选择" }}
- </text>
- </picker>
- <text class="arrow">›</text>
- </view>
- <view class="divider"></view>
- <view class="field">
- <text class="label">实际生效日期</text>
- <picker class="field-picker" :disabled="formDisabled" mode="date" :value="form.effectiveDate"
- @change="onDateChange('effectiveDate', $event)">
- <text :class="form.effectiveDate ? 'value' : 'placeholder'">
- {{ form.effectiveDate || "为空按计划日期生效" }}
- </text>
- </picker>
- <text class="arrow">›</text>
- </view>
- <view class="divider"></view>
- <view class="field file-field">
- <text class="label">申请资料</text>
- <view class="field-file">
- <fileMain v-model="form.attachmentIds" :type="isReadonly ? 'view' : ''" />
- </view>
- </view>
- </view>
- <!-- ============ 延期信息(转正类型 = 延期转正)============ -->
- <view v-if="isExtended" class="form-card card section">
- <view class="section-head">
- <view class="section-title"><text>延期信息</text></view>
- <text class="section-tip">延期转正需填写延期天数与原因</text>
- </view>
- <view class="field">
- <text class="label required">延期天数</text>
- <input v-model="form.extensionDays" :disabled="formDisabled" type="number" class="number-input"
- placeholder="请输入延期天数" />
- </view>
- <view class="divider"></view>
- <view class="field">
- <text class="label">延期后试用结束日</text>
- <picker class="field-picker" :disabled="formDisabled" mode="date" :value="form.newProbationEndDate"
- @change="onDateChange('newProbationEndDate', $event)">
- <text :class="form.newProbationEndDate ? 'value' : 'placeholder'">
- {{ form.newProbationEndDate || "请选择" }}
- </text>
- </picker>
- <text class="arrow">›</text>
- </view>
- <view class="divider"></view>
- <view class="field textarea-field">
- <text class="label required">延期原因</text>
- <textarea v-model="form.extensionReason" :disabled="formDisabled" maxlength="200"
- placeholder="请说明延期原因" />
- <text class="counter">{{ (form.extensionReason || "").length }}/200</text>
- </view>
- </view>
- <view v-if="showBottomBar" class="bottom-action">
- <view v-if="isCreateMode" class="draft-btn press" @click="onSaveDraft">保存草稿</view>
- <view class="submit-btn press" @click="onBottomAction">{{ bottomButtonText }}</view>
- </view>
- <!-- 直属上级:与「个人档案」一致的选人弹窗(搜索 + 机构筛选 + 列表) -->
- <selectUserDialog ref="userPicker" @confirm="onUserPicked" />
- </view>
- </template>
- <script>
- import {
- applicationTypes
- } from "@/pages/hr/data/apply-config";
- import fileMain from "@/pages/doc/index.vue";
- import {
- REGULARIZATION_TYPE_LABELS,
- REGULARIZATION_TYPE_OPTIONS,
- canEditRegularization,
- fetchRegularizationEmployee,
- hydrateRegularizationForm,
- loadEmploymentPeriod,
- loadRegularizationDetail,
- loadRegularizationFormOptions,
- resolveRegularizationId,
- saveRegularizationDraft,
- submitRegularizationFlow,
- } from "@/api/hr/regularization";
- import {
- applicationModeMixin,
- APPLICATION_MODE
- } from "@/utils/hr/application-mode";
- import {
- positionCascadeMixin
- } from "@/utils/hr/position-cascade";
- import {
- currentUserId
- } from "@/api/hr/user";
- import HrPickerRow from "@/components/hr/HrPickerRow.vue";
- import selectUserDialog from "@/components/selectUserDialog.vue";
- import {
- back
- } from "@/utils/hr/router";
- /** 附件数组 → 非空 id 列表 */
- function attachmentsOf(form = {}) {
- return (Array.isArray(form.attachmentIds) ? form.attachmentIds : []).filter(Boolean).map((id) => String(id));
- }
- function emptyForm() {
- return {
- regularizationId: null,
- // —— 员工信息 ——
- userId: "",
- name: "",
- employeeNo: "",
- avatar: "",
- phone: "",
- groupId: "",
- company: "",
- deptId: "",
- department: "",
- positionSequenceId: "",
- positionSequence: "",
- positionId: "",
- position: "",
- positionType: "",
- positionTypeName: "",
- positionLevelId: "",
- positionLevel: "",
- managerUserId: "",
- managerUserName: "",
- manager: "",
- hireDate: "",
- // —— 转正信息 ——
- regularizationType: "NORMAL",
- plannedRegularDate: "",
- effectiveDate: "",
- // 签署方式固定线下签署(页面上不做切换)
- signingMethod: "OFFLINE",
- online: false,
- onlineDocumentTemplateId: null,
- employmentPeriodId: null,
- extensionDays: "",
- extensionReason: "",
- newProbationEndDate: "",
- // —— 评估意见 ——
- // 表单已不再录入这三项(2026-10-15 按要求去掉页面上的「评估意见」模块),
- // 但字段必须保留:一是历史驳回单回填的值不能被清空,二是提交载荷口径不变
- // (见 api/hr/regularization.js 的 buildRegularizationPayload)。
- userSummary: "",
- managerEvaluation: "",
- hrComment: "",
- // —— 状态 ——
- status: "",
- statusLabel: "",
- attachmentIds: [],
- };
- }
- export default {
- components: {
- fileMain,
- HrPickerRow,
- selectUserDialog
- },
- mixins: [applicationModeMixin, positionCascadeMixin],
- data() {
- return {
- form: emptyForm(),
- employees: [],
- submitting: false,
- };
- },
- computed: {
- /** 统一选择行(HrPickerRow)的行度量:与本页 .field/.label 口径一致,避免左侧对不齐 */
- pickerRowMetrics() {
- return {
- rowMinHeight: "108rpx",
- rowPadding: "28rpx",
- labelWidth: "180rpx",
- };
- },
- meta() {
- return applicationTypes.regular || {
- title: "转正申请",
- subtitle: "",
- icon: "正",
- color: "#269a99"
- };
- },
- navTitle() {
- return this.applicationTitle(this.meta.title);
- },
- pageTitle() {
- return this.meta.title;
- },
- /**
- * 申请员工:移动端是**员工自助**,只能给自己申请 —— 恒锁定不可更换
- * (与 PC 的远程搜人员工选择器口径不同,这是移动端既定定位)
- */
- lockedEmployee() {
- return true;
- },
- employeeText() {
- return this.form.userId ? `${this.form.name || "—"}(${this.form.employeeNo || "-"})` : "当前登录人";
- },
- typeLabel() {
- return REGULARIZATION_TYPE_LABELS[this.form.regularizationType] || "按期转正";
- },
- /** 申请员工候选(统一选择器用;显示与原来 actionSheet 一致:姓名(工号)) */
- employeeOptions() {
- return (this.employees || []).map((item) => ({
- label: `${item.name}(${item.employeeNo || "-"})`,
- value: item.id,
- }));
- },
- /** 转正类型候选 */
- typeOptions() {
- return REGULARIZATION_TYPE_OPTIONS.map((item) => ({
- label: item.label,
- value: item.value
- }));
- },
- isExtended() {
- return this.form.regularizationType === "EXTENDED";
- },
- deptPlaceholder() {
- return this.form.groupId ? "请选择" : "请先选择所属机构";
- },
- sequencePlaceholder() {
- return this.form.deptId ? "请选择" : "请先选择所属部门";
- },
- positionPlaceholder() {
- return this.form.positionSequenceId ? "请选择" : "请先选择岗位序列";
- },
- levelPlaceholder() {
- return this.form.positionId ? "请选择" : "请先选择岗位名称";
- },
- },
- onLoad(options) {
- const opts = options || {};
- this.initApplicationMode(opts);
- // 本页不直接承接 mode=edit(编辑由详情页内「编辑」按钮进入)
- if (this.appMode === APPLICATION_MODE.EDIT) this.appMode = APPLICATION_MODE.DETAIL;
- if (this.isCreateMode) {
- this.init(opts);
- return;
- }
- uni.setNavigationBarTitle({
- title: this.navTitle
- });
- // 选项(员工 / 模板)仍需加载,只读详情要显示模板名
- this.loadOptions().then(() => this.initDetailMode());
- },
- methods: {
- // 返回上一页;页面栈里只有本页时(菜单直达 / 深链进入)兜底回「工作中心」。
- // 注意:不能再兜底到转正办理列表页——该列表已下线(pages.json 路由一并删除),
- // 否则会出现「凭空跳到一个自己从没进过的页面」。
- backToList() {
- back("/pages/index/index");
- },
- async loadOptions() {
- try {
- const options = await loadRegularizationFormOptions();
- this.employees = options.employees || [];
- } catch (error) {
- uni.showToast({
- title: (error && error.message) || "选项加载失败",
- icon: "none"
- });
- }
- },
- async init(options) {
- uni.setNavigationBarTitle({
- title: this.navTitle
- });
- await this.loadOptions();
- const id = options.id || "";
- if (id) {
- uni.showLoading({
- title: "加载中..."
- });
- try {
- const row = await loadRegularizationDetail(id);
- if (!row) throw new Error("未找到该转正单");
- if (!canEditRegularization(row)) throw new Error("当前状态不可编辑");
- await this.hydrateBill(row.regularizationId || row.id);
- } catch (e) {
- uni.hideLoading();
- uni.showToast({
- title: (e && e.message) || "加载失败",
- icon: "none"
- });
- setTimeout(() => this.backToList(), 900);
- return;
- }
- uni.hideLoading();
- return;
- }
- // 员工自助:只能给自己申请 —— 未显式带 userId 时取当前登录人
- const userId = options.userId || currentUserId() || "";
- if (userId) {
- const employee = this.employees.find((item) => String(item.id) === String(userId)) || null;
- if (employee) {
- await this.selectEmployee(employee);
- } else {
- this.form.userId = userId;
- // 不在「试用期员工」候选里(例如非试用期)也要把本人档案带出来,便于填表
- const brief = await fetchRegularizationEmployee(userId, false);
- await this.selectEmployee(Object.assign({
- id: userId
- }, brief || {}));
- const billId = await resolveRegularizationId(userId);
- if (billId) {
- await this.hydrateBill(billId);
- if (this.form.status && !canEditRegularization({
- status: this.form.status
- })) {
- uni.showToast({
- title: "该员工当前状态不可申请转正",
- icon: "none"
- });
- setTimeout(() => this.backToList(), 900);
- }
- }
- }
- }
- // 未带 userId(菜单直达)时也要把机构 / 部门候选加载出来并带出默认机构,
- // 否则「所属机构」下拉是空的(原先只有选人后才触发加载)→ 用户以为选不出来
- await this.initCascadeOrg();
- },
- /** 申请员工:统一选择器回传员工主键,再走原有的档案带出逻辑 */
- onEmployeePick(userId) {
- if (this.isReadonly || this.lockedEmployee) return;
- const picked = this.employees.find((item) => String(item.id) === String(userId));
- if (picked) this.selectEmployee(picked);
- },
- /** 选中员工:按 PC 逻辑带出档案快照(含组织 / 岗位级联主键) */
- async selectEmployee(employee) {
- const brief = employee || {};
- Object.assign(this.form, {
- userId: brief.id,
- name: brief.name || "",
- employeeNo: brief.employeeNo || "",
- avatar: brief.avatar || String(brief.name || "").charAt(0),
- phone: brief.phone || "",
- groupId: brief.groupId || "",
- company: brief.company || "",
- deptId: brief.deptId || "",
- department: brief.department || "",
- positionSequenceId: brief.positionSequenceId || "",
- positionSequence: brief.positionSequence || "",
- positionId: brief.positionId || "",
- position: brief.position || "",
- positionType: brief.positionType || "",
- positionTypeName: brief.positionTypeName || "",
- positionLevelId: brief.positionLevelId || "",
- positionLevel: brief.positionLevel || "",
- managerUserId: brief.managerUserId || "",
- managerUserName: brief.managerUserName || brief.manager || "",
- manager: brief.managerUserName || brief.manager || "",
- hireDate: brief.hireDate || "",
- });
- if (!this.form.plannedRegularDate) {
- this.form.plannedRegularDate = brief.regularDate || "";
- }
- // 列表行缺组织 / 岗位主键时,用完整档案补齐并预热级联
- if (!this.form.groupId || !this.form.deptId || !this.form.positionId) {
- const detail = await this.loadEmployeeDetail(brief.id);
- if (detail) Object.assign(this.form, detail);
- }
- await this.primeCascade();
- const period = await loadEmploymentPeriod(brief.id);
- if (period) {
- this.form.employmentPeriodId = period.id != null ? period.id : null;
- if (!this.form.plannedRegularDate) {
- this.form.plannedRegularDate = String(period.probationEndDate || period.regularDate || "")
- .slice(0, 10);
- }
- }
- const billId = await resolveRegularizationId(brief.id);
- if (billId) await this.hydrateBill(billId);
- },
- /** 员工档案补全(返回可合并进 form 的级联字段) */
- async loadEmployeeDetail(userId) {
- if (!userId) return null;
- try {
- const brief = await fetchRegularizationEmployee(userId, false);
- if (!brief) return null;
- return {
- phone: this.form.phone || brief.phone || "",
- groupId: this.form.groupId || brief.groupId || "",
- company: this.form.company || brief.company || brief.companyName || "",
- deptId: this.form.deptId || brief.deptId || "",
- department: this.form.department || brief.department || "",
- positionSequenceId: this.form.positionSequenceId || brief.positionSequenceId || "",
- positionSequence: this.form.positionSequence || brief.positionSequence || "",
- positionId: this.form.positionId || brief.positionId || "",
- position: this.form.position || brief.position || brief.positionName || "",
- positionType: this.form.positionType || brief.positionType || "",
- positionTypeName: this.form.positionTypeName || brief.positionTypeName || "",
- positionLevelId: this.form.positionLevelId || brief.positionLevelId || "",
- positionLevel: this.form.positionLevel || brief.positionLevel || "",
- managerUserId: this.form.managerUserId || brief.managerUserId || "",
- managerUserName: this.form.managerUserName || brief.managerUserName || "",
- hireDate: this.form.hireDate || brief.hireDate || "",
- };
- } catch (error) {
- return null;
- }
- },
- async hydrateBill(billId) {
- if (!billId) return;
- try {
- const row = await loadRegularizationDetail(billId);
- if (!row) return;
- const hydrated = hydrateRegularizationForm(row, {
- userId: this.form.userId,
- name: this.form.name,
- employeeNo: this.form.employeeNo,
- hireDate: this.form.hireDate,
- });
- this.form = Object.assign(emptyForm(), this.form, hydrated, {
- regularizationId: row.regularizationId || row.id,
- userId: row.userId || this.form.userId,
- status: row.status || this.form.status,
- statusLabel: row.statusLabel || "",
- });
- // 详情接口不返回岗位主键时,用员工档案补齐并预热级联
- if (!this.form.groupId || !this.form.deptId || !this.form.positionId) {
- const detail = await this.loadEmployeeDetail(this.form.userId);
- if (detail) Object.assign(this.form, detail);
- }
- await this.primeCascade();
- } catch (error) {
- uni.showToast({
- title: (error && error.message) || "转正单加载失败",
- icon: "none"
- });
- }
- },
- /** 转正类型 */
- onTypeChange(type) {
- if (this.isReadonly) return;
- this.form.regularizationType = type;
- },
- /** 通用日期选择:dateKey 为表单字段名 */
- onDateChange(dateKey, e) {
- if (this.isReadonly) return;
- this.form[dateKey] = (e && e.detail && e.detail.value) || "";
- },
- onPhoneInput(e) {
- if (this.isReadonly) return;
- this.form.phone = String((e && e.detail && e.detail.value) || "").slice(0, 11);
- },
- /** 直属上级:打开选人弹窗(与「个人档案」页同一套交互) */
- openManagerPicker() {
- if (this.isReadonly) return;
- if (this.$refs.userPicker) this.$refs.userPicker.open("2");
- },
- /** 选人弹窗确认:单选模式回传数组,取第一个 */
- onUserPicked(list) {
- const user = Array.isArray(list) ? list[0] : list;
- if (!user) return;
- this.applyManager({
- id: user.id,
- name: user.name || user.userName || ""
- });
- },
- applyManager(picked) {
- this.form.managerUserId = picked.id != null ? String(picked.id) : "";
- this.form.managerUserName = picked.name || "";
- this.form.manager = picked.name || "";
- },
- // ============ 详情 / 编辑模式 ============
- async loadDetailBusiness() {
- await this.fetchSourceDetail(this.appBusinessId);
- },
- /** 只读详情与编辑态共用:按转正单主键拉取业务明细并回填 */
- async fetchSourceDetail(businessId) {
- const id = businessId || this.appBusinessId;
- if (!id) throw new Error("缺少转正单主键,无法加载申请明细");
- await this.hydrateBill(id);
- if (!this.form.regularizationId) {
- throw new Error("未获取到转正申请明细,暂时无法编辑");
- }
- },
- validate() {
- if (!this.form.userId) return "请选择申请员工";
- if (
- this.form.regularizationId &&
- this.form.status &&
- !canEditRegularization({
- status: this.form.status,
- regularizationId: this.form.regularizationId
- })
- ) {
- return "当前单据状态不可编辑或提交";
- }
- if (!/^1[3-9]\d{9}$/.test(String(this.form.phone || "").trim())) return "请输入正确的 11 位手机号码";
- if (!this.form.groupId) return "请选择所属机构";
- if (!this.form.deptId) return "请选择所属部门";
- if (!this.form.positionSequenceId) return "请选择岗位序列";
- if (!this.form.positionId) return "请选择岗位名称";
- // 岗位性质:PC 的 cascadeRequiredRules 里为必填(选岗位后由接口自动带出)
- if (!this.form.positionType) return "请先选择岗位名称以自动带出岗位性质";
- if (!this.form.positionLevelId) return "请选择岗位层级";
- if (!this.form.regularizationType) return "请选择转正类型";
- if (!this.form.plannedRegularDate) return "请选择计划转正日期";
- if (this.form.effectiveDate && this.form.effectiveDate < this.form.plannedRegularDate) {
- return "实际生效日期不能早于计划转正日期";
- }
- // 申请资料:PC 表单未设为必填(rules 里没有 offlineFileIds),此处对齐
- if (this.isExtended) {
- if (!(Number(this.form.extensionDays) > 0)) return "请填写延期天数";
- if (!String(this.form.extensionReason || "").trim()) return "请填写延期原因";
- }
- return "";
- },
- /** 组装提交参数:附件数组 → 逗号拼接主键 */
- buildFormForSubmit() {
- return Object.assign({}, this.form, {
- approvalDocumentFileId: attachmentsOf(this.form).join(","),
- manager: this.form.managerUserName || "",
- // 页面已取消「线上签署 / 线下附件」切换,签署方式固定线下签署
- signingMethod: "OFFLINE",
- online: false,
- onlineDocumentTemplateId: null,
- });
- },
- async save(submit) {
- if (this.submitting) return;
- const error = this.validate();
- if (error) {
- uni.showToast({
- title: error,
- icon: "none"
- });
- return;
- }
- this.submitting = true;
- uni.showLoading({
- title: submit ? "提交中..." : "保存中..."
- });
- try {
- const form = this.buildFormForSubmit();
- if (!submit) {
- const id = await saveRegularizationDraft(form, false);
- if (id != null) this.form.regularizationId = id;
- uni.hideLoading();
- uni.showModal({
- title: "已保存草稿",
- content: "草稿已保存,可在「转正办理」列表中继续提交或编辑。",
- showCancel: false,
- success: () => this.leaveAfterSave(),
- });
- return;
- }
- const result = await submitRegularizationFlow({
- form,
- existingId: this.form.regularizationId,
- loding: false,
- });
- if (result.id != null) this.form.regularizationId = result.id;
- uni.hideLoading();
- uni.showModal({
- title: this.isEditMode ? "修改成功" : "提交成功",
- content: result.approvalStarted ?
- this.isEditMode ?
- "转正申请已修改并重新提交审批,可在「转正办理」列表中查看进度。" :
- "转正申请已提交审批,可在「转正办理」列表中查看进度。" :
- `转正申请已保存,但审批流程未发起:${result.approvalError}。请联系管理员处理。`,
- showCancel: false,
- success: () => this.leaveAfterSave(),
- });
- } catch (e) {
- uni.hideLoading();
- uni.showToast({
- title: (e && e.message) || "操作失败",
- icon: "none"
- });
- } finally {
- this.submitting = false;
- }
- },
- leaveAfterSave() {
- if (this.isEditMode) {
- this.backToApplicationList("/pages/hr/apply/list");
- return;
- }
- this.backToList();
- },
- onSaveDraft() {
- this.save(false);
- },
- submitApplication() {
- this.save(true);
- },
- },
- };
- </script>
- <style scoped lang="scss">
- @import "@/pages/hr/common-style.scss";
- .page-no-tab {
- padding-bottom: 170rpx;
- }
- .form-card {
- overflow: hidden;
- margin-top: 20rpx;
- }
- /* ============ 详情态(只读) ============ */
- .readonly-mode .arrow {
- display: none;
- }
- .detail-head {
- margin: 24rpx 24rpx 0;
- padding: 28rpx;
- }
- .detail-head-top {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 20rpx;
- }
- .detail-title {
- font-size: 32rpx;
- font-weight: 600;
- color: #1f2329;
- }
- .detail-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 12rpx 0;
- font-size: 26rpx;
- }
- .detail-label {
- color: #8b939f;
- }
- .detail-value {
- color: #1f2329;
- }
- .masked-card {
- margin: 20rpx 24rpx 0;
- padding: 26rpx 28rpx;
- }
- .masked-label {
- display: block;
- margin-bottom: 12rpx;
- color: #8b939f;
- font-size: 23rpx;
- }
- .masked-text {
- color: #4b5563;
- font-size: 26rpx;
- line-height: 1.6;
- }
- .status-tag.status-draft,
- .status-tag.status-cancelled {
- color: #6f7782;
- background: #eef1f3;
- }
- /* .section-head / .section-title / .section-tip 统一取自
- @/pages/hr/common-style.scss,本页不再单独覆盖,避免各页标题深浅不一。 */
- .field {
- position: relative;
- min-height: 108rpx;
- padding: 28rpx;
- display: flex;
- align-items: center;
- }
- .label {
- /* 「延期后试用结束日」8 个字在 200rpx 下会折行 → 加宽到 240rpx 并禁止换行 */
- width: 240rpx;
- flex-shrink: 0;
- white-space: nowrap;
- font-size: 28rpx;
- }
- .required::before {
- content: "*";
- color: #ee4d4d;
- margin-right: 6rpx;
- }
- .value {
- flex: 1;
- text-align: right;
- color: #1f2329;
- font-size: 27rpx;
- }
- .readonly-text {
- flex: 1;
- text-align: right;
- color: #8b939f;
- font-size: 27rpx;
- }
- .placeholder {
- flex: 1;
- text-align: right;
- color: #b2b7be;
- font-size: 27rpx;
- }
- .arrow {
- margin-left: 13rpx;
- color: #c4c8ce;
- font-size: 38rpx;
- }
- .input-flex {
- flex: 1;
- text-align: right;
- font-size: 27rpx;
- color: #1f2329;
- padding: 0 6rpx;
- }
- .number-input {
- flex: 1;
- height: 68rpx;
- text-align: right;
- font-size: 27rpx;
- color: #1f2329;
- }
- /* 签署方式固定线下签署(不提供线上模板切换),相关切换样式已移除 */
- .textarea-field {
- display: block;
- min-height: 240rpx;
- }
- .textarea-field .label {
- display: block;
- width: auto;
- margin-bottom: 20rpx;
- }
- .textarea-field textarea {
- width: 100%;
- height: 130rpx;
- padding: 18rpx;
- border-radius: 12rpx;
- background: #f6f7f8;
- font-size: 27rpx;
- }
- .counter {
- position: absolute;
- right: 45rpx;
- bottom: 38rpx;
- color: #b2b7be;
- font-size: 21rpx;
- }
- .field-file {
- flex: 1;
- display: flex;
- justify-content: flex-end;
- }
- .bottom-action {
- position: fixed;
- z-index: 10;
- left: 0;
- right: 0;
- bottom: 0;
- height: 120rpx;
- padding: 16rpx 24rpx;
- display: flex;
- gap: 18rpx;
- background: #fff;
- border-top: 1rpx solid #e8e9eb;
- }
- .draft-btn,
- .submit-btn {
- height: 84rpx;
- border-radius: 12rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 30rpx;
- }
- .draft-btn {
- width: 210rpx;
- border: 1rpx solid #d7dce2;
- color: #4b5563;
- }
- .submit-btn {
- flex: 1;
- background: #157a2c;
- color: #fff;
- }
- @media (min-width: 800px) {
- .bottom-action {
- max-width: 520px;
- left: 50%;
- transform: translateX(-50%);
- }
- }
- </style>
|