考試系統前端項目復盤

来源:https://www.cnblogs.com/feixianxing/archive/2023/09/09/front-end-project-vue-multiple-ends-element-plus-router-pinia-sass-exam-system.html
-Advertisement-
Play Games

前段時間和朋友做了一個區域網考試系統,總共有3個端:考生端、監考端、管理端。 框架與相關的庫 先簡單說明一下我使用的框架和相關的庫: 構建工具:Vite 框架:Vue3 UI組件庫:element-plus 網路請求庫:axios 路由跳轉:vue-router 狀態管理:pinia CSS擴展語言 ...


前段時間和朋友做了一個區域網考試系統,總共有3個端:考生端、監考端、管理端。

框架與相關的庫

先簡單說明一下我使用的框架和相關的庫:

  • 構建工具:Vite

  • 框架:Vue3

  • UI組件庫:element-plus

  • 網路請求庫:axios

  • 路由跳轉:vue-router

  • 狀態管理:pinia

  • CSS擴展語言:sass

  • 其它與項目功能需求相關的庫這裡就不一一列出了

多端非根路徑部署

考慮到每一個用戶理論上只會使用其中一個端,如果將三個端綁定在一個Vue項目上,則會導致“捆綁銷售”。因此,將三個端用三個Vue項目完成,然後讓後端開發人員使用nginx配置好映射。最後我需要再寫一個根路徑的入口頁面,用於跳轉到三個端。

  • /:根路徑,頁面的內容主要是三個按鈕,分別跳轉到三個端;
  • /admin:管理端;
  • /teacher:監考端;
  • /student:考生端。

三個端的路徑經由nginx配置之後,指向三個Vue項目的index.html,然後再載入各自的main.js

與以往將前端項目部署在根路徑的情況不同,將前端項目部署在非根路徑需要做相關配置。

主要是需要修改vite.config.jsvue-router的配置文件。

以管理端為例,由於其項目部署在/admin,因此需要配置項目的base

vite.config.js

export default defineConfig({
    ...
    base: '/admin/',
    ...
})

vue-router配置文件

const router = createRouter({
    ...
    history: createWebHistory(import.meta.env.BASE_URL),
	...
})

使用history模式,需要後端在nginx上做配置。而createWebHistory函數的參數需要傳入base,即上面配置的/admin/

而餘下的routes配置,就根據以往的編寫方式就可以。

例如,管理端的登錄頁面,在配置了base: '/admin/'的情況下,在配置登錄頁面的路由的時候,只需要寫/login

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      redirect: {name: 'login'}
    },
    {
      path: '/login',
      name: 'login',
      component: ()=>import('../views/LoginView.vue')
    }
  ]
})

實際上,從整個考試系統的角度看來,它匹配到的路徑應該是:/admin/login

這是因為/admin/會先被nginx的配置捕獲到,然後指向管理端這個Vue項目,返回管理端的index.htmlmain.js給用戶(該系統的管理員),然後路徑後續的/login會因為main.js中引入的路由配置文件,匹配上LoginView.vue,即登錄頁面。

盒子的最大寬度

頁面中的文字依據來源可以分為兩種:

  • 靜態文本:即本身固化在代碼中的文本;
  • 動態文本:由用戶輸入並顯示在頁面中的文本。

靜態文本,例如側邊導航欄的按鈕的文本,文本的字數是固定的。因此,側邊導航欄的寬度可以寫成固定的。

而動態文本,是由用戶輸入的,並且大多數時候沒有嚴格的字數限制。

我一開始犯了一個錯,就是只使用flex:3flex:7簡單地將頁面分為左右佈局,然後左邊是一個列表,每一項都是一行用戶輸入的數據,即不做換行處理。

當用戶輸入了長文本之後,左邊的列表會被子元素撐大,從而導致頁面的左右佈局比例被破壞。

因此這裡由用戶輸入的數據構成的列表,應該使用css設置一個max-width,限制其最大寬度。

對象的深拷貝

使用JSON簡單地實現了對象的深拷貝

// 存儲對象的數組
list: []

// 添加新對象
list.push(JSON.stringify(newItem))

// 獲取對象
function getItem(params){
    ...do some search
    return Json.parse(target)
}

pinia 實現試題管理模塊

這裡的試題是指添加試題時的階段,即需要提供讀與寫操作。

image-20230909155800269

  • state:
state: ()=>({
    // 題目列表,存儲題目對象,使用JSON簡單實現了對象的深拷貝
    qList: [],
    // 當前編輯的題目的指針
    currIdx: -1
}),
  • getter:(返回常用數據)
getters: {
    // 題目數量
    count(){
        return this.qList.length
    },
    // 當前編輯的題目是否存在“上一題”
    hasPrev(){
        return this.currIdx>0
    },
    hasNext(){
        return this.currIdx<this.count
    }
},
  • actions:向外提供操作方法
actions: {
    // 初始化
    init(){
        this.qList.length = 0
        this.currIdx = 0
    },
    // 寫操作
    saveQuestion(q){
        this.qList[this.currIdx] = JSON.stringify(q)
    },
    // 前一道題
    goPrevQuestion(){
        if(this.hasPrev){
            return JSON.parse(this.qList[--this.currIdx] || "")
        }
    },
    // 後一道題
    goNextQuestion(){
        const q = this.qList[++this.currIdx]
        return q===undefined?undefined:JSON.parse(q)
    },
    // 上傳題目列表到後端
    async uploadQuestionList(){
        for await (let q of this.qList){
            q = JSON.parse(q)
            if(this.checkCompleteness(q)){
                await uploadQuestion(q)
            }
        }
    },
    checkCompleteness(q){
        // 用於檢查一道題目是否設置完整
    },
    isEmpty(q){
        // 用於檢查一道題目是否沒有填寫任何內容
    }
}

上述代碼中的checkCompletenessisEmpty函數的實現涉及到試題對象的設計,較為複雜,這裡不給出代碼。

上傳題目列表到後端的操作中,為了實現按順序上傳,需要使用for await(... of ...),而不能使用foreach await,後者無法保證上傳順序。

vite打包配置

vite.config.js中,通過如下配置,可以去除代碼中的console.log,避免將數據帶到生產環境,同時將js文件和assets文件打包到不同文件夾。

export default defineConfig({
    ...
    build: {
        terserOptions: {
            compress: {
                // 生產環境時移除console.log調試代碼
                drop_console:true,
                drop_debugger: true
            }
        },
        rollupOptions: {
            output: {
                //對靜態文件進行打包處理(文件分類)
                chunkFileNames: 'assets/js/[name]-[hash].js',
                entryFileNames: 'assets/js/[name]-[hash].js',
                assetFileNames: 'assets/[ext]/[name]-[hash].[ext]'
            }
        }
      }
    ...
})

文件下載功能

項目中有需求是:用戶點擊按鈕之後下載文件。使用js實現:

// 下載文件
const downloadFile = () => {
    const tempDom = document.createElement('a')
    tempDom.href = "/file/demo.txt"
    tempDom.download = 'fileName.txt'
    tempDom.click()
}

這裡創建了一個DOM對象,路徑href是伺服器上的文件路徑,download屬性的字元串是用戶下載到的文件名。

pdf預覽功能

我寫了一個pdf-previewer.html文件,並放在根路徑下,然後每次不同端的項目中,需要訪問pdf文件的時候,就調用:

window.open('/pdf-preview.html?url='+path)

path是後端傳過來的文件路徑。

pdf-previewer.html中,

  • 使用iframe標簽;
  • 封裝了getQueryVariable函數,用來獲取訪問地址攜帶的參數(即文件的地址);
  • 為瞭解決緩存問題(利用iframe打開pdf後,當再次利用iframe打開另一個pdf時會顯示第一份pdf,原因是瀏覽器對url的緩存處理),在url上添加時間戳。

參考自:PDF預覽完整解決方案及各種相容(VUE版) - 掘金 (juejin.cn)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PDF預覽視窗</title>
    <link rel="icon" href="/icon.png">
    <style>
        *{
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body{
            width: 100%;
            height: 100vh;
            overflow: hidden;
        }
    </style>
</head>
<body>
    <iframe id="viewer" src="" style="width: 100%;height: 100vh;" frameborder="0"></iframe>
    <script>
        window.addEventListener ('load', () => {
            function getQueryVariable(variable) {
                let query = window.location.search.substring(1);
                let vars = query.split("&");
                for (let i = 0; i < vars.length; i++) {
                    let pair = vars[i].split("=");
                    if (pair[0] === variable) { return pair[1]; }
                }
                return (false);
            }
            
            let path = getQueryVariable('url')
            const fresh = new Date().getTime()

            path += '?fresh=' + fresh

            document.getElementById('viewer').setAttribute('src', path)
        });
    </script>
</body>
</html>

常用Message封裝

el-message組件對於反饋功能很常用,封裝成函數:

import { ElMessage } from 'element-plus'

const showError = (msg)=>{
    return ElMessage({
        type: 'error',
        message: msg
    })
}

const showSuccess = (msg)=>{
    return ElMessage({
        type: 'success',
        message: msg
    })
}

const showInfo = (msg)=>{
    return ElMessage({
        type: 'info',
        message: msg
    })
}

export { showError, showSuccess, showInfo }

使用CSS常量

使用CSS常量記錄常用的尺寸、顏色,可以改一處,而變全局。

以下常量是我的項目中的一部分顏色,僅供參考,不具有普適性。

:root {
  --main-color: #31364d;
  --header-height: 60px;
  --border-color: #DCDFE6;
  --border-color-light: #E4E7ED;
  --border-color-darker: #CDD0D6;
  --page-background: #F2F3F5;
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 脫殼修複是指在進行加殼保護後的二進位程式脫殼操作後,由於加殼操作的不同,有些程式的導入表可能會受到影響,導致脫殼後程式無法正常運行。因此,需要進行修複操作,將脫殼前的導入表覆蓋到脫殼後的程式中,以使程式恢復正常運行。一般情況下,導入表被分為IAT(Import Address Table,導入地址表... ...
  • [toc] # 一、爬蟲對象-豆瓣電影短評 您好!我是[@馬哥python說](https://www.cnblogs.com/mashukui/),一名10年程式猿。 今天分享一期爬蟲案例,爬取的目標是:豆瓣上任意一部電影的短評(註意:是短評,不是影評!),以《熱烈》這部電影為例: ![爬取目標] ...
  • 在我們的SqlSugar的開發框架中,整合了Winform端、Vue3+ElementPlus的前端、以及基於UniApp+Vue+ThorUI的移動前端幾個前端處理,基本上覆蓋了我們日常的應用模式了,本篇隨筆進一步介紹前端應用的領域,研究集成WPF的應用端,循序漸進介紹基於CommunityToo... ...
  • 通過對鍵盤輸入的處理過程和設備控制器的作用的瞭解,我們可以更好地理解操作系統如何與鍵盤設備進行交互,並正確處理鍵盤輸入。同時,瞭解設備控制器的作用可以幫助我們更好地理解操作系統與外設硬體之間的通信和控制過程。 ...
  • 在Windows10/11Microsoft Store上安裝應用時,提示錯誤0x80070005,通過修改C:\Program Files\WindowsApps文件夾的許可權解決問題 ...
  • 1、前期工作 下載CentOS7鏡像:CentOS-7-x86_64-DVD-2009.iso 安裝虛擬機工具:VirtualBox 2、新建虛擬機 2.1、設置新建虛擬機的名稱、安裝路徑、類型及版本 註意:CentOS Linux 的發行版本是通過編譯 Red Hat, Inc 公開提供的 Red ...
  • 防火牆配置 # 啟動防火牆服務 systemctl start firewalld # 關閉防火牆服務 systemctl stop firewalld # 查看防火牆服務狀態 systemctl status firewalld # 開機禁用防火牆服務 systemctl disable fire ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 一. keep-alive 的作用 二. keep-alive 的原理 三. keep-alive 的應用 四. keep-alive 的刷新 五. keep-alive 頁面緩存思路 一. keep-alive 的作用 首先引用官 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...