123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668 |
- <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 #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 } 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 } 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;
- /* const result = XEUtils.toArrayTree(responseData, {
- parentKey: 'parent',
- children: 'children',
- strict: true,
- }); */
- documentTreeData.value = responseData;
- });
- };
- // 处理树节点点击
- const handleTreeClick = (record: any) => {
- console.log('record', record);
-
- // 保存当前选中的分类ID
- selectedCategoryId.value = record?.id || '';
-
- // 重置上传文件状态
- uploadedFile.value = null;
-
- // 构建搜索参数
- const searchParams = {
- form: {
- limit:100,
- job_id: selectedCategoryId.value
- }
- };
-
- // 执行搜索
- 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('绑定开放问题失败');
- }
- };
- </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;
- }
- .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>
|