|
@@ -387,8 +387,8 @@
|
|
|
>
|
|
|
<div class="process-dialog-content">
|
|
|
<div v-for="(step, index) in recruitmentProcess" :key="step.id">
|
|
|
- <!-- 添加在每个步骤上方的加号按钮 -->
|
|
|
- <div class="add-process-btn-top" v-if="index === 0 || true">
|
|
|
+ <!-- 添加在每个步骤上方的加号按钮,最后两个步骤不显示加号 -->
|
|
|
+ <div class="add-process-btn-top" v-if="index < recruitmentProcess.length - 1">
|
|
|
<el-button type="text" @click="showStepOptions(index, $event)">
|
|
|
<el-icon><Plus /></el-icon>
|
|
|
</el-button>
|
|
@@ -408,7 +408,7 @@
|
|
|
type="info"
|
|
|
size="small"
|
|
|
plain
|
|
|
- @click="editAIVideo(step)"
|
|
|
+ @click="editProcessStep(step, index)"
|
|
|
>
|
|
|
修改
|
|
|
</el-button>
|
|
@@ -2064,8 +2064,8 @@
|
|
|
<!-- 分页 -->
|
|
|
<div class="pagination-container">
|
|
|
<el-pagination
|
|
|
- v-model:current-page="questionList.page"
|
|
|
- v-model:page-size="questionList.pageSize"
|
|
|
+ v-model:current-page="questionListQuery.page"
|
|
|
+ v-model:page-size="questionListQuery.pageSize"
|
|
|
:total="totalQuestions2"
|
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
|
layout="total, sizes, prev, pager, next"
|
|
@@ -2083,6 +2083,130 @@
|
|
|
</span>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
+
|
|
|
+ <!-- 候选人提问环节配置弹窗 -->
|
|
|
+ <el-dialog
|
|
|
+ :title="editingCandidateQuestionsIndex >= 0 ? '编辑候选人提问环节' : '配置候选人提问环节'"
|
|
|
+ v-model="showCandidateQuestionsDialog"
|
|
|
+ width="600px"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ class="candidate-questions-dialog"
|
|
|
+ >
|
|
|
+ <div class="candidate-questions-form">
|
|
|
+ <div class="form-item">
|
|
|
+ <div class="form-label">
|
|
|
+ 环节名称<span class="required">*</span>
|
|
|
+ </div>
|
|
|
+ <el-input
|
|
|
+ v-model="candidateQuestionsForm.title"
|
|
|
+ placeholder="请输入环节名称"
|
|
|
+ maxlength="30"
|
|
|
+ show-word-limit
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-item">
|
|
|
+ <div class="form-label">
|
|
|
+ 提问时长<span class="required">*</span>
|
|
|
+ </div>
|
|
|
+ <el-input-number
|
|
|
+ v-model="candidateQuestionsForm.duration"
|
|
|
+ :min="1"
|
|
|
+ :max="30"
|
|
|
+ :step="1"
|
|
|
+ controls-position="right"
|
|
|
+ style="width: 200px"
|
|
|
+ />
|
|
|
+ <span class="form-unit">分钟</span>
|
|
|
+ <div class="form-tip">候选人可用于提问的总时长</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- <div class="form-item">
|
|
|
+ <div class="form-label">
|
|
|
+ 最多提问数量<span class="required">*</span>
|
|
|
+ </div>
|
|
|
+ <el-input-number
|
|
|
+ v-model="candidateQuestionsForm.maxQuestions"
|
|
|
+ :min="1"
|
|
|
+ :max="10"
|
|
|
+ :step="1"
|
|
|
+ controls-position="right"
|
|
|
+ style="width: 200px"
|
|
|
+ />
|
|
|
+ <span class="form-unit">个</span>
|
|
|
+ <div class="form-tip">候选人最多可以提出的问题数量</div>
|
|
|
+ </div> -->
|
|
|
+
|
|
|
+ <div class="form-item">
|
|
|
+ <div class="form-label">环节描述</div>
|
|
|
+ <el-input
|
|
|
+ v-model="candidateQuestionsForm.description"
|
|
|
+ type="textarea"
|
|
|
+ rows="3"
|
|
|
+ placeholder="请输入环节描述"
|
|
|
+ maxlength="200"
|
|
|
+ show-word-limit
|
|
|
+ />
|
|
|
+ <div class="form-tip">向候选人说明此环节的目的和意义</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- <div class="form-item">
|
|
|
+ <div class="form-label">环节设置</div>
|
|
|
+ <div class="form-checkboxes">
|
|
|
+ <el-checkbox v-model="candidateQuestionsForm.isRequired">
|
|
|
+ 必须环节(候选人必须完成此环节)
|
|
|
+ </el-checkbox>
|
|
|
+ <el-checkbox v-model="candidateQuestionsForm.allowSkip">
|
|
|
+ 允许跳过(候选人可选择跳过此环节)
|
|
|
+ </el-checkbox>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-item">
|
|
|
+ <div class="form-label">提问指引</div>
|
|
|
+ <div class="guidelines-list">
|
|
|
+ <div
|
|
|
+ v-for="(guideline, index) in candidateQuestionsForm.guidelines"
|
|
|
+ :key="index"
|
|
|
+ class="guideline-item"
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ v-model="candidateQuestionsForm.guidelines[index]"
|
|
|
+ placeholder="请输入提问指引"
|
|
|
+ maxlength="100"
|
|
|
+ >
|
|
|
+ <template #append>
|
|
|
+ <el-button
|
|
|
+ type="danger"
|
|
|
+ @click="removeGuideline(index)"
|
|
|
+ v-if="candidateQuestionsForm.guidelines.length > 1"
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+ </div>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ plain
|
|
|
+ @click="addGuideline"
|
|
|
+ v-if="candidateQuestionsForm.guidelines.length < 5"
|
|
|
+ >
|
|
|
+ <el-icon><Plus /></el-icon>
|
|
|
+ 添加指引
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <div class="form-tip">为候选人提供提问的指导建议,最多5条</div>
|
|
|
+ </div> -->
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button @click="cancelCandidateQuestions">取消</el-button>
|
|
|
+ <el-button type="primary" @click="confirmCandidateQuestions">确认</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
@@ -2193,15 +2317,32 @@ const positionData = reactive({
|
|
|
status: 0
|
|
|
});
|
|
|
|
|
|
-// 招聘流程数据 - 修改为普通数组而非 ref 对象
|
|
|
+// 招聘流程数据 - 修改为普通数组而非 ref 对象,添加type字段用于重复检查
|
|
|
const recruitmentProcess = reactive([
|
|
|
- { id: 5, name: '资料收集', description: '资料收集', active: true },
|
|
|
- /* { id: 7, name: '常识问题', description: '常识问题', active: true },
|
|
|
- { id: 6, name: '心理问题', description: '心理问题', active: true }, */
|
|
|
- { id: 2, name: 'AI考察', description: 'AI考察', active: true },
|
|
|
- { id: 1, name: '待核验', description: '待核验', active: true },
|
|
|
- { id: 3, name: '已通过', description: '已通过', active: true },
|
|
|
- { id: 4, name: '已淘汰', description: '已淘汰', active: false },
|
|
|
+ { id: 6, name: '简历收集', description: '简历收集', active: true, type: 'resume_collection' },
|
|
|
+ { id: 5, name: '资料收集', description: '资料收集', active: true, type: 'data_collection' },
|
|
|
+ { id: 2, name: 'AI考察', description: 'AI考察', active: true, type: 'ai_video' },
|
|
|
+ { id: 7, name: '候选人提问', description: '时长5分钟,最多3个问题', active: true, type: 'candidate_questions',
|
|
|
+ config: {
|
|
|
+ title: '候选人提问',
|
|
|
+ duration: 5,
|
|
|
+ maxQuestions: 3,
|
|
|
+ description: '候选人可以向面试官提问关于公司、职位或团队的问题',
|
|
|
+ isRequired: true,
|
|
|
+ allowSkip: false,
|
|
|
+ guidelines: [
|
|
|
+ '请提出与职位、公司文化或团队相关的问题',
|
|
|
+ '避免询问已在职位描述中明确说明的信息',
|
|
|
+ '可以询问职业发展机会和团队工作方式'
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /* { id: 8, name: '常识问题', description: '常识问题', active: true, type: 'common_sense' },
|
|
|
+ { id: 9, name: '心理问题', description: '心理问题', active: true, type: 'psychological_problem' }, */
|
|
|
+
|
|
|
+ /* { id: 1, name: '待核验', description: '待核验', active: true, type: 'pending_verification' }, */
|
|
|
+ { id: 3, name: '已通过', description: '已通过', active: true, type: 'passed' },
|
|
|
+ { id: 4, name: '已淘汰', description: '已淘汰', active: false, type: 'rejected' },
|
|
|
|
|
|
]);
|
|
|
|
|
@@ -2211,10 +2352,11 @@ const showProcessDialog = ref(false);
|
|
|
// 定义可选的流程步骤类型
|
|
|
const processStepOptions = [
|
|
|
{ label: 'AI考察', value: 'ai_video' },
|
|
|
- { label: '视频宣讲', value: 'video_presentation' },
|
|
|
- { label: 'AI实时对话', value: 'ai_chat' },
|
|
|
+ /* { label: '视频宣讲', value: 'video_presentation' },
|
|
|
+ { label: 'AI实时对话', value: 'ai_chat' },*/
|
|
|
{ label: '资料收集', value: 'data_collection' },
|
|
|
{ label: '简历收集', value: 'resume_collection' },
|
|
|
+ { label: '候选人提问', value: 'candidate_questions' },
|
|
|
{ label: '心理问题', value: 'psychological_problem' },
|
|
|
{ label: '常识问题', value: 'common_sense' },
|
|
|
/* { label: '代码测试', value: 'code_test' },
|
|
@@ -2774,7 +2916,7 @@ const cancelEditProcess = () => {
|
|
|
showProcessDialog.value = false;
|
|
|
};
|
|
|
|
|
|
-// 删除流程步骤
|
|
|
+// 删除流程步骤 - 删除后允许重新添加相同类型的步骤
|
|
|
const deleteProcessStep = (index: number) => {
|
|
|
recruitmentProcess.splice(index, 1);
|
|
|
};
|
|
@@ -2790,7 +2932,8 @@ const addProcessStepAt = (index: number) => {
|
|
|
id: Date.now(),
|
|
|
name: '新步骤',
|
|
|
description: '新步骤',
|
|
|
- active: true
|
|
|
+ active: true,
|
|
|
+ type: 'custom' // 自定义步骤类型
|
|
|
};
|
|
|
|
|
|
recruitmentProcess.splice(index, 0, newStep);
|
|
@@ -2832,7 +2975,7 @@ const getJobTypeText = (type: number) => {
|
|
|
return typeMap[type] || '未知';
|
|
|
};
|
|
|
|
|
|
-// 显示选项菜单
|
|
|
+// 显示选项菜单 - 用于在指定位置添加招聘流程步骤
|
|
|
const showStepOptions = (index: number, event: MouseEvent) => {
|
|
|
currentAddIndex.value = index;
|
|
|
showOptionsMenu.value = true;
|
|
@@ -2862,6 +3005,25 @@ const showStepOptions = (index: number, event: MouseEvent) => {
|
|
|
};
|
|
|
// 修改添加选定类型的步骤方法
|
|
|
const addSelectedStepType = async (type: string, label: string) => {
|
|
|
+ // 检查是否已存在相同类型的步骤,防止重复添加
|
|
|
+ const existingStep = recruitmentProcess.find(step => {
|
|
|
+ // 根据步骤名称或类型进行判断
|
|
|
+ if (step.type && step.type === type) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ // 对于没有type字段的旧数据,根据name进行判断
|
|
|
+ if (step.name === label) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ });
|
|
|
+
|
|
|
+ if (existingStep) {
|
|
|
+ ElMessage.warning(`"${label}"步骤已存在,不能重复添加`);
|
|
|
+ showOptionsMenu.value = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
if (type === 'ai_video') {
|
|
|
showAIVideoDialog.value = true;
|
|
|
showOptionsMenu.value = false;
|
|
@@ -2918,12 +3080,20 @@ const addSelectedStepType = async (type: string, label: string) => {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 其他类型的处理保持不变
|
|
|
+ // 添加候选人提问环节的处理
|
|
|
+ if (type === 'candidate_questions') {
|
|
|
+ showCandidateQuestionsDialog.value = true;
|
|
|
+ showOptionsMenu.value = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 其他类型的处理保持不变 - 检查流程步骤数量限制
|
|
|
if (recruitmentProcess.length >= 6) {
|
|
|
ElMessage.warning('最多只能添加6个流程步骤');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ // 创建新的招聘流程步骤,包含type字段用于重复检查
|
|
|
const newStep = {
|
|
|
id: Date.now(),
|
|
|
name: label,
|
|
@@ -2932,6 +3102,7 @@ const addSelectedStepType = async (type: string, label: string) => {
|
|
|
type: type
|
|
|
};
|
|
|
|
|
|
+ // 在指定位置插入新步骤
|
|
|
recruitmentProcess.splice(currentAddIndex.value, 0, newStep);
|
|
|
showOptionsMenu.value = false;
|
|
|
};
|
|
@@ -3239,6 +3410,82 @@ const cancelAIChatConfig = () => {
|
|
|
showAIChatDialog.value = false;
|
|
|
};
|
|
|
|
|
|
+// 候选人提问环节相关方法 - 用于配置候选人提问环节
|
|
|
+const confirmCandidateQuestions = () => {
|
|
|
+ // 验证表单数据
|
|
|
+ if (!candidateQuestionsForm.title.trim()) {
|
|
|
+ ElMessage.warning('请输入环节名称');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (candidateQuestionsForm.duration <= 0) {
|
|
|
+ ElMessage.warning('提问时长必须大于0分钟');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (candidateQuestionsForm.maxQuestions <= 0) {
|
|
|
+ ElMessage.warning('最多提问数量必须大于0');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断是编辑模式还是新增模式
|
|
|
+ if (editingCandidateQuestionsIndex.value >= 0) {
|
|
|
+ // 编辑模式:更新现有步骤
|
|
|
+ const existingStep = recruitmentProcess[editingCandidateQuestionsIndex.value];
|
|
|
+ existingStep.name = candidateQuestionsForm.title;
|
|
|
+ existingStep.description = `时长${candidateQuestionsForm.duration}分钟,最多${candidateQuestionsForm.maxQuestions}个问题`;
|
|
|
+ existingStep.config = { ...candidateQuestionsForm };
|
|
|
+
|
|
|
+ ElMessage.success('候选人提问环节已更新');
|
|
|
+ } else {
|
|
|
+ // 新增模式:创建新的候选人提问步骤
|
|
|
+ const newStep = {
|
|
|
+ id: Date.now(),
|
|
|
+ name: candidateQuestionsForm.title,
|
|
|
+ description: `时长${candidateQuestionsForm.duration}分钟,最多${candidateQuestionsForm.maxQuestions}个问题`,
|
|
|
+ active: true,
|
|
|
+ type: 'candidate_questions',
|
|
|
+ config: {
|
|
|
+ ...candidateQuestionsForm
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 添加到流程中
|
|
|
+ recruitmentProcess.splice(currentAddIndex.value, 0, newStep);
|
|
|
+
|
|
|
+ ElMessage.success('候选人提问环节已添加');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关闭弹窗
|
|
|
+ showCandidateQuestionsDialog.value = false;
|
|
|
+
|
|
|
+ // 重置表单为默认值
|
|
|
+ resetCandidateQuestionsForm();
|
|
|
+};
|
|
|
+
|
|
|
+const cancelCandidateQuestions = () => {
|
|
|
+ showCandidateQuestionsDialog.value = false;
|
|
|
+ resetCandidateQuestionsForm();
|
|
|
+};
|
|
|
+
|
|
|
+// 重置候选人提问表单为默认值
|
|
|
+const resetCandidateQuestionsForm = () => {
|
|
|
+ editingCandidateQuestionsIndex.value = -1; // 重置编辑索引
|
|
|
+ Object.assign(candidateQuestionsForm, {
|
|
|
+ title: '候选人提问',
|
|
|
+ duration: 5,
|
|
|
+ maxQuestions: 3,
|
|
|
+ description: '候选人可以向面试官提问关于公司、职位或团队的问题',
|
|
|
+ isRequired: true,
|
|
|
+ allowSkip: false,
|
|
|
+ guidelines: [
|
|
|
+ '请提出与职位、公司文化或团队相关的问题',
|
|
|
+ '避免询问已在职位描述中明确说明的信息',
|
|
|
+ '可以询问职业发展机会和团队工作方式'
|
|
|
+ ]
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
// 添加问题列表数据
|
|
|
const chatQuestions = ref<ChatQuestion[]>([]);
|
|
|
|
|
@@ -5559,6 +5806,80 @@ const handleQuestionTypeChange = (value: string) => {
|
|
|
// 添加状态变量
|
|
|
const competencyLoading = ref(false);
|
|
|
|
|
|
+// 候选人提问环节相关变量 - 用于候选人向面试官提问的环节配置
|
|
|
+const showCandidateQuestionsDialog = ref(false); // 控制候选人提问弹窗显示
|
|
|
+const editingCandidateQuestionsIndex = ref(-1); // 编辑候选人提问环节的索引
|
|
|
+const candidateQuestionsForm = reactive({
|
|
|
+ title: '候选人提问', // 环节名称
|
|
|
+ duration: 5, // 提问时长(分钟)
|
|
|
+ maxQuestions: 3, // 最多提问数量
|
|
|
+ description: '候选人可以向面试官提问关于公司、职位或团队的问题', // 环节描述
|
|
|
+ isRequired: true, // 是否必须环节
|
|
|
+ allowSkip: false, // 是否允许跳过
|
|
|
+ guidelines: [ // 提问指引
|
|
|
+ '请提出与职位、公司文化或团队相关的问题',
|
|
|
+ '避免询问已在职位描述中明确说明的信息',
|
|
|
+ '可以询问职业发展机会和团队工作方式'
|
|
|
+ ]
|
|
|
+});
|
|
|
+
|
|
|
+const removeGuideline = (index: number) => {
|
|
|
+ candidateQuestionsForm.guidelines.splice(index, 1);
|
|
|
+};
|
|
|
+
|
|
|
+const addGuideline = () => {
|
|
|
+ candidateQuestionsForm.guidelines.push('');
|
|
|
+};
|
|
|
+
|
|
|
+// 编辑候选人提问环节
|
|
|
+const editCandidateQuestions = (step: any, index: number) => {
|
|
|
+ editingCandidateQuestionsIndex.value = index;
|
|
|
+
|
|
|
+ // 如果步骤有配置信息,加载到表单中
|
|
|
+ if (step.config) {
|
|
|
+ Object.assign(candidateQuestionsForm, step.config);
|
|
|
+ } else {
|
|
|
+ // 如果没有配置信息,从步骤的基本信息推断
|
|
|
+ candidateQuestionsForm.title = step.name;
|
|
|
+ candidateQuestionsForm.description = step.description || '候选人可以向面试官提问关于公司、职位或团队的问题';
|
|
|
+ candidateQuestionsForm.duration = 5;
|
|
|
+ candidateQuestionsForm.maxQuestions = 3;
|
|
|
+ candidateQuestionsForm.isRequired = step.active;
|
|
|
+ candidateQuestionsForm.allowSkip = false;
|
|
|
+ candidateQuestionsForm.guidelines = [
|
|
|
+ '请提出与职位、公司文化或团队相关的问题',
|
|
|
+ '避免询问已在职位描述中明确说明的信息',
|
|
|
+ '可以询问职业发展机会和团队工作方式'
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ showCandidateQuestionsDialog.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+// 统一的编辑流程步骤方法
|
|
|
+const editProcessStep = (step: any, index: number) => {
|
|
|
+ switch (step.type) {
|
|
|
+ case 'ai_video':
|
|
|
+ editAIVideo(step);
|
|
|
+ break;
|
|
|
+ case 'candidate_questions':
|
|
|
+ editCandidateQuestions(step, index);
|
|
|
+ break;
|
|
|
+ case 'data_collection':
|
|
|
+ // TODO: 添加资料收集编辑功能
|
|
|
+ editAIVideo(step);
|
|
|
+ /* ElMessage.info('资料收集编辑功能开发中'); */
|
|
|
+ break;
|
|
|
+ case 'resume_collection':
|
|
|
+ // TODO: 添加简历收集编辑功能
|
|
|
+ ElMessage.info('简历收集编辑功能开发中');
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ ElMessage.info(`${step.name}编辑功能开发中`);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|