|
@@ -0,0 +1,436 @@
|
|
|
+<template>
|
|
|
+ <div class="question-bank-form">
|
|
|
+ <el-form
|
|
|
+ ref="formRef"
|
|
|
+ :model="formData"
|
|
|
+ :rules="formRules"
|
|
|
+ label-width="120px"
|
|
|
+ @submit.prevent="handleSubmit"
|
|
|
+ >
|
|
|
+ <!-- 题目内容 -->
|
|
|
+ <el-form-item label="题目内容" prop="question">
|
|
|
+ <el-input
|
|
|
+ v-model="formData.question"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="请输入题目内容"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 题目形式 -->
|
|
|
+ <el-form-item label="题目形式" prop="question_form">
|
|
|
+ <el-select
|
|
|
+ v-model="formData.question_form"
|
|
|
+ placeholder="请选择题目形式"
|
|
|
+ @change="handleQuestionFormChange"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in questionFormOptions"
|
|
|
+ :key="item.value"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 胜任力标签 -->
|
|
|
+ <el-form-item label="胜任力标签">
|
|
|
+ <el-select
|
|
|
+ v-model="formData.competency_tags"
|
|
|
+ multiple
|
|
|
+ filterable
|
|
|
+ placeholder="请选择胜任力标签"
|
|
|
+ @change="handleCompetencyTagsChange"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in competencyTagsOptions"
|
|
|
+ :key="item.id"
|
|
|
+ :label="item.name"
|
|
|
+ :value="item.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 胜任力配置表格 -->
|
|
|
+ <el-form-item
|
|
|
+ v-if="formData.competency_tags && formData.competency_tags.length > 0"
|
|
|
+ label="胜任力配置"
|
|
|
+ >
|
|
|
+ <el-card>
|
|
|
+ <el-table :data="formData.competency_tags_config" border>
|
|
|
+ <el-table-column label="标签" prop="competency_name" width="200" />
|
|
|
+ <el-table-column label="权重" width="150">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div style="display: flex; align-items: center;">
|
|
|
+ <el-input-number
|
|
|
+ v-model="row.weight"
|
|
|
+ :min="0"
|
|
|
+ :max="100"
|
|
|
+ :step="1"
|
|
|
+ :precision="0"
|
|
|
+ :controls="false"
|
|
|
+ style="width: calc(100% - 20px)"
|
|
|
+ />
|
|
|
+ <span style="margin-left: 4px;">%</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="级别" width="100">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-input-number
|
|
|
+ v-model="row.importance"
|
|
|
+ :min="1"
|
|
|
+ :controls="false"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 适用职位 -->
|
|
|
+ <el-form-item label="适用职位">
|
|
|
+ <el-select
|
|
|
+ v-model="formData.position_types"
|
|
|
+ multiple
|
|
|
+ filterable
|
|
|
+ placeholder="请选择适用职位类型"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in positionOptions"
|
|
|
+ :key="item.id"
|
|
|
+ :label="item.title"
|
|
|
+ :value="item.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 分类 -->
|
|
|
+ <el-form-item label="分类">
|
|
|
+ <el-select
|
|
|
+ v-model="formData.category_id"
|
|
|
+ placeholder="请选择题目分类"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in categoryOptions"
|
|
|
+ :key="item.id"
|
|
|
+ :label="item.name"
|
|
|
+ :value="item.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 标签 -->
|
|
|
+ <el-form-item label="标签">
|
|
|
+ <el-select
|
|
|
+ v-model="formData.tags"
|
|
|
+ multiple
|
|
|
+ filterable
|
|
|
+ placeholder="请选择标签"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in tagOptions"
|
|
|
+ :key="item.id"
|
|
|
+ :label="item.name"
|
|
|
+ :value="item.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 建议时长 -->
|
|
|
+ <el-form-item label="建议时长(秒)">
|
|
|
+ <el-input-number
|
|
|
+ v-model="formData.recommended_duration"
|
|
|
+ :min="10"
|
|
|
+ :max="600"
|
|
|
+ :step="10"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 状态 -->
|
|
|
+ <el-form-item label="状态" prop="status">
|
|
|
+ <el-select v-model="formData.status" placeholder="请选择状态">
|
|
|
+ <el-option label="停用" :value="0" />
|
|
|
+ <el-option label="启用" :value="1" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 红线问题 (仅单选题和多选题显示) -->
|
|
|
+ <el-form-item
|
|
|
+ v-if="formData.question_form === 1 || formData.question_form === 2"
|
|
|
+ label="红线问题"
|
|
|
+ >
|
|
|
+ <el-select v-model="formData.is_required_correct" placeholder="请选择是否为红线问题">
|
|
|
+ <el-option label="否" :value="false" />
|
|
|
+ <el-option label="是" :value="true" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 系统题目 -->
|
|
|
+ <el-form-item label="系统题目">
|
|
|
+ <el-select v-model="formData.is_system" placeholder="请选择是否为系统题目">
|
|
|
+ <el-option label="否" :value="false" />
|
|
|
+ <el-option label="是" :value="true" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 排序 -->
|
|
|
+ <el-form-item label="排序">
|
|
|
+ <el-input-number
|
|
|
+ v-model="formData.sort"
|
|
|
+ :min="1"
|
|
|
+ :max="999"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 选项列表 (单选题、多选题、心理评估题) -->
|
|
|
+ <el-form-item
|
|
|
+ v-if="[1, 2, 4].includes(formData.question_form)"
|
|
|
+ label="选项列表"
|
|
|
+ >
|
|
|
+ <QuestionOptionsEditor
|
|
|
+ v-model="formData.options"
|
|
|
+ :question-form="formData.question_form"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 评分标准 -->
|
|
|
+ <el-form-item
|
|
|
+ v-if="[4, 6].includes(formData.question_form)"
|
|
|
+ label="评分标准"
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ v-model="formData.scoring_reference"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="请输入评分标准说明"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 填空答案模板 -->
|
|
|
+ <el-form-item
|
|
|
+ v-if="formData.question_form === 6"
|
|
|
+ label="填空答案模板"
|
|
|
+ >
|
|
|
+ <BlankAnswerEditor
|
|
|
+ v-model="formData.blank_answer_template"
|
|
|
+ :question="formData.question"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 操作按钮 -->
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="handleSubmit">确定</el-button>
|
|
|
+ <el-button @click="handleCancel">取消</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, reactive, watch, onMounted } from 'vue';
|
|
|
+import type { FormInstance } from 'element-plus';
|
|
|
+import * as api from '../api';
|
|
|
+import QuestionOptionsEditor from './QuestionOptionsEditor.vue';
|
|
|
+import BlankAnswerEditor from './BlankAnswerEditor.vue';
|
|
|
+
|
|
|
+interface Props {
|
|
|
+ modelValue?: any;
|
|
|
+ isEdit?: boolean;
|
|
|
+}
|
|
|
+
|
|
|
+interface Emits {
|
|
|
+ (e: 'update:modelValue', value: any): void;
|
|
|
+ (e: 'submit', value: any): void;
|
|
|
+ (e: 'cancel'): void;
|
|
|
+}
|
|
|
+
|
|
|
+const props = withDefaults(defineProps<Props>(), {
|
|
|
+ modelValue: () => ({}),
|
|
|
+ isEdit: false
|
|
|
+});
|
|
|
+
|
|
|
+const emit = defineEmits<Emits>();
|
|
|
+
|
|
|
+const formRef = ref<FormInstance>();
|
|
|
+
|
|
|
+// 表单数据
|
|
|
+const formData = reactive({
|
|
|
+ question: '',
|
|
|
+ question_form: null,
|
|
|
+ competency_tags: [],
|
|
|
+ competency_tags_config: [],
|
|
|
+ position_types: [],
|
|
|
+ category_id: null,
|
|
|
+ tags: [],
|
|
|
+ recommended_duration: 60,
|
|
|
+ status: 1,
|
|
|
+ is_required_correct: false,
|
|
|
+ is_system: false,
|
|
|
+ sort: 1,
|
|
|
+ options: [],
|
|
|
+ scoring_reference: '',
|
|
|
+ blank_answer_template: [],
|
|
|
+ ...props.modelValue
|
|
|
+});
|
|
|
+
|
|
|
+// 表单验证规则
|
|
|
+const formRules = {
|
|
|
+ question: [{ required: true, message: '题目内容必填', trigger: 'blur' }],
|
|
|
+ question_form: [{ required: true, message: '题目形式必填', trigger: 'change' }],
|
|
|
+ status: [{ required: true, message: '状态必填', trigger: 'change' }]
|
|
|
+};
|
|
|
+
|
|
|
+// 选项数据
|
|
|
+const questionFormOptions = ref([]);
|
|
|
+const competencyTagsOptions = ref([]);
|
|
|
+const positionOptions = ref([]);
|
|
|
+const categoryOptions = ref([]);
|
|
|
+const tagOptions = ref([]);
|
|
|
+
|
|
|
+// 监听数据变化
|
|
|
+watch(() => formData, (newVal) => {
|
|
|
+ emit('update:modelValue', newVal);
|
|
|
+}, { deep: true });
|
|
|
+
|
|
|
+watch(() => props.modelValue, (newVal) => {
|
|
|
+ Object.assign(formData, newVal);
|
|
|
+}, { deep: true });
|
|
|
+
|
|
|
+// 处理题目形式变化
|
|
|
+const handleQuestionFormChange = (value: number) => {
|
|
|
+ if (value === 1 || value === 2) {
|
|
|
+ // 单选题或多选题
|
|
|
+ formData.options = [
|
|
|
+ { option_text: '', is_correct: false, sort: 1 },
|
|
|
+ { option_text: '', is_correct: false, sort: 2 }
|
|
|
+ ];
|
|
|
+ } else if (value === 4) {
|
|
|
+ // 心理评估题
|
|
|
+ formData.options = [
|
|
|
+ { option_text: '无', score: 0, sort: 1 },
|
|
|
+ { option_text: '轻度', score: 1, sort: 2 },
|
|
|
+ { option_text: '中度', score: 2, sort: 3 },
|
|
|
+ { option_text: '偏重', score: 3, sort: 4 },
|
|
|
+ { option_text: '严重', score: 4, sort: 5 }
|
|
|
+ ];
|
|
|
+ if (!formData.scoring_reference) {
|
|
|
+ formData.scoring_reference = '该评分反映了症状表现的频率与严重程度。';
|
|
|
+ }
|
|
|
+ } else if (value === 6) {
|
|
|
+ // 填空题
|
|
|
+ formData.blank_answer_template = [
|
|
|
+ { blank_index: 1, correct_answer: '', score: 5 }
|
|
|
+ ];
|
|
|
+ if (!formData.scoring_reference) {
|
|
|
+ formData.scoring_reference = '根据答案的准确性评分';
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 开放题
|
|
|
+ formData.options = [];
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 处理胜任力标签变化
|
|
|
+const handleCompetencyTagsChange = (value: number[]) => {
|
|
|
+ if (!formData.competency_tags_config) {
|
|
|
+ formData.competency_tags_config = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ const existingConfigIds = formData.competency_tags_config.map((config: any) => config.id);
|
|
|
+
|
|
|
+ // 添加新选择的标签配置
|
|
|
+ value.forEach((tagId: number) => {
|
|
|
+ if (!existingConfigIds.includes(tagId)) {
|
|
|
+ const tagInfo = competencyTagsOptions.value.find((tag: any) => tag.id === tagId);
|
|
|
+ if (tagInfo) {
|
|
|
+ formData.competency_tags_config.push({
|
|
|
+ id: tagId,
|
|
|
+ competency_name: tagInfo.name,
|
|
|
+ weight: 100,
|
|
|
+ importance: 1,
|
|
|
+ remark: ''
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 移除不再选中的标签配置
|
|
|
+ formData.competency_tags_config = formData.competency_tags_config.filter(
|
|
|
+ (config: any) => value.includes(config.id)
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+// 获取选项数据
|
|
|
+const fetchOptions = async () => {
|
|
|
+ try {
|
|
|
+ // 获取题目形式选项
|
|
|
+ const questionFormRes = await api.GetQuestionTypeList();
|
|
|
+ questionFormOptions.value = questionFormRes.data.question_forms || [];
|
|
|
+
|
|
|
+ // 获取胜任力标签选项
|
|
|
+ const competencyRes = await api.GetCompetencyList({page:1, limit:100, tenant_id:1});
|
|
|
+ competencyTagsOptions.value = competencyRes.data.items || [];
|
|
|
+
|
|
|
+ // 获取职位选项
|
|
|
+ const positionRes = await api.GetPositionList({page:1, limit:1000, tenant_id:1});
|
|
|
+ positionOptions.value = positionRes.data || [];
|
|
|
+
|
|
|
+ // 获取分类选项
|
|
|
+ const categoryRes = await api.GetcategoryList({page:1, limit:1000, tenant_id:1});
|
|
|
+ categoryOptions.value = categoryRes.data.items || [];
|
|
|
+
|
|
|
+ // 获取标签选项
|
|
|
+ const tagRes = await api.GetTagList({page:1, limit:1000, tenant_id:1});
|
|
|
+ tagOptions.value = tagRes.data.items || [];
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取选项数据失败:', error);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 表单提交
|
|
|
+const handleSubmit = async () => {
|
|
|
+ try {
|
|
|
+ await formRef.value?.validate();
|
|
|
+
|
|
|
+ // 处理胜任力标签配置
|
|
|
+ if (formData.competency_tags && formData.competency_tags.length > 0 && formData.competency_tags_config) {
|
|
|
+ formData.competency_tags = formData.competency_tags_config.map((config: any) => ({
|
|
|
+ id: config.id,
|
|
|
+ weight: config.weight || 100,
|
|
|
+ importance: config.importance || 1,
|
|
|
+ remark: config.remark || ''
|
|
|
+ }));
|
|
|
+ } else {
|
|
|
+ formData.competency_tags_config = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ emit('submit', formData);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('表单验证失败:', error);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 取消操作
|
|
|
+const handleCancel = () => {
|
|
|
+ emit('cancel');
|
|
|
+};
|
|
|
+
|
|
|
+// 组件挂载时获取选项数据
|
|
|
+onMounted(() => {
|
|
|
+ fetchOptions();
|
|
|
+});
|
|
|
+
|
|
|
+// 暴露方法
|
|
|
+defineExpose({
|
|
|
+ validate: () => formRef.value?.validate(),
|
|
|
+ resetFields: () => formRef.value?.resetFields(),
|
|
|
+ getFormData: () => formData
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.question-bank-form {
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+</style>
|