[sduoj] Topic set design

2021SC@SDUSC

architecture design

Following the example of PTA , SDUOJ provides users with the function of answering questions through the way of question sets . Teachers can publish a topic set at any time, and modify the basic information of the topic set, such as name, introduction, open status, etc. Students can also enter the set of questions that are open to themselves, choose a topic to write code to answer, and can view the result of the problem in real time.
The entire topic set page is divided into the following modules:

  • Topic set details page : As the first page module of the topic set, the basic information of the entire topic set is presented here. Teachers can update their published topic sets on this page .
  • Question list : This module displays all the questions in the question set, and users can click the link to jump to the corresponding answer area.
  • Score ranking : This module is responsible for calculating the scores of all users' current topic sets and sorting them. An XLSX file of the current ranking can be exported .
  • Topic Management : This module is only open to topic set creators . The teacher who created the topic set can see all valid topics in the current system in this module, and the teacher can add/remove questions from the topic set by himself.
  • Open Information Management : This module is only open to topic set creators . The teacher who created the topic set can see all user groups in this module , and give/delete answering rights to certain user groups.
  • Question details page : This module is used to display the details of a specific question and provide an answer area.

interface design

The module interface mainly consists of two parts:

  • One, is the navigation bar located on the left side of the page. Through the navigation bar, users can switch between different modules.
  • The second is the content container that occupies most of the page area. Here, through a <router-view />tag , different modules are loaded into the content container and displayed to the user according to the corresponding nested routing .

navigation bar design

The navigation bar module is used <el-aside>as a sidebar container, and the whole is composed of two parts. The upper part is a menu navigation button <el-menu />composed , through which you can navigate to the five modules of topic set details page , topic list , grade ranking , topic management , and open information management .
The lower part is the question button designed in imitation of the PTA question set. Users can quickly direct to the corresponding question by clicking the button corresponding to the question number. At the same time, the style of the button will also reflect the user's answering status of the question.

<template>
	<div>
		<el-container>
			<el-aside style="width: 15%; height: 100vh; position: fixed; overflow-y: overlay;">
				<el-menu>
					<!-- 菜单导航模块
						<el-menu-item />
					 -->
					<el-divider />
					<!-- 分割线 -->
					<div style="display: flex; padding: 0 1em; flex-wrap: wrap;">
						<button v-for="(item, index) in problemSetInfo.problems" :key="index + 1" :class="{
     
     'problemBtn': true, 'passed': item.selfComplication===2, 'failed': item.selfComplication===3}" :style="menuIndex===(index+1).toString()?{}:{borderColor: 'rgba(0,0,0,0)'}" @click="getToProblem(index + 1, item)">
							<div class="passStatusCover">
								{
   
   {
									(function(passedStatus) {
										switch(passedStatus) {
											case 1:
												return index + 1;
											case 2:
												return '✔';
											case 3:
												return '✘';
										}
									})(item.selfComplication)
								}}
							</div>
							<div class="problemIndex">{
   
   {index + 1}}</div>
						</button>	
					</div>
				</el-menu>
			</el-aside>
		</el-container>
	</div>
</template>

<script>
export default {
      
      
	name: "problemSet",
	data() {
      
      
		return {
      
      
			menuIndex: undefined,
			// ...
		}
	},
	methods: {
      
      
		// ... ,
		getToProblem(index, problem) {
      
       // 跳转至对应题目的答题模块
			if (this.menuIndex != index) {
      
      
				this.menuIndex = index.toString();
				this.$router.push({
      
      
					path: `/problem-set/${ 
        this.problemSetInfo.problemSetId}/problems/${ 
        problem.id}`,
				});
			}
		},
	},
	computed: {
      
      
		problemSetInfo() {
      
      
			return this.$store.state.problemSetInfo;
		},
	},
}
</script>

<style scoped>
.problemBtn {
      
      
	background: hsl(0, 0%, 97%);
	border: none;
	cursor: pointer;
	font-size: 1rem;
	width: 1.75rem;
	height: 1.75rem;
	margin-inline: 0.2rem;
	margin-block: 0.15rem;
	border-radius: 0.15rem;
	color: #2794f8;
	border: 0.1em solid #2794f8;
	transition: 0.35s;
	outline: none;
	line-height: 1;
}

.problemBtn:hover {
      
      
	background: hsl(0, 0%, 90%);
}

.problemBtn:hover .passStatusCover {
      
      
	display: none;
}

.problemIndex {
      
      
	display: none;
}

.problemBtn:hover .problemIndex {
      
      
	display: block;
}

.passed {
      
      
	color: limegreen;
	border: 0.1em solid limegreen;
}

.failed {
      
      
	color: red;
	border: 0.1em solid red;
}
</style>

The above is the code display of ProblemSet.vue focused on the answer button .
It can be seen that we use v-forsyntactic sugar $store.state.problemSetInfo.problemsto extract the information of all the topics contained in the topic set, and generate the corresponding button . In each <button />tag , there are two <div />elements. One class is called passStatusCover , which is used to display the answer status of the question, and is covered above the button; the other class is called problemIndex , which internally displays the corresponding order of the question.
When we move the mouse over passStatusCover , displaychange to noneand at the same time change problemIndexdisplay from to , so as to realize the alternate display of . Inside passStatusCover , write an anonymous function in Mustache , pass in the answer status of the question to it, and execute it immediately. According to different answer statuses, different characters are returned and displayed in this DOM element.noneblock<div />

(function(passedStatus) {
    
    
	switch(passedStatus) {
    
    
		case 1: // 该题未回答过
			return index + 1;
		case 2: // 该题回答正确过
			return '✔';
		case 3: // 该题回答过,但没有正确记录
			return '✘';
	}
})(item.selfComplication)
/**
 * 这是一种 js 的匿名函数写法
 * 通过 '()' 将这个函数整体作为一个对象
 * 返回了一个匿名的 Function 对象
 * 因此,这段代码即是当场定义一个 Function 对象并当场调用它
 */

Data management (VueX actual combat)

In the architecture analysis of this module, we mentioned that this module is composed of multiple sub-modules. At the same time, as a topic set, this module has a complete data object. Therefore, some data in this data object may be shared by multiple submodules.
In order to allow all sub-modules to share data, and to reduce high-risk problems in software development such as difficulty in maintenance due to excessive coupling , we use the method of introducing VueX instances for data management.
As the parent module of the topic set, this module should undertake the function of managing data objects. Therefore, we initialize the data object in createdthe life .

methods: {
    
    
	// ... ,
	async getProblemSet() {
    
    
		let res = await this.$ajax.post(
			"/problemset/getProblemset",
			{
    
    
				id: this.problemSetInfo.problemSetId,
			},
			{
    
    
				headers: {
    
    
					Authorization: `Bearer ${
      
      this.$store.state.token}`,
				},
			}
		);
		if (res.data.code === 0) {
    
    
			const data = res.data.data;
			const author = data.author.toString();
			const problems = data.problems;
			let problemIds = [];
			problems.forEach((element) => {
    
    
				problemIds.push(element.id);
			});
			const jwt = require("jsonwebtoken");
			const TOKEN = jwt.decode(this.$store.state.token);
			const isMyProblemSet = author == TOKEN.USER_ID; // 判断当前用户是否是该题目集的创建者
			this.$store
				.dispatch("setProblemSetInfo", {
    
    
					name: data.name,
					announcement: data.announcement,
					author,
					open: data.open,
					endTime: data.endTime,			
					beginTime: data.beginTime,
					problems: data.problems,
					status: data.status,
					problemIds,
					isMyProblemSet,
					canUseOnlineJudge: data.canUseOnlineJudge === 1,
					canViewTestPoint: data.canViewTestPoint === 1,
				});
		}
	}
},
created() {
    
    
	this.$store
		.dispatch("setProblemSetInfo", {
    
    
			// 从路由中提取该题目集对应的 Id ,将其存放至 VueX 实例对象中
			problemSetId: Number(this.$route.params.problemSetId),
		})
		.then(() => {
    
    
			/**
			 * 由于 dispatch 是异步操作
			 * 我们需要在 then 回调中通过初始化的题目集id,获取题目集的其他基本信息
			 */
			this.getProblemSet();
		});
}

Guess you like

Origin blog.csdn.net/qq_53126706/article/details/122102585