1.0.201
This commit is contained in:
parent
0ea53bb30e
commit
0de17d0251
@ -370,15 +370,57 @@ function getAppSaveDir() {
|
|||||||
const portableFlagPath = path.join(appDir, 'portable.txt');
|
const portableFlagPath = path.join(appDir, 'portable.txt');
|
||||||
const isPortable = fs.existsSync(portableFlagPath);
|
const isPortable = fs.existsSync(portableFlagPath);
|
||||||
|
|
||||||
// 所有生产环境(包括便携和非便携)都使用应用同级的output目录
|
// 便携模式:使用应用根目录
|
||||||
const outputDir = path.join(appDir, 'output');
|
if (isPortable) {
|
||||||
if (!fs.existsSync(outputDir)) {
|
return appDir;
|
||||||
fs.mkdirSync(outputDir, { recursive: true });
|
} else {
|
||||||
|
// 非便携模式:使用应用同级的output目录
|
||||||
|
const outputDir = path.join(appDir, 'output');
|
||||||
|
if (!fs.existsSync(outputDir)) {
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
return outputDir;
|
||||||
}
|
}
|
||||||
return outputDir;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户桌面目录路径
|
||||||
|
* @returns {string} 用户桌面绝对路径
|
||||||
|
*/
|
||||||
|
function getDesktopDir() {
|
||||||
|
try {
|
||||||
|
// 使用Electron的app.getPath方法获取桌面路径
|
||||||
|
return app.getPath('desktop');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取桌面路径失败:', error);
|
||||||
|
// 发生错误时,返回当前工作目录作为备选
|
||||||
|
return process.cwd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将文件复制到用户桌面
|
||||||
|
* @param {string} filePath - 要复制的文件的绝对路径
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function copyToDesk(filePath) {
|
||||||
|
try {
|
||||||
|
const desktopDir = getDesktopDir();
|
||||||
|
const fileName = path.basename(filePath);
|
||||||
|
const destPath = path.join(desktopDir, fileName);
|
||||||
|
|
||||||
|
// 使用fs.promises进行文件复制
|
||||||
|
await fs.promises.copyFile(filePath, destPath);
|
||||||
|
console.log(`文件已成功复制到桌面: ${destPath}`);
|
||||||
|
return destPath;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('复制文件到桌面失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成试卷PDF文件
|
* 生成试卷PDF文件
|
||||||
* @param {string} jsonString - 包含试卷信息的JSON字符串
|
* @param {string} jsonString - 包含试卷信息的JSON字符串
|
||||||
@ -431,7 +473,15 @@ export async function generatePaperPdf(jsonString) {
|
|||||||
|
|
||||||
// 生成文件名
|
// 生成文件名
|
||||||
// 格式化paper_end_time,移除特殊字符
|
// 格式化paper_end_time,移除特殊字符
|
||||||
const formattedEndTime = endTime.replace(/[:\s]/g, '-');
|
const endDate = new Date(endTime.replace(/-/g, '/'));
|
||||||
|
const formattedEndTime = [
|
||||||
|
endDate.getFullYear(),
|
||||||
|
String(endDate.getMonth() + 1).padStart(2, '0'),
|
||||||
|
String(endDate.getDate()).padStart(2, '0'),
|
||||||
|
String(endDate.getHours()).padStart(2, '0'),
|
||||||
|
String(endDate.getMinutes()).padStart(2, '0'),
|
||||||
|
String(endDate.getSeconds()).padStart(2, '0')
|
||||||
|
].join('');
|
||||||
const fileName = `${examineeName}_${idCard}_${formattedEndTime}.pdf`;
|
const fileName = `${examineeName}_${idCard}_${formattedEndTime}.pdf`;
|
||||||
|
|
||||||
// 获取保存路径
|
// 获取保存路径
|
||||||
@ -774,7 +824,15 @@ export async function generatePaperPdf(jsonString) {
|
|||||||
doc.end();
|
doc.end();
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
writeStream.on('finish', () => resolve(filePath));
|
writeStream.on('finish', async () => {
|
||||||
|
try {
|
||||||
|
// 调用copyToDesk方法将文件复制到桌面,获取新的路径
|
||||||
|
const newFilePath = await copyToDesk(filePath);
|
||||||
|
resolve(newFilePath);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
writeStream.on('error', reject);
|
writeStream.on('error', reject);
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "electron-exam",
|
"name": "electron-exam",
|
||||||
"version": "1.0.2",
|
"version": "1.0.201",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "electron/main.js",
|
"main": "electron/main.js",
|
||||||
|
@ -313,8 +313,9 @@ const validateAdminPassword = (password) => {
|
|||||||
|
|
||||||
.main-background {
|
.main-background {
|
||||||
background-image: url('@/assets/bg.jpeg');
|
background-image: url('@/assets/bg.jpeg');
|
||||||
background-size: 'cover';
|
background-size: 100% 100%; /* 拉伸背景图以填满容器 */
|
||||||
background-position: 'center';
|
background-position: center; /* 保持居中 */
|
||||||
|
background-repeat: no-repeat; /* 避免重复 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 适配移动设备 */
|
/* 适配移动设备 */
|
||||||
|
@ -4,26 +4,35 @@
|
|||||||
<div class="container mx-auto py-6">
|
<div class="container mx-auto py-6">
|
||||||
<h1 class="text-2xl font-bold mb-6">管理员首页</h1>
|
<h1 class="text-2xl font-bold mb-6">管理员首页</h1>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
|
||||||
<div class="bg-blue-50 p-4 rounded-lg border border-blue-100 hover:shadow-md transition-shadow duration-300">
|
<div class="bg-blue-50 p-4 rounded-lg border border-blue-100 hover:shadow-md transition-shadow duration-300 cursor-pointer" @click="navigateToQuestionManagement">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<el-statistic :value="questionsCount">
|
||||||
<h3 class="text-lg font-medium text-blue-700">总试题数</h3>
|
<template #title>
|
||||||
<font-awesome-icon icon="newspaper" class="text-blue-500" />
|
<div style="display: inline-flex; align-items: center;font-size:1rem">
|
||||||
</div>
|
总试题数
|
||||||
<p class="text-3xl font-bold text-blue-900 mt-2">120</p>
|
<font-awesome-icon icon="newspaper" class="text-blue-500" style="margin-left: 4px" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-statistic>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-green-50 p-4 rounded-lg border border-green-100 hover:shadow-md transition-shadow duration-300">
|
<div class="bg-green-50 p-4 rounded-lg border border-green-100 hover:shadow-md transition-shadow duration-300">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<el-statistic :value="totalScore">
|
||||||
<h3 class="text-lg font-medium text-green-700">总考试数</h3>
|
<template #title>
|
||||||
<font-awesome-icon icon="calendar-check" class="text-green-500" />
|
<div style="display: inline-flex; align-items: center;font-size:1rem">
|
||||||
</div>
|
总分数
|
||||||
<p class="text-3xl font-bold text-green-900 mt-2">8</p>
|
<font-awesome-icon icon="calendar-check" class="text-green-500" style="margin-left: 4px" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-statistic>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-purple-50 p-4 rounded-lg border border-purple-100 hover:shadow-md transition-shadow duration-300">
|
<div class="bg-purple-50 p-4 rounded-lg border border-purple-100 hover:shadow-md transition-shadow duration-300 cursor-pointer" @click="navigateToExamineeManagement">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<el-statistic :value="examineesCount">
|
||||||
<h3 class="text-lg font-medium text-purple-700">总考生数</h3>
|
<template #title>
|
||||||
<font-awesome-icon icon="users" class="text-purple-500" />
|
<div style="display: inline-flex; align-items: center;font-size:1rem">
|
||||||
</div>
|
总考生数
|
||||||
<p class="text-3xl font-bold text-purple-900 mt-2">156</p>
|
<font-awesome-icon icon="users" class="text-purple-500" style="margin-left: 4px" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-statistic>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -31,18 +40,54 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineComponent, ref } from 'vue'
|
import { defineComponent, ref, onMounted } from 'vue'
|
||||||
import AdminLayout from '@/components/admin/AdminLayout.vue'
|
import AdminLayout from '@/components/admin/AdminLayout.vue'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ElStatistic } from 'element-plus'
|
||||||
|
|
||||||
// 示例数据
|
// 响应式数据
|
||||||
const recentExams = ref([])
|
const questionsCount = ref(0)
|
||||||
|
const totalScore = ref(0)
|
||||||
|
const examineesCount = ref(0)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
// 处理菜单点击事件
|
// 处理菜单点击事件
|
||||||
const handleMenuClick = (menuKey) => {
|
const handleMenuClick = (menuKey) => {
|
||||||
console.log('Menu clicked:', menuKey)
|
console.log('Menu clicked:', menuKey)
|
||||||
// 这里可以添加路由跳转逻辑
|
// 这里可以添加路由跳转逻辑
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 跳转到试题管理
|
||||||
|
const navigateToQuestionManagement = () => {
|
||||||
|
router.push('/admin/question-management')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳转到老生管理
|
||||||
|
const navigateToExamineeManagement = () => {
|
||||||
|
router.push('/admin/examinee-management')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取数据
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
// 获取试题数量和分数
|
||||||
|
const questionsData = await window.electronAPI.getQuestionsCountAndScore()
|
||||||
|
questionsCount.value = questionsData.totalQuestions
|
||||||
|
totalScore.value = questionsData.totalScore
|
||||||
|
|
||||||
|
// 获取考生数据并过滤
|
||||||
|
const allExaminees = await window.electronAPI.fetchAllExaminees()
|
||||||
|
examineesCount.value = allExaminees.filter(examinee => examinee.examinee_id_card.length > 2).length
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时获取数据
|
||||||
|
onMounted(() => {
|
||||||
|
fetchData()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -140,7 +140,25 @@ const handleAddExam = () => {
|
|||||||
const handleEditExam = (row) => {
|
const handleEditExam = (row) => {
|
||||||
isEdit.value = true
|
isEdit.value = true
|
||||||
dialogTitle.value = '编辑考试'
|
dialogTitle.value = '编辑考试'
|
||||||
formData.value = {...row}
|
// 深拷贝row对象
|
||||||
|
const examData = {...row}
|
||||||
|
|
||||||
|
// 将考试须知JSON数组转换为换行分隔的字符串
|
||||||
|
if (examData.exam_notice) {
|
||||||
|
try {
|
||||||
|
// 尝试解析JSON字符串为数组
|
||||||
|
const noticeArray = JSON.parse(examData.exam_notice)
|
||||||
|
if (Array.isArray(noticeArray)) {
|
||||||
|
// 将数组转换为换行分隔的字符串
|
||||||
|
examData.exam_notice = noticeArray.join('\n')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 如果解析失败,保持原字符串不变
|
||||||
|
console.error('解析考试须知失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formData.value = examData
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,11 +9,9 @@
|
|||||||
<!-- 交卷结果卡片 -->
|
<!-- 交卷结果卡片 -->
|
||||||
<div class="bg-white rounded-4 shadow-lg p-4 w-100 max-w-2xl border-2 border-primary/20">
|
<div class="bg-white rounded-4 shadow-lg p-4 w-100 max-w-2xl border-2 border-primary/20">
|
||||||
<el-result icon="success" title="考试已完成" sub-title="您已成功提交试卷,感谢您的参与!">
|
<el-result icon="success" title="考试已完成" sub-title="您已成功提交试卷,感谢您的参与!">
|
||||||
<!---
|
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<el-button type="primary" @click="goHome">返回首页</el-button>
|
<p v-if="pdfPath">{{ pdfPath.filePath }}</p>
|
||||||
</template>
|
</template>
|
||||||
-->
|
|
||||||
</el-result>
|
</el-result>
|
||||||
|
|
||||||
<el-divider />
|
<el-divider />
|
||||||
@ -138,6 +136,8 @@ if (!userStore.state.isLoggedIn) {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const size = ref('default')
|
const size = ref('default')
|
||||||
|
|
||||||
|
const pdfPath = ref('')
|
||||||
|
|
||||||
// 图标样式计算属性
|
// 图标样式计算属性
|
||||||
const iconStyle = computed(() => {
|
const iconStyle = computed(() => {
|
||||||
const marginMap = {
|
const marginMap = {
|
||||||
@ -209,8 +209,8 @@ const checkAnswers = async () => {
|
|||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
// 调用 generatePaperPdf 接口并传入 result.data
|
// 调用 generatePaperPdf 接口并传入 result.data
|
||||||
const pdfPath = await window.electronAPI.generatePaperPdf(result.data)
|
pdfPath.value = await window.electronAPI.generatePaperPdf(result.data)
|
||||||
console.log('生成的试卷PDF文件路径:', pdfPath)
|
console.log('生成的试卷PDF文件路径:', pdfPath.value)
|
||||||
// 更新store中的试卷数据
|
// 更新store中的试卷数据
|
||||||
userStore.setPaper(JSON.stringify(result.data))
|
userStore.setPaper(JSON.stringify(result.data))
|
||||||
console.log('试卷数据已更新到store,得分:', result.data.paper_score_real)
|
console.log('试卷数据已更新到store,得分:', result.data.paper_score_real)
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
<div class="question-images" v-if="currentQuestionData?.question?.images?.length > 0">
|
<div class="question-images" v-if="currentQuestionData?.question?.images?.length > 0">
|
||||||
<div v-for="(image, index) in currentQuestionData?.question?.images" :key="index"
|
<div v-for="(image, index) in currentQuestionData?.question?.images" :key="index"
|
||||||
class="question-image">
|
class="question-image">
|
||||||
<img :src="image.image_base64" :alt="image.image_name" @click="viewImage(image.image_base64)">
|
<img :src="image.image_base64" :alt="image.image_name" @click="viewImage(image.image_base64)" style="max-height:300px;max-width:500px;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -87,10 +87,12 @@
|
|||||||
<div class="question-answer"
|
<div class="question-answer"
|
||||||
v-else-if="currentQuestionData?.question?.question_type === 'fill_blank'">
|
v-else-if="currentQuestionData?.question?.question_type === 'fill_blank'">
|
||||||
<!-- 显示fill_blank_description -->
|
<!-- 显示fill_blank_description -->
|
||||||
<div style="font-weight:bold"
|
<p style="font-weight:bold"
|
||||||
v-html="currentQuestionData?.question_detail?.blank_description || ''"></div>
|
v-html="currentQuestionData?.question_detail?.blank_description || ''"></p>
|
||||||
|
|
||||||
<input type="text" placeholder="请在此填写答案"
|
<input type="text" placeholder="请在此填写答案"
|
||||||
v-model="currentQuestionData.question_detail.examinee_answers" @input="saveFillAnswer">
|
v-model="currentQuestionData.question_detail.examinee_answers" @input="saveFillAnswer">
|
||||||
|
<p style="color: #9f9f9f; line-height: 200%">(结果保留2位小数,如 100.00)</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -587,46 +589,6 @@ const handleAutoSubmit = async () => {
|
|||||||
ElMessage.error(`自动交卷失败: ${error.message || '系统错误'}`)
|
ElMessage.error(`自动交卷失败: ${error.message || '系统错误'}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建文件(PDF)方法
|
|
||||||
// const createFile = async () => {
|
|
||||||
// try {
|
|
||||||
// // 准备PDF数据
|
|
||||||
// const pdfData = {
|
|
||||||
// title: '考试记录',
|
|
||||||
// content: [
|
|
||||||
// { type: 'heading', text: '考试记录', fontSize: 20, options: { align: 'center' } },
|
|
||||||
// { type: 'table',
|
|
||||||
// headers: ['项目', '内容'],
|
|
||||||
// rows: [
|
|
||||||
// ['考生姓名', examinee.value.examinee_name],
|
|
||||||
// ['身份证号', examinee.value.examinee_id_card],
|
|
||||||
// ['准考证号', examinee.value.examinee_admission_ticket],
|
|
||||||
// ['试卷总分', paper.value.paper_score],
|
|
||||||
// ['考试时长', `${paper.value.paper_minutes}分钟`],
|
|
||||||
// ['开始时间', new Date(paper.value.paper_start_time).toLocaleString()],
|
|
||||||
// ['结束时间', new Date().toLocaleString()],
|
|
||||||
// ['已用时间', `${Math.floor((new Date() - new Date(paper.value.paper_start_time)) / (1000 * 60))}分钟`]
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// { type: 'heading', text: '答题情况', fontSize: 16 },
|
|
||||||
// // { type: 'text', text: `共${questionList.value.length}题,已答${answeredCount.value}题,未答${questionList.value.length - answeredCount.value}题` }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 调用generatePdf接口
|
|
||||||
// const result = await window.electronAPI.generatePdf(pdfData)
|
|
||||||
|
|
||||||
// if (result.success) {
|
|
||||||
// ElMessage.success('PDF文件生成成功,已保存至应用同级目录')
|
|
||||||
// } else {
|
|
||||||
// ElMessage.error(`PDF文件生成失败: ${result.message || '未知错误'}`)
|
|
||||||
// }
|
|
||||||
// } catch (error) {
|
|
||||||
// console.error('生成PDF文件失败:', error)
|
|
||||||
// ElMessage.error(`生成PDF文件失败: ${error.message || '系统错误'}`)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
Loading…
Reference in New Issue
Block a user