新增在线练题示例

This commit is contained in:
Guarp 2025-02-27 23:03:46 +08:00
parent c3b455b05d
commit e553404b0a
8 changed files with 155 additions and 39 deletions

View File

@ -1,13 +1,13 @@
@font-face {
font-family: 'Netron';
src: url('../../../public/fonts/Netron.ttf') format('truetype');
src: url('/public/fonts/Netron.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'huangkaihua';
src: url('../../../public/fonts/huangkaihuaLawyerfont-2.ttf') format('truetype');
src: url('/public/fonts/huangkaihuaLawyerfont-2.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;

View File

@ -21,15 +21,22 @@ const formattedLatex = ref('');
//
watch(() => props.contentInput, (newValue) => {
localInput.value = newValue.replaceAll('<code>', '$')
localInput.value = newValue;
});
function processFormat(text) {
return String(text).replaceAll('<code>', '$')
.replaceAll('</code>', '$')
.replaceAll('/lt', '<')
.replaceAll('/gt', '>');
});
.replaceAll('/gt', '>')
.replaceAll(/(?<!<)\/(\w+)/g, '\\$1')
.replaceAll('m\\s', 'm/s')
;
}
// localInput
watch(localInput, async (newValue) => {
formattedLatex.value = marked.parse(newValue);
formattedLatex.value = marked.parse(processFormat(newValue));
await nextTick(); // DOM
if (window.MathJax) {
MathJax.typeset(); // MathJax
@ -45,7 +52,7 @@ const checkWindowSize = () => {
onMounted(async () => {
checkWindowSize();
window.addEventListener('resize', checkWindowSize);
formattedLatex.value = marked.parse(localInput.value);
formattedLatex.value = marked.parse(processFormat(localInput.value));
await nextTick();
if (window.MathJax) {
MathJax.typeset();

View File

@ -53,8 +53,8 @@ const navItems = [
{name: '主页', link: '/'},
'divider',
{name: '博客', link: '/blog'},
{name: '项目', link: '/projects'},
// {name: '', link: '/demos'},
// {name: '', link: '/projects'},
{name: '实例', link: '/demos'},
{name: '小工具', link: '/tools'},
{name: '留言板', link: '/demos/board'},
'divider',

View File

@ -1,14 +1,21 @@
<script setup>
import { ref } from 'vue'
import Demos_box from '../components/Demos_box.vue'
import {getDomain} from "../utils/getDomain.js";
const searchQuery = ref('')
const demos = ref([
{
id: 1,
id: 'board',
name: '留言板',
author: ["麦克荣", "路易斯周"],
author: ["Mike Rong", "Louis Zhou"],
tags: ['功能']
},{
id: 'pod',
name: '在线练题',
author: ["Louis Zhou"],
tags: ['课内'],
image: 'https://' + getDomain() + '/data/file/pod.png'
},
]);

View File

@ -0,0 +1,74 @@
<script setup>
import {ref} from "vue";
const podList = ref([
{
title: 'PHY MCQ 1',
file: 'phy250226',
describe: '',
date: '2025-2-26'
},{
title: 'PHY MCQ 2',
file: 'phy250227',
describe: '',
date: '2025-2-27'
},
])
</script>
<template>
<div class="container" v-if="/^\/demos\/pod\/?$/.test($route.path)">
<h1>无限制做题大赛</h1>
<div class="pod-container">
<router-link class="pods" v-for="pod in podList" :to="`/demos/pod/quiz?id=${pod.file}`">
<div class="title">{{ pod.title }}</div>
<div class="date">{{ pod.date }}</div>
</router-link>
</div>
</div>
<router-view v-else />
</template>
<style scoped>
.container {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
flex-wrap: wrap;
gap: 20px;
}
.pod-container {
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-start;
flex-wrap: wrap;
gap: 20px;
}
.pods {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 150px;
height: 150px;
background: #2a2a2a;
max-width: 800px;
cursor: pointer;
}
.theme-light .pods {
background: #efefef;
}
.title {
font-size: 20px;
font-weight: bold;
}
.date {
font-size: 15px;
opacity: 0.5;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<general-renderer :content-input="processedText" class="question-text"></general-renderer>
<!-- <div v-html="" class="question-text"></div>-->
<!-- <div v-html="processedText" class="question-text"></div>-->
</template>
<script setup>
@ -26,7 +26,7 @@ function replaceMedia(text, medias) {
if (medias[mediaKey]) {
const [type, url, width, height] = medias[mediaKey];
if (type === 'image') {
return `<img src="${url}" width="${width}" height="${height}" alt="Question Image" />`;
return `<img src="${url}" width="100%" height="auto" alt="Question Image" />`;
}
}
return '';
@ -37,12 +37,8 @@ const processedText = computed(() => replaceMedia(props.text, props.medias));
</script>
<style scoped>
.question-text {
line-height: 1.6;
}
img {
.question-text img {
max-width: 100%;
height: auto;
margin: 10px 0;
}

View File

@ -50,11 +50,11 @@
<!-- 进度条 -->
<div v-if="!isLoading" class="progress-bar">
<div
<div v-if="progressPercentage"
class="progress"
:style="{
width: `${progressPercentage}%`,
background: `linear-gradient(to right, red, yellow, green ${progressPercentage}%, transparent ${progressPercentage}%)`
background: getColor(progressPercentage)
}"
>
{{ progressPercentage.toFixed(1) }}%
@ -64,10 +64,14 @@
</template>
<script setup>
import { reactive, ref, onMounted, computed } from 'vue';
import {reactive, ref, onMounted, computed, onUpdated} from 'vue';
import axios from 'axios';
import QuestionText from './QuestionText.vue';
import GeneralRenderer from "../../../components/GeneralRenderer.vue";
import { useRoute } from 'vue-router'
import swal from "../../../utils/sweetalert.js";
const route = useRoute()
// JSON
const data = reactive({
@ -103,21 +107,35 @@ const progressPercentage = computed(() => {
return (correctCount / Math.min(recentResults.value.length, totalQuestions.value)) * 100;
});
//
onMounted(async () => {
const initial = async () => {
const file = route.query.id || 'phy250226';
try {
const response = await axios.get('https://mva-cyber.club/data/file/phy250226.json');
const response = await axios.get(`https://mva-cyber.club/data/file/${file}.json`);
const jsonData = response.data;
Object.assign(data, jsonData);
generateNewExam(); //
} catch (error) {
console.error('加载数据失败:', error);
alert('数据加载失败,请稍后重试。');
swal.tip('error', '数据加载失败,请稍后重试。')
} finally {
isLoading.value = false;
}
});
}
//
onMounted(initial);
function getColor(progressPercentage) {
let r, g, b;
// 0% 100% 绿
r = Math.floor(200 * (1 - progressPercentage / 100)); //
g = Math.floor(200 * (progressPercentage / 100)); // 绿
b = 0; // 0
// rgb
return `rgb(${r}, ${g}, ${b})`;
}
//
function selectRandomQuestion() {
if (data.texts.length <= 1) return;
@ -143,7 +161,7 @@ function processMedia(text, medias) {
if (medias[mediaKey]) {
const [type, url, width, height] = medias[mediaKey];
if (type === 'image') {
return `<img src="${url}" width="${width}" height="${height}" alt="Question Image" />`;
return `<img src="${url}" width="100%" height="auto" alt="Question Image" />`;
}
}
return '';
@ -232,7 +250,6 @@ function findSimilarQuestions() {
const currentText = currentQuestion.value.text;
const similar = [];
for (let i = 1; i < data.itemtypes.length; i++) {
console.log(calculateStringSimilarity(data.texts[i], currentText), {1: data.texts[i]})
if (
calculateStringSimilarity(data.texts[i], currentText) > 50 && //
data.texts[i] !== currentQuestion.value.text//
@ -258,7 +275,7 @@ function selectCurrentQuestion() {
//
function submitAnswer() {
if (userAnswer.value === null) {
alert('请选择一个答案!');
swal.tip('info', '必须选一个')
return;
}
correctIndex.value = currentQuestion.value.answerkeys.findIndex((key, idx) => idx > 0 && key === 1) - 1;
@ -282,7 +299,7 @@ function nextQuestion() {
questionCount.value++;
selectCurrentQuestion();
} else {
alert('试卷已完成!将生成新试卷。');
swal.tip('success', '所有题目已完成!将生成新试卷。')
generateNewExam(); //
}
}
@ -290,9 +307,14 @@ function nextQuestion() {
<style scoped>
.quiz-container {
max-width: 800px;
margin: 20px auto;
height: calc(100vh - 110px);
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
width: calc(100% - 40px);
//max-width: 800px;
margin: auto;
height: calc(100vh - 120px);
overflow-y: auto;
padding: 20px;
font-family: Arial, sans-serif;
@ -306,16 +328,18 @@ function nextQuestion() {
.question-container {
margin-bottom: 20px;
max-width: 800px;
}
.progress-bar {
width: 100%;
width: calc(100% - 100px);
height: 20px;
background-color: #f0f0f0;
background-color: rgba(126, 126, 126, 0.23);
margin-top: 20px;
position: relative;
border-radius: 10px;
overflow: hidden;
position: fixed;
bottom: 20px;
}
.progress {
@ -334,10 +358,12 @@ function nextQuestion() {
.option {
margin: 10px 0;
}
.option label {
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
padding: 10px 0;

View File

@ -15,7 +15,8 @@ import Account_admin_userManage from "../pages/accountPages/Account_admin_userMa
import Projects from "../pages/Projects_home.vue";
import Demos_home from "../pages/Demos_home.vue";
import Board_page from "../pages/demoPages/messageBoard/Board_page.vue";
import Pod_page from "../pages/demoPages/podExercise/Quiz.vue";
import Pod_page from "../pages/demoPages/podExercise/Pod_page.vue";
import Pod_quiz from "../pages/demoPages/podExercise/Quiz.vue";
import Tools_home from "../pages/Tools_home.vue";
import GpaCalculator_page from "../pages/toolPages/gpaCalculator/gpaCalculator_page.vue";
import About from "../pages/About.vue";
@ -43,9 +44,14 @@ const routes = [
name: 'Demos',
component: Demos_home,
children: [
{path: "1", component: Board_page},
{path: "board", component: Board_page},
{path: "pod", component: Pod_page}
{
path: "pod",
component: Pod_page,
children: [
{path: "quiz", component: Pod_quiz}
]
}
]
}, {
path: '/tools',