|
|
@@ -0,0 +1,770 @@
|
|
|
+<template>
|
|
|
+ <PageShell>
|
|
|
+ <div class="post-create-page">
|
|
|
+ <div class="post-create-card">
|
|
|
+ <div class="post-create-header">
|
|
|
+ <div>
|
|
|
+ <div class="post-create-title">{{ pageTitle }}</div>
|
|
|
+ <div class="post-create-subtitle">分三步完成岗位信息填写</div>
|
|
|
+ </div>
|
|
|
+ <el-button @click="handleCancel">返回</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-steps :active="activeStep" finish-status="success" simple class="post-steps">
|
|
|
+ <el-step title="第一步骤" />
|
|
|
+ <el-step title="第二步骤" />
|
|
|
+ <el-step title="第三步骤" />
|
|
|
+ </el-steps>
|
|
|
+
|
|
|
+ <div class="post-create-body">
|
|
|
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="110px" class="post-create-form">
|
|
|
+ <div v-show="activeStep === 0" class="step-panel">
|
|
|
+ <el-form-item label="岗位名称" prop="postNamePath">
|
|
|
+ <el-cascader
|
|
|
+ v-model="form.postNamePath"
|
|
|
+ :options="postNameOptions"
|
|
|
+ :props="postNameProps"
|
|
|
+ clearable
|
|
|
+ filterable
|
|
|
+ placeholder="请选择岗位名称"
|
|
|
+ class="full-width-select"
|
|
|
+ @change="handlePostNameChange"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="岗位编码" prop="postCode">
|
|
|
+ <el-input v-model="form.postCode" placeholder="请输入岗位编码" maxlength="64" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="地区" prop="regionCodes">
|
|
|
+ <el-cascader v-model="form.regionCodes" :options="regionOptions" :props="regionProps" clearable filterable placeholder="请选择地区" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="详细地址" prop="addressDetail">
|
|
|
+ <el-input v-model="form.addressDetail" placeholder="请输入详细地址" maxlength="200" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="岗位类型" prop="jobType">
|
|
|
+ <el-select v-model="form.jobType" placeholder="请选择岗位类型" clearable>
|
|
|
+ <el-option v-for="item in jobTypeOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="薪资" prop="salary">
|
|
|
+ <el-input v-model="form.salary" placeholder="请输入薪资" maxlength="50" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="岗位标签" prop="tags">
|
|
|
+ <el-select
|
|
|
+ v-model="form.tags"
|
|
|
+ multiple
|
|
|
+ filterable
|
|
|
+ collapse-tags
|
|
|
+ collapse-tags-tooltip
|
|
|
+ placeholder="请选择岗位标签"
|
|
|
+ class="full-width-select"
|
|
|
+ >
|
|
|
+ <el-option v-for="item in tagOptions" :key="item.id" :label="item.name" :value="item.name" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="招聘人数" prop="recruitCount">
|
|
|
+ <div class="inline-mixed-field">
|
|
|
+ <el-input-number v-model="form.recruitCount" :min="1" :disabled="form.unlimitedRecruitCount" placeholder="请输入招聘人数" />
|
|
|
+ <el-checkbox v-model="form.unlimitedRecruitCount" @change="handleRecruitUnlimitedChange">不限</el-checkbox>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="报名时间" prop="applyTimeRange">
|
|
|
+ <div class="inline-mixed-field full-width-row">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="form.applyTimeRange"
|
|
|
+ type="daterange"
|
|
|
+ is-range
|
|
|
+ range-separator="至"
|
|
|
+ start-placeholder="开始日期"
|
|
|
+ end-placeholder="结束日期"
|
|
|
+ format="YYYY-MM-DD"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ :disabled="form.unlimitedApplyTime"
|
|
|
+ />
|
|
|
+ <el-checkbox v-model="form.unlimitedApplyTime" @change="handleApplyUnlimitedChange">不限</el-checkbox>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="急招" prop="urgent">
|
|
|
+ <el-radio-group v-model="form.urgent">
|
|
|
+ <el-radio :value="'0'">否</el-radio>
|
|
|
+ <el-radio :value="'1'">是</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="学校要求" prop="schoolRequirement">
|
|
|
+ <el-select v-model="form.schoolRequirement" placeholder="请选择学校要求" clearable>
|
|
|
+ <el-option v-for="item in schoolRequirementOptions" :key="item" :label="item" :value="item" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="性别" prop="genderRequirement">
|
|
|
+ <el-select v-model="form.genderRequirement" placeholder="请选择性别" clearable>
|
|
|
+ <el-option v-for="item in genderOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="年级" prop="gradeRequirement">
|
|
|
+ <el-select v-model="form.gradeRequirement" placeholder="请选择年级" clearable>
|
|
|
+ <el-option v-for="item in gradeOptions" :key="item" :label="item" :value="item" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="到岗时间" prop="arrivalTime">
|
|
|
+ <el-select v-model="form.arrivalTime" placeholder="请选择到岗时间" clearable>
|
|
|
+ <el-option v-for="item in arrivalTimeOptions" :key="item" :label="item" :value="item" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="实习时间" prop="internshipDuration">
|
|
|
+ <el-select v-model="form.internshipDuration" placeholder="请选择实习时间" clearable>
|
|
|
+ <el-option v-for="item in internshipDurationOptions" :key="item" :label="item" :value="item" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="是否出差" prop="travelRequired">
|
|
|
+ <el-radio-group v-model="form.travelRequired">
|
|
|
+ <el-radio :value="'0'">否</el-radio>
|
|
|
+ <el-radio :value="'1'">是</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="岗位描述" prop="jobDescription">
|
|
|
+ <el-input v-model="form.jobDescription" type="textarea" :rows="5" placeholder="请输入岗位描述" maxlength="500" show-word-limit />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-show="activeStep === 1" class="step-panel">
|
|
|
+ <el-form-item label="岗位级别" prop="postCategory">
|
|
|
+ <el-select v-model="form.postCategory" placeholder="请选择岗位级别" clearable>
|
|
|
+ <el-option v-for="item in postLevelOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="测评时长" prop="evaluationDuration">
|
|
|
+ <div class="inline-mixed-field input-unit-field">
|
|
|
+ <el-input v-model="form.evaluationDuration" placeholder="请输入" maxlength="10" />
|
|
|
+ <span class="field-unit">分钟</span>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="及格线" prop="passingScores">
|
|
|
+ <div class="score-list">
|
|
|
+ <div v-for="(item, index) in form.passingScores" :key="item.key" class="score-row">
|
|
|
+ <span class="score-label">能力{{ index + 1 }}(总分100)</span>
|
|
|
+ <div class="inline-mixed-field input-unit-field score-input-wrap">
|
|
|
+ <el-input v-model="item.score" placeholder="请输入及格分" maxlength="10" />
|
|
|
+ <span class="field-unit">分</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ <div v-show="activeStep === 2" class="step-panel finish-panel">
|
|
|
+ <div class="finish-icon">✓</div>
|
|
|
+ <div class="finish-title">创建成功</div>
|
|
|
+ <div class="finish-subtitle">快去平台审核</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="post-create-footer">
|
|
|
+ <template v-if="activeStep < 2">
|
|
|
+ <el-button @click="handleCancel">取消</el-button>
|
|
|
+ <el-button v-if="activeStep > 0" @click="activeStep -= 1">上一步</el-button>
|
|
|
+ <el-button v-if="activeStep === 0" type="primary" @click="handleNext">下一步</el-button>
|
|
|
+ <el-button v-if="activeStep === 1" type="primary" :loading="submitting" @click="handleSubmit">{{ submitButtonText }}</el-button>
|
|
|
+ </template>
|
|
|
+ <el-button v-else type="primary" @click="handleFinish">完成</el-button>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </PageShell>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup name="PostManageCreate" lang="ts">
|
|
|
+import { computed, getCurrentInstance, onMounted, reactive, ref, toRefs, type ComponentInternalInstance } from 'vue';
|
|
|
+import { useRoute, useRouter } from 'vue-router';
|
|
|
+import { regionData, codeToText } from 'element-china-area-data';
|
|
|
+import type { CascaderOption } from 'element-plus';
|
|
|
+import PageShell from '@/components/PageShell/index.vue';
|
|
|
+import { addPostManage, getPostManage, updatePostManage } from '@/api/main/postManage';
|
|
|
+import type { MainPostApplyVO } from '@/api/main/postManage/types';
|
|
|
+import { getDicts } from '@/api/system/dict/data';
|
|
|
+import { listIndustry, listIndustrySkill } from '@/api/system/industry';
|
|
|
+import type { IndustrySkillVO, IndustryVO } from '@/api/system/industry/types';
|
|
|
+import { listTag } from '@/api/system/tag';
|
|
|
+import type { TagVO } from '@/api/system/tag/types';
|
|
|
+import type { DictDataVO } from '@/api/system/dict/data/types';
|
|
|
+import { useUserStore } from '@/store/modules/user';
|
|
|
+
|
|
|
+interface PostCreateFormModel {
|
|
|
+ id?: number | string;
|
|
|
+ auditId?: number | string;
|
|
|
+ postName: string;
|
|
|
+ postNamePath: Array<string | number>;
|
|
|
+ regionCodes: string[];
|
|
|
+ addressDetail: string;
|
|
|
+ jobType: string;
|
|
|
+ salary: string;
|
|
|
+ tags: string[];
|
|
|
+ recruitCount: number | undefined;
|
|
|
+ unlimitedRecruitCount: boolean;
|
|
|
+ applyTimeRange: string[];
|
|
|
+ unlimitedApplyTime: boolean;
|
|
|
+ urgent: string;
|
|
|
+ schoolRequirement: string;
|
|
|
+ genderRequirement: string;
|
|
|
+ gradeRequirement: string;
|
|
|
+ arrivalTime: string;
|
|
|
+ internshipDuration: string;
|
|
|
+ travelRequired: string;
|
|
|
+ jobDescription: string;
|
|
|
+ postCode: string;
|
|
|
+ postCategory: string;
|
|
|
+ evaluationDuration: string;
|
|
|
+ passingScores: Array<{ key: string; score: string }>;
|
|
|
+ postSort: number;
|
|
|
+ status: string;
|
|
|
+ remark: string;
|
|
|
+}
|
|
|
+
|
|
|
+const router = useRouter();
|
|
|
+const route = useRoute();
|
|
|
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
+const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
|
|
|
+const userStore = useUserStore();
|
|
|
+
|
|
|
+const formRef = ref<ElFormInstance>();
|
|
|
+const activeStep = ref(0);
|
|
|
+const submitting = ref(false);
|
|
|
+const editId = computed(() => route.query.id as string | undefined);
|
|
|
+const isEdit = computed(() => Boolean(editId.value));
|
|
|
+const pageTitle = computed(() => (isEdit.value ? '编辑岗位' : '添加岗位'));
|
|
|
+const submitButtonText = computed(() => (isEdit.value ? '保存并提交审核' : '提交审核'));
|
|
|
+const regionOptions = regionData as unknown as CascaderOption[];
|
|
|
+const regionProps = { value: 'code', label: 'label', children: 'children' };
|
|
|
+const postNameOptions = ref<CascaderOption[]>([]);
|
|
|
+const postNameProps = { value: 'value', label: 'label', children: 'children', emitPath: true, checkStrictly: false };
|
|
|
+const jobTypeOptions = ref<DictDataVO[]>([]);
|
|
|
+const postLevelOptions = ref<DictDataVO[]>([]);
|
|
|
+const tagOptions = ref<TagVO[]>([]);
|
|
|
+const schoolRequirementOptions = ['不限', '大专', '本科', '硕士及以上', '双一流优先'];
|
|
|
+const genderOptions = [
|
|
|
+ { label: '不限', value: '0' },
|
|
|
+ { label: '男', value: '1' },
|
|
|
+ { label: '女', value: '2' }
|
|
|
+];
|
|
|
+const gradeOptions = ['不限', '大一', '大二', '大三', '大四', '研一', '研二', '研三'];
|
|
|
+const arrivalTimeOptions = ['立即到岗', '3天内', '一周内', '两周内', '一个月内'];
|
|
|
+const internshipDurationOptions = ['不限', '1个月', '3个月', '6个月', '长期'];
|
|
|
+
|
|
|
+const form = reactive<PostCreateFormModel>({
|
|
|
+ id: undefined,
|
|
|
+ auditId: undefined,
|
|
|
+ postName: '',
|
|
|
+ postNamePath: [],
|
|
|
+ regionCodes: [],
|
|
|
+ addressDetail: '',
|
|
|
+ jobType: '',
|
|
|
+ salary: '',
|
|
|
+ tags: [],
|
|
|
+ recruitCount: undefined,
|
|
|
+ unlimitedRecruitCount: false,
|
|
|
+ applyTimeRange: [],
|
|
|
+ unlimitedApplyTime: false,
|
|
|
+ urgent: '0',
|
|
|
+ schoolRequirement: '',
|
|
|
+ genderRequirement: '0',
|
|
|
+ gradeRequirement: '',
|
|
|
+ arrivalTime: '',
|
|
|
+ internshipDuration: '',
|
|
|
+ travelRequired: '0',
|
|
|
+ jobDescription: '',
|
|
|
+ postCode: '',
|
|
|
+ postCategory: '',
|
|
|
+ evaluationDuration: '',
|
|
|
+ passingScores: [
|
|
|
+ { key: 'ability-1', score: '' },
|
|
|
+ { key: 'ability-2', score: '' },
|
|
|
+ { key: 'ability-3', score: '' }
|
|
|
+ ],
|
|
|
+ postSort: 0,
|
|
|
+ status: '0',
|
|
|
+ remark: ''
|
|
|
+});
|
|
|
+
|
|
|
+const rules = reactive({
|
|
|
+ postNamePath: [{ required: true, message: '岗位名称不能为空', trigger: 'change' }],
|
|
|
+ regionCodes: [{ required: true, message: '地区不能为空', trigger: 'change' }],
|
|
|
+ addressDetail: [{ required: true, message: '详细地址不能为空', trigger: 'blur' }],
|
|
|
+ jobType: [{ required: true, message: '岗位类型不能为空', trigger: 'change' }],
|
|
|
+ salary: [{ required: true, message: '薪资不能为空', trigger: 'blur' }],
|
|
|
+ postCode: [{ required: true, message: '岗位编码不能为空', trigger: 'blur' }],
|
|
|
+ postCategory: [{ required: true, message: '岗位级别不能为空', trigger: 'change' }],
|
|
|
+ evaluationDuration: [{ required: true, message: '测评时长不能为空', trigger: 'blur' }],
|
|
|
+ postSort: [{ required: true, message: '岗位顺序不能为空', trigger: 'blur' }]
|
|
|
+});
|
|
|
+
|
|
|
+const regionText = computed(() =>
|
|
|
+ form.regionCodes
|
|
|
+ .map((code) => codeToText[code] || '')
|
|
|
+ .filter(Boolean)
|
|
|
+ .join(' / ')
|
|
|
+);
|
|
|
+const applyTimeText = computed(() => (form.applyTimeRange.length === 2 ? `${form.applyTimeRange[0]} 至 ${form.applyTimeRange[1]}` : '--'));
|
|
|
+const statusText = computed(() => sys_normal_disable.value?.find((item: any) => item.value === form.status)?.label || '--');
|
|
|
+const yesNoText = (value: string) => (value === '1' ? '是' : '否');
|
|
|
+const genderText = (value: string) => genderOptions.find((item) => item.value === value)?.label || '--';
|
|
|
+
|
|
|
+const buildPostNameOptions = (industryList: IndustryVO[], skillList: IndustrySkillVO[]) => {
|
|
|
+ const rootIndustryMap = new Map<string, CascaderOption>();
|
|
|
+ const secondIndustryMap = new Map<string, CascaderOption>();
|
|
|
+
|
|
|
+ industryList.forEach((item) => {
|
|
|
+ const node: CascaderOption = {
|
|
|
+ value: item.industryId,
|
|
|
+ label: item.industryName,
|
|
|
+ children: []
|
|
|
+ };
|
|
|
+ if (Number(item.parentId) === 0) {
|
|
|
+ rootIndustryMap.set(String(item.industryId), node);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ secondIndustryMap.set(String(item.industryId), node);
|
|
|
+ });
|
|
|
+
|
|
|
+ industryList.forEach((item) => {
|
|
|
+ if (Number(item.parentId) === 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const parentNode = rootIndustryMap.get(String(item.parentId));
|
|
|
+ const currentNode = secondIndustryMap.get(String(item.industryId));
|
|
|
+ if (parentNode && currentNode) {
|
|
|
+ (parentNode.children ||= []).push(currentNode);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ skillList.forEach((item) => {
|
|
|
+ const parentNode = secondIndustryMap.get(String(item.industryId));
|
|
|
+ if (!parentNode) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ (parentNode.children ||= []).push({
|
|
|
+ value: item.skillId,
|
|
|
+ label: item.skillName
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ postNameOptions.value = Array.from(rootIndustryMap.values());
|
|
|
+};
|
|
|
+
|
|
|
+const findPostNameLabel = (options: CascaderOption[], values: Array<string | number>, depth = 0): string => {
|
|
|
+ const currentValue = values[depth];
|
|
|
+ const currentNode = options.find((item) => item.value === currentValue);
|
|
|
+ if (!currentNode) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ if (depth === values.length - 1 || !currentNode.children?.length) {
|
|
|
+ return String(currentNode.label || '');
|
|
|
+ }
|
|
|
+ return findPostNameLabel(currentNode.children as CascaderOption[], values, depth + 1);
|
|
|
+};
|
|
|
+
|
|
|
+const handlePostNameChange = (value: Array<string | number>) => {
|
|
|
+ form.postName = value.length ? findPostNameLabel(postNameOptions.value, value) : '';
|
|
|
+};
|
|
|
+
|
|
|
+const findPostNamePathByLabel = (options: CascaderOption[], label: string, path: Array<string | number> = []): Array<string | number> => {
|
|
|
+ for (const option of options) {
|
|
|
+ const nextPath = [...path, option.value as string | number];
|
|
|
+ if (String(option.label || '') === label && (!option.children || option.children.length === 0)) {
|
|
|
+ return nextPath;
|
|
|
+ }
|
|
|
+ if (option.children?.length) {
|
|
|
+ const childPath = findPostNamePathByLabel(option.children as CascaderOption[], label, nextPath);
|
|
|
+ if (childPath.length) {
|
|
|
+ return childPath;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return [];
|
|
|
+};
|
|
|
+
|
|
|
+const findRegionPathByLabels = (province?: string, city?: string, district?: string): string[] => {
|
|
|
+ if (!province) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ const provinceOption = regionOptions.find((item) => String(item.label || '') === province);
|
|
|
+ if (!provinceOption) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ const provinceCode = String((provinceOption as any).code || provinceOption.value || '');
|
|
|
+ if (!city) {
|
|
|
+ return provinceCode ? [provinceCode] : [];
|
|
|
+ }
|
|
|
+
|
|
|
+ const cityOptions = (provinceOption.children || []) as CascaderOption[];
|
|
|
+ const cityOption = cityOptions.find((item) => String(item.label || '') === city);
|
|
|
+ if (!cityOption) {
|
|
|
+ return provinceCode ? [provinceCode] : [];
|
|
|
+ }
|
|
|
+
|
|
|
+ const cityCode = String((cityOption as any).code || cityOption.value || '');
|
|
|
+ if (!district) {
|
|
|
+ return [provinceCode, cityCode].filter(Boolean);
|
|
|
+ }
|
|
|
+
|
|
|
+ const districtOptions = (cityOption.children || []) as CascaderOption[];
|
|
|
+ const districtOption = districtOptions.find((item) => String(item.label || '') === district);
|
|
|
+ if (!districtOption) {
|
|
|
+ return [provinceCode, cityCode].filter(Boolean);
|
|
|
+ }
|
|
|
+
|
|
|
+ const districtCode = String((districtOption as any).code || districtOption.value || '');
|
|
|
+ return [provinceCode, cityCode, districtCode].filter(Boolean);
|
|
|
+};
|
|
|
+
|
|
|
+const loadPostNameOptions = async () => {
|
|
|
+ const [industryRes, skillRes] = await Promise.all([listIndustry({ status: '0' }), listIndustrySkill({ status: '0' })]);
|
|
|
+ buildPostNameOptions(industryRes.data || [], skillRes.data || []);
|
|
|
+};
|
|
|
+
|
|
|
+const loadJobTypeOptions = async () => {
|
|
|
+ const res = await getDicts('main_position_type');
|
|
|
+ jobTypeOptions.value = res.data || [];
|
|
|
+};
|
|
|
+
|
|
|
+const loadPostLevelOptions = async () => {
|
|
|
+ const res = await getDicts('main_position_level');
|
|
|
+ postLevelOptions.value = res.data || [];
|
|
|
+};
|
|
|
+
|
|
|
+const loadTagOptions = async () => {
|
|
|
+ const res = await listTag({
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 1000
|
|
|
+ });
|
|
|
+ tagOptions.value = res.rows || [];
|
|
|
+};
|
|
|
+
|
|
|
+const fillForm = (data: MainPostApplyVO) => {
|
|
|
+ form.id = data.id;
|
|
|
+ form.auditId = data.auditId;
|
|
|
+ form.postName = data.postName || '';
|
|
|
+ form.postNamePath = data.postName ? findPostNamePathByLabel(postNameOptions.value, data.postName) : [];
|
|
|
+ form.postCode = data.applyNo || '';
|
|
|
+ form.regionCodes = [];
|
|
|
+ form.addressDetail = data.workAddress || '';
|
|
|
+ form.jobType = data.postType || '';
|
|
|
+ form.salary = data.salaryRange || '';
|
|
|
+ form.tags = data.welfareTags ? data.welfareTags.split(',').filter(Boolean) : [];
|
|
|
+ form.recruitCount = data.recruitNum && data.recruitNum !== 99999 ? data.recruitNum : undefined;
|
|
|
+ form.unlimitedRecruitCount = data.recruitNum === 99999;
|
|
|
+ form.applyTimeRange = data.registrationStartDate && data.registrationEndDate ? [data.registrationStartDate, data.registrationEndDate] : [];
|
|
|
+ form.unlimitedApplyTime = !(data.registrationStartDate && data.registrationEndDate);
|
|
|
+ form.urgent = String(data.isUrgent ?? 0);
|
|
|
+ form.schoolRequirement = data.schoolRequirement || '';
|
|
|
+ form.genderRequirement = data.genderRequirement || '0';
|
|
|
+ form.gradeRequirement = data.gradeRequirement || '';
|
|
|
+ form.arrivalTime = data.arrivalTime || '';
|
|
|
+ form.internshipDuration = data.internshipDuration || '';
|
|
|
+ form.travelRequired = String(data.willingToTravel ?? 0);
|
|
|
+ form.jobDescription = data.jobRequirement || data.postDescription || '';
|
|
|
+ form.postCategory = data.postLevel || '';
|
|
|
+ form.evaluationDuration = data.assessmentTime || '';
|
|
|
+ form.status = data.status || '0';
|
|
|
+ form.passingScores = [
|
|
|
+ { key: 'ability-1', score: data.gradeA != null ? String(data.gradeA) : '' },
|
|
|
+ { key: 'ability-2', score: data.gradeB != null ? String(data.gradeB) : '' },
|
|
|
+ { key: 'ability-3', score: data.gradeC != null ? String(data.gradeC) : '' }
|
|
|
+ ];
|
|
|
+ form.regionCodes = findRegionPathByLabels(data.workProvince, data.workCity, data.workDistrict);
|
|
|
+};
|
|
|
+
|
|
|
+const loadDetail = async () => {
|
|
|
+ if (!editId.value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const res = await getPostManage(editId.value);
|
|
|
+ fillForm(res.data);
|
|
|
+};
|
|
|
+
|
|
|
+// TODO: 待实现部门相关接口
|
|
|
+// const getTreeSelect = async () => {
|
|
|
+// const res = await deptTreeSelect();
|
|
|
+// deptOptions.value = res.data;
|
|
|
+// };
|
|
|
+
|
|
|
+const handleRecruitUnlimitedChange = (value: boolean) => {
|
|
|
+ if (value) form.recruitCount = undefined;
|
|
|
+};
|
|
|
+
|
|
|
+const handleApplyUnlimitedChange = (value: boolean) => {
|
|
|
+ if (value) form.applyTimeRange = [];
|
|
|
+};
|
|
|
+
|
|
|
+const validateStep = async () => {
|
|
|
+ if (activeStep.value === 0) {
|
|
|
+ await formRef.value?.validateField(['postNamePath', 'postCode', 'regionCodes', 'addressDetail', 'jobType', 'salary']);
|
|
|
+ if (!form.unlimitedRecruitCount && !form.recruitCount) throw new Error('招聘人数不能为空');
|
|
|
+ if (!form.unlimitedApplyTime && form.applyTimeRange.length !== 2) throw new Error('报名时间不能为空');
|
|
|
+ }
|
|
|
+ if (activeStep.value === 1) {
|
|
|
+ await formRef.value?.validateField(['postCategory', 'evaluationDuration']);
|
|
|
+ if (form.passingScores.some((item) => !item.score)) throw new Error('及格线不能为空');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleNext = async () => {
|
|
|
+ try {
|
|
|
+ await validateStep();
|
|
|
+ activeStep.value += 1;
|
|
|
+ } catch (error: any) {
|
|
|
+ if (error?.message) proxy?.$modal.msgError(error.message);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const buildApplyNo = () =>
|
|
|
+ `PA${Date.now()}${Math.floor(Math.random() * 1000)
|
|
|
+ .toString()
|
|
|
+ .padStart(3, '0')}`;
|
|
|
+
|
|
|
+const buildPayload = () => ({
|
|
|
+ id: form.id,
|
|
|
+ applyNo: buildApplyNo(),
|
|
|
+ tenantId: userStore.tenantId,
|
|
|
+ postName: form.postName,
|
|
|
+ companyName: '',
|
|
|
+ postDescription: form.jobDescription,
|
|
|
+ workProvince: form.regionCodes[0] ? codeToText[form.regionCodes[0]] || '' : '',
|
|
|
+ workCity: form.regionCodes[1] ? codeToText[form.regionCodes[1]] || '' : '',
|
|
|
+ workDistrict: form.regionCodes[2] ? codeToText[form.regionCodes[2]] || '' : '',
|
|
|
+ workAddress: form.addressDetail,
|
|
|
+ postType: form.jobType,
|
|
|
+ salaryType: '',
|
|
|
+ salaryRange: form.salary,
|
|
|
+ recruitNum: form.unlimitedRecruitCount ? 99999 : form.recruitCount,
|
|
|
+ registrationStartDate: form.unlimitedApplyTime ? undefined : form.applyTimeRange[0],
|
|
|
+ registrationEndDate: form.unlimitedApplyTime ? undefined : form.applyTimeRange[1],
|
|
|
+ isUrgent: Number(form.urgent),
|
|
|
+ schoolRequirement: form.schoolRequirement,
|
|
|
+ genderRequirement: form.genderRequirement,
|
|
|
+ gradeRequirement: form.gradeRequirement,
|
|
|
+ arrivalTime: form.arrivalTime,
|
|
|
+ internshipDuration: form.internshipDuration,
|
|
|
+ willingToTravel: Number(form.travelRequired),
|
|
|
+ welfareTags: form.tags.join(','),
|
|
|
+ jobRequirement: form.jobDescription,
|
|
|
+ postLevel: form.postCategory,
|
|
|
+ assessmentTime: form.evaluationDuration,
|
|
|
+ gradeA: form.passingScores[0]?.score ? Number(form.passingScores[0].score) : undefined,
|
|
|
+ gradeB: form.passingScores[1]?.score ? Number(form.passingScores[1].score) : undefined,
|
|
|
+ gradeC: form.passingScores[2]?.score ? Number(form.passingScores[2].score) : undefined,
|
|
|
+ applyStatus: 0,
|
|
|
+ status: form.status
|
|
|
+});
|
|
|
+
|
|
|
+const handleSubmit = async () => {
|
|
|
+ try {
|
|
|
+ submitting.value = true;
|
|
|
+ await validateStep();
|
|
|
+ const payload = buildPayload();
|
|
|
+ if (isEdit.value) {
|
|
|
+ payload.applyNo = form.postCode;
|
|
|
+ await updatePostManage(payload);
|
|
|
+ } else {
|
|
|
+ await addPostManage(payload);
|
|
|
+ }
|
|
|
+ activeStep.value = 2;
|
|
|
+ } finally {
|
|
|
+ submitting.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleFinish = () => {
|
|
|
+ proxy?.$tab.closeOpenPage({ path: '/postManage', query: { refresh: 'true' } });
|
|
|
+};
|
|
|
+
|
|
|
+const handleCancel = () => {
|
|
|
+ proxy?.$tab.closePage();
|
|
|
+ router.push('/postManage');
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ await Promise.all([loadPostNameOptions(), loadJobTypeOptions(), loadPostLevelOptions(), loadTagOptions()]);
|
|
|
+ await loadDetail();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.post-create-page {
|
|
|
+ min-height: 100%;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.post-create-card {
|
|
|
+ height: calc(100vh - 150px);
|
|
|
+ width: 960px;
|
|
|
+ max-width: calc(100vw - 48px);
|
|
|
+ padding: 20px 24px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 6px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.post-create-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 16px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.post-create-title {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.post-create-subtitle {
|
|
|
+ margin-top: 6px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+.post-steps {
|
|
|
+ background: white;
|
|
|
+ margin-bottom: 24px;
|
|
|
+ max-width: 760px;
|
|
|
+ margin-left: auto;
|
|
|
+ margin-right: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.post-steps :deep(.el-step) {
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.post-steps :deep(.el-step__head) {
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.post-steps :deep(.el-step__title) {
|
|
|
+ white-space: nowrap;
|
|
|
+ font-size: 14px;
|
|
|
+ padding-right: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.post-steps :deep(.el-step.is-simple .el-step__title) {
|
|
|
+ margin-right: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.post-steps :deep(.el-step.is-simple:not(:last-child) .el-step__arrow) {
|
|
|
+ display: none;
|
|
|
+}
|
|
|
+
|
|
|
+.post-create-form {
|
|
|
+ width: 760px;
|
|
|
+ max-width: 100%;
|
|
|
+ margin: 0 auto;
|
|
|
+}
|
|
|
+
|
|
|
+.post-create-body {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow-y: auto;
|
|
|
+ overflow-x: hidden;
|
|
|
+ padding-right: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.step-panel {
|
|
|
+ padding: 8px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.post-create-form :deep(.el-input),
|
|
|
+.post-create-form :deep(.el-select),
|
|
|
+.post-create-form :deep(.el-tree-select),
|
|
|
+.post-create-form :deep(.el-cascader),
|
|
|
+.post-create-form :deep(.el-time-editor.el-input),
|
|
|
+.post-create-form :deep(.el-time-editor.el-input__wrapper),
|
|
|
+.full-width-select {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.inline-mixed-field {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.input-unit-field {
|
|
|
+ width: 100%;
|
|
|
+ flex-wrap: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.field-unit {
|
|
|
+ white-space: nowrap;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.score-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.score-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.score-label {
|
|
|
+ width: 120px;
|
|
|
+ line-height: 32px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.score-input-wrap {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.full-width-row :deep(.el-date-editor) {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.finish-panel {
|
|
|
+ min-height: 300px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.finish-icon {
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: #e8f8ec;
|
|
|
+ color: #35c759;
|
|
|
+ font-size: 28px;
|
|
|
+ font-weight: 700;
|
|
|
+}
|
|
|
+
|
|
|
+.finish-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.finish-subtitle {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
+}
|
|
|
+
|
|
|
+.post-create-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 12px;
|
|
|
+ margin-top: 24px;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+</style>
|