| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 |
- <template>
- <view class="page-no-tab">
- <!-- 提交成功后的简洁成功页:只保留成功提示与返回按钮,不再跳转入职列表页 -->
- <view v-if="submitted" class="success-page">
- <view class="success-icon"><text>✓</text></view>
- <text class="success-title">提交成功</text>
- <text class="success-desc">员工「{{ submittedName }}」的入职登记已提交</text>
- <view class="success-btn press" @click="backToWorkbench">返 回</view>
- </view>
- <block v-else>
- <uni-nav-bar
- fixed="true"
- statusBar="true"
- left-icon="back"
- title="入职登记表"
- background-color="#157A2C"
- color="#fff"
- @clickLeft="goBack"
- ></uni-nav-bar>
- <view class="section-title"><text>基础信息</text></view>
- <view class="form-card card section">
- <view class="field">
- <text class="label required">姓名</text>
- <input v-model="form.name" class="value-input" placeholder="请输入员工姓名" maxlength="20" />
- </view>
- <view class="divider"></view>
- <view class="field press">
- <text class="label required">入职日期</text>
- <picker class="field-picker" mode="date" :value="form.hireDate" :start="todayIso" @change="onHireDateChange">
- <text :class="form.hireDate ? 'value' : 'placeholder'">{{ form.hireDate || '请选择' }}</text>
- </picker>
- <text class="arrow">›</text>
- </view>
- <view class="divider"></view>
- <!-- 选择器统一用 hr-picker-row(口径与「个人档案」页一致:枚举走原生 picker,部门走树选择) -->
- <hr-picker-row
- label="所属机构"
- required
- :value="form.legalId"
- :display="form.legalName"
- :options="companies.map((c) => ({ label: c.name, value: c.id }))"
- :disabled="!companies.length"
- hint="当前账号未配置所属公司"
- @change="onLegalChange"
- />
- <view class="divider"></view>
- <hr-picker-row
- mode="tree"
- label="目标部门"
- required
- :value="form.deptId"
- :display="form.deptPath"
- :tree="deptTree"
- :disabled="!form.legalId"
- hint="请先选择所属机构"
- @change="onDeptTreePick"
- />
- <view class="divider"></view>
- <hr-picker-row
- label="岗位序列"
- required
- :value="form.positionSequenceId"
- :display="form.positionSequence"
- :options="sequenceOptions.map((item) => ({ label: item.name, value: item.id }))"
- :disabled="!form.deptId"
- hint="请先选择目标部门"
- @change="onSequenceChange"
- />
- <view class="divider"></view>
- <hr-picker-row
- label="岗位"
- required
- :value="form.positionId"
- :display="form.positionName"
- :options="positionOptions.map((item) => ({ label: item.name, value: item.id }))"
- :disabled="!form.positionSequenceId"
- hint="请先选择岗位序列"
- @change="onPositionChange"
- />
- <view class="divider"></view>
- <view class="field">
- <text class="label required">岗位性质</text>
- <text :class="form.positionTypeName ? 'value' : 'placeholder'">{{ form.positionTypeName || '选择岗位后自动带出' }}</text>
- </view>
- <view class="divider"></view>
- <hr-picker-row
- label="岗位层级"
- required
- :value="form.positionLevelId"
- :display="form.positionLevel"
- :options="levelOptions.map((item) => ({ label: item.name, value: item.id }))"
- :disabled="!form.positionId"
- hint="请先选择岗位"
- @change="onLevelChange"
- />
- <view class="divider"></view>
- <hr-picker-row
- label="员工状态"
- required
- :value="form.status"
- :display="statusLabel"
- :options="statusOptions"
- @change="onStatusChange"
- />
- </view>
- <view class="section-title"><text>联系信息</text></view>
- <view class="form-card card section">
- <view class="field">
- <text class="label required">手机号</text>
- <input v-model="form.phone" class="value-input" type="number" placeholder="11位手机号" maxlength="11" />
- </view>
- <view class="divider"></view>
- <view class="field">
- <text class="label required">身份证号</text>
- <input v-model="form.idNumber" class="value-input" placeholder="18位身份证号" maxlength="18" />
- </view>
- </view>
- <view class="section-title"><text>入职附件</text></view>
- <view class="section attachment-section">
- <fileMain v-model="form.attachmentIds" />
- </view>
- <view class="footer-tip muted">
- <text>提交后将直接创建员工档案,请确认信息填写无误。</text>
- </view>
- <view class="footer-actions">
- <view class="ghost-btn press" @click="onCancel">取消</view>
- <view class="primary-btn press" @click="onSubmit">提交入职登记</view>
- </view>
- </block>
- </view>
- </template>
- <script>
- import { createUserOnboarding, USER_STATUS_OPTIONS, USER_STATUS_LABEL } from "@/api/hr/onboarding";
- import { back, switchMain } from "@/utils/hr/router";
- import { listOrganizations } from "@/api/hr/organization";
- import {
- getDeptPositionSequences,
- getDeptPositions,
- getDeptPositionType,
- getDeptPositionLevels,
- } from "@/api/hr/position";
- import { buildDeptTreeUnderLegal } from "@/utils/hr/organization-tree";
- import { getLoginGroupOptions } from "@/utils/hr/current-org";
- import fileMain from "@/pages/doc/index.vue";
- import HrPickerRow from "@/components/hr/HrPickerRow.vue";
- function todayIso() {
- const d = new Date();
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
- }
- export default {
- components: { fileMain, HrPickerRow },
- data() {
- return {
- todayIso: todayIso(),
- form: {
- name: "",
- hireDate: "",
- legalId: null,
- legalName: "",
- deptIds: [],
- deptId: null,
- deptPath: "",
- positionSequenceId: null,
- positionSequence: "",
- positionId: null,
- positionName: "",
- positionType: "",
- positionTypeName: "",
- positionLevelId: null,
- positionLevel: "",
- status: 1,
- phone: "",
- idNumber: "",
- attachmentIds: [],
- },
- orgList: [],
- companies: [],
- deptTree: [],
- sequenceOptions: [],
- sequenceLoading: false,
- positionOptions: [],
- positionLoading: false,
- levelOptions: [],
- levelLoading: false,
- submitting: false,
- // 提交成功后切换到页内成功页(不再回入职列表页,该页已删除)
- submitted: false,
- submittedName: "",
- };
- },
- computed: {
- statusLabel() {
- return USER_STATUS_LABEL[this.form.status] || "全职";
- },
- /** 员工状态选项:模块级常量要挂到实例上,模板才能访问 */
- statusOptions() {
- return USER_STATUS_OPTIONS;
- },
- },
- onLoad() {
- uni.setNavigationBarTitle({ title: "入职登记表" });
- // 机构选项与 PC 一致:只取当前登录账号所属公司(登录缓存 loginChangeGroupVOList),
- // 默认带出第一条;部门树仍从组织全量列表按所选机构过滤
- this.refreshCompanies();
- this.refreshOrgList();
- this.clearPositionCascade();
- },
- onShow() {
- if (!this.companies.length) this.refreshCompanies();
- },
- onUnload() {
- this.resetForm();
- },
- methods: {
- goBack() {
- back("/pages/home/home");
- },
- // 成功页的返回按钮:回到工作台
- backToWorkbench() {
- switchMain("/pages/home/home");
- },
- /** 机构选项:只列当前账号所属公司,默认选中第一条(与 PC「默认取缓存第一条」约定一致) */
- refreshCompanies() {
- this.companies = getLoginGroupOptions();
- if (!this.companies.length) {
- uni.showToast({ title: "当前账号未配置所属公司,请联系管理员", icon: "none" });
- return;
- }
- const first = this.companies[0];
- this.form.legalId = first.id;
- this.form.legalName = first.name;
- this.refreshDeptTree(first.id);
- },
- /** 组织全量列表仅用于部门级联(与 PC loadOrganizations 分工一致) */
- async refreshOrgList() {
- try {
- this.orgList = await listOrganizations();
- if (this.form.legalId) this.refreshDeptTree(this.form.legalId);
- } catch (err) {
- this.orgList = [];
- uni.showToast({ title: "组织列表加载失败", icon: "none" });
- }
- },
- refreshDeptTree(legalId) {
- if (!legalId) {
- this.deptTree = [];
- return;
- }
- // 所属机构已经在上面的字段选过,不再作为目标部门的第一列重复展示:
- // 直接从部门层开始,选出来的部门路径也不带公司名。
- const nodes = buildDeptTreeUnderLegal(this.orgList, legalId);
- const legal = nodes.find((node) => String(node.id) === String(legalId)) || nodes[0];
- this.deptTree = legal && Array.isArray(legal.children) ? legal.children : [];
- },
- onHireDateChange(e) {
- this.form.hireDate = (e && e.detail && e.detail.value) || "";
- },
- /** 所属机构:换机构要清空部门与整条岗位级联,并按新机构重建部门树 */
- onLegalChange(legalId) {
- const hit = this.companies.find((c) => String(c.id) === String(legalId));
- this.form.legalId = legalId;
- this.form.legalName = (hit && hit.name) || "";
- this.form.deptIds = [];
- this.form.deptId = null;
- this.form.deptPath = "";
- this.clearPositionCascade();
- this.refreshDeptTree(legalId);
- },
- /** 目标部门:树选择器回传节点 id,这里补齐祖先链(deptIds)与路径名 */
- onDeptTreePick(deptId) {
- const chain = this.deptChainById(this.deptTree, deptId);
- this.form.deptIds = chain.map((node) => node.id);
- this.form.deptId = deptId;
- this.form.deptPath = chain.map((node) => node.name).filter(Boolean).join(" / ");
- // 部门变化 → 岗位级联全部重置,再拉该部门的岗位序列(与 PC onLegalChange/onDeptChange 语义一致)
- this.clearPositionCascade();
- this.loadSequences();
- },
- /** 在部门树里按 id 取「根 → 自身」的节点链 */
- deptChainById(tree, id, parents = []) {
- for (const node of tree || []) {
- const path = parents.concat(node);
- if (String(node.id) === String(id)) return path;
- const hit = this.deptChainById(node.children || [], id, path);
- if (hit.length) return hit;
- }
- return [];
- },
- /** 员工状态 */
- onStatusChange(status) {
- this.form.status = status;
- },
- // ============ 岗位级联:部门 → 岗位序列 → 岗位 → 性质(自动)/层级(与 PC 一致) ============
- /** 部门 → 岗位序列 */
- async loadSequences() {
- if (!this.form.deptId) {
- this.sequenceOptions = [];
- return;
- }
- this.sequenceLoading = true;
- try {
- const data = await getDeptPositionSequences(this.form.deptId, false);
- this.sequenceOptions = (Array.isArray(data) ? data : [])
- .filter((item) => item && item.positionSequenceId != null)
- .map((item) => ({
- id: String(item.positionSequenceId),
- name: item.positionSequenceName || item.positionSequence || "",
- }));
- } catch (error) {
- this.sequenceOptions = [];
- } finally {
- this.sequenceLoading = false;
- }
- },
- /** 岗位序列:序列变化 → 岗位及以下重置,再拉该序列下的岗位 */
- async onSequenceChange(sequenceId) {
- const hit = this.sequenceOptions.find((item) => String(item.id) === String(sequenceId));
- this.form.positionSequenceId = sequenceId;
- this.form.positionSequence = (hit && hit.name) || "";
- this.clearPositionBelow();
- await this.loadPositions();
- },
- /** 部门 + 序列 → 岗位 */
- async loadPositions() {
- if (!this.form.deptId || !this.form.positionSequenceId) {
- this.positionOptions = [];
- return;
- }
- this.positionLoading = true;
- try {
- const data = await getDeptPositions(
- { deptId: this.form.deptId, positionSequenceId: this.form.positionSequenceId },
- false
- );
- this.positionOptions = (Array.isArray(data) ? data : [])
- .filter((item) => item && item.positionId != null)
- .map((item) => ({ id: String(item.positionId), name: item.positionName || "" }));
- } catch (error) {
- this.positionOptions = [];
- } finally {
- this.positionLoading = false;
- }
- },
- /** 岗位:岗位变化 → 性质重新自动带出,层级重置 */
- async onPositionChange(positionId) {
- const hit = this.positionOptions.find((item) => String(item.id) === String(positionId));
- this.form.positionId = positionId;
- this.form.positionName = (hit && hit.name) || "";
- this.form.positionType = "";
- this.form.positionTypeName = "";
- this.form.positionLevelId = null;
- this.form.positionLevel = "";
- this.levelOptions = [];
- await this.loadTypeAndLevels();
- },
- /** 岗位性质自动带出 + 岗位层级下拉(与 PC loadPositionType/loadPositionLevels 一致) */
- async loadTypeAndLevels() {
- const { deptId, positionId, positionSequenceId } = this.form;
- if (!deptId || !positionId || !positionSequenceId) return;
- try {
- const data = await getDeptPositionType(
- { deptId, positionId, positionSequenceId },
- false
- );
- const first = (Array.isArray(data) ? data : [])[0];
- this.form.positionType = (first && first.positionType) || "";
- this.form.positionTypeName = (first && first.positionTypeName) || "";
- } catch (error) {
- this.form.positionType = "";
- this.form.positionTypeName = "";
- }
- if (!this.form.positionType) return;
- this.levelLoading = true;
- try {
- const data = await getDeptPositionLevels(
- { deptId, positionId, positionSequenceId, positionType: this.form.positionType },
- false
- );
- this.levelOptions = (Array.isArray(data) ? data : [])
- .filter((item) => item && item.positionLevelId != null)
- .map((item) => ({ id: String(item.positionLevelId), name: item.positionLevel || "" }));
- } catch (error) {
- this.levelOptions = [];
- } finally {
- this.levelLoading = false;
- }
- },
- /** 岗位层级 */
- onLevelChange(levelId) {
- const hit = this.levelOptions.find((item) => String(item.id) === String(levelId));
- this.form.positionLevelId = levelId;
- this.form.positionLevel = (hit && hit.name) || "";
- },
- /** 部门变化时清空整条岗位级联 */
- clearPositionCascade() {
- this.form.positionSequenceId = null;
- this.form.positionSequence = "";
- this.clearPositionBelow();
- this.sequenceOptions = [];
- },
- /** 序列以下(岗位/性质/层级)清空 */
- clearPositionBelow() {
- this.form.positionId = null;
- this.form.positionName = "";
- this.form.positionType = "";
- this.form.positionTypeName = "";
- this.form.positionLevelId = null;
- this.form.positionLevel = "";
- this.positionOptions = [];
- this.levelOptions = [];
- },
- validate() {
- const f = this.form;
- if (!f.name.trim()) return "请填写姓名";
- if (!f.hireDate) return "请选择入职日期";
- if (!f.legalId) return "请选择所属机构";
- if (!f.deptId) return "请选择目标部门";
- if (!f.positionSequenceId) return "请选择岗位序列";
- if (!f.positionId) return "请选择岗位";
- if (!f.positionTypeName) return "岗位性质未自动带出,请重新选择岗位";
- if (!f.positionLevelId) return "请选择岗位层级";
- if (!f.status) return "请选择员工状态";
- if (!f.phone.trim()) return "请填写手机号";
- if (!/^1[3-9]\d{9}$/.test(f.phone.trim())) return "手机号格式不正确";
- if (!f.idNumber.trim()) return "请填写身份证号";
- if (!/^\d{17}[\dXx]$/.test(f.idNumber.trim())) return "身份证号格式不正确";
- return null;
- },
- async onSubmit() {
- const error = this.validate();
- if (error) {
- uni.showToast({ title: error, icon: "none" });
- return;
- }
- if (this.submitting) return;
- this.submitting = true;
- uni.showLoading({ title: "提交中..." });
- try {
- await createUserOnboarding(this.form);
- uni.hideLoading();
- // 提交成功后不再跳回入职列表页(该页已删除),页内切换到简洁成功页
- this.submittedName = this.form.name.trim();
- this.submitted = true;
- } catch (e) {
- uni.hideLoading();
- uni.showToast({ title: (e && e.message) || "提交失败", icon: "none" });
- } finally {
- this.submitting = false;
- }
- },
- onCancel() {
- uni.showModal({
- title: "放弃填写",
- content: "当前填写的内容将不会保留,确定离开?",
- success: (r) => {
- if (r.confirm) this.goBack();
- },
- });
- },
- resetForm() {
- Object.assign(this.form, {
- name: "",
- hireDate: "",
- legalId: null,
- legalName: "",
- deptIds: [],
- deptId: null,
- deptPath: "",
- positionSequenceId: null,
- positionSequence: "",
- positionId: null,
- positionName: "",
- positionType: "",
- positionTypeName: "",
- positionLevelId: null,
- positionLevel: "",
- status: 1,
- phone: "",
- idNumber: "",
- attachmentIds: [],
- });
- this.clearPositionCascade();
- this.orgList = [];
- this.companies = [];
- this.deptTree = [];
- },
- },
- };
- </script>
- <style scoped lang="scss">
- @import "@/pages/hr/common-style.scss";
- .page-no-tab {
- padding-bottom: 40rpx;
- }
- /* .section-title 统一取自 @/pages/hr/common-style.scss */
- .attachment-section {
- margin: 0 24rpx;
- }
- .form-card {
- padding: 4rpx 28rpx;
- }
- .field {
- display: flex;
- align-items: center;
- min-height: 96rpx;
- font-size: 28rpx;
- }
- .field .label {
- width: 180rpx;
- color: #4b5563;
- }
- .field .label.required::before {
- content: "*";
- color: #ee4d4d;
- margin-right: 4rpx;
- }
- .field .value,
- .field input,
- .field picker,
- .field .placeholder {
- flex: 1;
- text-align: right;
- color: #1f2329;
- font-size: 28rpx;
- }
- .field picker {
- min-width: 0;
- }
- .field .placeholder {
- color: #b5bac3;
- }
- .value-input {
- text-align: right;
- height: 96rpx;
- }
- .placeholder {
- color: #b5bac3;
- }
- .arrow {
- margin-left: 12rpx;
- color: #c4c8ce;
- font-size: 32rpx;
- }
- .divider {
- height: 1rpx;
- background: #edf0f2;
- margin-left: 180rpx;
- }
- .footer-tip {
- padding: 28rpx 28rpx 8rpx;
- font-size: 22rpx;
- text-align: center;
- }
- .footer-actions {
- display: flex;
- gap: 18rpx;
- padding: 24rpx 24rpx calc(40rpx + env(safe-area-inset-bottom));
- }
- .footer-actions > view {
- flex: 1;
- }
- /* 提交成功页 */
- .success-page {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 220rpx 60rpx 0;
- }
- .success-icon {
- width: 132rpx;
- height: 132rpx;
- border-radius: 50%;
- background: #157a2c;
- display: flex;
- align-items: center;
- justify-content: center;
- text {
- color: #fff;
- font-size: 76rpx;
- line-height: 1;
- }
- }
- .success-title {
- margin-top: 40rpx;
- font-size: 36rpx;
- font-weight: 600;
- color: #263238;
- }
- .success-desc {
- margin-top: 18rpx;
- font-size: 26rpx;
- color: #78909c;
- text-align: center;
- }
- .success-btn {
- margin-top: 96rpx;
- width: 100%;
- height: 88rpx;
- line-height: 88rpx;
- text-align: center;
- border-radius: 12rpx;
- background: #157a2c;
- color: #fff;
- font-size: 30rpx;
- }
- </style>
|