|
|
@@ -0,0 +1,1581 @@
|
|
|
+<template>
|
|
|
+ <div class="p-2 page-container">
|
|
|
+ <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
|
|
+ <div ref="searchRef" v-show="showSearch" class="mb-[10px]">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
|
|
+ <!-- <el-form-item label="删除标志" prop="deleted">
|
|
|
+ <el-select v-model="queryParams.deleted" placeholder="请选择删除标志" clearable >
|
|
|
+ <el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="dict.value"/>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item> -->
|
|
|
+ <el-form-item label="问卷编号" prop="surveyCode">
|
|
|
+ <el-input v-model="queryParams.surveyCode" placeholder="请输入问卷编号" clearable @keyup.enter="handleQuery" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="问卷标题" prop="surveyTitle">
|
|
|
+ <el-input v-model="queryParams.surveyTitle" placeholder="请输入问卷标题" clearable @keyup.enter="handleQuery" />
|
|
|
+ </el-form-item>
|
|
|
+ <!-- <el-form-item label="问卷描述" prop="surveyDesc">
|
|
|
+ <el-input v-model="queryParams.surveyDesc" placeholder="请输入问卷描述" clearable @keyup.enter="handleQuery" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="填写说明" prop="surveyInstruction">
|
|
|
+ <el-input v-model="queryParams.surveyInstruction" placeholder="请输入填写说明" clearable @keyup.enter="handleQuery" />
|
|
|
+ </el-form-item> -->
|
|
|
+ <el-form-item label="目标群体" prop="targetGroup">
|
|
|
+ <el-select v-model="queryParams.targetGroup" placeholder="请选择目标群体" clearable>
|
|
|
+ <el-option v-for="dict in sys_survey_people" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="开始时间" prop="startTime">
|
|
|
+ <el-date-picker clearable v-model="queryParams.startTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择开始时间" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="结束时间" prop="endTime">
|
|
|
+ <el-date-picker clearable v-model="queryParams.endTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择结束时间" />
|
|
|
+ </el-form-item>
|
|
|
+ <!-- <el-form-item label="最大提交次数" prop="maxSubmitTimes">
|
|
|
+ <el-input v-model="queryParams.maxSubmitTimes" placeholder="请输入最大提交次数" clearable @keyup.enter="handleQuery" />
|
|
|
+ </el-form-item> -->
|
|
|
+ <el-form-item label="是否匿名" prop="isAnonymous">
|
|
|
+ <el-select v-model="queryParams.isAnonymous" placeholder="请选择是否匿名" clearable>
|
|
|
+ <el-option v-for="dict in sys_yesno" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="是否发布" prop="isPublished">
|
|
|
+ <el-select v-model="queryParams.isPublished" placeholder="请选择是否发布" clearable>
|
|
|
+ <el-option v-for="dict in sys_yesno" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="状态" prop="status">
|
|
|
+ <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
|
|
+ <el-option v-for="dict in sys_survey_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
|
|
+ <el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+ </transition>
|
|
|
+
|
|
|
+ <el-card shadow="never" class="button-card table-card">
|
|
|
+ <template #header>
|
|
|
+ <el-row ref="toolbarRef" :gutter="10" class="mb8">
|
|
|
+ <el-col :span="1.5">
|
|
|
+ <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['hq:satSurvey:add']">新增</el-button>
|
|
|
+ </el-col>
|
|
|
+ <!-- <el-col :span="1.5">
|
|
|
+ <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:satSurvey:edit']">修改</el-button>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="1.5">
|
|
|
+ <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:satSurvey:remove']"
|
|
|
+ >删除</el-button
|
|
|
+ >
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="1.5">
|
|
|
+ <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['hq:satSurvey:export']">导出</el-button>
|
|
|
+ </el-col> -->
|
|
|
+ <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
|
|
+ </el-row>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <div class="table-container">
|
|
|
+ <el-table
|
|
|
+ :height="tableHeight"
|
|
|
+ v-loading="loading"
|
|
|
+ border
|
|
|
+ :data="satSurveyList"
|
|
|
+ @selection-change="handleSelectionChange"
|
|
|
+ style="width: 100%"
|
|
|
+ :scroll-x="true"
|
|
|
+ class="scroll-table"
|
|
|
+ >
|
|
|
+ <el-table-column type="selection" width="55" align="center" fixed="left" />
|
|
|
+ <!-- <el-table-column label="主键ID" align="center" prop="id" v-if="true" /> -->
|
|
|
+ <!-- <el-table-column label="删除标志" align="center" prop="deleted">
|
|
|
+ <template #default="scope">
|
|
|
+ <dict-tag :options="sys_item_delete" :value="scope.row.deleted" />
|
|
|
+ </template>
|
|
|
+ </el-table-column> -->
|
|
|
+ <el-table-column label="问卷编号" align="center" prop="surveyCode" width="140" show-overflow-tooltip />
|
|
|
+ <el-table-column label="问卷标题" align="center" prop="surveyTitle" min-width="200" show-overflow-tooltip />
|
|
|
+ <el-table-column label="问卷描述" align="center" prop="surveyDesc" min-width="250" show-overflow-tooltip />
|
|
|
+ <el-table-column label="填写说明" align="center" prop="surveyInstruction" min-width="250" show-overflow-tooltip />
|
|
|
+ <el-table-column label="目标群体" align="center" prop="targetGroup" width="120">
|
|
|
+ <template #default="scope">
|
|
|
+ <dict-tag :options="sys_survey_people" :value="scope.row.targetGroup" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="开始时间" align="center" prop="startTime" width="180">
|
|
|
+ <template #default="scope">
|
|
|
+ <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="结束时间" align="center" prop="endTime" width="180">
|
|
|
+ <template #default="scope">
|
|
|
+ <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <!-- <el-table-column label="最大提交次数" align="center" prop="maxSubmitTimes" /> -->
|
|
|
+ <el-table-column label="是否匿名" align="center" prop="isAnonymous" width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ <dict-tag :options="sys_yesno" :value="scope.row.isAnonymous" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="是否发布" align="center" prop="isPublished" width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ <dict-tag :options="sys_yesno" :value="scope.row.isPublished" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="状态" align="center" prop="status" width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ <dict-tag :options="sys_survey_status" :value="scope.row.status" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="备注" align="center" prop="remark" min-width="200" show-overflow-tooltip />
|
|
|
+ <el-table-column label="操作" align="center" width="250" fixed="right" class-name="small-padding fixed-width">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tooltip content="题目管理" placement="top">
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ icon="EditPen"
|
|
|
+ @click="handleQuestionManage(scope.row)"
|
|
|
+ v-hasPermi="['hq:satSurvey:question']"
|
|
|
+ ></el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ <el-tooltip content="修改" placement="top">
|
|
|
+ <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['hq:satSurvey:edit']"></el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ <el-tooltip v-if="scope.row.isPublished === 0 || scope.row.isPublished === '0'" content="发布" placement="top">
|
|
|
+ <el-button link type="success" icon="Upload" @click="handlePublish(scope.row)" v-hasPermi="['hq:satSurvey:publish']"></el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ <el-tooltip v-else content="撤销发布" placement="top">
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="warning"
|
|
|
+ icon="Download"
|
|
|
+ @click="handleUnpublish(scope.row)"
|
|
|
+ v-hasPermi="['hq:satSurvey:unpublish']"
|
|
|
+ ></el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ <el-tooltip content="删除" placement="top">
|
|
|
+ <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['hq:satSurvey:remove']"></el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ <div class="pagination-container">
|
|
|
+ <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ <!-- 添加或修改问卷对话框 -->
|
|
|
+ <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
|
|
|
+ <el-form ref="satSurveyFormRef" :model="form" :rules="rules" label-width="80px">
|
|
|
+ <!-- <el-form-item label="删除标志" prop="deleted">
|
|
|
+ <el-select v-model="form.deleted" placeholder="请选择删除标志">
|
|
|
+ <el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item> -->
|
|
|
+ <el-form-item label="问卷编号" prop="surveyCode">
|
|
|
+ <el-input v-model="form.surveyCode" placeholder="请输入问卷编号" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="问卷标题" prop="surveyTitle">
|
|
|
+ <el-input v-model="form.surveyTitle" placeholder="请输入问卷标题" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="问卷描述" prop="surveyDesc">
|
|
|
+ <el-input v-model="form.surveyDesc" type="textarea" placeholder="请输入内容" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="填写说明" prop="surveyInstruction">
|
|
|
+ <el-input v-model="form.surveyInstruction" type="textarea" placeholder="请输入内容" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="目标群体" prop="targetGroup">
|
|
|
+ <el-select v-model="form.targetGroup" placeholder="请选择目标群体">
|
|
|
+ <el-option v-for="dict in sys_survey_people" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="开始时间" prop="startTime">
|
|
|
+ <el-date-picker clearable v-model="form.startTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择开始时间">
|
|
|
+ </el-date-picker>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="结束时间" prop="endTime">
|
|
|
+ <el-date-picker clearable v-model="form.endTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择结束时间">
|
|
|
+ </el-date-picker>
|
|
|
+ </el-form-item>
|
|
|
+ <!-- <el-form-item label="最大提交次数" prop="maxSubmitTimes">
|
|
|
+ <el-input v-model="form.maxSubmitTimes" placeholder="请输入最大提交次数" />
|
|
|
+ </el-form-item> -->
|
|
|
+ <el-form-item label="是否匿名" prop="isAnonymous">
|
|
|
+ <el-select v-model="form.isAnonymous" placeholder="请选择是否匿名">
|
|
|
+ <el-option v-for="dict in sys_yesno" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="是否发布" prop="isPublished">
|
|
|
+ <el-select v-model="form.isPublished" placeholder="请选择是否发布">
|
|
|
+ <el-option v-for="dict in sys_yesno" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="状态" prop="status">
|
|
|
+ <el-select v-model="form.status" placeholder="请选择状态">
|
|
|
+ <el-option v-for="dict in sys_survey_status" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="备注" prop="remark">
|
|
|
+ <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
|
|
+ <el-button @click="cancel">取 消</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 题目管理对话框 -->
|
|
|
+ <el-dialog :title="questionDialog.title" v-model="questionDialog.visible" width="1200px" append-to-body>
|
|
|
+ <div class="new-question-manage-container" v-loading="questionLoading">
|
|
|
+ <!-- 题目管理工具栏 -->
|
|
|
+ <div v-if="!readonly && selectedQuestions.length > 0" class="question-toolbar">
|
|
|
+ <div class="toolbar-left">
|
|
|
+ <span class="question-count">共 {{ selectedQuestions.length }} 个题目</span>
|
|
|
+ </div>
|
|
|
+ <div class="toolbar-right">
|
|
|
+ <el-button type="danger" icon="Delete" @click="confirmDeleteAllQuestions" size="small"> 删除所有题目 </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 题目列表 -->
|
|
|
+ <div class="question-builder">
|
|
|
+ <!-- 题目显示区域 -->
|
|
|
+ <div class="question-list-container">
|
|
|
+ <div class="question-list">
|
|
|
+ <div v-for="(question, index) in selectedQuestions" :key="question.id || question.questionId" class="question-card">
|
|
|
+ <div class="question-header">
|
|
|
+ <div class="question-number">第 {{ index + 1 }} 题</div>
|
|
|
+ <div class="question-actions" v-if="!readonly">
|
|
|
+ <el-button type="text" size="small" @click="removeQuestion(index)">
|
|
|
+ <el-icon><Delete /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="question-content-display">
|
|
|
+ <div class="question-title-display">{{ question.questionTitle }}</div>
|
|
|
+ <div class="question-meta-info">
|
|
|
+ <el-tag size="small" type="primary">{{ getQuestionType(question.questionType) }}</el-tag>
|
|
|
+ <el-tag v-if="question.isRequired === 1 || question.isRequired === '1'" size="small" type="danger">必填</el-tag>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 题目答案展示区域 -->
|
|
|
+ <div class="answer-display-area">
|
|
|
+ <!-- 单选题 -->
|
|
|
+ <el-radio-group v-if="question.questionType === '1' || question.questionType === 1" disabled>
|
|
|
+ <el-radio v-for="option in question.options || []" :key="option.id" :label="option.optionValue" class="answer-option">
|
|
|
+ {{ option.optionText }}
|
|
|
+ </el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+
|
|
|
+ <!-- 多选题 -->
|
|
|
+ <el-checkbox-group v-else-if="question.questionType === '2' || question.questionType === 2" disabled>
|
|
|
+ <el-checkbox v-for="option in question.options || []" :key="option.id" :label="option.optionValue" class="answer-option">
|
|
|
+ {{ option.optionText }}
|
|
|
+ </el-checkbox>
|
|
|
+ </el-checkbox-group>
|
|
|
+
|
|
|
+ <!-- 问答题 -->
|
|
|
+ <el-input
|
|
|
+ v-else-if="question.questionType === '3' || question.questionType === 3"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="请输入答案..."
|
|
|
+ disabled
|
|
|
+ class="answer-textarea"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 空状态显示区域 -->
|
|
|
+ <div class="action-area">
|
|
|
+ <div v-if="selectedQuestions.length === 0" class="empty-state">
|
|
|
+ <el-icon class="empty-icon"><DocumentAdd /></el-icon>
|
|
|
+ <p class="empty-text">{{ readonly ? '该问卷还没有配置题目' : '问卷还没有题目,请添加第一个题目' }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer question-manage-footer">
|
|
|
+ <el-button v-if="!readonly" type="primary" icon="Plus" @click="openQuestionSelector">
|
|
|
+ {{ selectedQuestions.length === 0 ? '添加第一个题目' : '继续添加题目' }}
|
|
|
+ </el-button>
|
|
|
+ <el-button v-if="!readonly && hasLoadedData" type="success" icon="Check" :loading="questionLoading" @click="saveQuestionChanges">
|
|
|
+ 保存
|
|
|
+ </el-button>
|
|
|
+ <el-button @click="cancelQuestionManage">{{ readonly ? '关闭' : '取消' }}</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 题目选择对话框 -->
|
|
|
+ <el-dialog title="选择题目" v-model="showQuestionSelector" width="1400px" append-to-body>
|
|
|
+ <div class="question-selector">
|
|
|
+ <!-- 搜索区域 -->
|
|
|
+ <div class="selector-search">
|
|
|
+ <div class="search-row">
|
|
|
+ <div class="search-group">
|
|
|
+ <label class="search-label">题目搜索:</label>
|
|
|
+ <el-input v-model="questionSearchText" placeholder="输入关键词搜索题目标题..." clearable style="width: 350px" size="default" />
|
|
|
+ </div>
|
|
|
+ <div class="search-group">
|
|
|
+ <label class="search-label">题目类型:</label>
|
|
|
+ <el-select v-model="questionTypeFilter" placeholder="请选择题目类型" clearable style="width: 150px" size="default">
|
|
|
+ <el-option label="单选题" value="1" />
|
|
|
+ <el-option label="多选题" value="2" />
|
|
|
+ <el-option label="问答题" value="3" />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ <div class="search-group">
|
|
|
+ <label class="search-label">题目分类:</label>
|
|
|
+ <el-select v-model="questionCategoryFilter" placeholder="请选择分类" clearable style="width: 180px" size="default">
|
|
|
+ <el-option v-for="category in categories" :key="category.id" :label="category.categoryName" :value="category.id" />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="search-stats">
|
|
|
+ <span class="stats-text">共找到 {{ filteredAvailableQuestions.length }} 个题目</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 题目列表 -->
|
|
|
+ <div class="selector-question-list">
|
|
|
+ <div
|
|
|
+ v-for="question in filteredAvailableQuestions"
|
|
|
+ :key="question.id"
|
|
|
+ class="selector-question-item"
|
|
|
+ @click="toggleQuestionSelection(question.id)"
|
|
|
+ >
|
|
|
+ <div class="selector-question-checkbox">
|
|
|
+ <el-checkbox :model-value="selectedQuestionIds.includes(question.id)" @change="toggleQuestionSelection(question.id)" />
|
|
|
+ </div>
|
|
|
+ <div class="selector-question-content">
|
|
|
+ <div class="selector-question-header">
|
|
|
+ <div class="selector-question-title">{{ question.questionTitle }}</div>
|
|
|
+ <div class="selector-question-meta">
|
|
|
+ <el-tag size="default" type="primary">{{ getQuestionType(question.questionType) }}</el-tag>
|
|
|
+ <el-tag size="default" type="info">{{ getCategoryName(question.categoryId) }}</el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="selector-question-description" v-if="question.questionContent">
|
|
|
+ {{ question.questionContent }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="filteredAvailableQuestions.length === 0" class="no-questions">
|
|
|
+ <el-icon class="no-questions-icon"><DocumentRemove /></el-icon>
|
|
|
+ <p class="no-questions-text">暂无符合条件的题目</p>
|
|
|
+ <p class="no-questions-hint">请尝试调整搜索条件或清空筛选器</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <div class="footer-left">
|
|
|
+ <el-checkbox :model-value="isAllQuestionsSelected" :indeterminate="isIndeterminate" @change="toggleSelectAll"> 全选 </el-checkbox>
|
|
|
+ <span class="selected-count">已选择 {{ selectedQuestionIds.length }} 个题目</span>
|
|
|
+ </div>
|
|
|
+ <div class="footer-right">
|
|
|
+ <el-button @click="confirmSelectQuestions" type="primary" :disabled="selectedQuestionIds.length === 0"> 确认添加 </el-button>
|
|
|
+ <el-button @click="cancelQuestionSelector">取消</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup name="SatSurvey" lang="ts">
|
|
|
+import {
|
|
|
+ listSatSurvey,
|
|
|
+ getSatSurvey,
|
|
|
+ delSatSurvey,
|
|
|
+ addSatSurvey,
|
|
|
+ updateSatSurvey,
|
|
|
+ getSurveyQuestions,
|
|
|
+ updateSurveyQuestions,
|
|
|
+ publishSurvey,
|
|
|
+ unpublishSurvey
|
|
|
+} from '@/api/hq/satSurvey';
|
|
|
+import { SatSurveyVO, SatSurveyQuery, SatSurveyForm } from '@/api/hq/satSurvey/types';
|
|
|
+import { listSatQuestion, getQuestionOptions } from '@/api/hq/satQuestion';
|
|
|
+import { SatQuestionVO } from '@/api/hq/satQuestion/types';
|
|
|
+import { listSatCategory } from '@/api/hq/satCategory';
|
|
|
+import { SatCategoryVO } from '@/api/hq/satCategory/types';
|
|
|
+
|
|
|
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
+const { sys_survey_people, sys_item_delete, sys_survey_status, sys_yesno } = toRefs<any>(
|
|
|
+ proxy?.useDict('sys_survey_people', 'sys_item_delete', 'sys_survey_status', 'sys_yesno')
|
|
|
+);
|
|
|
+
|
|
|
+// 注册draggable组件
|
|
|
+defineOptions({
|
|
|
+ name: 'SatSurvey'
|
|
|
+});
|
|
|
+
|
|
|
+const satSurveyList = ref<SatSurveyVO[]>([]);
|
|
|
+const buttonLoading = ref(false);
|
|
|
+const loading = ref(true);
|
|
|
+const showSearch = ref(true);
|
|
|
+const ids = ref<Array<string | number>>([]);
|
|
|
+const single = ref(true);
|
|
|
+const multiple = ref(true);
|
|
|
+const total = ref(0);
|
|
|
+
|
|
|
+const queryFormRef = ref<ElFormInstance>();
|
|
|
+const satSurveyFormRef = ref<ElFormInstance>();
|
|
|
+
|
|
|
+const dialog = reactive<DialogOption>({
|
|
|
+ visible: false,
|
|
|
+ title: ''
|
|
|
+});
|
|
|
+
|
|
|
+// 题目管理相关状态
|
|
|
+const questionDialog = reactive<DialogOption>({
|
|
|
+ visible: false,
|
|
|
+ title: ''
|
|
|
+});
|
|
|
+const currentSurvey = ref<SatSurveyVO | null>(null);
|
|
|
+const selectedQuestions = ref<any[]>([]);
|
|
|
+const availableQuestions = ref<SatQuestionVO[]>([]);
|
|
|
+const categories = ref<SatCategoryVO[]>([]);
|
|
|
+const questionLoading = ref(false);
|
|
|
+const readonly = ref(false); // 是否只读模式
|
|
|
+const hasLoadedData = ref(false); // 是否已经加载过数据
|
|
|
+
|
|
|
+// 题目选择器相关状态
|
|
|
+const showQuestionSelector = ref(false);
|
|
|
+const questionSearchText = ref('');
|
|
|
+const questionTypeFilter = ref('');
|
|
|
+const questionCategoryFilter = ref('');
|
|
|
+const selectedQuestionIds = ref<Array<string | number>>([]); // 选择器中选中的题目ID列表
|
|
|
+
|
|
|
+const initFormData: SatSurveyForm = {
|
|
|
+ id: undefined,
|
|
|
+ deleted: undefined,
|
|
|
+ surveyCode: undefined,
|
|
|
+ surveyTitle: undefined,
|
|
|
+ surveyDesc: undefined,
|
|
|
+ surveyInstruction: undefined,
|
|
|
+ targetGroup: undefined,
|
|
|
+ startTime: undefined,
|
|
|
+ endTime: undefined,
|
|
|
+ maxSubmitTimes: undefined,
|
|
|
+ isAnonymous: undefined,
|
|
|
+ isPublished: undefined,
|
|
|
+ status: undefined,
|
|
|
+ remark: undefined
|
|
|
+};
|
|
|
+const data = reactive<PageData<SatSurveyForm, SatSurveyQuery>>({
|
|
|
+ form: { ...initFormData },
|
|
|
+ queryParams: {
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ deleted: undefined,
|
|
|
+ surveyCode: undefined,
|
|
|
+ surveyTitle: undefined,
|
|
|
+ surveyDesc: undefined,
|
|
|
+ surveyInstruction: undefined,
|
|
|
+ targetGroup: undefined,
|
|
|
+ startTime: undefined,
|
|
|
+ endTime: undefined,
|
|
|
+ maxSubmitTimes: undefined,
|
|
|
+ isAnonymous: undefined,
|
|
|
+ isPublished: undefined,
|
|
|
+ status: undefined,
|
|
|
+ params: {}
|
|
|
+ },
|
|
|
+ rules: {
|
|
|
+ id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
|
|
|
+ surveyCode: [{ required: true, message: '问卷编号不能为空', trigger: 'blur' }],
|
|
|
+ surveyTitle: [{ required: true, message: '问卷标题不能为空', trigger: 'blur' }],
|
|
|
+ targetGroup: [{ required: true, message: '目标群体不能为空', trigger: 'change' }],
|
|
|
+ startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
|
|
|
+ endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }]
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const { queryParams, form, rules } = toRefs(data);
|
|
|
+
|
|
|
+/** 查询问卷列表 */
|
|
|
+const getList = async () => {
|
|
|
+ loading.value = true;
|
|
|
+ const res = await listSatSurvey(queryParams.value);
|
|
|
+ satSurveyList.value = res.rows;
|
|
|
+ total.value = res.total;
|
|
|
+ loading.value = false;
|
|
|
+};
|
|
|
+
|
|
|
+/** 取消按钮 */
|
|
|
+const cancel = () => {
|
|
|
+ reset();
|
|
|
+ dialog.visible = false;
|
|
|
+};
|
|
|
+
|
|
|
+/** 表单重置 */
|
|
|
+const reset = () => {
|
|
|
+ form.value = { ...initFormData };
|
|
|
+ satSurveyFormRef.value?.resetFields();
|
|
|
+};
|
|
|
+
|
|
|
+/** 搜索按钮操作 */
|
|
|
+const handleQuery = () => {
|
|
|
+ queryParams.value.pageNum = 1;
|
|
|
+ getList();
|
|
|
+};
|
|
|
+
|
|
|
+/** 重置按钮操作 */
|
|
|
+const resetQuery = () => {
|
|
|
+ queryFormRef.value?.resetFields();
|
|
|
+ handleQuery();
|
|
|
+};
|
|
|
+
|
|
|
+/** 多选框选中数据 */
|
|
|
+const handleSelectionChange = (selection: SatSurveyVO[]) => {
|
|
|
+ ids.value = selection.map((item) => item.id);
|
|
|
+ single.value = selection.length != 1;
|
|
|
+ multiple.value = !selection.length;
|
|
|
+};
|
|
|
+
|
|
|
+/** 新增按钮操作 */
|
|
|
+const handleAdd = () => {
|
|
|
+ reset();
|
|
|
+ dialog.visible = true;
|
|
|
+ dialog.title = '添加问卷';
|
|
|
+};
|
|
|
+
|
|
|
+/** 修改按钮操作 */
|
|
|
+const handleUpdate = async (row?: SatSurveyVO) => {
|
|
|
+ reset();
|
|
|
+ const _id = row?.id || ids.value[0];
|
|
|
+ const res = await getSatSurvey(_id);
|
|
|
+ Object.assign(form.value, res.data);
|
|
|
+ dialog.visible = true;
|
|
|
+ dialog.title = '修改问卷';
|
|
|
+};
|
|
|
+
|
|
|
+/** 提交按钮 */
|
|
|
+const submitForm = () => {
|
|
|
+ satSurveyFormRef.value?.validate(async (valid: boolean) => {
|
|
|
+ if (valid) {
|
|
|
+ buttonLoading.value = true;
|
|
|
+ if (form.value.id) {
|
|
|
+ await updateSatSurvey(form.value).finally(() => (buttonLoading.value = false));
|
|
|
+ } else {
|
|
|
+ await addSatSurvey(form.value).finally(() => (buttonLoading.value = false));
|
|
|
+ }
|
|
|
+ proxy?.$modal.msgSuccess('操作成功');
|
|
|
+ dialog.visible = false;
|
|
|
+ await getList();
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+/** 删除按钮操作 */
|
|
|
+const handleDelete = async (row?: SatSurveyVO) => {
|
|
|
+ const _ids = row?.id || ids.value;
|
|
|
+ await proxy?.$modal.confirm('是否确认删除问卷编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
|
|
|
+ await delSatSurvey(_ids);
|
|
|
+ proxy?.$modal.msgSuccess('删除成功');
|
|
|
+ await getList();
|
|
|
+};
|
|
|
+
|
|
|
+/** 发布问卷操作 */
|
|
|
+const handlePublish = async (row: SatSurveyVO) => {
|
|
|
+ try {
|
|
|
+ await proxy?.$modal.confirm(`确定要发布问卷"${row.surveyTitle}"吗?发布后问卷将对目标群体可见。`);
|
|
|
+ await publishSurvey(row.id);
|
|
|
+ proxy?.$modal.msgSuccess('发布成功');
|
|
|
+ await getList();
|
|
|
+ } catch (error) {
|
|
|
+ // 用户取消或发布失败
|
|
|
+ if (error !== 'cancel') {
|
|
|
+ proxy?.$modal.msgError('发布失败,请重试');
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/** 撤销发布问卷操作 */
|
|
|
+const handleUnpublish = async (row: SatSurveyVO) => {
|
|
|
+ try {
|
|
|
+ await proxy?.$modal.confirm(`确定要撤销发布问卷"${row.surveyTitle}"吗?撤销后问卷将不再对外开放。`);
|
|
|
+ await unpublishSurvey(row.id);
|
|
|
+ proxy?.$modal.msgSuccess('撤销发布成功');
|
|
|
+ await getList();
|
|
|
+ } catch (error) {
|
|
|
+ // 用户取消或撤销失败
|
|
|
+ if (error !== 'cancel') {
|
|
|
+ proxy?.$modal.msgError('撤销发布失败,请重试');
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/** 导出按钮操作 */
|
|
|
+const handleExport = () => {
|
|
|
+ proxy?.download(
|
|
|
+ 'hq/satSurvey/export',
|
|
|
+ {
|
|
|
+ ...queryParams.value
|
|
|
+ },
|
|
|
+ `satSurvey_${new Date().getTime()}.xlsx`
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+/** 题目管理按钮操作 */
|
|
|
+const handleQuestionManage = async (row: SatSurveyVO) => {
|
|
|
+ console.log('=== 题目管理按钮被点击 ===');
|
|
|
+ console.log('当前问卷数据:', row);
|
|
|
+ console.log('问卷ID:', row.id);
|
|
|
+ console.log('问卷状态:', row.status);
|
|
|
+
|
|
|
+ // 重置状态
|
|
|
+ selectedQuestions.value = [];
|
|
|
+ availableQuestions.value = [];
|
|
|
+ categories.value = [];
|
|
|
+ hasLoadedData.value = false;
|
|
|
+
|
|
|
+ currentSurvey.value = row;
|
|
|
+ // 根据问卷状态判断是否只读
|
|
|
+ readonly.value = row.status !== '0'; // 状态为草稿时可编辑,其他状态只读
|
|
|
+
|
|
|
+ console.log('是否只读模式:', readonly.value);
|
|
|
+
|
|
|
+ questionDialog.visible = true;
|
|
|
+ questionDialog.title = readonly.value ? '查看问卷题目' : '管理问卷题目';
|
|
|
+
|
|
|
+ console.log('对话框已打开,准备加载题目数据');
|
|
|
+ console.log('当前selectedQuestions长度:', selectedQuestions.value.length);
|
|
|
+
|
|
|
+ try {
|
|
|
+ await loadQuestionData();
|
|
|
+ console.log('题目数据加载完成,最终selectedQuestions长度:', selectedQuestions.value.length);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载题目数据时发生错误:', error);
|
|
|
+ proxy?.$modal.msgError('加载题目数据失败,请重试');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/** 加载题目管理数据 */
|
|
|
+const loadQuestionData = async () => {
|
|
|
+ console.log('=== 开始执行 loadQuestionData 方法 ===');
|
|
|
+ console.log('当前问卷ID:', currentSurvey.value?.id);
|
|
|
+
|
|
|
+ questionLoading.value = true;
|
|
|
+ try {
|
|
|
+ console.log('开始并行加载三个API数据...');
|
|
|
+
|
|
|
+ // 并行加载数据
|
|
|
+ const [surveyQuestionsRes, availableQuestionsRes, categoriesRes] = await Promise.all([
|
|
|
+ getSurveyQuestions(currentSurvey.value!.id),
|
|
|
+ listSatQuestion({ pageNum: 1, pageSize: 999, status: '0' }),
|
|
|
+ listSatCategory({ pageNum: 1, pageSize: 999, status: '0' })
|
|
|
+ ]);
|
|
|
+
|
|
|
+ console.log('API请求完成,开始处理数据...');
|
|
|
+ console.log('问卷题目数据完整响应:', surveyQuestionsRes);
|
|
|
+ console.log('问卷题目数据data字段:', surveyQuestionsRes.data);
|
|
|
+ console.log('分类数据加载结果:', categoriesRes.rows);
|
|
|
+ console.log('题目数据加载结果:', availableQuestionsRes.rows);
|
|
|
+
|
|
|
+ // 处理已选题目数据 - 确保数据结构正确
|
|
|
+ let selectedQuestionsData = surveyQuestionsRes.data || [];
|
|
|
+ console.log('选中的题目数量:', selectedQuestionsData.length);
|
|
|
+ console.log('选中的题目详细数据:', selectedQuestionsData);
|
|
|
+
|
|
|
+ // 检查第一个题目的数据结构
|
|
|
+ if (selectedQuestionsData.length > 0) {
|
|
|
+ console.log('第一个题目的完整数据结构:', selectedQuestionsData[0]);
|
|
|
+ console.log('第一个题目的questionTitle字段:', selectedQuestionsData[0].questionTitle);
|
|
|
+ console.log('第一个题目的questionType字段:', selectedQuestionsData[0].questionType);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 为每个题目加载选项(异步并行处理提高性能)
|
|
|
+ if (selectedQuestionsData.length > 0) {
|
|
|
+ console.log('开始并行加载所有题目的选项...');
|
|
|
+ const optionsPromises = selectedQuestionsData.map(async (question, index) => {
|
|
|
+ try {
|
|
|
+ // 确保 questionId 存在
|
|
|
+ const questionId = question.questionId || question.id;
|
|
|
+ if (!questionId) {
|
|
|
+ console.warn(`题目 ${index} 缺少 questionId:`, question);
|
|
|
+ question.options = [];
|
|
|
+ return question;
|
|
|
+ }
|
|
|
+
|
|
|
+ const optionsRes = await getQuestionOptions(questionId);
|
|
|
+ question.options = optionsRes.data || [];
|
|
|
+ console.log(`题目${questionId}的选项:`, question.options);
|
|
|
+ return question;
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`获取题目${question.questionId || question.id}选项失败:`, error);
|
|
|
+ question.options = [];
|
|
|
+ return question;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 等待所有选项加载完成
|
|
|
+ selectedQuestionsData = await Promise.all(optionsPromises);
|
|
|
+ console.log('所有题目选项加载完成');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保数据响应性更新
|
|
|
+ selectedQuestions.value = [...selectedQuestionsData];
|
|
|
+ availableQuestions.value = availableQuestionsRes.rows || [];
|
|
|
+ categories.value = categoriesRes.rows || [];
|
|
|
+
|
|
|
+ console.log('最终selectedQuestions数据:', selectedQuestions.value);
|
|
|
+ console.log('最终分类数据:', categories.value);
|
|
|
+ console.log('最终题目数据:', availableQuestions.value);
|
|
|
+
|
|
|
+ // 强制触发视图更新
|
|
|
+ nextTick(() => {
|
|
|
+ console.log('视图已更新,当前selectedQuestions长度:', selectedQuestions.value.length);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 标记已经加载过数据
|
|
|
+ hasLoadedData.value = true;
|
|
|
+ console.log('=== loadQuestionData 执行成功完成 ===');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('=== loadQuestionData 执行失败 ===');
|
|
|
+ console.error('错误详情:', error);
|
|
|
+ proxy?.$modal.msgError('加载数据失败');
|
|
|
+ } finally {
|
|
|
+ questionLoading.value = false;
|
|
|
+ console.log('loadQuestionData 方法执行结束,loading状态已重置');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/** 保存问卷题目 */
|
|
|
+const saveQuestionChanges = async () => {
|
|
|
+ if (readonly.value) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ questionLoading.value = true;
|
|
|
+ await updateSurveyQuestions(currentSurvey.value!.id, selectedQuestions.value);
|
|
|
+ proxy?.$modal.msgSuccess('保存成功');
|
|
|
+ questionDialog.visible = false;
|
|
|
+ } catch (error) {
|
|
|
+ proxy?.$modal.msgError('保存失败');
|
|
|
+ } finally {
|
|
|
+ questionLoading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/** 取消题目管理 */
|
|
|
+const cancelQuestionManage = () => {
|
|
|
+ questionDialog.visible = false;
|
|
|
+ selectedQuestions.value = [];
|
|
|
+ availableQuestions.value = [];
|
|
|
+ currentSurvey.value = null;
|
|
|
+ hasLoadedData.value = false;
|
|
|
+ // 重置题目选择器状态
|
|
|
+ showQuestionSelector.value = false;
|
|
|
+ questionSearchText.value = '';
|
|
|
+ questionTypeFilter.value = '';
|
|
|
+ questionCategoryFilter.value = '';
|
|
|
+};
|
|
|
+
|
|
|
+/** 获取题目类型名称 */
|
|
|
+const getQuestionType = (type: string) => {
|
|
|
+ const typeMap: Record<string, string> = {
|
|
|
+ '1': '单选题',
|
|
|
+ '2': '多选题',
|
|
|
+ '3': '问答题'
|
|
|
+ };
|
|
|
+ return typeMap[type] || '未知';
|
|
|
+};
|
|
|
+
|
|
|
+/** 获取分类名称 */
|
|
|
+const getCategoryName = (categoryId: string | number) => {
|
|
|
+ console.log('查找分类, categoryId:', categoryId, '类型:', typeof categoryId);
|
|
|
+ console.log('可用分类列表:', categories.value);
|
|
|
+
|
|
|
+ const category = categories.value.find((c) => {
|
|
|
+ const match = String(c.id) === String(categoryId);
|
|
|
+ console.log(`分类ID ${c.id} vs ${categoryId}, 匹配: ${match}`);
|
|
|
+ return match;
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = category?.categoryName || '未分类';
|
|
|
+ console.log('找到的分类名称:', result);
|
|
|
+ return result;
|
|
|
+};
|
|
|
+
|
|
|
+/** 确认删除所有题目 */
|
|
|
+const confirmDeleteAllQuestions = async () => {
|
|
|
+ try {
|
|
|
+ await proxy?.$modal.confirm(`确定要删除所有 ${selectedQuestions.value.length} 个题目吗?此操作不可撤销。`);
|
|
|
+
|
|
|
+ // 清空所有题目
|
|
|
+ selectedQuestions.value = [];
|
|
|
+ proxy?.$modal.msgSuccess('已删除所有题目');
|
|
|
+ } catch (error) {
|
|
|
+ // 用户取消删除
|
|
|
+ console.log('用户取消删除操作');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/** 移除题目 */
|
|
|
+const removeQuestion = (index: number) => {
|
|
|
+ selectedQuestions.value.splice(index, 1);
|
|
|
+ // 重新排序
|
|
|
+ selectedQuestions.value.forEach((item, idx) => {
|
|
|
+ item.sortOrder = idx + 1;
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+/** 计算过滤后的可选题目 */
|
|
|
+const filteredAvailableQuestions = computed(() => {
|
|
|
+ let filtered = availableQuestions.value;
|
|
|
+
|
|
|
+ console.log('过滤前题目数量:', filtered.length);
|
|
|
+ console.log('搜索文本:', questionSearchText.value);
|
|
|
+ console.log('类型过滤:', questionTypeFilter.value);
|
|
|
+ console.log('分类过滤:', questionCategoryFilter.value);
|
|
|
+
|
|
|
+ // 过滤掉已经在问卷中的题目
|
|
|
+ const selectedQuestionIdSet = new Set(selectedQuestions.value.map((q) => q.questionId || q.id));
|
|
|
+ filtered = filtered.filter((q) => !selectedQuestionIdSet.has(q.id));
|
|
|
+ console.log('过滤已选题目后:', filtered.length);
|
|
|
+
|
|
|
+ // 根据搜索文本过滤
|
|
|
+ if (questionSearchText.value) {
|
|
|
+ filtered = filtered.filter((q) => q.questionTitle.toLowerCase().includes(questionSearchText.value.toLowerCase()));
|
|
|
+ console.log('搜索过滤后:', filtered.length);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据题目类型过滤
|
|
|
+ if (questionTypeFilter.value) {
|
|
|
+ console.log('开始类型过滤,过滤值:', questionTypeFilter.value);
|
|
|
+ const beforeTypeFilter = filtered.length;
|
|
|
+ filtered = filtered.filter((q) => {
|
|
|
+ // 确保数据类型匹配,将两者都转换为字符串进行比较
|
|
|
+ const questionType = String(q.questionType);
|
|
|
+ const filterType = String(questionTypeFilter.value);
|
|
|
+ console.log(`题目类型: ${questionType}, 过滤类型: ${filterType}, 匹配: ${questionType === filterType}`);
|
|
|
+ return questionType === filterType;
|
|
|
+ });
|
|
|
+ console.log(`类型过滤: ${beforeTypeFilter} -> ${filtered.length}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据分类过滤
|
|
|
+ if (questionCategoryFilter.value) {
|
|
|
+ filtered = filtered.filter((q) => {
|
|
|
+ // 确保数据类型匹配
|
|
|
+ const categoryId = String(q.categoryId);
|
|
|
+ const filterId = String(questionCategoryFilter.value);
|
|
|
+ return categoryId === filterId;
|
|
|
+ });
|
|
|
+ console.log('分类过滤后:', filtered.length);
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('最终过滤结果:', filtered.length);
|
|
|
+ return filtered;
|
|
|
+});
|
|
|
+
|
|
|
+/** 是否全选 */
|
|
|
+const isAllQuestionsSelected = computed(() => {
|
|
|
+ return filteredAvailableQuestions.value.length > 0 && selectedQuestionIds.value.length === filteredAvailableQuestions.value.length;
|
|
|
+});
|
|
|
+
|
|
|
+/** 是否半选状态 */
|
|
|
+const isIndeterminate = computed(() => {
|
|
|
+ return selectedQuestionIds.value.length > 0 && selectedQuestionIds.value.length < filteredAvailableQuestions.value.length;
|
|
|
+});
|
|
|
+
|
|
|
+/** 打开题目选择器 */
|
|
|
+const openQuestionSelector = () => {
|
|
|
+ // 重置过滤状态
|
|
|
+ questionSearchText.value = '';
|
|
|
+ questionTypeFilter.value = '';
|
|
|
+ questionCategoryFilter.value = '';
|
|
|
+ selectedQuestionIds.value = [];
|
|
|
+ showQuestionSelector.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+/** 切换题目选择状态 */
|
|
|
+const toggleQuestionSelection = (questionId: string | number) => {
|
|
|
+ const index = selectedQuestionIds.value.indexOf(questionId);
|
|
|
+ if (index > -1) {
|
|
|
+ selectedQuestionIds.value.splice(index, 1);
|
|
|
+ } else {
|
|
|
+ selectedQuestionIds.value.push(questionId);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/** 全选/取消全选 */
|
|
|
+const toggleSelectAll = (checked: boolean) => {
|
|
|
+ if (checked) {
|
|
|
+ selectedQuestionIds.value = filteredAvailableQuestions.value.map((q) => q.id);
|
|
|
+ } else {
|
|
|
+ selectedQuestionIds.value = [];
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/** 确认添加选中的题目 */
|
|
|
+const confirmSelectQuestions = async () => {
|
|
|
+ try {
|
|
|
+ // 批量获取选中题目的详细信息和选项
|
|
|
+ const selectedQuestionDetails = [];
|
|
|
+
|
|
|
+ for (const questionId of selectedQuestionIds.value) {
|
|
|
+ const question = availableQuestions.value.find((q) => q.id === questionId);
|
|
|
+ if (question) {
|
|
|
+ try {
|
|
|
+ // 获取题目选项
|
|
|
+ const optionsRes = await getQuestionOptions(questionId);
|
|
|
+
|
|
|
+ const newQuestion = {
|
|
|
+ questionId: question.id,
|
|
|
+ questionTitle: question.questionTitle,
|
|
|
+ questionType: question.questionType,
|
|
|
+ categoryId: question.categoryId,
|
|
|
+ sortOrder: selectedQuestions.value.length + selectedQuestionDetails.length + 1,
|
|
|
+ weight: 1.0,
|
|
|
+ isRequired: 1,
|
|
|
+ options: optionsRes.data || []
|
|
|
+ };
|
|
|
+
|
|
|
+ selectedQuestionDetails.push(newQuestion);
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`获取题目${questionId}选项失败:`, error);
|
|
|
+ // 即使获取选项失败,也添加题目(选项为空)
|
|
|
+ const newQuestion = {
|
|
|
+ questionId: question.id,
|
|
|
+ questionTitle: question.questionTitle,
|
|
|
+ questionType: question.questionType,
|
|
|
+ categoryId: question.categoryId,
|
|
|
+ sortOrder: selectedQuestions.value.length + selectedQuestionDetails.length + 1,
|
|
|
+ weight: 1.0,
|
|
|
+ isRequired: 1,
|
|
|
+ options: []
|
|
|
+ };
|
|
|
+ selectedQuestionDetails.push(newQuestion);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加到已选题目列表
|
|
|
+ selectedQuestions.value.push(...selectedQuestionDetails);
|
|
|
+
|
|
|
+ // 关闭选择器
|
|
|
+ showQuestionSelector.value = false;
|
|
|
+ selectedQuestionIds.value = [];
|
|
|
+
|
|
|
+ proxy?.$modal.msgSuccess(`成功添加 ${selectedQuestionDetails.length} 个题目`);
|
|
|
+ } catch (error) {
|
|
|
+ proxy?.$modal.msgError('添加题目失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/** 取消题目选择器 */
|
|
|
+const cancelQuestionSelector = () => {
|
|
|
+ showQuestionSelector.value = false;
|
|
|
+ selectedQuestionIds.value = [];
|
|
|
+};
|
|
|
+
|
|
|
+const toolbarRef = ref<any>();
|
|
|
+const searchRef = ref<HTMLElement>();
|
|
|
+const actualToolbarHeight = ref(70); // 默认工具栏高度
|
|
|
+const actualSearchHeight = ref(160); // 默认搜索栏高度
|
|
|
+const windowHeight = ref(window.innerHeight);
|
|
|
+
|
|
|
+/** 计算搜索栏实际高度 */
|
|
|
+const calculateSearchHeight = () => {
|
|
|
+ nextTick(() => {
|
|
|
+ if (searchRef.value && showSearch.value) {
|
|
|
+ // 获取搜索栏元素的完整高度,包括边距和内边距
|
|
|
+ const rect = searchRef.value.getBoundingClientRect();
|
|
|
+ const computedStyle = window.getComputedStyle(searchRef.value);
|
|
|
+ const marginTop = parseFloat(computedStyle.marginTop);
|
|
|
+ const marginBottom = parseFloat(computedStyle.marginBottom);
|
|
|
+ actualSearchHeight.value = rect.height + marginTop + marginBottom;
|
|
|
+ } else {
|
|
|
+ // 如果搜索栏隐藏,高度为0
|
|
|
+ actualSearchHeight.value = 0;
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+/** 计算工具栏实际高度 */
|
|
|
+const calculateToolbarHeight = () => {
|
|
|
+ nextTick(() => {
|
|
|
+ if (toolbarRef.value) {
|
|
|
+ // 获取工具栏DOM元素,toolbarRef.value是组件实例,需要通过$el获取DOM元素
|
|
|
+ const toolbarDom = toolbarRef.value.$el || toolbarRef.value;
|
|
|
+
|
|
|
+ // 检查是否是有效的DOM元素
|
|
|
+ if (toolbarDom && typeof toolbarDom.closest === 'function') {
|
|
|
+ // 获取工具栏元素的完整高度,包括边距和内边距
|
|
|
+ const toolbarElement = toolbarDom.closest('.el-card__header');
|
|
|
+ if (toolbarElement) {
|
|
|
+ const rect = toolbarElement.getBoundingClientRect();
|
|
|
+ const computedStyle = window.getComputedStyle(toolbarElement);
|
|
|
+ const marginTop = parseFloat(computedStyle.marginTop);
|
|
|
+ const marginBottom = parseFloat(computedStyle.marginBottom);
|
|
|
+ actualToolbarHeight.value = rect.height + marginTop + marginBottom;
|
|
|
+ } else {
|
|
|
+ // 如果找不到父元素,直接使用工具栏元素高度
|
|
|
+ const rect = toolbarDom.getBoundingClientRect();
|
|
|
+ actualToolbarHeight.value = rect.height + 20; // 添加一些边距
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 如果无法获取DOM元素,使用默认高度
|
|
|
+ actualToolbarHeight.value = 70;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+/** 窗口大小变化监听 */
|
|
|
+const handleResize = () => {
|
|
|
+ windowHeight.value = window.innerHeight;
|
|
|
+ // 窗口大小变化时重新计算搜索栏和工具栏高度
|
|
|
+ calculateSearchHeight();
|
|
|
+ calculateToolbarHeight();
|
|
|
+};
|
|
|
+
|
|
|
+/** 计算表格高度 */
|
|
|
+const tableHeight = computed(() => {
|
|
|
+ // 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
|
|
|
+ const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
|
|
|
+ const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
|
|
|
+ const pageTopPadding = 8; // 页面顶部边距
|
|
|
+ const pageBottomMargin = 10; // 分页栏距离底部的边距
|
|
|
+ const paginationOwnHeight = 32; // 分页组件自身高度
|
|
|
+ const paginationPadding = 20; // 分页栏上下内边距
|
|
|
+ const cardPadding = 40; // 卡片内边距(上下各20px)
|
|
|
+
|
|
|
+ // 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
|
|
|
+ const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
|
|
|
+
|
|
|
+ const calculatedHeight = windowHeight.value - fixedHeight - 45;
|
|
|
+
|
|
|
+ // 确保至少有300px的表格高度,避免过小
|
|
|
+ return Math.max(calculatedHeight, 300);
|
|
|
+});
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ getList();
|
|
|
+ // 初始计算搜索栏和工具栏高度
|
|
|
+ calculateSearchHeight();
|
|
|
+ calculateToolbarHeight();
|
|
|
+ // 监听窗口大小变化
|
|
|
+ window.addEventListener('resize', handleResize);
|
|
|
+});
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ // 移除监听器
|
|
|
+ window.removeEventListener('resize', handleResize);
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* 页面容器样式 */
|
|
|
+.page-container {
|
|
|
+ height: calc(100vh - 85px);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+ box-sizing: border-box;
|
|
|
+ padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 自定义页面滚动条样式 */
|
|
|
+.page-container::-webkit-scrollbar {
|
|
|
+ width: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.page-container::-webkit-scrollbar-track {
|
|
|
+ background: #f1f1f1;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.page-container::-webkit-scrollbar-thumb {
|
|
|
+ background: #c1c1c1;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.page-container::-webkit-scrollbar-thumb:hover {
|
|
|
+ background: #a8a8a8;
|
|
|
+}
|
|
|
+
|
|
|
+/* 表格卡片布局 */
|
|
|
+.table-card {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.table-card :deep(.el-card__body) {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+ padding: 0px 20px 0 20px !important;
|
|
|
+ min-height: 0;
|
|
|
+ margin-bottom: 0 !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 表格主体 */
|
|
|
+.table-main {
|
|
|
+ flex: 1;
|
|
|
+ overflow: auto;
|
|
|
+}
|
|
|
+
|
|
|
+/* 表格容器 - 支持横向滚动 */
|
|
|
+.table-container {
|
|
|
+ width: 100%;
|
|
|
+ /* overflow: hidden; */
|
|
|
+}
|
|
|
+
|
|
|
+/* 横向滚动表格样式 */
|
|
|
+.scroll-table {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+/* 表格横向滚动条样式优化 */
|
|
|
+.scroll-table :deep(.el-table__body-wrapper) {
|
|
|
+ overflow-x: auto !important; /* 自动显示横向滚动条 */
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.scroll-table :deep(.el-table__header-wrapper) {
|
|
|
+ overflow-x: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+/* 强制显示表格横向滚动条 - 适用于所有浏览器 */
|
|
|
+.scroll-table :deep(.el-table__body-wrapper) {
|
|
|
+ scrollbar-width: thin; /* Firefox */
|
|
|
+ scrollbar-color: #409eff #f0f0f0; /* Firefox - thumb track */
|
|
|
+}
|
|
|
+
|
|
|
+/* Webkit 浏览器的横向滚动条样式 */
|
|
|
+.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar {
|
|
|
+ height: 12px !important; /* 增加滚动条高度 */
|
|
|
+ width: 12px !important;
|
|
|
+}
|
|
|
+
|
|
|
+.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-track {
|
|
|
+ background: #f0f0f0 !important;
|
|
|
+ border-radius: 6px !important;
|
|
|
+ border: 1px solid #e4e7ed !important; /* 添加边框使其更明显 */
|
|
|
+}
|
|
|
+
|
|
|
+.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb {
|
|
|
+ background: #409eff !important; /* 使用主题色,更明显 */
|
|
|
+ border-radius: 6px !important;
|
|
|
+ border: 2px solid #f0f0f0 !important; /* 添加白色边框 */
|
|
|
+ min-height: 20px !important; /* 确保最小高度 */
|
|
|
+}
|
|
|
+
|
|
|
+.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:hover {
|
|
|
+ background: #337ecc !important; /* 悬停时更深的蓝色 */
|
|
|
+}
|
|
|
+
|
|
|
+.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:active {
|
|
|
+ background: #2b6cb0 !important; /* 点击时的颜色 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 确保滚动条始终可见的额外设置 */
|
|
|
+.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-corner {
|
|
|
+ background: #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 固定列样式优化 */
|
|
|
+.scroll-table :deep(.el-table__fixed-right) {
|
|
|
+ box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.scroll-table :deep(.el-table__fixed-left) {
|
|
|
+ box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+/* 确保固定列在滚动时有正确的背景色 */
|
|
|
+.scroll-table :deep(.el-table__fixed-right .el-table__cell),
|
|
|
+.scroll-table :deep(.el-table__fixed-left .el-table__cell) {
|
|
|
+ background-color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+/* 表格行悬停效果在固定列上的同步 */
|
|
|
+.scroll-table :deep(.el-table__row:hover .el-table__cell) {
|
|
|
+ background-color: #f5f7fa !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 分页容器固定在底部 */
|
|
|
+.pagination-container {
|
|
|
+ flex-shrink: 0;
|
|
|
+ padding-top: 10px 0 0 0 !important;
|
|
|
+ margin: 0 !important;
|
|
|
+ /* padding-bottom: 10px !important; 距离底部10px */
|
|
|
+ /* margin: 0 !important; */
|
|
|
+ /* margin-bottom: 10px !important; 确保距离视窗底部10px */
|
|
|
+}
|
|
|
+
|
|
|
+/* 移除按钮栏底部边框 */
|
|
|
+.button-card :deep(.el-card__header) {
|
|
|
+ border-bottom: none !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 新版题目管理对话框样式 */
|
|
|
+.new-question-manage-container {
|
|
|
+ height: 600px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.question-builder {
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+/* 题目管理工具栏样式 */
|
|
|
+.question-toolbar {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 12px 16px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+ border-radius: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.toolbar-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.toolbar-right {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.question-count {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #606266;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.question-list-container {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding-bottom: 20px;
|
|
|
+ min-height: 300px;
|
|
|
+}
|
|
|
+
|
|
|
+.question-list {
|
|
|
+ width: 100%;
|
|
|
+ min-height: 50px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 题目卡片样式 */
|
|
|
+.question-card {
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ background: #fff;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.question-card:hover {
|
|
|
+ border-color: #409eff;
|
|
|
+ box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
|
|
|
+}
|
|
|
+
|
|
|
+.question-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 12px 16px;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border-bottom: 1px solid #e4e7ed;
|
|
|
+ border-radius: 8px 8px 0 0;
|
|
|
+}
|
|
|
+
|
|
|
+.question-number {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #409eff;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.question-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.question-content-display {
|
|
|
+ padding: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.question-title-display {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #303133;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+
|
|
|
+.question-meta-info {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.answer-display-area {
|
|
|
+ margin-top: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.answer-option {
|
|
|
+ display: block !important;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ white-space: normal;
|
|
|
+ word-break: break-all;
|
|
|
+}
|
|
|
+
|
|
|
+.answer-textarea {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+/* 空状态样式 */
|
|
|
+.action-area {
|
|
|
+ flex-shrink: 0;
|
|
|
+ text-align: center;
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-state {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-icon {
|
|
|
+ font-size: 48px;
|
|
|
+ color: #c0c4cc;
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-text {
|
|
|
+ color: #909399;
|
|
|
+ font-size: 14px;
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* Dialog footer样式 */
|
|
|
+.dialog-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+/* 题目管理对话框footer - 按钮右对齐 */
|
|
|
+.question-manage-footer {
|
|
|
+ justify-content: flex-end !important;
|
|
|
+}
|
|
|
+
|
|
|
+.footer-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.footer-right {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.selected-count {
|
|
|
+ color: #909399;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 题目选择器样式 */
|
|
|
+.question-selector {
|
|
|
+ height: 700px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.selector-search {
|
|
|
+ margin-bottom: 20px;
|
|
|
+ padding: 20px;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+}
|
|
|
+
|
|
|
+.search-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 20px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+}
|
|
|
+
|
|
|
+.search-group {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.search-label {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #606266;
|
|
|
+ white-space: nowrap;
|
|
|
+ min-width: 80px;
|
|
|
+}
|
|
|
+
|
|
|
+.search-stats {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ padding-top: 8px;
|
|
|
+ border-top: 1px solid #e4e7ed;
|
|
|
+ margin-top: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.stats-text {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #909399;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.selector-question-list {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.selector-question-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ padding: 16px 20px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: #fff;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
|
+}
|
|
|
+
|
|
|
+.selector-question-item:hover {
|
|
|
+ border-color: #409eff;
|
|
|
+ background: #f0f9ff;
|
|
|
+ box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
|
|
|
+}
|
|
|
+
|
|
|
+.selector-question-checkbox {
|
|
|
+ margin-right: 16px;
|
|
|
+ margin-top: 4px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.selector-question-content {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.selector-question-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: flex-start;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.selector-question-title {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #303133;
|
|
|
+ line-height: 1.5;
|
|
|
+ margin-right: 12px;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.selector-question-meta {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.selector-question-description {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #909399;
|
|
|
+ line-height: 1.4;
|
|
|
+ margin-top: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.category-name {
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+.no-questions {
|
|
|
+ text-align: center;
|
|
|
+ padding: 60px 20px;
|
|
|
+ background: #fafafa;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin: 20px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.no-questions-icon {
|
|
|
+ font-size: 64px;
|
|
|
+ color: #c0c4cc;
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.no-questions-text {
|
|
|
+ font-size: 16px;
|
|
|
+ color: #606266;
|
|
|
+ margin: 0 0 8px 0;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.no-questions-hint {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #909399;
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 滚动条样式 */
|
|
|
+.question-list-container::-webkit-scrollbar,
|
|
|
+.selector-question-list::-webkit-scrollbar {
|
|
|
+ width: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.question-list-container::-webkit-scrollbar-track,
|
|
|
+.selector-question-list::-webkit-scrollbar-track {
|
|
|
+ background: #f1f1f1;
|
|
|
+ border-radius: 3px;
|
|
|
+}
|
|
|
+
|
|
|
+.question-list-container::-webkit-scrollbar-thumb,
|
|
|
+.selector-question-list::-webkit-scrollbar-thumb {
|
|
|
+ background: #c1c1c1;
|
|
|
+ border-radius: 3px;
|
|
|
+}
|
|
|
+
|
|
|
+.question-list-container::-webkit-scrollbar-thumb:hover,
|
|
|
+.selector-question-list::-webkit-scrollbar-thumb:hover {
|
|
|
+ background: #a8a8a8;
|
|
|
+}
|
|
|
+</style>
|