Files
pengchen-exam-vue/src/views/paper/question/WpsXlsxForm.vue

1251 lines
40 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="edit-dialog">
<Dialog v-model="dialogVisible" :title="dialogTitle" width="85%" top="10vh">
<el-scrollbar>
<div class="main">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="80px"
>
<el-row>
<el-col :span="12">
<el-form-item label="标题" prop="contentText">
<el-input v-model="formData.contentText" placeholder="请输入标题" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="启用状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="'0'">启用</el-radio>
<el-radio :label="'1'">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="专业" prop="specialtyName">
<el-input v-model="formData.specialtyName" placeholder="请输入专业" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="章节名称" prop="chapteridDictText">
<el-input
v-model="formData.chapteridDictTextVo"
placeholder="请输入章节名称"
disabled
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="课程" prop="courseName">
<el-input v-model="formData.courseName" placeholder="请输入课程" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="题型难度" prop="quLevel">
<el-select v-model="formData.quLevel" placeholder="请选择题型难度" clearable>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.EXAM_QUE_DIFF)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="题型" prop="subjectName">
<el-input v-model="formData.subjectName" placeholder="请输入题型" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="知识点" prop="pointNames">
<el-input
v-model="formData.pointNamesVo"
placeholder="请选择知识点"
readonly
@click="openPoints()"
/>
</el-form-item>
</el-col>
<el-dialog v-model="dialogVisiblePoints" title="选择知识点" width="30%">
<el-tree
ref="treeRef"
:data="deptList"
node-key="id"
:props="{ label: 'name', children: 'children' }"
highlight-current
default-expand-all
@node-click="handleNodeClick"
/>
</el-dialog>
</el-row>
</el-form>
<div class="edit-bottom">
<div class="edit-left bottom-common">
<el-tabs v-model="leftActiveName" class="demo-tabs">
<el-tab-pane label="试题描述" name="desc">
<div class="block">
<Editor v-model="formData.content" height="150px" />
</div>
</el-tab-pane>
</el-tabs>
</div>
<div class="edit-right bottom-common">
<el-tabs v-model="rightActiveName" class="demo-tabs" @tab-click="rightHandleClick">
<el-tab-pane label="试题解析" name="analysis">
<div class="block">
<Editor v-model="formData.analysis" height="150px" />
</div>
</el-tab-pane>
<el-tab-pane name="keyword">
<template #label>
<div class="custom-tabs-label">
<p>关键字</p>
<el-dropdown>
<span class="el-dropdown-link" @click.stop="false">
<div class="setting_icon"></div>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="editKeyword('create')">新建</el-dropdown-item>
<el-dropdown-item @click="editKeyword('update')">编辑</el-dropdown-item>
<el-dropdown-item @click="editKeyword('delete')">删除</el-dropdown-item>
<el-dropdown-item @click="editKeyword('deleteall')"
>删除全部</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<div class="block">
<el-table
:data="keywordList"
style="width: 100%"
@selection-change="handleKeywordSelectionChange"
>
<el-table-column type="index" width="50" />
<el-table-column type="selection" width="55" />
<el-table-column prop="keyword" label="关键字" />
</el-table>
<el-dialog
v-model="keyVisible"
title="编辑关键字"
width="50%"
:before-close="keyDialogClose"
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<div class="main" style="width: 100%; height: 100%">
<el-input v-model="keyWord" placeholder="请输入关键字" size="large" />
<div class="dialog-footer" style="margin-top: 20px;">
<el-button @click="keyDialogClose">取消</el-button>
<el-button type="primary" @click="confirmKeyDialogVisible">
确定
</el-button>
</div>
</div>
</el-dialog>
</div>
</el-tab-pane>
<el-tab-pane name="answer">
<template #label>
<div class="custom-tabs-label">
<p>试题考点</p>
<el-dropdown>
<span class="el-dropdown-link" @click.stop="false">
<div class="setting_icon"></div>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<el-button @click="addXlsxForm">添加</el-button>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<!-- <el-button type="danger">删除</el-button> -->
<div class="block">
<el-table
v-loading="loading"
:data="list"
@selection-change="handleSelectionChange"
style="width: 100%"
>
<!-- <el-table-column type="selection" width="55" /> -->
<el-table-column label="考点" align="center" prop="contentIn" />
<el-table-column label="权值" align="center" prop="scoreRate" width="100px" />
<el-table-column label="操作" align="center" width="100px">
<template #default="scope">
<el-button type="primary" link @click="handleDelete(scope.row)">
<Icon icon="ep:delete" />删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
<!-- <el-tab-pane name="medium">
<template #label>
<div class="custom-tabs-label">
<p>媒体文件</p>
<el-dropdown>
<span class="el-dropdown-link" @click.stop="false">
<div class="setting_icon"></div>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<el-upload
v-model:file-list="fileList"
class="upload-demo"
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
>
<el-button>导入</el-button>
</el-upload>
</el-dropdown-item>
<el-dropdown-item>导出</el-dropdown-item>
<el-dropdown-item>播放</el-dropdown-item>
<el-dropdown-item>编辑</el-dropdown-item>
<el-dropdown-item>删除选中</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<div class="block">
<el-table
:data="mediumList"
style="width: 100%"
@selection-change="handleMediumSelectionChange"
>
<el-table-column type="index" width="50" />
<el-table-column type="selection" width="55" />
<el-table-column prop="type" label="媒体类型" width="80" />
<el-table-column prop="title" label="标题" />
<el-table-column prop="displayIndex" label="显示序号" />
<el-table-column prop="size" label="大小" />
</el-table>
</div>
</el-tab-pane> -->
<el-tab-pane name="annex">
<template #label>
<div class="custom-tabs-label">
<p>试题附件</p>
</div>
</template>
<!-- 提示 -->
<el-alert type="warning" show-icon :closable="false">
<template #default>
<span>提示文件名称可默认不设置</span>
</template>
</el-alert>
<div class="block">
<el-table :data="documentList" style="width: 100%">
<el-table-column type="index" label="#" width="50" />
<el-table-column label="文件名称" width="250">
<template #default="scope">
<el-input
v-model="scope.row.fileName"
size="small"
placeholder="请输入文件名称(未输入自动命名)"
/>
</template>
</el-table-column>
<el-table-column
prop="fileType"
label="类型"
:formatter="fileTypeFormatter"
/>
<el-table-column prop="url" label="地址" width="200">
<template #default="{ row }">
<div
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
:title="row.url"
>
{{ row.url }}
</div>
</template>
</el-table-column>
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button
type="primary"
plain
@click="openForm(scope.row.fileType)"
size="small"
>
<Icon icon="ep:upload" class="mr-5px" /> 上传
</el-button>
<el-button
type="success"
plain
@click="downloadFile(scope.row.url)"
size="small"
>
<Icon icon="ep:download" class="mr-5px" /> 下载
</el-button>
<el-button
type="danger"
plain
@click="deleteUrl(scope.$index)"
size="small"
><Icon icon="ep:download" class="mr-5px" />删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</el-scrollbar>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</div>
<!-- 表单弹窗添加/修改 -->
<FileForm ref="FileRef" @success="handleUploadSuccess" />
<Dialog v-model="dialogFormVisibleXlsxInfo" title="考点设置" width="70%">
<input type="file" id="xlsxFile" accept=".xlsx" />
<button @click="getXlsxDataInfo">文件解析</button>
<div
style="border: 1px solid #ddd; padding: 10px; margin: 10px 0; border-radius: 6px; max-height: 100%; overflow: hidden;"
>
<h3 style="margin: 0 0 8px 0;">试题内容</h3>
<el-scrollbar height="200px">
<!-- 支持富文本渲染 -->
<div v-html="formData.content" style="white-space: pre-wrap;"></div>
</el-scrollbar>
</div>
<div style="height: 400px; overflow: hidden; display: flex; gap: 16px">
<div style="flex: 0.5; overflow: auto; border: 1px solid #eee; padding: 8px">
<h3>考点</h3>
<el-tree
style="max-width: 600px"
:data="xlsxPointsList"
:props="defaultProps"
:expand-on-click-node="false"
@node-click="handleNodelClick"
/>
</div>
<div style="flex: 1.5; overflow: auto; border: 1px solid #eee; padding: 8px; width: 90%">
<h3>考点详情</h3>
<el-table :data="list" style="width: 100%">
<el-table-column prop="contentIn" label="值" />
<el-table-column prop="scoreRate" label="权值" width="100px">
<template #default="{ row }">
<el-input v-model="row.scoreRate" size="small" style="width: 50px" />
</template>
</el-table-column>
<el-table-column label="操作" width="140px">
<template #default="{ row }">
<el-button
:icon="ArrowUp"
circle
size="small"
@click="moveUp(row)"
title="上移"
/>
<el-button
:icon="ArrowDown"
circle
size="small"
@click="moveDown(row)"
title="下移"
/>
<el-button
@click="removePoint(row)"
></el-button
>
</template>
</el-table-column>
</el-table>
</div>
</div>
<!-- <el-skeleton :rows="5" animated v-if="XlsxPointsList.length < 0 && isLoading" />
-->
</Dialog>
<el-dialog v-model="dialogFormVisibleXlsxInfos" :title="titles" width="300px">
<el-tree
style="max-width: 600px"
:data="xlsxPointsInfoList"
:props="defaultProps"
:expand-on-click-node="false"
show-checkbox
@check-change="handleCheckChange"
/>
<!-- 当是单元格类型时显示可添加/删除的输入框列表 -->
<!-- 单元格类型才显示 -->
<div v-if="isCellType" style="margin-bottom: 12px">
<!-- 上方输入框和添加按钮 -->
<div style="display: flex; flex-direction: column; gap: 8px; margin-bottom: 8px;">
<!-- cell 类型两个输入框 -->
<template v-if="selectedType === 'cell'">
<div style="display: flex; gap: 8px; align-items: center;">
<el-input
v-model="cellInputValue"
:placeholder="getPlaceholder(selectedType)"
style="flex: 1"
/>
<el-button type="primary" @click="addCellValue">确定</el-button>
</div>
<el-input
v-model="cellImportValue"
placeholder="输入关键字"
style="flex: 1"
clearable
/>
</template>
<!-- range 类型 -->
<template v-else-if="selectedType === 'range'">
<el-input v-model="rangeStart" placeholder="开始单元格 (如 A1)" style="flex: 1" />
<span>:</span>
<el-input v-model="rangeEnd" placeholder="结束单元格 (如 C5)" style="flex: 1" />
<el-button type="primary" @click="addCellValue">确定</el-button>
</template>
<!-- 其他类型 -->
<template v-else>
<el-input
v-model="cellInputValue"
:placeholder="getPlaceholder(selectedType)"
style="flex: 1"
/>
<el-button type="primary" @click="addCellValue">确定</el-button>
</template>
</div>
<!-- 下方列表多选可删除 -->
<el-table
:data="cellValues"
border
size="small"
style="width: 100%; margin-bottom: 8px"
@selection-change="handleSelectionChanges"
>
<el-table-column type="selection" width="50" />
<el-table-column prop="value" label="单元格值" />
</el-table>
<el-button type="danger" :disabled="!selectedRows.length" @click="deleteSelected">
删除选中
</el-button>
</div>
<template #footer>
<el-button type="primary" @click="submitXlsxPoints"> </el-button>
<el-button @click="dialogFormVisibleXlsxInfos = false"> </el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import * as QuestionApi from '@/api/paper/question'
import * as SpecialtyApi from '@/api/points'
import * as XlsxApi from '@/api/wps/xlsx'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { defaultProps, handleTree } from '@/utils/tree'
import { FormRules } from 'element-plus'
import { cloneDeep } from 'lodash-es'
import FileForm from './components/FileForm.vue'
import { ArrowUp, ArrowDown, Delete } from '@element-plus/icons-vue'
defineOptions({ name: 'WpsXlsxFrom' })
const xlsxPointsList = ref<Tree[]>([]) // 树形结构
const xlsxPointsInfoList = ref<Tree[]>([]) // 树形结构
const isCellType = ref(false) // 是否单元格
const cellInputValue = ref('') // 上方输入框值
const cellImportValue = ref('') // 上方输入框值
const cellValues = ref<{ value: string }[]>([]) // 存储所有添加的数据
const selectedRows = ref<{ value: string }[]>([]) // 多选的行
const rangeStart = ref('') // range 开始
const rangeEnd = ref('') // range 结束
// 添加到列表(带重复判断)
const addCellValue = () => {
if (selectedType.value === 'range') {
if (!rangeStart.value || !rangeEnd.value) return
cellValues.value.push({
value: rangeStart.value + ':' + rangeEnd.value
})
rangeStart.value = ''
rangeEnd.value = ''
} else {
if (!cellInputValue.value) return
cellValues.value.push({ value: cellInputValue.value })
cellInputValue.value = ''
}
}
// 表格多选事件
const handleSelectionChanges = (rows: any[]) => {
selectedRows.value = rows
}
// 删除选中项
const deleteSelected = () => {
cellValues.value = cellValues.value.filter((item) => !selectedRows.value.includes(item))
selectedRows.value = []
}
// 明确 list 类型,避免 never[] 类型报错
interface AnswerItem {
image: string;
content: string;
contentIn: string;
scoreRate: string;
[key: string]: any;
}
const list = ref<AnswerItem[]>([])
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const loading = ref(false) // 列表的加载中
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formType = ref('') // 表单的类型create - 新增update - 修改
const formData = ref({
pointNamesVo: '',
chapteridDictTextVo: '',
content: '',
contentText:'',
specialtyName: '',
courseName: '',
quBankName: '',
required: '',
chapteridDictText: '',
analysis: '',
quLevel: 0,
pointNames: '',
subjectName: '',
status: ' ',
resourceValue: '',
answerList: [
{
image: '',
content: '',
contentIn: '',
scoreRate: ''
}
],
fileUploads: [
{
quId: '',
url: '',
fileType: '0',
fileName: ''
},
{
quId: '',
url: '',
fileType: '1',
fileName: ''
},
{
quId: '',
url: '',
fileType: '2',
fileName: ''
}
]
})
// 定义 xlsxPointsInfosList 类型,避免 xlsxPoints 未定义报错
interface XlsxPointsInfo {
firstName: string;
index: string;
function: string;
examName: string;
examCode: string;
method?: string;
}
let xlsxPointsInfosList: XlsxPointsInfo[] = []
// 上移
function moveUp(row) {
const index = list.value.findIndex(item => item === row);
if (index > 0) {
[list.value[index - 1], list.value[index]] = [list.value[index], list.value[index - 1]];
}
updateSort()
}
// 下移
function moveDown(row) {
const index = list.value.findIndex(item => item === row);
if (index !== -1 && index < list.value.length - 1) {
[list.value[index + 1], list.value[index]] = [list.value[index], list.value[index + 1]];
}
updateSort()
}
// 更新 sort 字段
const updateSort = () => {
list.value.forEach((item, index) => {
item.sort = index + 1
})
}
const removePoint = (row: any) => {
for (let i = 0; i < list.value.length; i++) {
if ((row.content ?? row.contentIn) == (list.value[i].content ?? list.value[i].contentIn)) {
list.value.splice(i, 1)
break
}
}
}
function fileTypeFormatter(_row: any, _column: any, cellValue: string) {
if (cellValue === '0') return '素材文件(上传ZIP)'
if (cellValue === '1') return '考试文件'
if (cellValue === '2') return '结果文件'
return '未知类型'
}
const documentList = ref([
{
quId: '',
url: '',
fileType: '0',
fileName: ''
},
{
quId: '',
url: '',
fileType: '1',
fileName: ''
},
{
quId: '',
url: '',
fileType: '2',
fileName: ''
}
])
const dialogFormVisibleXlsxInfo = ref(false)
const dialogFormVisibleXlsxInfos = ref(false)
const titles = ref('')
// 已声明未用,注释掉避免报错
// const filePath = ref('')
// Tree 类型补充 any避免类型报错
const handleCheckChange = (data: any, _checked: boolean, _indeterminate: boolean) => {
// 兼容属性名
const xlsxPoints: XlsxPointsInfo = {
firstName: chineseName.value,
index: textIndex.value,
function: data.functions ?? '',
examName: data.chineseName ?? '',
examCode: '111',
method: data.parameter ?? ''
}
// 查找当前节点是否已存在于列表中(根据 function 或 examName 唯一标识)
const existIndex = xlsxPointsInfosList.findIndex(
(item) => item.function === xlsxPoints.function && item.examName === xlsxPoints.examName
)
if (_checked) {
// ✅ 选中时添加(若已存在则不重复添加)
if (existIndex === -1) {
xlsxPointsInfosList.push(cloneDeep(xlsxPoints))
}
} else {
// ❌ 取消选中时删除
if (existIndex !== -1) {
xlsxPointsInfosList.splice(existIndex, 1)
}
}
console.log('📦 当前选中数据:', xlsxPointsInfosList)
// if (xlsxPoints.function) {
// xlsxPointsInfosList.push(cloneDeep(xlsxPoints))
// }
}
const file = ref<any>()
// 获取xlsx文件并使用文件流进行解析
const getXlsxDataInfo = async () => {
const fileInput = document.getElementById('xlsxFile') as HTMLInputElement
if (fileInput != null) {
if (fileInput.files && fileInput.files.length > 0) {
file.value = fileInput.files[0]
}
const res = await XlsxApi.getXlsxDataInfo({ file: file.value })
xlsxPointsList.value = []
xlsxPointsList.value.push(...handleTree(res.data))
}
}
// 打开考点窗口
const addXlsxForm = async () => {
dialogFormVisibleXlsxInfo.value = true
}
const chineseName = ref('')
const textIndex = ref()
const selectedType = ref('cell') // 初始值随便设
// 打开
const handleNodelClick = async (row: any) => {
// 获取名称
chineseName.value = '【' + row.name + '】'
// 判断是否是单元格
isCellType.value =
row.type === 'cell' || row.type === 'range' || row.type === 'row' || row.type === 'col'
if (isCellType.value) {
selectedType.value = row.type // 点击节点时更新
cellInputValue.value = ''
cellValues.value = [] // 新的 cell 节点清空数据
cellImportValue.value=''
}
console.log(row.index + 'row.indexrow.index')
textIndex.value = row.index
const res = await XlsxApi.getXlsxByNameList(row.type)
xlsxPointsInfoList.value = []
xlsxPointsInfoList.value.push(...handleTree(res))
dialogFormVisibleXlsxInfos.value = true
}
const getPlaceholder = (type) => {
console.log(type + 'typetypetype')
switch (type) {
case 'cell':
return '请输入单元格(如 A1'
case 'range':
return '请输入范围(如 A1:B5'
case 'row':
return '请输入行号(如 3'
case 'col':
return '请输入列号(如 B'
default:
return '请输入值'
}
}
const handleDelete = (row: any) => {
for (let i = 0; i < list.value.length; i++) {
if ((row.content ?? row.contentIn) == (list.value[i].content ?? list.value[i].contentIn)) {
list.value.splice(i, 1)
break
}
}
}
const submitXlsxPoints = async () => {
console.log('单元格输入内容:', cellImportValue.value)
console.log(xlsxPointsInfosList)
const res = await XlsxApi.getXlsxMaster({
data: JSON.stringify(xlsxPointsInfosList),
file: file.value,
cell: cellValues.value.map((item) => item.value), // 提取值数组
keyWords:cellImportValue.value
})
xlsxPointsInfosList = []
console.log(res)
for (let i = 0; i < res.data.length; i++) {
res.data[i].scoreRate = '1'
var indexFlag = false
for (let x = 0; x < list.value.length; x++) {
if ((res.data[i].content ?? res.data[i].contentIn) == (list.value[x].content ?? list.value[x].contentIn)) {
// 如果存在相同的数据话 不进入
indexFlag = true
}
}
if (!indexFlag) {
res.data[i].sort = list.value.length + 1
list.value.push(res.data[i] as any)
}
}
dialogFormVisibleXlsxInfos.value = false
}
const formRules = reactive<FormRules>({
status: [{ required: true, message: '启用状态必填', trigger: 'blur' }],
pointNames: [{ required: true, message: '知识点必填', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
// 左侧试题描述
const leftActiveName = ref('desc')
// 右侧tab
const rightActiveName = ref('annex')
const rightHandleClick = (tab: any) => {
rightActiveName.value = tab.paneName.value
}
const selections = ref([])
const handleSelectionChange = (rows) => {
selections.value = rows
}
/** 添加/修改操作 */
const FileRef = ref()
const openForm = (type: string) => {
FileRef.value.open(type)
}
const downloadFile = async (url: string) => {
if (!url) {
ElMessage.warning('暂无可下载的文件地址')
return
}
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error('下载失败')
}
const blob = await response.blob()
const blobUrl = window.URL.createObjectURL(blob)
// 提取文件名
const filename = url.substring(url.lastIndexOf('/') + 1).split('?')[0]
// 创建 a 标签并下载
const a = document.createElement('a')
a.href = blobUrl
a.download = filename
a.style.display = 'none'
document.body.appendChild(a)
a.click()
a.remove()
window.URL.revokeObjectURL(blobUrl)
} catch (err: any) {
ElMessage.error(`下载失败:${err.message}`)
}
}
const deleteUrl = (index: number) => {
formData.value.fileUploads[index].url = ''
formData.value.fileUploads[index].fileName = ''
}
// 媒体文件
const handleUploadSuccess = ({ url, fileType }) => {
const index = documentList.value.findIndex((item) => item.fileType === fileType)
if (index !== -1) {
documentList.value[index].url = url
}
}
/** 打开弹窗 */
const open = async (queryParams: any, type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
const res = await QuestionApi.getQuestion(id)
formData.value = res
console.log(formData.value)
list.value = formData.value.answerList as any[]
documentList.value = res.fileUploads
} finally {
formLoading.value = false
}
} else {
resetForm()
formData.value.specialtyName = queryParams.specialtyName
formData.value.courseName = queryParams.courseName
formData.value.subjectName = queryParams.subjectName
formData.value.pointNames = queryParams.pointNamesVo
formData.value.pointNamesVo = queryParams.pointNames
formData.value.chapteridDictText = queryParams.chapteridDictTextVo
formData.value.chapteridDictTextVo = queryParams.chapteridDictText
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
formData.value.answerList = list.value
formData.value.fileUploads = documentList.value
const values = Object.values(formData)
console.log(values)
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown
if (formType.value === 'create') {
await QuestionApi.addQuestion(data)
message.success(t('common.createSuccess'))
} else {
await QuestionApi.editQuestion(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
pointNamesVo: '',
chapteridDictTextVo: '',
content: '',
specialtyName: '',
courseName: '',
quBankName: '',
required: '',
chapteridDictText: '',
analysis: '',
quLevel: 0,
pointNames: '',
subjectName: '',
status: ' ',
resourceValue: '',
answerList: [
{
image: '',
content: '',
contentIn: '',
scoreRate: ''
}
],
fileUploads: [
{
quId: '',
url: '',
fileType: '0',
fileName: ''
},
{
quId: '',
url: '',
fileType: '1',
fileName: ''
},
{
quId: '',
url: '',
fileType: '2',
fileName: ''
}
]
}
documentList.value = [
{
quId: '',
url: '',
fileType: '0',
fileName: ''
},
{
quId: '',
url: '',
fileType: '1',
fileName: ''
},
{
quId: '',
url: '',
fileType: '2',
fileName: ''
}
]
list.value = []
formRef.value?.resetFields()
}
const dialogVisiblePoints = ref(false)
// 添加层级信息
const handleTreeWithLevel = (list, level = 1) => {
return list.map((item) => {
const node = { ...item, level }
if (item.children && item.children.length > 0) {
node.children = handleTreeWithLevel(item.children, level + 1)
}
return node
})
}
// 只允许点击第三级节点
const treeRef = ref() // 引用 el-tree
const handleNodeClick = (data: any) => {
if (data.level === 3) {
formData.value.pointNames = data.id
formData.value.pointNamesVo = data.name
// 获取父节点(章节名称)
const currentNode = treeRef.value.getNode(data)
const parentNode = currentNode.parent
if (parentNode && parentNode.data) {
formData.value.chapteridDictTextVo = parentNode.data.name
formData.value.chapteridDictText = parentNode.data.id
} else {
formData.value.chapteridDictText = ''
}
dialogVisiblePoints.value = false
} else {
}
}
const deptList = ref<Tree[]>([]) // 树形结构
/** 获得部门树 */
const getTree = async () => {
const res = await SpecialtyApi.listPoints()
const tree = handleTree(res)
deptList.value = []
deptList.value = handleTreeWithLevel(tree)
}
const openPoints = async () => {
await getTree()
dialogVisiblePoints.value = true
}
// 关键字
const keywordList = ref([] as any)
const multipleKeywordSelection = ref([] as any)
const handleKeywordSelectionChange = (val: any) => {
multipleKeywordSelection.value = val
}
const keyVisible = ref(false)
const keyEditType = ref('')
const keyWord = ref('')
const editKeyword = (key: string) => {
keyEditType.value = key
if (key === 'create') {
keyWord.value = ''
keyVisible.value = true
} else if (key === 'update') {
if (multipleKeywordSelection.value.length === 0) {
ElMessage.warning('请先选择一个要编辑的关键字')
return
}
keyWord.value = multipleKeywordSelection.value[0].keyword
keyVisible.value = true
} else if (key === 'delete') {
if (multipleKeywordSelection.value.length === 0) {
ElMessage.warning('请先选择要删除的关键字')
return
}
keywordList.value = keywordList.value.filter(
item => !multipleKeywordSelection.value.includes(item)
)
ElMessage.success('已删除选中项')
} else if (key === 'deleteall') {
keywordList.value = []
ElMessage.success('已清空关键字列表')
}
updateKeywordsToForm()
}
const updateKeywordsToForm = () => {
const keywordStr = keywordList.value
.map(item => item.keyword)
.filter(k => k && k.trim() !== '')
.join(',')
formData.value.keywords = keywordStr
console.log(formData.value.keywords+"formData.value.keywords")
}
function setKao() {
kaoDialogVisible.value = true
}
const keyDialogClose = () => {
keyVisible.value = false
}
const confirmKeyDialogVisible = () => {
if (keyEditType.value === 'create') {
keywordList.value.push({
keyword: keyWord.value
})
} else if (keyEditType.value === 'update') {
multipleKeywordSelection.value.forEach(item => {
item.keyword = keyWord.value
})
} else if (keyEditType.value === 'delete') {
keywordList.value = keywordList.value.filter(
item => !multipleKeywordSelection.value.includes(item)
)
} else if (keyEditType.value === 'deleteall') {
keywordList.value = []
}
updateKeywordsToForm()
keyVisible.value = false
}
</script>
<style lang="scss" scoped>
.edit-dialog {
:deep(.el-dialog) {
display: flex;
flex-direction: column;
.el-dialog__header {
border-bottom: 1px solid #ededed;
}
.el-dialog__footer {
border-top: 1px solid #ededed;
}
.el-dialog__body {
flex: 1;
overflow: hidden;
.main {
.el-form {
padding: 10px;
.el-row {
justify-content: space-between;
margin-bottom: 10px;
}
.el-form-item {
width: 100%;
margin-bottom: 0;
align-items: center;
}
}
.edit-bottom {
display: flex;
justify-content: space-between;
padding-bottom: 10px;
.bottom-common {
width: calc(50% - 10px);
.el-tabs {
height: 100%;
.el-tabs__content {
flex: 1;
overflow-y: auto;
.block {
width: 100%;
height: 200px;
overflow: auto;
}
.answer {
.tip {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #faebcc;
padding: 10px 20px;
display: flex;
align-items: center;
> p {
flex-shrink: 0;
}
}
.el-radio-group {
width: 100%;
display: inline-flex;
align-items: flex-start;
font-size: 0;
flex-direction: column;
.options {
width: 100%;
margin-top: 15px;
.content {
display: flex;
.text {
width: 100%;
height: 70px;
border: 1px solid #ededed;
.el-textarea__inner {
resize: none;
}
}
}
.more-btn {
margin-top: 8px;
background-color: #ffffff;
border-color: #007bff;
color: #007bff;
width: auto;
height: auto;
padding: 10px 10px;
}
}
}
}
}
.custom-tabs-label {
display: flex;
align-items: center;
.setting_icon {
width: 16px;
height: 16px;
background: url('@/assets/icon/setting_blue.png') no-repeat center;
background-size: 100%;
margin-left: 3px;
display: none;
}
}
.is-active {
.custom-tabs-label {
.setting_icon {
display: block;
}
}
}
:deep(.ele-pro-table) {
flex: 1;
margin-top: 10px;
.el-table--fit {
height: 100%;
}
}
}
}
}
}
}
}
}
:deep(.tox-tinymce) {
.tox-statusbar {
display: none;
}
}
:deep(.el-table) {
.el-table__header-wrapper {
.el-table__header {
thead {
tr {
th {
background: #ebebeb;
}
}
}
}
}
}
</style>