This commit is contained in:
chenqiang 2025-08-12 20:55:51 +08:00
parent 0ea53bb30e
commit 0de17d0251
7 changed files with 163 additions and 79 deletions

View File

@ -370,15 +370,57 @@ function getAppSaveDir() {
const portableFlagPath = path.join(appDir, 'portable.txt');
const isPortable = fs.existsSync(portableFlagPath);
// 所有生产环境包括便携和非便携都使用应用同级的output目录
// 便携模式:使用应用根目录
if (isPortable) {
return appDir;
} else {
// 非便携模式使用应用同级的output目录
const outputDir = path.join(appDir, 'output');
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
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文件
* @param {string} jsonString - 包含试卷信息的JSON字符串
@ -431,7 +473,15 @@ export async function generatePaperPdf(jsonString) {
// 生成文件名
// 格式化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`;
// 获取保存路径
@ -774,7 +824,15 @@ export async function generatePaperPdf(jsonString) {
doc.end();
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);
});
} catch (error) {

View File

@ -1,6 +1,6 @@
{
"name": "electron-exam",
"version": "1.0.2",
"version": "1.0.201",
"private": true,
"type": "module",
"main": "electron/main.js",

View File

@ -313,8 +313,9 @@ const validateAdminPassword = (password) => {
.main-background {
background-image: url('@/assets/bg.jpeg');
background-size: 'cover';
background-position: 'center';
background-size: 100% 100%; /* 拉伸背景图以填满容器 */
background-position: center; /* 保持居中 */
background-repeat: no-repeat; /* 避免重复 */
}
/* 适配移动设备 */

View File

@ -4,26 +4,35 @@
<div class="container mx-auto py-6">
<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="bg-blue-50 p-4 rounded-lg border border-blue-100 hover:shadow-md transition-shadow duration-300">
<div class="flex items-center justify-between mb-2">
<h3 class="text-lg font-medium text-blue-700">总试题数</h3>
<font-awesome-icon icon="newspaper" class="text-blue-500" />
<div class="bg-blue-50 p-4 rounded-lg border border-blue-100 hover:shadow-md transition-shadow duration-300 cursor-pointer" @click="navigateToQuestionManagement">
<el-statistic :value="questionsCount">
<template #title>
<div style="display: inline-flex; align-items: center;font-size:1rem">
总试题数
<font-awesome-icon icon="newspaper" class="text-blue-500" style="margin-left: 4px" />
</div>
<p class="text-3xl font-bold text-blue-900 mt-2">120</p>
</template>
</el-statistic>
</div>
<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">
<h3 class="text-lg font-medium text-green-700">总考试数</h3>
<font-awesome-icon icon="calendar-check" class="text-green-500" />
<el-statistic :value="totalScore">
<template #title>
<div style="display: inline-flex; align-items: center;font-size:1rem">
总分数
<font-awesome-icon icon="calendar-check" class="text-green-500" style="margin-left: 4px" />
</div>
<p class="text-3xl font-bold text-green-900 mt-2">8</p>
</template>
</el-statistic>
</div>
<div class="bg-purple-50 p-4 rounded-lg border border-purple-100 hover:shadow-md transition-shadow duration-300">
<div class="flex items-center justify-between mb-2">
<h3 class="text-lg font-medium text-purple-700">总考生数</h3>
<font-awesome-icon icon="users" class="text-purple-500" />
<div class="bg-purple-50 p-4 rounded-lg border border-purple-100 hover:shadow-md transition-shadow duration-300 cursor-pointer" @click="navigateToExamineeManagement">
<el-statistic :value="examineesCount">
<template #title>
<div style="display: inline-flex; align-items: center;font-size:1rem">
总考生数
<font-awesome-icon icon="users" class="text-purple-500" style="margin-left: 4px" />
</div>
<p class="text-3xl font-bold text-purple-900 mt-2">156</p>
</template>
</el-statistic>
</div>
</div>
</div>
@ -31,18 +40,54 @@
</template>
<script setup>
import { defineComponent, ref } from 'vue'
import { defineComponent, ref, onMounted } from 'vue'
import AdminLayout from '@/components/admin/AdminLayout.vue'
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) => {
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>
<style scoped>

View File

@ -140,7 +140,25 @@ const handleAddExam = () => {
const handleEditExam = (row) => {
isEdit.value = true
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
}

View File

@ -9,11 +9,9 @@
<!-- 交卷结果卡片 -->
<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="您已成功提交试卷感谢您的参与">
<!---
<template #extra>
<el-button type="primary" @click="goHome">返回首页</el-button>
<p v-if="pdfPath">{{ pdfPath.filePath }}</p>
</template>
-->
</el-result>
<el-divider />
@ -138,6 +136,8 @@ if (!userStore.state.isLoggedIn) {
const router = useRouter()
const size = ref('default')
const pdfPath = ref('')
//
const iconStyle = computed(() => {
const marginMap = {
@ -209,8 +209,8 @@ const checkAnswers = async () => {
if (result.success) {
// generatePaperPdf result.data
const pdfPath = await window.electronAPI.generatePaperPdf(result.data)
console.log('生成的试卷PDF文件路径:', pdfPath)
pdfPath.value = await window.electronAPI.generatePaperPdf(result.data)
console.log('生成的试卷PDF文件路径:', pdfPath.value)
// store
userStore.setPaper(JSON.stringify(result.data))
console.log('试卷数据已更新到store得分:', result.data.paper_score_real)

View File

@ -55,7 +55,7 @@
<div class="question-images" v-if="currentQuestionData?.question?.images?.length > 0">
<div v-for="(image, index) in currentQuestionData?.question?.images" :key="index"
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>
@ -87,10 +87,12 @@
<div class="question-answer"
v-else-if="currentQuestionData?.question?.question_type === 'fill_blank'">
<!-- 显示fill_blank_description -->
<div style="font-weight:bold"
v-html="currentQuestionData?.question_detail?.blank_description || ''"></div>
<p style="font-weight:bold"
v-html="currentQuestionData?.question_detail?.blank_description || ''"></p>
<input type="text" placeholder="请在此填写答案"
v-model="currentQuestionData.question_detail.examinee_answers" @input="saveFillAnswer">
<p style="color: #9f9f9f; line-height: 200%">结果保留2位小数 100.00</p>
</div>
</div>
</div>
@ -587,46 +589,6 @@ const handleAutoSubmit = async () => {
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>
<style scoped>