Files
pengchen-exam-vue/src/views/paper/question/MysqlForm.vue
2025-08-27 00:49:37 +08:00

1214 lines
45 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" class="custom-dialog">
<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="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-input v-model="formData.quLevel" placeholder="请输入题型难度" />
</el-form-item> -->
<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-row>
<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-col :span="12">
<el-form-item label="数据库名" prop="tname">
<el-input v-model="formData.tname" placeholder="请输入数据库名" />
</el-form-item>
</el-col>
</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="250px" />
</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 v-if="formType === 'update'" name="point">
<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="setKao()">考点设置</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<div class="block" style="height: 300px;width: 100% ;">
<el-table
:data="kaodianList"
style="width: 100%"
>
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="scoreRate" label="权值" width="60" />
<el-table-column prop="content" label="考点语句" width="800" />
</el-table>
</div>
<!-- 弹框 -->
<el-dialog
title="考点设置"
v-model="kaoDialogVisible"
width="60%"
:close-on-click-modal="false"
class="custom-dialog"
draggable
>
<!-- 可滚动容器 -->
<div style="height: 400px; overflow-y: auto;width:100%;">
<el-table
:data="kaodianList"
style="width: 100%; "
row-key="answerId"
:default-expand-all="false"
@row-contextmenu="(row, column, event) => handleTextRightClick(event, row)"
>
<el-table-column type="index" label="序号" />
<el-table-column label="权值" width="80">
<template #default="{ row }">
<el-input
v-model="row.scoreRate"
size="small"
placeholder="请输入"
style="width: 100%;"
/>
</template>
</el-table-column>
<el-table-column prop="content" label="考点语句" />
<el-table-column label="操作" width="160">
<template #default="{ $index }">
<el-button
:icon="ArrowUp"
circle
size="small"
@click="moveUp($index)"
title="上移"
/>
<el-button
:icon="ArrowDown"
circle
size="small"
@click="moveDown($index)"
title="下移"
/>
</template>
</el-table-column>
<!-- 展开行 -->
<el-table-column type="expand">
<template #default="props">
<el-table
:data="props.row.examMysqlKeywordList"
style="width: 100%;"
border
size="small"
>
<el-table-column type="index" label="序号" width="50" />
<el-table-column prop="keyword" label="关键词" />
<el-table-column label="权值" width="80">
<template #default="scope">
<el-input
v-model="scope.row.scoreRate"
size="small"
style="width: 70px;"
min="0"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="60">
<template #default="scope">
<el-button type="text" size="small" @click="removeKeyword(props.row, scope.$index)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</template>
</el-table-column>
</el-table>
</div>
<!-- 自定义右键菜单 -->
<div
v-if="contextMenuVisible"
class="context-menu"
:style="contextMenuStyle"
>
<div class="context-menu-item" @click="addKaodian(rightClickRow)">
设置此考点
</div>
</div>
<!-- 按钮放容器外 -->
<div style="margin-top: 12px; text-align: center;">
<el-button type="primary" @click="setKaodianRow">导入考点</el-button>
</div>
<template #footer>
<!--
<el-upload
ref="uploadRef"
class="upload"
:action="uploadUrl"
:limit="1"
:before-upload="beforeUpload"
:on-success="handleUploadSuccessFile"
:http-request="httpRequest"
drag
>
<el-button type="primary" style="margin-bottom: 16px;">上传文件</el-button>
</el-upload> -->
<el-button @click="kaoDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmKao">确定</el-button>
</template>
</el-dialog>
</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="formData.fileUploads" 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="320">
<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"/>
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { FormRules } from 'element-plus'
import * as QuestionApi from '@/api/paper/question'
import * as SpecialtyApi from '@/api/points'
import { defaultProps, handleTree } from '@/utils/tree'
import FileForm from './components/FileForm.vue';
import { ArrowUp, ArrowDown, Delete } from '@element-plus/icons-vue'
defineOptions({ name: 'ChoiceForm' })
import type { TabPaneName } from 'element-plus'
import { useUpload } from '@/components/UploadFile/src/useUpload'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const { uploadUrl, httpRequest } = useUpload()
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formType = ref('') // 表单的类型create - 新增update - 修改
const formData = ref({
pointNamesVo:'',
chapteridDictTextVo:'',
content: '',
specialtyName: '',
courseName: '',
quBankName: '',
required: '',
chapteridDictText: '',
analysis: '',
quLevel: 0,
pointNames: '',
tname:'',
subjectName: '',
status: ' ',
keywords: '',
resourceValue: '',
fileUploads: [ {
quId: '',
url: '',
fileType: '1',
fileName: '原始'
},
{
quId: '',
url: '',
fileType: '2',
fileName: '结果'
}]
})
// 当前激活的 tab
const activeTab = ref('basic') // 初始 tab name根据实际设置
// formData.value.quId 中包含 quId假设它已有值
const kaodianData = ref({
quId: '', // 你打开编辑弹窗时会赋值
})
const formRules = reactive<FormRules>({
// specialtyName: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }]
tname: [{ required: true, message: '数据库名称不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
// 左侧试题描述
const leftActiveName = ref('desc')
// 右侧tab
const rightActiveName = ref('analysis')
const rightHandleClick = (tab, e) => {
rightActiveName.value = tab.paneName;
if (tab.paneName === 'point' ) {
// 发起考点请求
QuestionApi.getListByQuId(kaodianData.value.quId).then(res => {
// res 是接口返回的数据数组
kaodianList.value = res;
});
}
};
//右键菜单状态
const kaoDialogVisible = ref(false) // 测试时默认打开
const kaodianList = ref<any[]>([])
const contextMenuVisible = ref(false)
const contextMenuStyle = ref({ top: '0px', left: '0px' })
const rightClickRow = ref<any>(null)
function handleTextRightClick(event: MouseEvent, row: any) {
event.preventDefault()
const selection = window.getSelection()
const selectedText = selection?.toString().trim()
if (selectedText) {
rightClickRow.value = row // 记录当前行
rightClickRow.value.selectedText = selectedText // 保存选中的文本
contextMenuStyle.value = {
top: `${event.clientY}px`,
left: `${event.clientX}px`
}
contextMenuVisible.value = true
document.addEventListener('click', closeContextMenu)
} else {
contextMenuVisible.value = false
}
}
function closeContextMenu() {
contextMenuVisible.value = false
document.removeEventListener('click', closeContextMenu)
}
// 设置考点:添加到下方
function addKaodian(row: any) {
const targetRow = rightClickRow.value;
const keyword = targetRow.selectedText;
if (!targetRow.examMysqlKeywordList) {
targetRow.examMysqlKeywordList = [];
}
const exists = targetRow.examMysqlKeywordList.find((item: any) => item.keyword === keyword);
if (!exists) {
const newKeyword = {
keyword,
scoreRate: 1,
answerId: targetRow.answerId // 添加 answerId
};
targetRow.examMysqlKeywordList.push(newKeyword);
// 累加关键字权值到语句的总权值
if (!targetRow.score) {
targetRow.score = 0;
}
targetRow.score += newKeyword.scoreRate;
}
contextMenuVisible.value = false;
}
function confirmKao() {
// 构建 questionAnswerList
const questionAnswerList = kaodianList.value.map(item => ({
answerId: item.answerId,
content: item.content,
scoreRate:item.scoreRate,
examMysqlKeywordList: (item.examMysqlKeywordList || []).map(kw => ({
keywordId: kw.keywordId,
answerId: kw.answerId,
keyword: kw.keyword,
scoreRate: kw.scoreRate
}))
}));
const payload = {
quId:kaodianData.value.quId,
questionAnswerList
};
console.log('确认后的结果:', payload)
QuestionApi.saveSelectedKaodian(payload)
kaoDialogVisible.value= false;
}
const setKaodianRow =async () => {
const uploads = formData.value.fileUploads;
console.log(uploads[0].url+"formData.value.fileUploads")
if (!uploads[0].url || !uploads[1].url) {
ElMessage.error('请先上传两个文件再导入考点');
return;
}
const fileUrl1 = uploads[0].url;
const fileUrl2 = uploads[1].url;
console.log('导入考点的文件地址为:', fileUrl1, fileUrl2);
const params = {
shucaiPath: fileUrl1,
answerPath : fileUrl2 // 如果不传可以是空字符串,也可以删除这个字段(根据后端是否必填)
};
const res = await QuestionApi.getMysqlPoint(params);
// 根据返回更新 kaodianList 或弹出提示等
kaodianList.value = res || [];
const sqlList = res || [];
// 将 SQL 字符串数组转换为 kaodianList 所需结构
kaodianList.value = sqlList.map((sql, index) => ({
answerId: index + 1, // 可自定义唯一ID生成逻辑
quId: kaodianData.value.quId, // 如果有题目ID可以在这里设置
content: sql,
scoreRate:1,
examMysqlKeywordList: [] // 默认空关键词列表
}));
};
const beforeUpload = (file: File) => {
const isTxt = file.type === 'text/plain';
const isLt5M = file.size / 1024 / 1024 < 5;
if (!isTxt) {
ElMessage.error('只能上传 TXT 文本文件');
return false;
}
if (!isLt5M) {
ElMessage.error('文件大小不能超过 5MB');
return false;
}
return true;
};
function removeKeyword(parentRow: any, keywordIndex: number) {
parentRow.examMysqlKeywordList.splice(keywordIndex, 1);
}
// 上移
function moveUp(index) {
if (index === 0) return;
const temp = this.kaodianList[index];
this.kaodianList.splice(index, 1);
this.kaodianList.splice(index - 1, 0, temp);
this.updateSort();
}
// 下移
function moveDown(index) {
if (index === this.kaodianList.length - 1) return;
const temp = this.kaodianList[index];
this.kaodianList.splice(index, 1);
this.kaodianList.splice(index + 1, 0, temp);
this.updateSort();
}
// 重新赋值 sort 字段(保持顺序)
function updateSort() {
this.kaodianList.forEach((item, index) => {
item.sort = index + 1;
});
}
const radio = ref('A')
// 关键字
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
}
/** 添加/修改操作 */
const FileRef = ref()
const openForm = (type: string) => {
FileRef.value.open(type)
}
// 媒体文件
const mediumList = ref([] as any)
const multipleMediumSelection = ref([] as any)
const handleMediumSelectionChange = (val: any) => {
multipleMediumSelection.value = val
}
const fileList = []
const multipleDocumentSelection = ref([] as any)
const handleDocumentSelectionChange = (val: any) => {
multipleDocumentSelection.value = val
}
//文件
function fileTypeFormatter(row, column, cellValue) {
if (cellValue === '1') return '考试文件'
if (cellValue === '2') return '结果文件'
return '未知类型'
}
const handleUploadSuccess = ({ url, fileType }) => {
const index = formData.value.fileUploads.findIndex(item => item.fileType === fileType)
if (index !== -1) {
formData.value.fileUploads[index].url = url
}
}
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 open = async (queryParams: any ,type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
rightActiveName.value = 'analysis'
// 修改时,设置数据
if (id) {
kaodianData.value.quId=id;
formLoading.value = true
try {
const res = await QuestionApi.getQuestion(id);
// 默认两个类型
const fileTypes = ['1', '2'];
// 后端返回的上传文件列表(可能为空)
const documentList = res.fileUploads ?? [];
// 遍历两种类型,找到对应的上传文件,如果没有就用默认值
const fileUploads = fileTypes.map(type => {
const match = documentList.find(file => file.fileType === type);
return {
quId: match?.quId ?? res.quId ?? '',
url: match?.url ?? '',
fileType: type,
fileName: match?.fileName ?? ''
};
});
formData.value = {
...res,
fileUploads,
};
keywordList.value = res.keywords
? res.keywords.split(',').filter(item => item.trim() !== '').map(item => ({ keyword: item.trim() }))
: []
} 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 方法,用于打开弹窗
// 转换函数:将大写字母 A-Z 映射为 0-25
const mappedNumber = computed(() => {
const char = radio.value.toUpperCase();
const code = char.charCodeAt(0);
if (code >= 65 && code <= 90) {
return code - 65;
} else {
return '请输入 A-Z 的字母';
}
});
// 选择答案数据
const answerData = ref([]);
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef) return;
const valid = await formRef.value.validate();
if (!valid) return;
formLoading.value = true;
try {
// 深拷贝一份 formData
const data = JSON.parse(JSON.stringify(formData.value));
// 过滤掉 url 为空的文件项
data.fileUploads = data.fileUploads?.filter(file => file.url && file.url.trim() !== '');
console.log(data, "提交的数据");
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 = {
content: `<p style="text-align: left;"><span style="font-family: 宋体;">--------------------------------------------------------------------------- </span></p><p style="text-align: left;"><span style="font-family: 宋体;">注意事项:</span></p><p style="text-align: left;"><span style="font-family: 宋体;">请利用图形化管理界面或者MySQL命令行工具在指定的试题数据库中进行答题。</span></p><p style="text-align: left;"><span style="font-family: 宋体;">请先点击答题作答,否则所有试题不得分! </span></p><p style="text-align: left;"><span style="font-family: 宋体;">MySQL连接地址为localhost</span></p><p style="text-align: left;"><span style="font-family: 宋体;">MySQL登录用户名为root</span></p><p style="text-align: left;"><span style="font-family: 宋体;">MySQL端口号为48086</span></p><p style="text-align: left;"><span style="font-family: 宋体;">MySQL密码为空</span></p><p style="text-align: left;"><span style="font-family: 宋体;">---------------------------------------------------------------------------</span></p><p style="text-align: left;"><span style="font-family: 宋体;">无人商超系统数据库db_smartmart包含了product商品表、device设备表、orders订单表和order_detail订单详情表以及t_user用户表。</span></p><p style="text-align: left;"><span style="font-family: 宋体;">product包含字段 product_id商品编号rfid_tagRFID电子标签name商品名称category类别weight重量current_stock当前库存position货架位置。</span></p><p style="text-align: left;"><span style="font-family: 宋体;">device包含字段 device_id设备编号type设备status实时状态last_check最后检测时间location部署位置。</span></p><p style="text-align: left;"><span style="font-family: 宋体;">orders包含字段 order_id订单编号user_id用户编号payment_method支付方式total_amount订单金额create_time下单时间checkout_device_id结算设备ID。</span></p><p style="text-align: left;"><span style="font-family: 宋体;">order_detail包含字段 detail_id订单详情编号order_id订单编号product_id商品编号quantity购买数量scan_time扫描时间。</span></p><p style="text-align: left;"><span style="font-family: 宋体;">t_user包含字段 user_id用户编号username用户姓名。</span></p><p style="text-align: left;"><span style="font-family: 宋体;"> 1、在数据库中已经存在了一个用户表但缺少其他相关字段请补全此表其他字段如下</span></p><table style="width: auto;"><tbody><tr><th colSpan="1" rowSpan="1" width="198.55">字段名称 </th><th colSpan="1" rowSpan="1" width="118.72">类型 </th><th colSpan="1" rowSpan="1" width="89.17">长度 </th><th colSpan="1" rowSpan="1" width="75.77"> 需求</th></tr><tr><td colSpan="1" rowSpan="1" width="auto">password密码</td><td colSpan="1" rowSpan="1" width="auto">VARCHAR</td><td colSpan="1" rowSpan="1" width="auto">255</td><td colSpan="1" rowSpan="1" width="auto">非空</td></tr><tr><td colSpan="1" rowSpan="1" width="auto">phone联系方式</td><td colSpan="1" rowSpan="1" width="auto">VARCHAR</td><td colSpan="1" rowSpan="1" width="auto">15</td><td colSpan="1" rowSpan="1" width="auto">唯一</td></tr><tr><td colSpan="1" rowSpan="1" width="auto">real_name用户姓名</td><td colSpan="1" rowSpan="1" width="auto">VARCHAR</td><td colSpan="1" rowSpan="1" width="auto">20</td><td colSpan="1" rowSpan="1" width="auto"></td></tr><tr><td colSpan="1" rowSpan="1" width="auto">status状态</td><td colSpan="1" rowSpan="1" width="auto">INT</td><td colSpan="1" rowSpan="1" width="auto"></td><td colSpan="1" rowSpan="1" width="auto">默认为1</td></tr></tbody></table><p style="text-align: left;"><br></p><p style="text-align: left;"><span style="font-family: 宋体;">2、添加一条商品记录('RFID_NSN001', &nbsp;'蒜鸟-樱花限定版', '日用品',0.35, 100, 'D05-1')。</span></p><p style="text-align: left;"><span style="font-family: 宋体;">3、在device设备表中修改"出口通道1号"的"自助结算机1-1"的状态为"维护中"。</span></p><p style="text-align: left;"><span style="font-family: 宋体;">4、在order_detail订单详情表中删除订单号为"cs001"的测试数据。</span></p><p style="text-align: left;"><span style="font-family: 宋体;">5、在orders订单表中删除订单号为"cs001"的测试数据。</span></p><p style="text-align: left;"><span style="font-family: 宋体;">6、使用sql语句查询所有处于正常状态的自助结算机的订单数和总订单金额要求显示字段名分别为机器编号位置订单数总金额。</span></p><p style="text-align: left;"><span style="font-family: 宋体;">(注意: 请将该查询语句以cx11.txt为文件名保存在试题文件夹中否则没有成绩</span></p><p style="text-align: left;"><span style="font-family: 宋体;">7、使用sql语句查询查询库存量低于平均库存的所有商品信息。</span></p><p style="text-align: left;"><span style="font-family: 宋体;">(注意: 请将该查询语句以cx12.txt为文件名保存在试题文件夹中否则没有成绩</span></p><p style="text-align: left;"><span style="font-family: 宋体;">8、创建视图名称为v_smartmart。要求显示类别为"日用品"的相关信息,要求显示:订单编号、商品名称、购买数量、订单金额、下单时间,按照订单金额升序排序。</span></p><p style="text-align: left;"><span style="font-family: 宋体;">9、创建一个名称为tri_food_reset 的触发器在device设备表中温度传感器每天会定时更新数据当数据更新之后将商品类别为"鲜食"的商品库存数量清零。</span></p><p style="text-align: left;"><span style="font-family: 宋体;">(在试题文件夹中"触发器.txt"已经给出部分程序,但是程序不完整,请考生在""标记处填上适当的内容后并把""标记删除,使程序补充完整,并按照原文件名保存在试题文件夹下或在数据库里创建,否则没有成绩。禁止修改"标记"以外的代码,否则没有成绩。)</span></p><p style="text-align: left;"><span style="font-family: 宋体;">10、创建一个名称为pro_getproduct的存储过程输入设备in_device_type VARCHAR(100),根据设备的部署位置查询该设备监控的商品信息。</span></p><p style="text-align: left;"><span style="font-family: 宋体;">(在试题文件夹中"存储过程.txt"已经给出部分程序,但是程序不完整,请考生在""标记处填上适当的内容后并把""标记删除,使程序补充完整,并按照原文件名保存在试题文件夹下或在数据库里创建,否则没有成绩。禁止修改"标记"以外的代码,否则没有成绩。)</span></p>`,
specialtyName: '',
courseName: '',
quBankName: '',
required: '',
chapteridDictText: '',
analysis: '',
quLevel: 0,
tname:'',
pointNames: '',
subjectName: '',
status: '0',
keywords: '',
resourceValue: '',
fileUploads: [
{
quId: '',
url: '',
fileType: '1',
fileName: '原始'
},
{
quId: '',
url: '',
fileType: '2',
fileName: '结果'
}
]
}
kaodianList.value=[],
keywordList.value=[],
formRef.value?.resetFields()
}
const selectedPointName = ref('')
const selectedchapterText = ref('')
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, node) => {
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;
}
onMounted(() => {
document.addEventListener('click', () => {
contextMenuVisible.value = false
})
})
</script>
<style lang="scss" scoped>
.context-menu {
position: fixed;
background-color: white;
border: 1px solid #ddd;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
z-index: 9999;
padding: 5px 0;
width: 120px;
}
.context-menu-item {
padding: 6px 12px;
cursor: pointer;
}
.context-menu-item:hover {
background-color: #f5f5f5;
}
.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: 80%;
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>