12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775 |
- <template>
- <fs-page>
- <el-row class="document-el-row">
- <el-col :span="3">
- <div class="document-box document-left-box">
- <DocumentTreeCom
- ref="documentTreeRef"
- :treeData="documentTreeData"
- @treeClick="handleTreeClick"
- @updateDocument="handleUpdateDocument"
- @deleteDocument="handleDeleteDocument"
- />
- </div>
- </el-col>
- <el-col :span="21">
- <div class="document-box document-right-box">
- <fs-crud ref="crudRef" v-bind="crudBinding">
- <!-- 行操作按钮插槽 -->
- <template #cell-rowHandle-middle="scope">
- <!-- 绑定开放问题按钮 -->
- <el-button
- v-if="scope.row.question_form === 5"
- type="text"
- @click="bindParentQuestion(scope.row)">
- <el-icon><Link /></el-icon>绑定开放问题
- </el-button>
- <!-- 编辑按钮 -->
- </template>
- <template #form_file="scope">
- <!-- 只在编辑和添加模式下显示上传功能 -->
- <template v-if="scope.mode !== 'view'">
- <el-upload :action="getBaseURL() + 'api/system/minioupload/'"
-
- :headers="{
- Authorization: 'JWT ' + Session.get('token')
- }"
- :multiple="false"
- :on-success="(response: any, file: any) => handleUploadSuccess(response, file, scope)"
- :drag="false"
- :show-file-list="false">
- <el-button type="primary" icon="plus">上传</el-button>
- </el-upload>
- <div v-if="uploadedFile" class="uploaded-file-info mt-2">
- <el-card shadow="hover" class="w-full">
- <div class="flex items-center justify-between">
- <div class="flex items-center">
- <el-icon class="mr-2"><Document /></el-icon>
- <span>{{ uploadedFile.name }}</span>
- </div>
- <div class="flex items-center">
- <span class="text-gray-500 text-sm mr-4">{{ formatFileSize(uploadedFile.file_size) }}</span>
- <el-button type="danger" link @click="handleRemoveFile(scope)">
- <el-icon><Delete /></el-icon>
- </el-button>
- </div>
- </div>
- </el-card>
- </div>
- </template>
- <!-- 查看模式下只显示文件名 -->
- <template v-else>
- <div class="flex items-center justify-between">
- <span>{{ scope.form.fileName }}</span>
- <el-button v-if="isPreviewable(scope.form.file_type)" type="primary" link @click="previewFile(scope.form)">
- <el-icon class="mr-1"><View /></el-icon>预览
- </el-button>
- </div>
- </template>
- </template>
- </fs-crud>
- </div>
- </el-col>
- </el-row>
- <el-drawer v-model="drawerVisible" title="添加分类" direction="rtl" size="500px" :close-on-click-modal="false" :before-close="handleDrawerClose">
- <DocumentFormCom
- v-if="drawerVisible"
- :initFormData="drawerFormData"
- :cacheData="documentTreeCacheData"
- :treeData="documentTreeData"
- @drawerClose="handleDrawerClose"
- />
- </el-drawer>
- <!-- 添加自定义查看详情对话框 -->
- <!-- <el-dialog
- v-model="detailDialogVisible"
- title="文档详情"
- width="50%"
- destroy-on-close
- >
- <div ref="printArea">
- <el-descriptions :column="2" size="small" border>
- <el-descriptions-item label-align="right" label="文档名称">{{ detailData.name || '-' }}</el-descriptions-item>
- <el-descriptions-item label-align="right" label="文档类型">
- {{ getDocTypeName(detailData.doc_type) || '-' }}
- </el-descriptions-item>
- <el-descriptions-item label-align="right" label="文件分类">
- {{ getCategoryName(detailData.category) || '-' }}
- </el-descriptions-item>
- <el-descriptions-item label-align="right" label="所属项目">
- {{ getProjectName(detailData.project) || '-' }}
- </el-descriptions-item>
- <el-descriptions-item label-align="right" label="文档版本">{{ detailData.version || '-' }}</el-descriptions-item>
- <el-descriptions-item label-align="right" label="文件类型">{{ detailData.file_type || '-' }}</el-descriptions-item>
- <el-descriptions-item label-align="right" label="文件大小">{{ formatFileSize(detailData.file_size) || '-' }}</el-descriptions-item>
- <el-descriptions-item label-align="right" label="状态">
- <el-tag :type="detailData.status ? 'success' : 'info'">
- {{ detailData.status ? '启用' : '禁用' }}
- </el-tag>
- </el-descriptions-item>
- <el-descriptions-item label-align="right" label="文档描述" :span="2">{{ detailData.doc_desc || '-' }}</el-descriptions-item>
- <el-descriptions-item label-align="right" label="更新时间">{{ detailData.update_datetime || '-' }}</el-descriptions-item>
- </el-descriptions>
- </div>
-
- <template #footer>
- <span class="dialog-footer">
- <el-button type="primary" @click="handlePrint">
- <el-icon><Printer /></el-icon>打印
- </el-button>
- <el-button @click="detailDialogVisible = false">关闭</el-button>
- </span>
- </template>
- </el-dialog> -->
- <!-- 修改视频上传对话框为表单格式 -->
- <el-dialog
- v-model="videoUploadDialogVisible"
- title="上传数字人视频及字幕"
- width="600px"
- destroy-on-close
- >
- <el-form
- ref="videoFormRef"
- :model="videoForm"
- :rules="videoFormRules"
- label-width="100px"
- label-position="top"
- >
- <el-form-item label="数字人视频" prop="videoUrl">
- <!-- <div class="upload-section"> -->
- <el-upload
- class="video-uploader"
- :action="getBaseURL() + 'api/system/admin_upload/'"
- :headers="{
- Authorization: 'JWT ' + Session.get('token')
- }"
- :data="{
- tenant_id:1
- }"
- :multiple="false"
- :on-success="handleVideoUploadSuccess"
- :on-error="handleUploadError"
- :before-upload="beforeVideoUpload"
- :show-file-list="false"
-
- ><!-- accept="video/*" -->
- <div v-if="videoInfo.url" class="video-preview">
- <video :src="videoInfo.url" controls class="video-player"></video>
- </div>
- <el-button v-else type="primary" icon="Plus">选择视频文件</el-button>
- <div v-if="videoInfo.name" class="file-info">
- <span>{{ videoInfo.name }}</span>
- <span class="file-size">{{ formatFileSize(videoInfo.size) }}</span>
- </div>
- </el-upload>
- <!-- </div> -->
- </el-form-item>
- <el-form-item label="字幕内容" prop="subtitleContent">
- <!-- <div class="upload-section"> -->
- <el-input
- v-model="videoForm.subtitleContent"
- type="textarea"
- :rows="4"
- placeholder="请输入数字人视频字幕内容"
- ></el-input>
-
- <!-- <el-upload
- class="subtitle-uploader mt-2"
- :action="getBaseURL() + 'api/system/minioupload/'"
- :headers="{
- Authorization: 'JWT ' + Session.get('token')
- }"
- :multiple="false"
- :on-success="handleSubtitleUploadSuccess"
- :on-error="handleUploadError"
- :before-upload="beforeSubtitleUpload"
- :show-file-list="false"
- accept=".srt,.vtt,.ass"
- >
- <div v-if="subtitleInfo.url" class="file-preview">
- <el-icon class="file-icon"><Document /></el-icon>
- <div class="file-info">
- <span>{{ subtitleInfo.name }}</span>
- <span class="file-size">{{ formatFileSize(subtitleInfo.size) }}</span>
- </div>
- </div>
- <el-button v-else type="primary" icon="Plus">选择字幕文件</el-button>
- </el-upload> -->
- <!-- </div> -->
- </el-form-item>
- </el-form>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="videoUploadDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="submitVideoForm" :disabled="!videoInfo.url">保存</el-button>
- </span>
- </template>
- </el-dialog>
-
- <!-- 添加排序对话框 -->
- <el-dialog
- v-model="sortDialogVisible"
- title="修改排序"
- width="400px"
- destroy-on-close
- >
- <el-form
- ref="sortFormRef"
- :model="sortForm"
- :rules="sortFormRules"
- label-width="100px"
- >
- <el-form-item label="排序值" prop="sequence_number">
- <el-input-number
- v-model="sortForm.sequence_number"
- :min="0"
- :max="9999"
- style="width: 100%"
- />
- </el-form-item>
- <div class="form-helper">数值越小,排序越靠前</div>
- </el-form>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="sortDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="submitSortForm">保存</el-button>
- </span>
- </template>
- </el-dialog>
- <!-- 添加职位问题对话框 -->
- <el-dialog
- v-model="positionQuestionDialogVisible"
- title="职位问题"
- width="60%"
- destroy-on-close
- >
- <el-form :model="positionQuestionForm" label-width="80px">
- <el-form-item label="职位">
- <el-select v-model="positionQuestionForm.position" placeholder="请选择" style="width: 25%">
- <el-option
- v-for="item in positionOptions"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select>
- </el-form-item>
-
- <el-form-item label="职位问题">
- <div class="question-selection-container">
- <div class="question-search">
- <div class="search-row">
- <el-input
- style="width: 200px;"
- v-model="questionSearchKeyword"
- placeholder="搜索问题"
- clearable
- @input="filterQuestions"
- >
- <template #prefix>
- <el-icon><Search /></el-icon>
- </template>
- </el-input>
-
- <el-select
- v-model="selectedQuestionCategory"
- placeholder="问题分类"
- clearable
- @change="filterQuestions"
- style="margin-left: 10px; width: 200px;"
- >
- <el-option
- v-for="item in questionCategoryOptions"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- />
- </el-select>
- </div>
- </div>
-
- <div class="question-table-container">
- <el-table
- v-loading="questionsLoading"
- :data="filteredQuestionOptions"
- style="width: 100%"
- height="300"
- @selection-change="handleQuestionSelectionChange"
- >
- <el-table-column type="selection" width="55" />
- <el-table-column prop="label" label="问题" show-overflow-tooltip />
- <el-table-column prop="category_id.name" label="分类" />
- <el-table-column prop="question_form_name" label="题目形式" show-overflow-tooltip />
- <el-table-column prop="chinese_explanation" label="描述" show-overflow-tooltip />
- </el-table>
-
- <div class="pagination-container">
- <el-pagination
- v-model:current-page="questionPagination.currentPage"
- v-model:page-size="questionPagination.pageSize"
- :page-sizes="[10, 20, 50, 100]"
- layout="total, sizes, prev, pager, next, jumper"
- :total="questionPagination.total"
- @size-change="handleQuestionSizeChange"
- @current-change="handleQuestionCurrentChange"
- />
- </div>
- </div>
- </div>
- </el-form-item>
-
- <el-form-item label="排序">
- <el-input style="width: 25%" v-model="positionQuestionForm.sequence" placeholder="请输入排序值" />
- </el-form-item>
- </el-form>
-
- <!-- <div class="question-list-container">
- <h3>已选问题列表</h3>
- <el-table :data="selectedQuestions" style="width: 100%">
- <el-table-column prop="label" label="问题" show-overflow-tooltip />
- <el-table-column prop="question_form_name" label="问题类型" show-overflow-tooltip />
- <el-table-column prop="chinese_explanation" label="中文释义" show-overflow-tooltip />
- <el-table-column label="操作" width="120">
- <template #default="scope">
- <el-button type="danger" link @click="removeQuestion(scope.row)">
- <el-icon><Delete /></el-icon>
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- </div> -->
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="positionQuestionDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="submitPositionQuestionForm">保存</el-button>
- </span>
- </template>
- </el-dialog>
- <!-- 修改绑定开放问题的对话框 -->
- <el-dialog
- v-model="bindParentQuestionDialogVisible"
- title="绑定开放问题"
- width="50%"
- :before-close="handleBindDialogClose"
- >
- <div>
- <!-- <div class="search-container" style="margin-bottom: 15px;">
- <el-input
- v-model="openQuestionKeyword"
- placeholder="搜索开放问题"
- clearable
- @input="searchOpenQuestions"
- style="width: 100%"
- >
- <template #prefix>
- <el-icon><Search /></el-icon>
- </template>
- </el-input>
- </div> -->
-
- <p>请选择要关联的开放问题:</p>
- <el-select
- v-model="selectedParentQuestionId"
- filterable
- clearable
- placeholder="请选择开放问题"
- style="width: 100%"
- :loading="openQuestionsLoading"
- >
- <el-option
- v-for="item in openQuestionsList"
- :key="item.id"
- :label="item.question"
- :value="item.id"
- />
- </el-select>
- </div>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="handleBindDialogClose">取消</el-button>
- <el-button type="primary" @click="confirmBindParentQuestion">确认</el-button>
- </span>
- </template>
- </el-dialog>
- </fs-page>
- </template>
- <script lang="ts" setup name="documentList">
- import { ref, onMounted, onBeforeUnmount, computed } from 'vue';
- import { useFs } from '@fast-crud/fast-crud';
- import XEUtils from 'xe-utils';
- import { ElMessageBox, ElMessage } from 'element-plus';
- import { createCrudOptions } from './crud';
- import { Session } from '/@/utils/storage';
- import { getBaseURL } from '/@/utils/baseUrl';
- import { Document, Delete, View, Printer, Search, Link } from '@element-plus/icons-vue';
- import { successNotification } from '/@/utils/message';
- import DocumentTreeCom from './components/DocumentTreeCom/index.vue';
- import DocumentFormCom from './components/DocumentFormCom/index.vue';
- import { GetDocumentTree, DeleteDocumentCategory, UpdateDocument ,UpdateSequence, GetInterviewQuestions,AddDocument,
- GetCategoryList, GetTagList
- } from './api';
- import { useRouter } from 'vue-router';
- /* import { print } from '/@/utils/print'; */
- const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
- const router = useRouter();
- // 添加 uploadedFile 类型定义
- interface UploadedFileInfo {
- name: string;
- file_path: string;
- file_type: string;
- file_size: number;
- doc_type: string;
- category: string;
- status: boolean;
- doc_desc: string;
- version: string;
- file: string;
- }
- // 修改 uploadedFile 的类型
- const uploadedFile = ref<UploadedFileInfo | null>(null);
- // 树形结构相关数据
- const documentTreeData = ref([]);
- const documentTreeCacheData = ref<any[]>([]);
- const drawerVisible = ref(false);
- const drawerFormData = ref({});
- const documentTreeRef = ref<DocumentTreeRef | null>(null);
- // 添加一个响应式变量来存储当前选中的分类ID
- const selectedCategoryId = ref('');
- // 添加详情对话框相关数据
- const detailDialogVisible = ref(false);
- const detailData = ref({
- id: 0,
- name: '',
- doc_type: '',
- category: '',
- project: '',
- version: '',
- file_type: '',
- file_size: 0,
- status: true,
- doc_desc: '',
- update_datetime: '',
- file_path: '',
- file: ''
- });
- // 添加打印区域的ref
- const printArea = ref(null);
- // 视频和字幕上传相关状态
- const videoInfo = ref({
- url: '',
- name: '',
- size: 0,
- file_type: ''
- });
- const subtitleInfo = ref({
- url: '',
- name: '',
- size: 0,
- file_type: ''
- });
- // 视频上传对话框状态
- const videoUploadDialogVisible = ref(false);
- const currentRow = ref<QuestionRow | null>(null);
- // 添加字幕内容输入框
- const subtitleContent = ref('');
- // 添加表单引用
- const videoFormRef = ref<{
- validate: (callback: (valid: boolean) => void) => void;
- } | null>(null);
- // 添加表单数据对象
- const videoForm = ref({
- videoUrl: '',
- subtitleContent: ''
- });
- // 添加表单验证规则
- const videoFormRules = {
- videoUrl: [
- { required: true, message: '请上传数字人视频', trigger: 'change' }
- ]
- };
- // 添加排序对话框相关状态
- const sortDialogVisible = ref(false);
- const sortForm = ref({
- sequence_number: 0
- });
- const sortFormRules = {
- sequence_number: [
- { required: true, message: '请输入排序值', trigger: 'blur' },
- { type: 'number', message: '排序值必须为数字', trigger: 'blur' }
- ]
- };
- const sortFormRef = ref<any>(null);
- // 添加类型定义
- interface DocumentTreeRef {
- treeRef?: {
- currentNode?: {
- parent?: {
- data: any;
- };
- };
- };
- }
- // 修改 currentRow 的类型
- interface QuestionRow {
- id?: number;
- question_id?: string | number;
- sequence_number?: number;
- }
- // 职位问题对话框相关状态
- const positionQuestionDialogVisible = ref(false);
- const positionQuestionForm = ref({
- position: '',
- questions: [] as string[],
- sequence: '0'
- });
- // 职位选项和问题选项
- const positionOptions = ref<{ value: string, label: string }[]>([]);
- const questionOptions = ref<{ value: string, label: string, chinese_explanation: string, question_form_name: string ,category_id: string}[]>([]);
- // 计算属性:已选问题列表
- const selectedQuestions = computed(() => {
- // 根据已选ID查找完整的问题对象
- return positionQuestionForm.value.questions.map(questionId => {
- // 先在当前页面的问题中查找
- const question = questionOptions.value.find(q => q.value === questionId);
- if (question) return question;
-
- // 如果在当前页面找不到,可能需要额外请求
- // 这里简化处理,只返回ID
- return { value: questionId, label: `问题 ${questionId}`, chinese_explanation: '' ,question_form_name: '' ,category_id: ''};
- });
- });
- // 问题加载状态
- const questionsLoading = ref(false);
- // 问题分页参数
- const questionPagination = ref({
- currentPage: 1,
- pageSize: 10,
- total: 0
- });
- // 添加问题分类选项
- const questionCategoryOptions = ref<{ value: string, label: string }[]>([]);
- const selectedQuestionCategory = ref('');
- // 修改 loadQuestionOptions 函数,添加加载分类选项的逻辑
- const loadQuestionOptions = async () => {
- try {
- questionsLoading.value = true;
-
- // 构建请求参数,添加分类筛选
- const requestParams: any = {
- page: questionPagination.value.currentPage,
- limit: questionPagination.value.pageSize,
- tenant_id: 1,
- keyword: questionSearchKeyword.value
- };
-
- // 如果选择了分类,添加到请求参数
- if (selectedQuestionCategory.value) {
- requestParams.category_id = selectedQuestionCategory.value;
- }
-
- const response = await GetInterviewQuestions(requestParams);
-
- if (response && response.code === 2000 && response.data) {
- // 转换数据格式
- questionOptions.value = response.data.items.map((item: any) => ({
- value: item.id.toString(),
- label: item.question,
- chinese_explanation: item.scoring_reference || '暂无释义',
- question_form_name: item.question_form_name || '暂无问题类型',
- category_id: item.category || '暂无分类'
- }));
-
- // 更新总数
- questionPagination.value.total = response.data.total;
-
- // 如果是第一次加载,获取所有分类选项
- if (questionCategoryOptions.value.length === 0) {
- loadQuestionCategories();
- }
- } else {
- ElMessage.error('获取问题列表失败');
- }
- } catch (error) {
- console.error('加载问题选项失败:', error);
- ElMessage.error('加载问题选项失败');
- } finally {
- questionsLoading.value = false;
- }
- };
- // 添加加载问题分类的方法
- const loadQuestionCategories = async () => {
- try {
- // 调用API获取问题分类列表
- const response = await fetch(getBaseURL() + 'api/system/question_category/list?tenant_id=1', {
- method: 'GET',
- headers: {
- 'Authorization': 'JWT ' + Session.get('token'),
- 'Content-Type': 'application/json'
- }
- });
-
- if (!response.ok) {
- throw new Error('获取问题分类列表失败');
- }
-
- const data = await response.json();
-
- if (data && data.code === 2000 && data.data.items) {
- // 转换数据格式
- questionCategoryOptions.value = data.data.items.map((item: any) => ({
- value: item.id.toString(),
- label: item.name
- }));
- }
- } catch (error) {
- console.error('加载问题分类选项失败:', error);
- }
- };
- // 修改 filterQuestions 方法
- const filterQuestions = () => {
- // 重置到第一页
- questionPagination.value.currentPage = 1;
- // 重新加载数据
- loadQuestionOptions();
- };
- // 修改计算属性:过滤后的问题选项
- const filteredQuestionOptions = computed(() => {
- // 由于我们已经在API请求中处理了关键词和分类筛选,
- // 这里直接返回问题选项即可
- return questionOptions.value;
- });
- // 处理问题选择变化
- const handleQuestionSelectionChange = (selection: any[]) => {
- positionQuestionForm.value.questions = selection.map(item => item.value);
- };
- // 处理问题分页大小变化
- const handleQuestionSizeChange = (size: number) => {
- questionPagination.value.pageSize = size;
- loadQuestionOptions();
- };
- // 处理问题当前页变化
- const handleQuestionCurrentChange = (page: number) => {
- questionPagination.value.currentPage = page;
- loadQuestionOptions();
- };
- // 修改打开职位问题对话框方法
- const openPositionQuestionDialog = () => {
- // 加载职位选项
- loadPositionOptions();
- // 加载问题选项
- loadQuestionOptions();
- positionQuestionDialogVisible.value = true;
- };
- // 修改 loadPositionOptions 函数,从实际API获取职位数据
- const loadPositionOptions = async () => {
- try {
- // 调用实际的API获取职位列表
- const response = await fetch(getBaseURL() + 'api/system/job/list?tenant_id=1', {
- method: 'GET',
- headers: {
- 'Authorization': 'JWT ' + Session.get('token'),
- 'Content-Type': 'application/json'
- }
- });
-
- if (!response.ok) {
- throw new Error('获取职位列表失败');
- }
-
- const data = await response.json();
-
- if (data && data.code === 2000 && data.data) {
- // 转换数据格式
- positionOptions.value = data.data.map((item: any) => ({
- value: item.id.toString(),
- label: item.title,
- // 如果有中文释义字段,可以在这里添加
- description: item.description || ''
- }));
- } else {
- ElMessage.error('获取职位列表失败');
- }
- } catch (error) {
- console.error('加载职位选项失败:', error);
- ElMessage.error('加载职位选项失败');
- }
- };
- // 添加一个函数来获取职位名称
- const getPositionName = (positionId: string | number) => {
- if (!positionId) return '';
- const position = positionOptions.value.find(p => p.value === positionId.toString());
- return position ? position.label : positionId.toString();
- };
- // 修改 submitPositionQuestionForm 函数,添加中文释义处理
- const submitPositionQuestionForm = async () => {
- try {
- if (!positionQuestionForm.value.position) {
- ElMessage.warning('请选择职位');
- return;
- }
-
- if (positionQuestionForm.value.questions.length === 0) {
- ElMessage.warning('请选择至少一个问题');
- return;
- }
-
- // 获取职位名称,用于显示成功消息
- const positionName = getPositionName(positionQuestionForm.value.position);
-
- // 构建API请求数据 - 修改为符合API期望的格式
- const requestData = {
- position_id: parseInt(positionQuestionForm.value.position),
- question_id: {
- question_id: positionQuestionForm.value.questions.map(questionId => parseInt(questionId))
- },
- duration: parseInt(positionQuestionForm.value.sequence) || 60,
- tenant_id: 1
- };
-
- console.log('提交的数据:', requestData);
-
- // 调用实际的API保存职位问题关联
- const response = await AddDocument(requestData);
-
- if (response && response.code === 2000) {
- ElMessage.success(`${positionName}职位问题保存成功`);
- positionQuestionDialogVisible.value = false;
-
- // 重置表单
- positionQuestionForm.value = {
- position: '',
- questions: [],
- sequence: '0'
- };
-
- // 刷新列表
- crudExpose.doRefresh();
- } else {
- ElMessage.error(response?.msg || '保存失败');
- }
- } catch (error) {
- console.error('保存职位问题失败:', error);
- ElMessage.error('保存失败,请重试');
- }
- };
- // 移除已选问题
- const removeQuestion = (question: { value: string, label: string }) => {
- positionQuestionForm.value.questions = positionQuestionForm.value.questions.filter(
- id => id !== question.value
- );
- };
- // 获取文档树数据
- const getTreeData = () => {
- GetDocumentTree({}).then((ret: any) => {
- console.log('ret', ret);
- const responseData = ret.data;
- GetCategoryList({}).then((res: any) => {
- console.log('res', res);
- const categoryList = res.data.items;
- GetTagList({}).then((res: any) => {
- console.log('res', res);
- const tagList = res.data.items;
-
- // 创建根节点
- const rootNode = [
- {
- id: 'root',
- title: '职位开放题',
- children: responseData.map((item: any) => ({
- ...item,
- id: `open_${item.id}`, // 为职位开放题添加特定前缀
- title: item.name || item.title,
- question_type: 'open' // 添加类型标识
- }))
- },
- {
- id: 'professional_questions',
- title: '专业考察题库',
- children: responseData.map((item: any) => ({
- ...item,
- id: `prof_${item.id}`, // 为专业考察题添加特定前缀
- title: item.name || item.title,
- question_type: 'professional' // 添加类型标识
- }))
- },
- /* ...categoryList.map((item: any) => ({
- id: `category_${item.id}`,
- title: item.name,
- children: tagList.filter((tag: any) => tag.category_id === item.id).map((tag: any) => ({
- id: `tag_${tag.id}`,
- title: tag.name
- }))
- }))
- {
- id: categoryList.find((item: any) => item.name === '基本常识')?.id || '7',
- title: categoryList.find((item: any) => item.name === '基本常识')?.name || '基本常识',
- },
- {
- id: 'psychology_questions',
- title: '心理题库',
- children: [
- {
- id: 'psychology_questions_1',
- title: 'DVF心理测评',
- },
- {
- id: 'psychology_questions_2',
- title: '卡特尔人格因素测验',
- },
- {
- id: 'psychology_questions_3',
- title: '明尼苏达多相人格测验',
- }
- ]
- } */
- ]
- documentTreeData.value = rootNode
- });
- })
- });
- };
- // 处理树节点点击
- const handleTreeClick = (record: any) => {
- console.log('record', record);
-
- // 获取当前节点ID并处理前缀
- const currentNodeId = record?.currentNode?.id || '';
- const originalId = currentNodeId.replace(/^(open_|prof_|category_|tag_)/, '');
-
- // 保存当前选中的分类ID,如果ID不是数字则使用默认值1
- selectedCategoryId.value = (!isNaN(Number(originalId)) && originalId !== null) ? originalId : 1;
-
- // 根据不同的问题类型设置question_form
- let question_form;
- const firstLevelId = record?.firstLevel?.id;
- const nodeType = currentNodeId.startsWith('open_') ? 'open' :
- currentNodeId.startsWith('prof_') ? 'professional' :
- currentNodeId.startsWith('category_') ? 'category' :
- currentNodeId.startsWith('tag_') ? 'tag' :
- firstLevelId;
-
-
- if (nodeType === 'open' || firstLevelId === 'root') {
- question_form = '0'; // 职位开放题
- } else if (firstLevelId === 'psychology_questions') {
- question_form = '4'; // 心理题库
- } else if (firstLevelId === 'common_questions') {
- question_form = '1'; // 常识题库
- } else if (nodeType === 'professional' || firstLevelId === 'professional_questions') {
- question_form = '1,2,3,4,6'; // 专业考察题库
- } /* else {
- question_form = '0'; // 默认为职位开放题
- } */
- // 重置上传文件状态
- uploadedFile.value = null;
-
- // 构建搜索参数
- const searchParams = {
- form: {
- limit: 100,
- job_id: selectedCategoryId.value,
- question_form: question_form,
- ...(nodeType === 'category' ? { category: originalId } : {}),
- ...(nodeType === 'tag' ? { tag: originalId } : {})
- }
- };
-
- // 执行搜索
- crudExpose.doSearch(searchParams);
- };
- // 处理文档分类的新增或编辑
- const handleUpdateDocument = (type: any, record: any) => {
- if (type === 'update' && record) {
- const parentData = documentTreeRef.value?.treeRef?.currentNode?.parent?.data || {};
- documentTreeCacheData.value = [parentData];
- drawerFormData.value = record;
- }
- drawerVisible.value = true;
- };
- // 关闭抽屉
- const handleDrawerClose = (type: string) => {
- if (type === 'submit') {
- getTreeData();
- }
- drawerVisible.value = false;
- drawerFormData.value = {};
- };
- // 删除文档分类
- const handleDeleteDocument = (id: any, callback: any) => {
- ElMessageBox.confirm('您确认删除该文档分类吗?', '温馨提示', {
- confirmButtonText: '确认',
- cancelButtonText: '取消',
- type: 'warning',
- }).then(async () => {
- const res = await DeleteDocumentCategory(id);
- callback();
- if (res?.code === 2000) {
- successNotification(res.msg);
- getTreeData();
- }
- });
- };
- // 处理上传成功
- const handleUploadSuccess = (response: any, uploadFile: any, scope: any) => {
- if (!response || !response.data) {
- console.error('Upload response is invalid:', response);
- return;
- }
- const fileInfo = {
- name: uploadFile.name,
- file_path: response.data.bucket_url,
- file_type: response.data.file_type,
- file_size: uploadFile.size,
- doc_type: scope.form.doc_type || '',
- category: selectedCategoryId.value, // 使用当前选中的分类ID
- status: true,
- doc_desc: '',
- version: '',
- /* merchant: Session.get('merchant_info').merchant_id, */
- file: response.data.bucket_url
- };
-
- // 更新表单数据
- if (scope.form) {
- Object.assign(scope.form, fileInfo);
- } else {
- console.error('Form reference is not available');
- return;
- }
-
- // 更新已上传文件显示
- uploadedFile.value = fileInfo;
-
- // 刷新列表时传递category参数
- crudExpose.doRefresh();
- };
- // 处理文件删除
- const handleRemoveFile = (scope: any) => {
- uploadedFile.value = null;
- // 清空表单数据
- if (crudRef.value?.form) {
- const form = crudRef.value.form;
- form.name = '';
- form.file_path = '';
- form.file_type = '';
- form.file_size = '';
- form.doc_type = '';
- form.category = '';
- form.doc_desc = '';
- form.version = '';
- form.file = '';
- scope.value = '';
- }
- };
- // 格式化文件大小
- const formatFileSize = (bytes: number) => {
- if (bytes === 0) return '0 B';
- const k = 1024;
- const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
- };
- // 判断文件是否可预览
- const isPreviewable = (fileType: string) => {
- if (!fileType) return false;
-
- // 支持的文件类型
- const supportedTypes = [
- 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
- 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'
- ];
-
- return supportedTypes.includes(fileType.toLowerCase());
- };
- // 预览文件
- const previewFile = (fileData: any) => {
- if (!fileData || !fileData.file) {
- ElMessageBox.alert('文件链接不存在', '预览失败', { type: 'error' });
- return;
- }
-
- const fileType = fileData.file_type || '';
- const filePath = fileData.file;
- const previewUrl = `/#/preview?url=${encodeURIComponent(filePath)}&type=${fileType}`;
- window.open(previewUrl, '_blank');
- };
- // 处理查看详情
- const handleViewDetail = async (event: any) => {
- const row = event.detail || event;
- try {
- // 可以选择直接使用传入的行数据,或者重新请求完整数据
- // 如果需要重新请求数据,可以添加一个 GetDocument API 方法
- // const res = await GetDocument(row.id);
- // detailData.value = res.data;
-
- // 这里直接使用行数据
- detailData.value = row;
- detailDialogVisible.value = true;
- } catch (error) {
- ElMessage.error('获取文档详情失败');
- }
- };
- // 获取文档类型名称
- const getDocTypeName = (docTypeId: number | string) => {
- if (!docTypeId) return '';
- const docTypeDict = [
- { value: 1, label: '合同文档' },
- { value: 2, label: '技术文档' },
- { value: 3, label: '操作手册' },
- { value: 4, label: '维护记录' },
- { value: 5, label: '其他文档' }
- ];
- const docType = docTypeDict.find(item => item.value === docTypeId);
- return docType ? docType.label : docTypeId;
- };
- // 获取分类名称
- const getCategoryName = (categoryId: number | string) => {
- if (!categoryId) return '';
- const categoryDict = (window as any).__categoryDict || [];
- const category = categoryDict.find((item: any) => item.id === categoryId);
- return category ? category.name : categoryId;
- };
- // 获取项目名称
- const getProjectName = (projectId: number | string) => {
- if (!projectId) return '';
- const projectDict = (window as any).__projectDict || [];
- const project = projectDict.find((item: any) => item.id === projectId);
- return project ? project.name : projectId;
- };
- // 预览详情中的文件
- const previewDetailFile = (fileData: any) => {
- if (!fileData || !fileData.file_path) {
- ElMessage.error('文件路径无效');
- return;
- }
- const filePath = fileData.file_path;
- const fileType = fileData.file_type;
-
- // 支持的文件类型列表
- const supportedTypes = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
-
- if (!supportedTypes.includes(fileType)) {
- ElMessage.warning(`暂不支持预览该文件类型: ${fileType}`);
- return;
- }
- // 使用window.open在新窗口中打开预览页面
- const previewUrl = `/#/preview?url=${encodeURIComponent(filePath)}&type=${fileType}`;
- window.open(previewUrl, '_blank');
- };
- // 处理打印功能
- const handlePrint = () => {
- /* print(printArea.value, {
- title: `${detailData.value.name}文档详情`,
- style: `
- .el-descriptions { margin: 10px 0; }
- .el-descriptions-item__label { font-weight: bold; }
- .el-tag { padding: 2px 6px; }
- @media print {
- .dialog-footer { display: none; }
- }
- `
- }); */
- };
- // 视频上传前的验证
- const beforeVideoUpload = (file: any) => {
- const isVideo = file.type.startsWith('video/');
- const isLt500M = file.size / 1024 / 1024 < 500;
- if (!isVideo) {
- ElMessage.error('请上传视频文件!');
- return false;
- }
- if (!isLt500M) {
- ElMessage.error('视频大小不能超过 500MB!');
- return false;
- }
- return true;
- };
- // 字幕上传前的验证
- const beforeSubtitleUpload = (file: any) => {
- const validExtensions = ['.srt', '.vtt', '.ass'];
- const extension = file.name.substring(file.name.lastIndexOf('.')).toLowerCase();
- const isValidType = validExtensions.includes(extension);
- const isLt2M = file.size / 1024 / 1024 < 2;
- if (!isValidType) {
- ElMessage.error('请上传有效的字幕文件 (.srt, .vtt, .ass)!');
- return false;
- }
- if (!isLt2M) {
- ElMessage.error('字幕文件大小不能超过 2MB!');
- return false;
- }
- return true;
- };
- // 修改视频上传成功处理函数
- const handleVideoUploadSuccess = (response: any, file: any) => {
- console.log('response', response);
- console.log('file', file);
-
- // 检查响应格式,确保能正确获取URL
- if (response && response.url) {
- // 直接使用响应中的URL
- videoInfo.value = {
- url: response.url,
- name: file.name || response.filename || '',
- size: file.size || 0,
- file_type: file.type || response.mime_type || 'video/mp4'
- };
-
- // 更新表单数据
- videoForm.value.videoUrl = response.url;
- ElMessage.success('视频上传成功');
- } else if (response && response.data && response.data.url) {
- // 兼容另一种可能的响应格式
- videoInfo.value = {
- url: response.data.url,
- name: file.name || response.data.filename || '',
- size: file.size || 0,
- file_type: file.type || response.data.mime_type || 'video/mp4'
- };
-
- // 更新表单数据
- videoForm.value.videoUrl = response.data.url;
- ElMessage.success('视频上传成功');
- } else {
- // 尝试从您提供的响应格式中获取URL
- try {
- // 根据您提供的响应示例,URL可能在这个位置
- const url = response.url ||
- (response.response && response.response.url) ||
- (response[0] && response[0].url) ||
- (response.data && response.data.bucket_url);
-
- if (url) {
- videoInfo.value = {
- url: url,
- name: file.name || response.filename || response.original_filename || '',
- size: file.size || response.size || 0,
- file_type: file.type || response.mime_type || 'video/mp4'
- };
-
- // 更新表单数据
- videoForm.value.videoUrl = url;
- ElMessage.success('视频上传成功');
- } else {
- throw new Error('无法从响应中获取URL');
- }
- } catch (error) {
- console.error('处理上传响应时出错:', error);
- ElMessage.error('视频上传成功,但无法解析响应数据');
- }
- }
- };
- // 修改字幕上传成功处理函数
- const handleSubtitleUploadSuccess = (response: any, file: any) => {
- if (response && response.data) {
- subtitleInfo.value = {
- url: response.data.bucket_url,
- name: file.name,
- size: file.size,
- file_type: file.name.substring(file.name.lastIndexOf('.') + 1)
- };
-
- // 尝试读取字幕文件内容
- fetch(response.data.bucket_url)
- .then(res => res.text())
- .then(text => {
- videoForm.value.subtitleContent = text;
- ElMessage.success('字幕文件已上传并内容已加载');
- })
- .catch(err => {
- console.error('无法读取字幕文件内容:', err);
- ElMessage.warning('字幕文件已上传,但无法读取内容');
- });
- } else {
- ElMessage.error('字幕上传失败');
- }
- };
- // 上传错误处理
- const handleUploadError = () => {
- ElMessage.error('上传失败,请重试');
- };
- // 处理打开视频上传对话框
- const handleOpenVideoUploadDialog = (event: any) => {
- const row = event.detail;
- if (row) {
- currentRow.value = row;
- videoUploadDialogVisible.value = true;
-
- // 重置上传状态
- videoInfo.value = { url: '', name: '', size: 0, file_type: '' };
- subtitleInfo.value = { url: '', name: '', size: 0, file_type: '' };
-
- // 重置表单数据
- videoForm.value = {
- videoUrl: '',
- subtitleContent: ''
- };
- }
- };
- // 提交表单
- const submitVideoForm = () => {
- if (!videoFormRef.value) return;
-
- videoFormRef.value.validate(async (valid: boolean) => {
- if (valid) {
- try {
- if (!currentRow.value || !currentRow.value.id) {
- ElMessage.error('当前选中的题目信息无效');
- return;
- }
- // 确保我们有视频URL
- if (!videoForm.value.videoUrl && !videoInfo.value.url) {
- ElMessage.warning('请先上传数字人视频');
- return;
- }
- // 修改为符合API要求的参数格式
- const updateData = {
- question_id: currentRow.value.question_id, // 必填,问题ID
- video_url: videoForm.value.videoUrl || videoInfo.value.url, // 数字人视频URL
- digital_human_video_subtitle: videoForm.value.subtitleContent, // 数字人视频字幕内容
- tenant_id: 1
- };
- console.log('提交的数据:', updateData);
-
- const response = await UpdateDocument(updateData);
-
- if (response && response.code === 2000) {
- ElMessage.success('数字人视频和字幕信息保存成功');
- videoUploadDialogVisible.value = false;
-
- // 重置上传状态
- videoInfo.value = { url: '', name: '', size: 0, file_type: '' };
- subtitleInfo.value = { url: '', name: '', size: 0, file_type: '' };
-
- // 重置表单数据
- videoForm.value = {
- videoUrl: '',
- subtitleContent: ''
- };
-
- // 刷新列表
- crudExpose.doRefresh();
- } else {
- ElMessage.error(response?.msg || '保存失败');
- }
- } catch (error) {
- console.error('保存视频和字幕信息时出错:', error);
- ElMessage.error('保存失败,请重试');
- }
- } else {
- ElMessage.warning('请完成必填项');
- return false;
- }
- });
- };
- // 处理打开排序对话框
- const handleOpenSortDialog = (event: any) => {
- const row = event.detail;
- if (row) {
- currentRow.value = row;
- sortForm.value.sequence_number = row.sequence_number || 0;
- sortDialogVisible.value = true;
- }
- };
- // 修改提交排序表单函数
- const submitSortForm = () => {
- if (!sortFormRef.value) return;
-
- sortFormRef.value.validate(async (valid: boolean) => {
- if (valid) {
- try {
- if (!currentRow.value || !currentRow.value.question_id) {
- ElMessage.error('当前选中的题目信息无效');
- return;
- }
- // 获取当前职位ID
- const positionId = selectedCategoryId.value;
- if (!positionId) {
- ElMessage.error('未选择职位分类');
- return;
- }
- // 构建符合新格式的更新数据
- const updateData = {
- position_id: positionId,
- questions: [
- {
- question_id: currentRow.value.question_id,
- sequence_number: sortForm.value.sequence_number
- }
- ],
- tenant_id: 1
- };
- console.log('提交的排序数据:', updateData);
-
- const response = await UpdateSequence(updateData);
-
- if (response && response.code === 2000) {
- ElMessage.success('排序修改成功');
- sortDialogVisible.value = false;
-
- // 刷新列表
- crudExpose.doRefresh();
- } else {
- ElMessage.error(response?.msg || '保存失败');
- }
- } catch (error) {
- console.error('保存排序信息时出错:', error);
- ElMessage.error('保存失败,请重试');
- }
- } else {
- ElMessage.warning('请输入有效的排序值');
- return false;
- }
- });
- };
- // 页面加载时获取数据
- onMounted(() => {
- getTreeData();
-
- // 先执行一次刷新,确保组件已完全初始化
- crudExpose.doRefresh();
-
- // 监听crud组件初始化完成
- setTimeout(() => {
- if (crudRef.value) {
- // 确保搜索表单已初始化
- if (!crudRef.value.searchForm) {
- crudRef.value.searchForm = {};
- }
-
- // 如果有默认选中的分类,设置搜索参数并执行搜索
- if (selectedCategoryId.value) {
- crudRef.value.searchForm.category = selectedCategoryId.value;
- crudExpose.doSearch({
- form: {
- category: selectedCategoryId.value
- }
- });
- }
- }
- }, 300);
- // 添加全局事件监听器
- window.addEventListener('viewDocumentDetail', handleViewDetail);
- window.addEventListener('openVideoUploadDialog', handleOpenVideoUploadDialog);
- window.addEventListener('openSortDialog', handleOpenSortDialog);
- window.addEventListener('openPositionQuestionDialog', openPositionQuestionDialog);
- // 添加绑定开放问题对话框的事件监听器
- window.addEventListener('openBindParentQuestionDialog', handleOpenBindParentQuestionDialog as EventListener);
- });
- // 添加 onBeforeUnmount 钩子移除事件监听器
- onBeforeUnmount(() => {
- window.removeEventListener('viewDocumentDetail', handleViewDetail);
- window.removeEventListener('openVideoUploadDialog', handleOpenVideoUploadDialog);
- window.removeEventListener('openSortDialog', handleOpenSortDialog);
- window.removeEventListener('openPositionQuestionDialog', openPositionQuestionDialog);
- // 移除绑定开放问题对话框的事件监听器
- window.removeEventListener('openBindParentQuestionDialog', handleOpenBindParentQuestionDialog as EventListener);
- });
- // 新增 questionSearchKeyword
- const questionSearchKeyword = ref('');
- // 绑定开放问题相关变量
- const bindParentQuestionDialogVisible = ref(false);
- const selectedParentQuestionId = ref<number | null>(null);
- const openQuestionsList = ref<any[]>([]);
- const openQuestionsLoading = ref(false);
- const openQuestionKeyword = ref('');
- // 获取开放问题列表
- const fetchOpenQuestions = async () => {
- try {
- openQuestionsLoading.value = true;
-
- // 构建请求参数,添加关键词搜索
- const params: any = {
- page: 1,
- limit: 200,
- tenant_id: 1,
- question_form: 0, // 开放问题类型
- };
-
- // 如果有关键词,添加到请求参数
- if (openQuestionKeyword.value) {
- params.keyword = openQuestionKeyword.value;
- }
-
- const res = await GetInterviewQuestions(params);
- openQuestionsList.value = res.data.items || [];
- } catch (error) {
- console.error('获取开放问题列表失败:', error);
- ElMessage.error('获取开放问题列表失败');
- } finally {
- openQuestionsLoading.value = false;
- }
- };
- // 搜索开放问题
- const searchOpenQuestions = () => {
- // 使用防抖函数,避免频繁请求
- if (searchOpenQuestionsDebounce) {
- clearTimeout(searchOpenQuestionsDebounce);
- }
-
- searchOpenQuestionsDebounce = setTimeout(() => {
- fetchOpenQuestions();
- }, 300);
- };
- // 防抖变量
- let searchOpenQuestionsDebounce: number | null = null;
- // 监听打开绑定对话框事件
- const handleOpenBindParentQuestionDialog = (event: CustomEvent) => {
- currentRow.value = event.detail;
- // 如果已有关联的开放问题,则预选中
- if (currentRow.value.parent_question_id) {
- selectedParentQuestionId.value = currentRow.value.parent_question_id;
- } else {
- selectedParentQuestionId.value = null;
- }
-
- // 重置搜索关键词
- openQuestionKeyword.value = '';
-
- // 获取开放问题列表
- fetchOpenQuestions();
-
- // 显示对话框
- bindParentQuestionDialogVisible.value = true;
- };
- // 添加处理绑定对话框关闭的函数
- const handleBindDialogClose = () => {
- bindParentQuestionDialogVisible.value = false;
- selectedParentQuestionId.value = null;
- currentRow.value = null;
- };
- // 添加确认绑定开放问题的函数
- const confirmBindParentQuestion = async () => {
- if (!currentRow.value || selectedParentQuestionId.value === null) {
- ElMessage.warning('请选择要关联的开放问题');
- return;
- }
-
- try {
- await UpdateDocument({
- id: currentRow.value.question_id,
- parent_question_id: selectedParentQuestionId.value,
- tenant_id: '1'
- });
-
- ElMessage.success('绑定开放问题成功');
- bindParentQuestionDialogVisible.value = false;
- selectedParentQuestionId.value = null;
-
- // 刷新表格数据
- crudExpose.doRefresh();
- } catch (error) {
- console.error('绑定开放问题失败:', error);
- ElMessage.error('绑定开放问题失败');
- }
- };
- // 添加 bindParentQuestion 方法
- const bindParentQuestion = (row: any) => {
- // 触发自定义事件,让父组件处理弹窗显示
- const event = new CustomEvent('openBindParentQuestionDialog', { detail: row });
- window.dispatchEvent(event);
- };
- </script>
- <style lang="scss" scoped>
- .document-el-row {
- height: 100%;
- overflow: hidden;
- .el-col {
- height: 100%;
- padding: 10px 0;
- box-sizing: border-box;
- }
- }
- .document-box {
- height: 100%;
- padding: 10px;
- background-color: var(--el-fill-color-blank);
- box-sizing: border-box;
- }
- .document-left-box {
- position: relative;
- border-radius: 0 8px 8px 0;
- margin-right: 10px;
- overflow-y: auto;
- }
- .document-right-box {
- border-radius: 8px 0 0 8px;
- }
- .mt-2 {
- margin-top: 0.5rem;
- }
- .w-full {
- width: 100%;
- }
- .mr-2 {
- margin-right: 0.5rem;
- }
- .mr-4 {
- margin-right: 1rem;
- }
- .text-gray-500 {
- color: #6b7280;
- }
- .text-sm {
- font-size: 0.875rem;
- }
- .flex {
- display: flex;
- }
- .items-center {
- align-items: center;
- }
- .justify-between {
- justify-content: space-between;
- }
- /* 添加详情对话框样式 */
- .el-descriptions {
- margin: 10px 0;
- }
- .dialog-footer {
- display: flex;
- justify-content: flex-end;
- }
- .mt-4 {
- margin-top: 1rem;
- }
- ::v-deep .el-upload{
- flex-direction: column;
- }
- /* 表单样式优化 */
- .el-form-item {
- margin-bottom: 20px;
- }
- .upload-section {
- border: 1px solid #ebeef5;
- border-radius: 4px;
- padding: 15px;
- background-color: #f8f9fa;
- }
- .video-uploader, .subtitle-uploader {
- width: 100%;
- }
- .video-preview {
- width: 100%;
- margin-bottom: 10px;
- }
- .video-player {
- width: 100%;
- max-height: 200px;
- object-fit: contain;
- border-radius: 4px;
- }
- .file-preview {
- display: flex;
- align-items: center;
- padding: 10px;
- border: 1px dashed #d9d9d9;
- border-radius: 4px;
- background-color: #fff;
- }
- .file-icon {
- font-size: 24px;
- margin-right: 10px;
- color: #409eff;
- }
- .file-info {
- display: flex;
- flex-direction: column;
- margin-top: 5px;
- }
- .file-size {
- font-size: 12px;
- color: #909399;
- margin-top: 3px;
- }
- .subtitle-tip {
- color: #909399;
- font-size: 12px;
- }
- .mt-2 {
- margin-top: 8px;
- }
- .question-list-container {
- margin-top: 20px;
- border: 1px solid #ebeef5;
- border-radius: 4px;
- padding: 15px;
- background-color: #f8f9fa;
- }
- .question-list-container h3 {
- margin-top: 0;
- margin-bottom: 15px;
- font-size: 16px;
- color: #303133;
- }
- .question-selection-container {
- width: 100%;
- border: 1px solid #ebeef5;
- border-radius: 4px;
- overflow: hidden;
- }
- .question-search {
- padding: 10px;
- background-color: #f5f7fa;
- border-bottom: 1px solid #ebeef5;
- }
- .search-row {
- display: flex;
- align-items: center;
- }
- .question-table-container {
- display: flex;
- flex-direction: column;
- height: 350px;
- }
- .pagination-container {
- margin-top: 10px;
- padding: 5px 0;
- display: flex;
- justify-content: flex-end;
- background-color: #f5f7fa;
- border-top: 1px solid #ebeef5;
- }
- .question-item {
- margin-bottom: 8px;
- padding: 8px;
- border-radius: 4px;
- transition: background-color 0.2s;
-
- &:hover {
- background-color: #f5f7fa;
- }
- }
- .no-data {
- padding: 20px 0;
- text-align: center;
- }
- .search-container {
- margin-bottom: 15px;
- }
- </style>
|