├── .gitignore ├── README.md ├── app ├── .gitignore ├── .vscode │ └── extensions.json ├── README.md ├── env.d.ts ├── index.html ├── package.json ├── public │ └── favicon.ico ├── src │ ├── App.vue │ ├── assets │ │ ├── base.css │ │ ├── logo.svg │ │ └── main.css │ ├── components │ │ ├── HelloWorld.vue │ │ ├── TheWelcome.vue │ │ ├── WelcomeItem.vue │ │ └── icons │ │ │ ├── IconCommunity.vue │ │ │ ├── IconDocumentation.vue │ │ │ ├── IconEcosystem.vue │ │ │ ├── IconSupport.vue │ │ │ └── IconTooling.vue │ ├── main.ts │ ├── router │ │ └── index.ts │ ├── stores │ │ └── counter.ts │ └── views │ │ ├── AboutView.vue │ │ └── HomeView.vue ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── backmanger ├── .gitignore ├── .vscode │ └── extensions.json ├── README.md ├── dist │ ├── assets │ │ ├── bg-Ccj7DF2B.jpg │ │ ├── create-B7odmZ_K.js │ │ ├── create-CABnjeqU.css │ │ ├── hooks-DGTDKDMV.js │ │ ├── index-BENb7eg2.js │ │ ├── index-BHQO7ODh.js │ │ ├── index-BphpIRfH.js │ │ ├── index-C1hh7dcC.js │ │ ├── index-CACTaSOq.css │ │ ├── index-COYrsJrE.css │ │ ├── index-CQlCNhaL.css │ │ ├── index-CgRR7fKr.js │ │ ├── index-CoY8a9Sg.js │ │ ├── index-Cw5ahS-Z.js │ │ ├── index-D7Efwe2T.js │ │ ├── index-DGRHULib.css │ │ ├── index-DOsF3NFu.js │ │ ├── index-Dj0m8xO2.js │ │ ├── index-TakMx3zO.css │ │ └── knowledgeHooks-CznZjvcB.js │ ├── favicon.ico │ └── index.html ├── env.d.ts ├── index.html ├── package.json ├── public │ └── favicon.ico ├── src │ ├── App.vue │ ├── apis │ │ ├── course │ │ │ ├── code.ts │ │ │ └── index.ts │ │ ├── grade │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── knowledge │ │ │ └── index.ts │ │ ├── login │ │ │ └── index.ts │ │ ├── route │ │ │ └── index.ts │ │ ├── subject │ │ │ └── index.ts │ │ └── user │ │ │ └── index.ts │ ├── assets │ │ ├── background │ │ │ └── bg.jpg │ │ └── css │ │ │ └── reset.css │ ├── components │ │ └── Editor.vue │ ├── config │ │ └── index.ts │ ├── layout │ │ ├── Content │ │ │ └── index.vue │ │ ├── Header │ │ │ └── index.vue │ │ ├── Menu │ │ │ └── index.vue │ │ └── index.vue │ ├── main.ts │ ├── router │ │ ├── course │ │ │ └── index.ts │ │ ├── home │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── knowledge │ │ │ └── index.ts │ │ ├── login │ │ │ └── index.ts │ │ ├── question │ │ │ └── index.ts │ │ ├── subject │ │ │ └── index.ts │ │ └── user │ │ │ └── index.ts │ ├── stores │ │ └── menu │ │ │ └── index.ts │ ├── utils │ │ └── time.ts │ └── views │ │ ├── Index.vue │ │ ├── course │ │ ├── code │ │ │ ├── create.vue │ │ │ ├── hooks.ts │ │ │ └── index.vue │ │ ├── create.vue │ │ ├── index.vue │ │ └── type.ts │ │ ├── home │ │ └── index.vue │ │ ├── knowledge │ │ ├── index.vue │ │ ├── knowledgeHooks.ts │ │ └── treeHooks.ts │ │ ├── login │ │ └── index.vue │ │ ├── question │ │ ├── components │ │ │ ├── FillInTheBlanks.vue │ │ │ ├── MultipleChoice.vue │ │ │ ├── MultipleChoiceQuestions.vue │ │ │ ├── ReadingQuestion.vue │ │ │ ├── ShortAnswer.vue │ │ │ ├── TrueOfFalse.vue │ │ │ └── index.vue │ │ ├── create.vue │ │ ├── hooks │ │ │ └── index.ts │ │ └── index.vue │ │ ├── subject │ │ ├── create.vue │ │ ├── hooks │ │ │ ├── gradeHooks.ts │ │ │ └── subjectHooks.ts │ │ └── index.vue │ │ └── user │ │ ├── create.vue │ │ ├── hooks │ │ └── index.ts │ │ └── index.vue ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── config ├── course │ ├── index.js │ └── index.ts ├── database │ ├── index.js │ └── index.ts ├── grade │ ├── index.js │ └── index.ts ├── index.js ├── index.ts ├── package.json ├── port │ ├── index.js │ └── index.ts ├── secret │ ├── index.js │ └── index.ts ├── subject │ ├── index.js │ └── index.ts ├── tsconfig.json └── user │ ├── index.js │ └── index.ts ├── fs ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── nest-cli.json ├── package.json ├── src │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── course │ │ ├── course.controller.ts │ │ ├── course.module.ts │ │ ├── course.service.ts │ │ ├── dto │ │ │ ├── create-course.dto.ts │ │ │ └── update-course.dto.ts │ │ └── entities │ │ │ └── course.entity.ts │ └── main.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json └── tsconfig.json ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── server ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── nest-cli.json ├── package-lock.json ├── package.json ├── src ├── app.controller.ts ├── app.module.ts ├── app.service.ts ├── auth │ ├── auth.controller.ts │ ├── auth.module.ts │ ├── auth.service.ts │ ├── guards │ │ ├── jwt-auth.guard.ts │ │ └── local-auth.guard.ts │ └── strategies │ │ ├── jwt.strategy.ts │ │ └── local.strategy.ts ├── course-code │ ├── course-code.controller.ts │ ├── course-code.module.ts │ ├── course-code.service.ts │ ├── dto │ │ ├── create-course-code.dto.ts │ │ └── update-course-code.dto.ts │ └── entities │ │ └── course-code.entity.ts ├── course │ ├── course.controller.ts │ ├── course.module.ts │ ├── course.service.ts │ ├── dto │ │ ├── create-course.dto.ts │ │ └── update-course.dto.ts │ └── entities │ │ └── course.entity.ts ├── db │ └── db.module.ts ├── enum │ └── authority.ts ├── env.d.ts ├── grade │ ├── dto │ │ ├── create-grade.dto.ts │ │ └── update-grade.dto.ts │ ├── entities │ │ └── grade.entity.ts │ ├── grade.controller.ts │ ├── grade.module.ts │ └── grade.service.ts ├── interceptor │ └── interceptor.interceptor.ts ├── knowledge │ ├── dto │ │ ├── create-knowledge.dto.ts │ │ └── update-knowledge.dto.ts │ ├── entities │ │ └── knowledge.entity.ts │ ├── knowledge.controller.ts │ ├── knowledge.module.ts │ └── knowledge.service.ts ├── main.ts ├── route │ ├── entities │ │ ├── route.children.ts │ │ └── route.entity.ts │ ├── route.controller.ts │ ├── route.module.ts │ ├── route.service.ts │ └── router │ │ └── index.ts ├── subject │ ├── dto │ │ ├── create-subject.dto.ts │ │ └── update-subject.dto.ts │ ├── entities │ │ └── subject.entity.ts │ ├── subject.controller.ts │ ├── subject.module.ts │ └── subject.service.ts └── user │ ├── dto │ ├── create-user.dto.ts │ └── update-user.dto.ts │ ├── entities │ └── user.entity.ts │ ├── user.controller.ts │ ├── user.module.ts │ └── user.service.ts ├── test ├── app.e2e-spec.ts ├── index.http └── jest-e2e.json ├── tsconfig.build.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | 38 | # dotenv environment variable files 39 | .env 40 | .env.development.local 41 | .env.test.local 42 | .env.production.local 43 | .env.local 44 | 45 | # temp directory 46 | .temp 47 | .tmp 48 | 49 | # Runtime data 50 | pids 51 | *.pid 52 | *.seed 53 | *.pid.lock 54 | 55 | # Diagnostic reports (https://nodejs.org/api/report.html) 56 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 题库管理系统 2 | 3 | 整体架构monorepo架构 使用pnpm构建 4 | 5 | ### 目录介绍 6 | 7 | 1. app h5页面前端 8 | 2. backmanger 后台管理 9 | 3. config 配置文件 10 | 4. fs 文件系统OSS 分布式 11 | 5. server 服务端 分布式 12 | 13 | 14 | ### 技术栈 15 | 16 | 前端 17 | 18 | 1. vue3 + vite + ts + pinia + vue-router + element-plus + vueuse 19 | 20 | 后端 21 | 22 | 1. Nestjs + ts + webpack + MQ + mongodb -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | *.tsbuildinfo 31 | -------------------------------------------------------------------------------- /app/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # app 2 | 3 | This template should help get you started developing with Vue 3 in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). 8 | 9 | ## Type Support for `.vue` Imports in TS 10 | 11 | TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types. 12 | 13 | If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: 14 | 15 | 1. Disable the built-in TypeScript Extension 16 | 1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette 17 | 2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` 18 | 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. 19 | 20 | ## Customize configuration 21 | 22 | See [Vite Configuration Reference](https://vitejs.dev/config/). 23 | 24 | ## Project Setup 25 | 26 | ```sh 27 | npm install 28 | ``` 29 | 30 | ### Compile and Hot-Reload for Development 31 | 32 | ```sh 33 | npm run dev 34 | ``` 35 | 36 | ### Type-Check, Compile and Minify for Production 37 | 38 | ```sh 39 | npm run build 40 | ``` 41 | -------------------------------------------------------------------------------- /app/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@questionbank/app", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "run-p type-check \"build-only {@}\" --", 9 | "preview": "vite preview", 10 | "build-only": "vite build", 11 | "type-check": "vue-tsc --build --force" 12 | }, 13 | "dependencies": { 14 | "@questionbank/config": "workspace:^", 15 | "pinia": "^2.1.7", 16 | "vue": "^3.3.11", 17 | "vue-router": "^4.2.5" 18 | }, 19 | "devDependencies": { 20 | "@tsconfig/node18": "^18.2.2", 21 | "@types/node": "^18.19.3", 22 | "@vitejs/plugin-vue": "^4.5.2", 23 | "@vue/tsconfig": "^0.5.0", 24 | "npm-run-all2": "^6.1.1", 25 | "typescript": "~5.3.0", 26 | "vite": "^5.0.10", 27 | "vue-tsc": "^1.8.25" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/message163/QuestionBank/6cb3049dee4041908653c75575fec9468f60e683/app/public/favicon.ico -------------------------------------------------------------------------------- /app/src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 22 | 23 | 86 | -------------------------------------------------------------------------------- /app/src/assets/base.css: -------------------------------------------------------------------------------- 1 | /* color palette from */ 2 | :root { 3 | --vt-c-white: #ffffff; 4 | --vt-c-white-soft: #f8f8f8; 5 | --vt-c-white-mute: #f2f2f2; 6 | 7 | --vt-c-black: #181818; 8 | --vt-c-black-soft: #222222; 9 | --vt-c-black-mute: #282828; 10 | 11 | --vt-c-indigo: #2c3e50; 12 | 13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); 14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); 15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); 16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); 17 | 18 | --vt-c-text-light-1: var(--vt-c-indigo); 19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66); 20 | --vt-c-text-dark-1: var(--vt-c-white); 21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); 22 | } 23 | 24 | /* semantic color variables for this project */ 25 | :root { 26 | --color-background: var(--vt-c-white); 27 | --color-background-soft: var(--vt-c-white-soft); 28 | --color-background-mute: var(--vt-c-white-mute); 29 | 30 | --color-border: var(--vt-c-divider-light-2); 31 | --color-border-hover: var(--vt-c-divider-light-1); 32 | 33 | --color-heading: var(--vt-c-text-light-1); 34 | --color-text: var(--vt-c-text-light-1); 35 | 36 | --section-gap: 160px; 37 | } 38 | 39 | @media (prefers-color-scheme: dark) { 40 | :root { 41 | --color-background: var(--vt-c-black); 42 | --color-background-soft: var(--vt-c-black-soft); 43 | --color-background-mute: var(--vt-c-black-mute); 44 | 45 | --color-border: var(--vt-c-divider-dark-2); 46 | --color-border-hover: var(--vt-c-divider-dark-1); 47 | 48 | --color-heading: var(--vt-c-text-dark-1); 49 | --color-text: var(--vt-c-text-dark-2); 50 | } 51 | } 52 | 53 | *, 54 | *::before, 55 | *::after { 56 | box-sizing: border-box; 57 | margin: 0; 58 | font-weight: normal; 59 | } 60 | 61 | body { 62 | min-height: 100vh; 63 | color: var(--color-text); 64 | background: var(--color-background); 65 | transition: 66 | color 0.5s, 67 | background-color 0.5s; 68 | line-height: 1.6; 69 | font-family: 70 | Inter, 71 | -apple-system, 72 | BlinkMacSystemFont, 73 | 'Segoe UI', 74 | Roboto, 75 | Oxygen, 76 | Ubuntu, 77 | Cantarell, 78 | 'Fira Sans', 79 | 'Droid Sans', 80 | 'Helvetica Neue', 81 | sans-serif; 82 | font-size: 15px; 83 | text-rendering: optimizeLegibility; 84 | -webkit-font-smoothing: antialiased; 85 | -moz-osx-font-smoothing: grayscale; 86 | } 87 | -------------------------------------------------------------------------------- /app/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/assets/main.css: -------------------------------------------------------------------------------- 1 | @import './base.css'; 2 | 3 | #app { 4 | max-width: 1280px; 5 | margin: 0 auto; 6 | padding: 2rem; 7 | font-weight: normal; 8 | } 9 | 10 | a, 11 | .green { 12 | text-decoration: none; 13 | color: hsla(160, 100%, 37%, 1); 14 | transition: 0.4s; 15 | padding: 3px; 16 | } 17 | 18 | @media (hover: hover) { 19 | a:hover { 20 | background-color: hsla(160, 100%, 37%, 0.2); 21 | } 22 | } 23 | 24 | @media (min-width: 1024px) { 25 | body { 26 | display: flex; 27 | place-items: center; 28 | } 29 | 30 | #app { 31 | display: grid; 32 | grid-template-columns: 1fr 1fr; 33 | padding: 0 2rem; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | 42 | -------------------------------------------------------------------------------- /app/src/components/TheWelcome.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 89 | -------------------------------------------------------------------------------- /app/src/components/WelcomeItem.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 88 | -------------------------------------------------------------------------------- /app/src/components/icons/IconCommunity.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /app/src/components/icons/IconDocumentation.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /app/src/components/icons/IconEcosystem.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /app/src/components/icons/IconSupport.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /app/src/components/icons/IconTooling.vue: -------------------------------------------------------------------------------- 1 | 2 | 20 | -------------------------------------------------------------------------------- /app/src/main.ts: -------------------------------------------------------------------------------- 1 | import './assets/main.css' 2 | 3 | import { createApp } from 'vue' 4 | import { createPinia } from 'pinia' 5 | 6 | import App from './App.vue' 7 | import router from './router' 8 | 9 | const app = createApp(App) 10 | 11 | app.use(createPinia()) 12 | app.use(router) 13 | 14 | app.mount('#app') 15 | -------------------------------------------------------------------------------- /app/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | import HomeView from '../views/HomeView.vue' 3 | 4 | const router = createRouter({ 5 | history: createWebHistory(import.meta.env.BASE_URL), 6 | routes: [ 7 | { 8 | path: '/', 9 | name: 'home', 10 | component: HomeView 11 | }, 12 | { 13 | path: '/about', 14 | name: 'about', 15 | // route level code-splitting 16 | // this generates a separate chunk (About.[hash].js) for this route 17 | // which is lazy-loaded when the route is visited. 18 | component: () => import('../views/AboutView.vue') 19 | } 20 | ] 21 | }) 22 | 23 | export default router 24 | -------------------------------------------------------------------------------- /app/src/stores/counter.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue' 2 | import { defineStore } from 'pinia' 3 | 4 | export const useCounterStore = defineStore('counter', () => { 5 | const count = ref(0) 6 | const doubleCount = computed(() => count.value * 2) 7 | function increment() { 8 | count.value++ 9 | } 10 | 11 | return { count, doubleCount, increment } 12 | }) 13 | -------------------------------------------------------------------------------- /app/src/views/AboutView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /app/src/views/HomeView.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "exclude": ["src/**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "noEmit": true, 8 | "baseUrl": ".", 9 | "paths": { 10 | "@/*": ["./src/*"] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /app/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18/tsconfig.json", 3 | "include": [ 4 | "vite.config.*", 5 | "vitest.config.*", 6 | "cypress.config.*", 7 | "nightwatch.conf.*", 8 | "playwright.config.*" 9 | ], 10 | "compilerOptions": { 11 | "composite": true, 12 | "noEmit": true, 13 | "module": "ESNext", 14 | "moduleResolution": "Bundler", 15 | "types": ["node"] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | import { defineConfig } from 'vite' 3 | import vue from '@vitejs/plugin-vue' 4 | import * as port from '@questionbank/config' 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [ 8 | vue(), 9 | ], 10 | server:{ 11 | port: port.appPort 12 | }, 13 | resolve: { 14 | alias: { 15 | '@': fileURLToPath(new URL('./src', import.meta.url)) 16 | } 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /backmanger/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | *.tsbuildinfo 31 | -------------------------------------------------------------------------------- /backmanger/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /backmanger/README.md: -------------------------------------------------------------------------------- 1 | # backmanger 2 | 3 | This template should help get you started developing with Vue 3 in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). 8 | 9 | ## Type Support for `.vue` Imports in TS 10 | 11 | TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types. 12 | 13 | If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: 14 | 15 | 1. Disable the built-in TypeScript Extension 16 | 1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette 17 | 2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` 18 | 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. 19 | 20 | ## Customize configuration 21 | 22 | See [Vite Configuration Reference](https://vitejs.dev/config/). 23 | 24 | ## Project Setup 25 | 26 | ```sh 27 | npm install 28 | ``` 29 | 30 | ### Compile and Hot-Reload for Development 31 | 32 | ```sh 33 | npm run dev 34 | ``` 35 | 36 | ### Type-Check, Compile and Minify for Production 37 | 38 | ```sh 39 | npm run build 40 | ``` 41 | -------------------------------------------------------------------------------- /backmanger/dist/assets/bg-Ccj7DF2B.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/message163/QuestionBank/6cb3049dee4041908653c75575fec9468f60e683/backmanger/dist/assets/bg-Ccj7DF2B.jpg -------------------------------------------------------------------------------- /backmanger/dist/assets/hooks-DGTDKDMV.js: -------------------------------------------------------------------------------- 1 | import{a,r}from"./index-BENb7eg2.js";const u=async e=>(await a.post("/api/course-code/create",e)).data,c=async e=>(await a.get("/api/course-code/list",{params:e})).data,n=async e=>(await a.patch(`/api/course-code/update/${e._id}`,e)).data,i=async e=>(await a.delete(`/api/course-code/delete/${e._id}`)).data,p=()=>{let e=r([]);const t=async()=>{const s=await c({});s.data.forEach(o=>{o.showpopover=!1}),e.value=s.data};return t(),{getList:t,data:e}};export{p as a,u as c,i as d,n as u}; 2 | -------------------------------------------------------------------------------- /backmanger/dist/assets/index-BHQO7ODh.js: -------------------------------------------------------------------------------- 1 | import{a as B,u as j}from"./knowledgeHooks-CznZjvcB.js";import{d as I,k as s,w as n,c as t,o as a,f as c,h as S,t as D,u as e,e as p,B as i,F as m,I as F,_ as L}from"./index-BENb7eg2.js";import"./index-Cw5ahS-Z.js";const N=I({__name:"index",setup(T){const{list:f,getLabel:r}=B(),{subjectId:l,dataSource:b,renderContent:k,submit:x,changeSubject:h,isData:d}=j();return($,u)=>{const C=t("el-option"),g=t("el-option-group"),v=t("el-select"),w=t("el-tree"),y=t("el-button"),V=t("el-card");return a(),s(V,{header:"绑定知识点",shadow:"never"},{footer:n(()=>[c(y,{onClick:e(x),type:e(d)?"primary":"success"},{default:n(()=>[S(D(e(d)?"修改":"保存"),1)]),_:1},8,["onClick","type"])]),default:n(()=>[c(v,{onChange:e(h),clearable:"",placeholder:"请选择科目绑定知识点",modelValue:e(l),"onUpdate:modelValue":u[0]||(u[0]=o=>F(l)?l.value=o:null)},{default:n(()=>[(a(!0),p(m,null,i(e(f),o=>(a(),s(g,{label:e(r)(o)},{default:n(()=>[(a(!0),p(m,null,i(o.subject,_=>(a(),s(C,{value:_.id,label:`${e(r)(o)}(${_.name})`},null,8,["value","label"]))),256))]),_:2},1032,["label"]))),256))]),_:1},8,["onChange","modelValue"]),c(w,{class:"m-t","node-key":"id","show-checkbox":"",data:e(b),"default-expand-all":"","expand-on-click-node":!1,"render-content":e(k)},null,8,["data","render-content"])]),_:1})}}}),R=L(N,[["__scopeId","data-v-e36b779f"]]);export{R as default}; 2 | -------------------------------------------------------------------------------- /backmanger/dist/assets/index-C1hh7dcC.js: -------------------------------------------------------------------------------- 1 | import{u as q,c as M,a as O,d as E}from"./hooks-DGTDKDMV.js";import{d as T,v as D,b as R,r as w,x as J,y as A,k as z,w as t,E as x,c as i,o as F,g as V,f as o,h as u,e as I,u as k,F as L,q as S,t as $,z as P,A as j}from"./index-BENb7eg2.js";const G={class:"dialog-footer"},H=T({__name:"create",props:{modelValue:{type:Boolean,required:!0,default:!1},modelModifiers:{}},emits:D(["on-submit"],["update:modelValue"]),setup(U,{expose:f,emit:h}){const a=R({name:"",desc:"",_id:""}),c=R({name:[{required:!0,trigger:"change",message:"请输入名称"},{message:"请输入名称",trigger:"blur"},{min:2,max:50,message:"长度在 2 到 50 个字符",trigger:"blur"}],desc:[{required:!0,message:"请输入描述",trigger:"change"},{message:"请输入描述",trigger:"blur"}]}),C=h,g=w(),b=w(!1),y=J(U,"modelValue");A(y,s=>{b.value=s});const r=()=>{y.value=!1},n=()=>{var s;(s=g.value)==null||s.validate(async d=>{if(d){const e=JSON.parse(JSON.stringify(a));e._id||Reflect.deleteProperty(e,"_id");const l=await(e._id?q(e):M({...e}));l.code==200?(x.success(l.message),C("on-submit"),r()):x.error(l.message)}})};return f({onReset:()=>{var s;(s=g.value)==null||s.resetFields(),a._id=""},submit:n,onUpdateForm:s=>{a._id=s._id,a.name=s.name,a.desc=s.desc}}),(s,d)=>{const e=i("el-input"),l=i("el-form-item"),m=i("el-form"),N=i("el-button"),B=i("el-dialog");return F(),z(B,{onClose:r,modelValue:b.value,"onUpdate:modelValue":d[2]||(d[2]=v=>b.value=v),draggable:"",title:`课程代码(${a._id?"修改":"新增"})`},{footer:t(()=>[V("span",G,[o(N,{onClick:r},{default:t(()=>[u("取消")]),_:1}),o(N,{type:"primary",onClick:n},{default:t(()=>[u("提交")]),_:1})])]),default:t(()=>[o(m,{"label-width":60,ref_key:"formRef",ref:g,model:a,rules:c},{default:t(()=>[o(l,{key:"1",prop:"name",label:"名称"},{default:t(()=>[o(e,{modelValue:a.name,"onUpdate:modelValue":d[0]||(d[0]=v=>a.name=v),placeholder:"请输入名称"},null,8,["modelValue"])]),_:1}),o(l,{key:"2",label:"其他"},{default:t(()=>[o(e,{placeholder:"待定暂时不需要填写",disabled:""})]),_:1}),o(l,{key:"3",prop:"desc",label:"描述"},{default:t(()=>[o(e,{type:"textarea",modelValue:a.desc,"onUpdate:modelValue":d[1]||(d[1]=v=>a.desc=v),placeholder:"请输入描述"},null,8,["modelValue"])]),_:1})]),_:1},8,["model","rules"])]),_:1},8,["modelValue","title"])}}}),K={style:{"margin-top":"10px"}},X=T({__name:"index",setup(U){const f=w(),{data:h,getList:a}=O(),c=w(!1),C=j(),g=()=>{c.value=!0,S(()=>{var r;(r=f.value)==null||r.onReset()})},b=async r=>{const n=await E({_id:r._id});n?(x.success(n.message),a()):x.error(n.message)},y=r=>{c.value=!0,S(()=>{var n;(n=f.value)==null||n.onUpdateForm(r)})};return(r,n)=>{const p=i("el-button"),_=i("el-table-column"),s=i("el-popover"),d=i("el-table");return F(),I(L,null,[V("div",null,[o(p,{type:"primary",onClick:g},{default:t(()=>[u("新增课程代码")]),_:1})]),o(d,{data:k(h),border:""},{default:t(()=>[o(_,{align:"center",label:"名称",prop:"name"}),o(_,{align:"center",label:"创建时间",prop:"createTime"},{default:t(e=>{var l,m;return[u($((m=(l=k(C))==null?void 0:l.proxy)==null?void 0:m.$timeFormat(e.row.createTime)),1)]}),_:1}),o(_,{align:"center",label:"更新时间",prop:"updateTime"},{default:t(e=>{var l,m;return[u($((m=(l=k(C))==null?void 0:l.proxy)==null?void 0:m.$timeFormat(e.row.updateTime)),1)]}),_:1}),o(_,{align:"center",label:"描述",prop:"desc"}),o(_,{align:"center",label:"操作"},{default:t(e=>[o(p,{onClick:l=>y(e.row),type:"primary",size:"small"},{default:t(()=>[u("编辑")]),_:2},1032,["onClick"]),o(s,{visible:e.row.showpopover,trigger:"click",placement:"bottom",width:160},{reference:t(()=>[e.row.role!==1?(F(),z(p,{key:0,onClick:l=>e.row.showpopover=!0,size:"small",type:"danger"},{default:t(()=>[u("删除")]),_:2},1032,["onClick"])):P("",!0)]),default:t(()=>[V("p",null,"是否删除"+$(e.row.username),1),V("div",K,[o(p,{onClick:l=>e.row.showpopover=!1,type:"primary",size:"small"},{default:t(()=>[u("取消")]),_:2},1032,["onClick"]),o(p,{type:"danger",size:"small",onClick:l=>b(e.row)},{default:t(()=>[u(" 确定 ")]),_:2},1032,["onClick"])])]),_:2},1032,["visible"])]),_:1})]),_:1},8,["data"]),o(H,{onOnSubmit:k(a),ref_key:"CourseCode",ref:f,modelValue:c.value,"onUpdate:modelValue":n[0]||(n[0]=e=>c.value=e)},null,8,["onOnSubmit","modelValue"])],64)}}});export{X as default}; 2 | -------------------------------------------------------------------------------- /backmanger/dist/assets/index-CACTaSOq.css: -------------------------------------------------------------------------------- 1 | .title[data-v-1f53592a]{margin-bottom:20px}.form[data-v-1f53592a]{width:90%;margin:30px}.form-btns[data-v-1f53592a]{display:flex;justify-content:center;width:100%}.course-header[data-v-4f176a8c]{display:flex;justify-content:space-between}.course-header-btn[data-v-4f176a8c]{display:flex}.course-header-btn .el-button[data-v-4f176a8c]{margin-left:10px} 2 | -------------------------------------------------------------------------------- /backmanger/dist/assets/index-COYrsJrE.css: -------------------------------------------------------------------------------- 1 | .question-card[data-v-765dbd27]{margin-bottom:10px}.question-card[data-v-765dbd27]:first-child{margin-top:10px} 2 | -------------------------------------------------------------------------------- /backmanger/dist/assets/index-CQlCNhaL.css: -------------------------------------------------------------------------------- 1 | .m-t[data-v-e36b779f]{margin-top:10px}.custom-tree-node{flex:1;display:flex;align-items:center;justify-content:space-between;font-size:14px;padding-right:8px} 2 | -------------------------------------------------------------------------------- /backmanger/dist/assets/index-CgRR7fKr.js: -------------------------------------------------------------------------------- 1 | import{_ as e}from"./index-BENb7eg2.js";const n={};function r(c,t){return null}const o=e(n,[["render",r]]);export{o as default}; 2 | -------------------------------------------------------------------------------- /backmanger/dist/assets/index-CoY8a9Sg.js: -------------------------------------------------------------------------------- 1 | import{u as x}from"./index-DOsF3NFu.js";import{d as N,e as c,f as n,w as o,g as e,F as H,B as M,u as p,c as u,C as A,o as r,h as t,k as b,t as a,z as v,p as D,i as q,_ as V}from"./index-BENb7eg2.js";import"./hooks-DGTDKDMV.js";const i=y=>(D("data-v-765dbd27"),y=y(),q(),y),j=["innerHTML"],F=["innerHTML"],z={key:0},E=i(()=>e("div",{class:"primary"},"【选项A】:",-1)),Q=["innerHTML"],R=i(()=>e("div",{class:"primary"},"【选项B】:",-1)),G=["innerHTML"],J=i(()=>e("div",{class:"primary"},"【选项C】:",-1)),K=["innerHTML"],O=i(()=>e("div",{class:"primary"},"【选项D】:",-1)),P=["innerHTML"],U=i(()=>e("span",{class:"primary"},"【正确答案】:",-1)),W=i(()=>e("div",{class:"primary"},"【解析A】:",-1)),X=["innerHTML"],Y=i(()=>e("div",{class:"primary"},"【解析B】:",-1)),Z=["innerHTML"],$=i(()=>e("div",{class:"primary"},"【解析C】:",-1)),I=["innerHTML"],ee=i(()=>e("div",{class:"primary"},"【解析D】:",-1)),ne=["innerHTML"],le={key:1},oe=i(()=>e("span",{class:"primary"},"【正确答案】:",-1)),se=i(()=>e("div",{class:"primary"},"【解析】:",-1)),te=["innerHTML"],ae={key:2},ie=i(()=>e("div",{class:"primary"},"【正确答案】",-1)),de=["innerHTML"],re={key:3},_e=i(()=>e("div",{class:"primary"},"【正确答案】",-1)),ce=["innerHTML"],ue={key:4},pe=i(()=>e("div",{class:"primary"},"【正确答案】",-1)),ve=["innerHTML"],ye=i(()=>e("div",{class:"primary"},"【问题描述】",-1)),he=["innerHTML"],Le=N({name:"Question",__name:"index",setup(y){const{data:C,getCourseLabel:k,findSubject:w,handleAnwer:h}=x(),m=A(),S=()=>{m.push("/page/question/create")};return(fe,Te)=>{const g=u("el-button"),d=u("el-descriptions-item"),L=u("el-descriptions"),_=u("el-table-column"),f=u("el-table"),T=u("el-card");return r(),c("div",null,[n(g,{type:"primary",onClick:S},{default:o(()=>[t("新建试题")]),_:1}),e("div",null,[(r(!0),c(H,null,M(p(C),(l,B)=>(r(),b(T,{class:"question-card"},{default:o(()=>[n(L,{column:5,title:l.subjectCode},{default:o(()=>[n(d,{label:"试卷名称:"},{default:o(()=>[t(a(l.questionSetName),1)]),_:2},1024),n(d,{label:"创建人:"},{default:o(()=>[t(a(l.username),1)]),_:2},1024),n(d,{label:"版本:"},{default:o(()=>[t(a(l.version),1)]),_:2},1024),n(d,{label:"序号:"},{default:o(()=>[t(a(B+1),1)]),_:2},1024),n(d,{label:"试卷编号:"},{default:o(()=>[t(a(l.testSetNumber),1)]),_:2},1024),n(d,{label:"课程代码:"},{default:o(()=>[t(a(p(k)(l.courseCode)),1)]),_:2},1024),n(d,{label:"题目:"},{default:o(()=>[t(a(l.type),1)]),_:2},1024),n(d,{label:"难度等级:"},{default:o(()=>[t(a(l.difficultyLevel),1)]),_:2},1024),n(d,{label:"分值:"},{default:o(()=>[t(a(l.score),1)]),_:2},1024),n(d,{label:"最快速度:"},{default:o(()=>[t(a(l.originalScore),1)]),_:2},1024),n(d,{label:"最慢速度:"},{default:o(()=>[t(a(l.originalScore),1)]),_:2},1024),n(d,{label:"阅读时间:"},{default:o(()=>[t(a(l.originalScore),1)]),_:2},1024)]),_:2},1032,["title"]),n(L,{column:1},{default:o(()=>[n(d,{label:"知识点:"},{default:o(()=>[t(a(l.knowledgeId),1)]),_:2},1024),n(d,{label:p(w)(l.type)+":"},{default:o(()=>[e("h2",{innerHTML:l.content},null,8,j)]),_:2},1032,["label"])]),_:2},1024),(r(!0),c(H,null,M(l.data,s=>(r(),b(T,{shadow:"never"},{default:o(()=>[e("div",{innerHTML:s.title||s.content},null,8,F),l.type==1||l.type==2?(r(),c("div",z,[e("div",null,[e("div",null,[E,e("div",{innerHTML:s.A},null,8,Q)]),e("div",null,[R,e("div",{innerHTML:s.B},null,8,G)]),e("div",null,[J,e("div",{innerHTML:s.C},null,8,K)]),e("div",null,[O,e("div",{innerHTML:s.D},null,8,P)])]),e("div",null,[U,t(),e("span",null,a(p(h)(s.answer)),1)]),e("div",null,[e("div",null,[W,e("div",{innerHTML:s.analysisA},null,8,X)]),e("div",null,[Y,e("div",{innerHTML:s.analysisB},null,8,Z)]),e("div",null,[$,e("div",{innerHTML:s.analysisC},null,8,I)]),e("div",null,[ee,e("div",{innerHTML:s.analysisD},null,8,ne)])])])):v("",!0),l.type==3?(r(),c("div",le,[e("div",null,[oe,t(),e("span",null,a(p(h)(s.answer)),1)]),e("div",null,[se,e("div",{innerHTML:s.analysis},null,8,te)])])):v("",!0),l.type==4?(r(),c("div",ae,[ie,e("div",{innerHTML:s.answer},null,8,de)])):v("",!0),l.type==5?(r(),c("div",re,[_e,e("div",{innerHTML:s.answer},null,8,ce),n(f,{border:"",data:s.terms},{default:o(()=>[n(_,{label:"关键词",prop:"keyword"}),n(_,{label:"同义词",prop:"synonym"}),n(_,{label:"反义词",prop:"antonym"}),n(_,{label:"得分",prop:"score"})]),_:2},1032,["data"])])):v("",!0),l.type==6?(r(),c("div",ue,[pe,e("div",{innerHTML:s.answer},null,8,ve),ye,e("div",{innerHTML:s.question},null,8,he),n(f,{border:"",data:s.terms},{default:o(()=>[n(_,{label:"关键词",prop:"keyword"}),n(_,{label:"同义词",prop:"synonym"}),n(_,{label:"反义词",prop:"antonym"}),n(_,{label:"得分",prop:"score"})]),_:2},1032,["data"])])):v("",!0)]),_:2},1024))),256))]),_:2},1024))),256))])])}}}),Ce=V(Le,[["__scopeId","data-v-765dbd27"]]);export{Ce as default}; 2 | -------------------------------------------------------------------------------- /backmanger/dist/assets/index-Cw5ahS-Z.js: -------------------------------------------------------------------------------- 1 | import{a as e}from"./index-BENb7eg2.js";const l=async a=>(await e.post("/api/grade/create",a)).data,r=async a=>(await e.get("/api/grade/list",{params:a})).data,d=async a=>(await e.delete(`/api/grade/delete/${a._id}`)).data,s=async a=>(await e.patch(`/api/grade/update/${a._id}`,a)).data,i=[{label:"小学",value:1},{label:"初中",value:2},{label:"高中",value:3},{label:"中专",value:4},{label:"大学",value:5},{label:"大专",value:6},{label:"硕士",value:7},{label:"博士",value:8}];export{i as a,l as c,d,r as g,s as u}; 2 | -------------------------------------------------------------------------------- /backmanger/dist/assets/index-DOsF3NFu.js: -------------------------------------------------------------------------------- 1 | import{a as s,r as i,l as b}from"./index-BENb7eg2.js";import{a as d}from"./hooks-DGTDKDMV.js";const f=[{value:1,label:"单选题"},{value:2,label:"多选题"},{value:3,label:"判断题"},{value:4,label:"填空题"},{value:5,label:"简答题"},{value:6,label:"阅读题"}],j=[{label:"d1",value:1},{label:"d2",value:2},{label:"d3",value:3},{label:"o1",value:4},{label:"o2",value:5},{label:"o3",value:6},{label:"p1",value:7},{label:"p2",value:8},{label:"p3",value:9}],m=async a=>(await s.post("/api/subject/create",a)).data,p=async a=>(await s.get("/api/subject/list",{params:a})).data,y=()=>{const a={testSetNumber:"",questionSetName:"",subjectCode:"",courseCode:"",originalScore:1,fastestSpeed:1,slowestSpeed:1,readingTime:1,difficultyLevel:"",knowledgeId:[],type:1,score:1};return window.structuredClone(a)},S=()=>{const a=i([]),u=d(),l=async()=>{const e=await p(b);a.value=e.data},r=e=>{var t;return((t=u.data.value.find(c=>c._id===e))==null?void 0:t.name)??""},n=e=>(console.log(e),f.find(t=>t.value===e).label),o=e=>typeof e=="string"?e:Array.isArray(e)?e.join(","):typeof e=="number"?e==1?"正确":"错误":e;return l(),{data:a,getSubjectList:l,getCourseLabel:r,findSubject:n,handleAnwer:o}};export{y as a,m as c,j as d,f as s,S as u}; 2 | -------------------------------------------------------------------------------- /backmanger/dist/assets/index-TakMx3zO.css: -------------------------------------------------------------------------------- 1 | .flex[data-v-cd056e75]{display:flex}.flex-1[data-v-cd056e75]{flex:1}.full[data-v-cd056e75]{width:100%}.m-t[data-v-cd056e75]{margin-top:10px}.m-l[data-v-cd056e75]{margin-left:10px}.block[data-v-cd056e75]{display:block}.button-group[data-v-cd056e75]{width:130px}.grade-header[data-v-bc700721]{display:flex;justify-content:space-between}.grade-header-select[data-v-bc700721]{display:flex;width:300px}.grade-header-btn[data-v-bc700721]{display:flex}.grade-header-btn .el-button[data-v-bc700721]{margin-left:10px} 2 | -------------------------------------------------------------------------------- /backmanger/dist/assets/knowledgeHooks-CznZjvcB.js: -------------------------------------------------------------------------------- 1 | import{g as $,a as b}from"./index-Cw5ahS-Z.js";import{r as u,a as v,E as f,A as k}from"./index-BENb7eg2.js";const S=()=>{const a=u([]),r=async()=>{const s=(await $({pageNo:1,pageSize:b.length})).data.data;s.forEach(l=>{l.subject.forEach(d=>{d.id=`${l._id}-${d.name}`})}),a.value=s},c=o=>{var s;return(s=b.find(l=>l.value===o.grade))==null?void 0:s.label};return r(),{list:a,getLabel:c}},j=async a=>(await v.post("/api/knowledge/create",a)).data,C=async a=>(await v.get(`/api/knowledge/list/${a.subjectId}`,{params:a})).data,E=async a=>(await v.patch(`/api/knowledge/update/${a.subjectId}`,a)).data,M=()=>{const a=k(),r=u(""),c=u(!1);let o=u(-1/0);const s=u([]),l=()=>{o.value=-1/0;const t=e=>{for(const n of e)n.id>o.value&&(o.value=n.id),n.children&&t(n.children)};t(s.value)},d=(t,{node:e,data:n,store:i})=>t("span",{class:"custom-tree-node"},[t("span",null,e.label),t("span",null,[t("a",{style:{"margin-right":"8px",color:"#67c23a"},onClick:()=>m(n)},"修改名称"),t("a",{style:{color:"#409eff"},onClick:()=>p(n)},"添加知识点 "),t("a",{style:{"margin-left":"8px",color:"#f56c6c"},onClick:()=>w(e,n)},"删除知识点")])]),p=async t=>{var i;const e=await((i=a==null?void 0:a.proxy)==null?void 0:i.$prompt("请输入知识点名称","添加知识点",{confirmButtonText:"确定",cancelButtonText:"取消",inputPattern:/\S/,inputErrorMessage:"知识点名称不能为空"}));if(!(e!=null&&e.value))return;const n={id:o.value+1,label:e.value,children:[]};t.children||(t.children=[]),t.children.push(n),s.value=[...s.value],l()},m=async t=>{var n;const e=await((n=a==null?void 0:a.proxy)==null?void 0:n.$prompt("请输入知识点名称","修改知识点",{confirmButtonText:"确定",cancelButtonText:"取消",inputValue:t.label,inputPattern:/\S/,inputErrorMessage:"知识点名称不能为空"}));t.label=e.value},w=(t,e)=>{var y;if(e.id===1){(y=a==null?void 0:a.proxy)==null||y.$message.error("根知识点不能删除");return}const n=t.parent,i=n.data.children||n.data,I=i.findIndex(T=>T.id===e.id);i.splice(I,1),l()},x=async()=>{if(!r.value)return f.error("请选择科目");const t={subjectId:r.value,gradeId:r.value.split("-")[0],gradeName:r.value.split("-")[1],TreeKnowledge:s.value},e=await(c.value?E(t):j(t));(e==null?void 0:e.code)==200?(f.success(e.message),g(r.value)):f.error((e==null?void 0:e.message)||"提交失败")},h=()=>{g(r.value)},g=async t=>{if(t){const e=await C({subjectId:t});e.data?(s.value=e.data.TreeKnowledge,c.value=!0):(s.value=[{id:1,label:"知识点",children:[]}],c.value=!1),l()}else s.value=[],c.value=!1};return{renderContent:d,append:p,edit:m,remove:w,submit:x,changeSubject:h,getTree:g,subjectId:r,dataSource:s,isData:c}};export{S as a,M as u}; 2 | -------------------------------------------------------------------------------- /backmanger/dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/message163/QuestionBank/6cb3049dee4041908653c75575fec9468f60e683/backmanger/dist/favicon.ico -------------------------------------------------------------------------------- /backmanger/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /backmanger/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 6 | const component: DefineComponent<{}, {}, any> 7 | export default component 8 | } -------------------------------------------------------------------------------- /backmanger/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /backmanger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@backmanger/manager", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "run-p type-check \"build-only {@}\" --", 9 | "preview": "vite preview", 10 | "build-only": "vite build", 11 | "type-check": "vue-tsc --build --force" 12 | }, 13 | "dependencies": { 14 | "@element-plus/icons-vue": "^2.3.1", 15 | "@questionbank/config": "workspace:^", 16 | "aieditor": "^1.0.14", 17 | "axios": "^1.7.2", 18 | "dayjs": "^1.11.11", 19 | "element-plus": "^2.7.5", 20 | "js-cookie": "^3.0.5", 21 | "pinia": "^2.1.7", 22 | "pinia-plugin-persistedstate": "^3.2.1", 23 | "vue": "^3.3.11", 24 | "vue-router": "^4.2.5" 25 | }, 26 | "devDependencies": { 27 | "@tsconfig/node18": "^18.2.2", 28 | "@types/js-cookie": "^3.0.6", 29 | "@types/node": "^18.19.3", 30 | "@vitejs/plugin-vue": "^4.5.2", 31 | "@vue/tsconfig": "^0.5.0", 32 | "less": "^4.2.0", 33 | "npm-run-all2": "^6.1.1", 34 | "typescript": "~5.3.0", 35 | "vite": "^5.0.10", 36 | "vue-tsc": "^1.8.25" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /backmanger/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/message163/QuestionBank/6cb3049dee4041908653c75575fec9468f60e683/backmanger/public/favicon.ico -------------------------------------------------------------------------------- /backmanger/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /backmanger/src/apis/course/code.ts: -------------------------------------------------------------------------------- 1 | import http from '..' 2 | 3 | export const createCourseCode = async (ruleForm: any): Promise => { 4 | return (await http.post('/api/course-code/create', ruleForm)).data 5 | } 6 | 7 | export const getCourseCodeList = async (params: any): Promise => { 8 | return (await http.get('/api/course-code/list', { params })).data 9 | } 10 | 11 | export const updateCourseCode = async (data: any): Promise => { 12 | return (await http.patch(`/api/course-code/update/${data._id}`,data)).data 13 | } 14 | 15 | export const deleteCourseCode = async (data: any): Promise => { 16 | return (await http.delete(`/api/course-code/delete/${data._id}`)).data 17 | } -------------------------------------------------------------------------------- /backmanger/src/apis/course/index.ts: -------------------------------------------------------------------------------- 1 | import http from '..' 2 | 3 | export const createCourse = async (ruleForm: any): Promise => { 4 | return (await http.post('/api/course/create', ruleForm)).data 5 | } 6 | 7 | export const getCourseList = async (params: any): Promise => { 8 | return (await http.get('/api/course/list', { params })).data 9 | } 10 | 11 | export const updateCourse = async (data: any): Promise => { 12 | return (await http.patch(`/api/course/update/${data._id}`,data)).data 13 | } 14 | 15 | export const deleteCourse = async (data: any): Promise => { 16 | return (await http.delete(`/api/course/delete/${data._id}`)).data 17 | } 18 | 19 | export const importExcelUrl = '/fs/course/upload/excel' -------------------------------------------------------------------------------- /backmanger/src/apis/grade/index.ts: -------------------------------------------------------------------------------- 1 | import http from '..' 2 | 3 | export const createGrade = async (data: any): Promise => { 4 | return (await http.post('/api/grade/create', data)).data 5 | } 6 | 7 | export const getList = async (params?: any): Promise => { 8 | return (await http.get('/api/grade/list', { params })).data 9 | } 10 | 11 | export const deleteGrade = async (data: any): Promise => { 12 | return (await http.delete(`/api/grade/delete/${data._id}`)).data 13 | } 14 | 15 | export const updateGrade = async (data: any): Promise => { 16 | return (await http.patch(`/api/grade/update/${data._id}`, data)).data 17 | } -------------------------------------------------------------------------------- /backmanger/src/apis/index.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { TOEKN } from '@/config' 3 | const env = import.meta.env 4 | //1. /api开头的接口是提供增删改差的核心逻辑接口 5 | //2. /fs开头的接口是提供文件读写的接口 6 | 7 | console.log(env) 8 | 9 | const api = axios.create({ 10 | baseURL: env.MODE === 'development' ? 'http://localhost:9002' : '', 11 | timeout: 5000 12 | }) 13 | 14 | axios.defaults.withCredentials = true 15 | 16 | api.interceptors.request.use(config => { 17 | //携带token 18 | config.headers['Authorization'] = 'Bearer ' + localStorage.getItem(TOEKN) 19 | return config 20 | }) 21 | 22 | api.interceptors.response.use(res => { 23 | return res 24 | }) 25 | 26 | export default api -------------------------------------------------------------------------------- /backmanger/src/apis/knowledge/index.ts: -------------------------------------------------------------------------------- 1 | import http from '..' 2 | export const createKnowledge = async (data: any): Promise => { 3 | return (await http.post('/api/knowledge/create', data)).data 4 | } 5 | 6 | export const getList = async (params: any): Promise => { 7 | return (await http.get(`/api/knowledge/list/${params.subjectId}`, { params })).data 8 | } 9 | 10 | export const updateKnowledge = async (data: any): Promise => { 11 | return (await http.patch(`/api/knowledge/update/${data.subjectId}`, data)).data 12 | } -------------------------------------------------------------------------------- /backmanger/src/apis/login/index.ts: -------------------------------------------------------------------------------- 1 | import http from '..' 2 | export const getCode = async (): Promise => { 3 | return (await http.get('/api/user/code', { responseType: 'text' })).data 4 | } 5 | 6 | export const login = async (ruleForm: any): Promise => { 7 | return (await http.post('/api/user/login', ruleForm)).data 8 | } -------------------------------------------------------------------------------- /backmanger/src/apis/route/index.ts: -------------------------------------------------------------------------------- 1 | import http from '..' 2 | 3 | export const getRouters = async (): Promise => { 4 | return (await http.get('/api/route/list')).data 5 | } -------------------------------------------------------------------------------- /backmanger/src/apis/subject/index.ts: -------------------------------------------------------------------------------- 1 | import http from '..' 2 | 3 | export const createSubject = async (data: any): Promise => { 4 | return (await http.post('/api/subject/create', data)).data 5 | } 6 | 7 | export const getList = async (params: any): Promise => { 8 | return (await http.get('/api/subject/list', { params })).data 9 | } -------------------------------------------------------------------------------- /backmanger/src/apis/user/index.ts: -------------------------------------------------------------------------------- 1 | import http from '..' 2 | 3 | 4 | export const getUserList = async (params?: any): Promise => { 5 | return (await http.get(`/api/user/list/`, { params })).data 6 | } 7 | 8 | export const getAccount = async (params?: any): Promise => { 9 | return (await http.get(`/api/user/account`, { params })).data 10 | } 11 | 12 | export const createUser = async (data: any): Promise => { 13 | return (await http.post('/api/user/create', data)).data 14 | } 15 | 16 | export const updateUser = async (data: any): Promise => { 17 | return (await http.patch(`/api/user/update/${data._id}`, data)).data 18 | } 19 | 20 | export const deleteUser = async (data: any): Promise => { 21 | return (await http.delete(`/api/user/delete/${data._id}`)).data 22 | } -------------------------------------------------------------------------------- /backmanger/src/assets/background/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/message163/QuestionBank/6cb3049dee4041908653c75575fec9468f60e683/backmanger/src/assets/background/bg.jpg -------------------------------------------------------------------------------- /backmanger/src/assets/css/reset.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | 19 | article, aside, details, figcaption, figure, 20 | footer, header, hgroup, menu, nav, section { 21 | display: block; 22 | } 23 | 24 | 25 | ol, ul { 26 | list-style: none; 27 | } 28 | 29 | html,body{ 30 | height: 100%; 31 | overflow: hidden; 32 | } -------------------------------------------------------------------------------- /backmanger/src/components/Editor.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /backmanger/src/config/index.ts: -------------------------------------------------------------------------------- 1 | export const TOEKN = '__xm_token__' 2 | //全局分页控制 3 | export const page = { 4 | pageSize: 10, 5 | pageNo: 1 6 | } -------------------------------------------------------------------------------- /backmanger/src/layout/Content/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /backmanger/src/layout/Header/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 43 | 44 | -------------------------------------------------------------------------------- /backmanger/src/layout/Menu/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | 25 | -------------------------------------------------------------------------------- /backmanger/src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /backmanger/src/main.ts: -------------------------------------------------------------------------------- 1 | import '@/assets/css/reset.css' 2 | import { createApp } from 'vue' 3 | import { createPinia } from 'pinia' 4 | import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' 5 | import App from './App.vue' 6 | import router from './router' 7 | import * as ElementPlusIconsVue from '@element-plus/icons-vue' 8 | import ElementPlus from 'element-plus' 9 | import 'element-plus/dist/index.css' 10 | import locale from 'element-plus/es/locale/lang/zh-cn' 11 | import { installTimeformat } from './utils/time' 12 | const app = createApp(App) 13 | const pinia = createPinia() 14 | pinia.use(piniaPluginPersistedstate) 15 | app.use(pinia) 16 | app.use(ElementPlus, { locale }) 17 | app.use(router) 18 | app.use(installTimeformat) 19 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 20 | app.component(key, component) 21 | } 22 | app.mount('#app') 23 | 24 | -------------------------------------------------------------------------------- /backmanger/src/router/course/index.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router' 2 | 3 | const routes: RouteRecordRaw[] = [ 4 | { 5 | path: 'course', 6 | name: 'course', 7 | component: () => import('@/views/course/index.vue') 8 | }, 9 | { 10 | path: 'course/code', 11 | name: 'course-code', 12 | component: () => import('@/views/course/code/index.vue') 13 | } 14 | ] 15 | export default routes -------------------------------------------------------------------------------- /backmanger/src/router/home/index.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router' 2 | 3 | const routes: RouteRecordRaw[] = [ 4 | { 5 | path: 'home', 6 | name: 'home', 7 | component: () => import('@/views/home/index.vue') 8 | } 9 | ] 10 | export default routes -------------------------------------------------------------------------------- /backmanger/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | import Login from './login' 3 | import Layout from '@/layout/index.vue' 4 | import Course from './course' 5 | import Home from './home' 6 | import question from './question' 7 | import knowledge from './knowledge' 8 | import subject from './subject' 9 | import user from './user' 10 | const router = createRouter({ 11 | history: createWebHistory(import.meta.env.BASE_URL), 12 | routes: [ 13 | ...Login, 14 | { 15 | path: '/page', 16 | component: Layout, 17 | children: [ 18 | ...Course, //课程 19 | ...Home, //首页 20 | ...question, 21 | ...knowledge, 22 | ...subject, 23 | ...user 24 | ] 25 | } 26 | ] 27 | }) 28 | 29 | export default router 30 | -------------------------------------------------------------------------------- /backmanger/src/router/knowledge/index.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router' 2 | 3 | const routes: RouteRecordRaw[] = [ 4 | { 5 | path: 'knowledge', 6 | name: 'knowledge', 7 | component: () => import('@/views/knowledge/index.vue') 8 | } 9 | ] 10 | export default routes -------------------------------------------------------------------------------- /backmanger/src/router/login/index.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router' 2 | import Login from '@/views/login/index.vue' 3 | const routes: RouteRecordRaw[] = [ 4 | { 5 | path: '/', 6 | alias: '/login', 7 | component: Login 8 | } 9 | ] 10 | export default routes -------------------------------------------------------------------------------- /backmanger/src/router/question/index.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router' 2 | 3 | const routes: RouteRecordRaw[] = [ 4 | { 5 | path: 'question', 6 | name: 'question', 7 | component: () => import('@/views/question/index.vue'), 8 | }, 9 | { 10 | path: 'question/create', 11 | name: 'question-create', 12 | component: () => import('@/views/question/create.vue'), 13 | } 14 | ] 15 | export default routes -------------------------------------------------------------------------------- /backmanger/src/router/subject/index.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router' 2 | 3 | const routes: RouteRecordRaw[] = [ 4 | { 5 | path: 'subject', 6 | name: 'subject', 7 | component: () => import('@/views/subject/index.vue') 8 | } 9 | ] 10 | export default routes -------------------------------------------------------------------------------- /backmanger/src/router/user/index.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router' 2 | 3 | const routes: RouteRecordRaw[] = [ 4 | { 5 | path: 'user', 6 | name: 'user', 7 | component: () => import('@/views/user/index.vue') 8 | } 9 | ] 10 | export default routes -------------------------------------------------------------------------------- /backmanger/src/stores/menu/index.ts: -------------------------------------------------------------------------------- 1 | import { getRouters } from "@/apis/route"; 2 | import { defineStore } from 'pinia' 3 | import { computed, ref } from "vue"; 4 | import { defineAsyncComponent } from "vue"; 5 | interface Menu { 6 | title: string 7 | authority: number 8 | icon: string 9 | id: number 10 | children: { 11 | title: string 12 | id: number 13 | path: string 14 | componentUrl: string 15 | name: string 16 | icon: string 17 | parentId: number | null 18 | order: number 19 | type: number 20 | authority: number 21 | children?: Menu[] 22 | component: () => Promise 23 | }[] 24 | } 25 | const execRouter = (routers: Menu[]): Menu[] => { 26 | routers.forEach((item) => { 27 | item.children.forEach((child) => { 28 | child.component = () => defineAsyncComponent(() => import(/* @vite-ignore */child.componentUrl)) 29 | }) 30 | }) 31 | return routers 32 | } 33 | export default defineStore('__MENU__', () => { 34 | let menu = ref([]) 35 | const getRoutersList = async () => { 36 | const { data } = await getRouters<{ data: Menu[] }>() 37 | menu.value = execRouter(data) 38 | } 39 | const menuList = computed(() => menu.value) 40 | return { 41 | getRoutersList, 42 | menu, 43 | menuList 44 | } 45 | }, { 46 | persist: { 47 | storage: sessionStorage 48 | } 49 | }) -------------------------------------------------------------------------------- /backmanger/src/utils/time.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs' 2 | import type { App } from 'vue' 3 | 4 | //可以在任何地方使用 5 | export const timeFormat = (time: number) => { 6 | return dayjs(Number(time)).format('YYYY-MM-DD HH:mm:ss') 7 | } 8 | //注册全局可以通过vue调用 9 | export const installTimeformat = (app: App) => { 10 | app.config.globalProperties.$timeFormat = timeFormat 11 | } 12 | //声明文件扩充 13 | declare module 'vue' { 14 | interface ComponentCustomProperties { 15 | $timeFormat: typeof timeFormat 16 | } 17 | } -------------------------------------------------------------------------------- /backmanger/src/views/Index.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/message163/QuestionBank/6cb3049dee4041908653c75575fec9468f60e683/backmanger/src/views/Index.vue -------------------------------------------------------------------------------- /backmanger/src/views/course/code/create.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 90 | 91 | -------------------------------------------------------------------------------- /backmanger/src/views/course/code/hooks.ts: -------------------------------------------------------------------------------- 1 | import { getCourseCodeList } from '@/apis/course/code' 2 | import { ref } from 'vue' 3 | import type { CourseCodeList, CourseCodeItem } from '@questionbank/config/course/index.ts' 4 | export const useCourseCode = () => { 5 | let data = ref([]) 6 | const getList = async () => { 7 | const res = await getCourseCodeList<{ data: CourseCodeList }>({}) 8 | res.data.forEach((item: CourseCodeItem) => { 9 | item.showpopover = false 10 | }) 11 | data.value = res.data 12 | } 13 | getList() 14 | return { 15 | getList, 16 | data 17 | } 18 | } -------------------------------------------------------------------------------- /backmanger/src/views/course/code/index.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 77 | 78 | -------------------------------------------------------------------------------- /backmanger/src/views/course/type.ts: -------------------------------------------------------------------------------- 1 | export interface Course { 2 | stage: number //阶段 3 | semester: number //学期 4 | unit: number //单元 5 | chapter: number //章节 6 | courseId: number //课程编号 7 | courseNumberd: number //课号 8 | courseName: string // 课程名称 9 | author: string // 课程作者 10 | courseCategories: string // 课程分类 11 | lessonType: string // 课程类型 12 | writingStyle: string //文体 13 | languageStyle: string //语体 14 | _id: string //MongoDB_Id 15 | showpopover: boolean 16 | } 17 | 18 | export interface Time extends Course { 19 | createTime: string //创建时间 20 | updateTime: string //更新时间 21 | } 22 | 23 | -------------------------------------------------------------------------------- /backmanger/src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /backmanger/src/views/knowledge/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 25 | 26 | 31 | 32 | -------------------------------------------------------------------------------- /backmanger/src/views/knowledge/treeHooks.ts: -------------------------------------------------------------------------------- 1 | import { getList } from '@/apis/grade/index' 2 | import { gradeList, type Knowledge } from '@questionbank/config/grade/index.ts' 3 | import { ref } from 'vue' 4 | export const useTreeHooks = () => { 5 | 6 | const list = ref([]) 7 | const getGradeList = async () => { 8 | const data = await getList<{ data: { data: Knowledge[], total: number } }>({ pageNo: 1, pageSize: gradeList.length }) 9 | const gradeLists: Knowledge[] = data.data.data 10 | gradeLists.forEach((item: Knowledge) => { 11 | item.subject.forEach((v) => { 12 | v.id = `${item._id}-${v.name}` 13 | }) 14 | }) 15 | list.value = gradeLists 16 | } 17 | const getLabel = (item: Knowledge) => { 18 | return gradeList.find((v) => v.value === item.grade)?.label 19 | } 20 | getGradeList() 21 | return { 22 | list, 23 | getLabel 24 | } 25 | } -------------------------------------------------------------------------------- /backmanger/src/views/question/components/FillInTheBlanks.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 75 | 76 | -------------------------------------------------------------------------------- /backmanger/src/views/question/components/MultipleChoice.vue: -------------------------------------------------------------------------------- 1 | 71 | 72 | 124 | 125 | -------------------------------------------------------------------------------- /backmanger/src/views/question/components/MultipleChoiceQuestions.vue: -------------------------------------------------------------------------------- 1 | 71 | 72 | 118 | 119 | -------------------------------------------------------------------------------- /backmanger/src/views/question/components/ReadingQuestion.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 118 | 119 | -------------------------------------------------------------------------------- /backmanger/src/views/question/components/ShortAnswer.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 109 | 110 | -------------------------------------------------------------------------------- /backmanger/src/views/question/components/TrueOfFalse.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 80 | 81 | -------------------------------------------------------------------------------- /backmanger/src/views/question/components/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 38 | 39 | -------------------------------------------------------------------------------- /backmanger/src/views/question/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import { type SubjectType, subjecetList } from '@questionbank/config/subject/index.ts' 2 | import { getList } from '@/apis/subject'; 3 | import { ref } from 'vue' 4 | import { page } from '@/config' 5 | import { useCourseCode } from '../../course/code/hooks' 6 | export interface Question { 7 | testSetNumber: string 8 | subjectCode: string 9 | type: SubjectType 10 | questionSetName: string 11 | knowledgeId: number[] 12 | score: number 13 | readingTime: number 14 | fastestSpeed: number 15 | slowestSpeed: number 16 | originalScore: number 17 | courseCode: string 18 | difficultyLevel: string 19 | username?: string 20 | role?: number 21 | uuid?: string 22 | version?: number 23 | content?: string 24 | data?: any[] 25 | } 26 | export const useInitObj = () => { 27 | const originObj: Question = { 28 | testSetNumber: '', //试卷编号 29 | questionSetName: '', //试卷名称 30 | subjectCode: '', //学科 31 | courseCode: '', //课程 32 | originalScore: 1, //原始分数 33 | fastestSpeed: 1, //最快速度 34 | slowestSpeed: 1, //最慢速度 35 | readingTime: 1, //阅读时间 36 | difficultyLevel: '', //难度等级 37 | knowledgeId: [], //关联知识点 38 | type: 1 as SubjectType, //类型 39 | score: 1, //分值 40 | } 41 | return window.structuredClone(originObj) 42 | } 43 | 44 | export const useSubjectList = () => { 45 | const data = ref([]) 46 | const course = useCourseCode() 47 | const getSubjectList = async () => { 48 | const res = await getList<{ data: Question[] }>(page) 49 | data.value = res.data 50 | } 51 | const getCourseLabel = (id: string) => { 52 | return course.data.value.find((v) => v._id === id)?.name ?? '' 53 | } 54 | const findSubject = (type: SubjectType) => { 55 | console.log(type) 56 | return subjecetList.find((v) => v.value === type).label 57 | } 58 | const handleAnwer = (val: any) => { 59 | if (typeof val === 'string') { 60 | return val 61 | } else if (Array.isArray(val)) { 62 | return val.join(',') 63 | } else if (typeof val === 'number') { 64 | return val == 1 ? '正确' : '错误' 65 | } else { 66 | return val 67 | } 68 | } 69 | getSubjectList() 70 | return { 71 | data, 72 | getSubjectList, 73 | getCourseLabel, 74 | findSubject, 75 | handleAnwer 76 | } 77 | } -------------------------------------------------------------------------------- /backmanger/src/views/subject/hooks/gradeHooks.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed, reactive } from 'vue' 2 | import { gradeList, type GradeForm,type Grade } from '@questionbank/config/grade/index.ts' 3 | 4 | export const useGrade = () => { 5 | 6 | let formData = reactive({ 7 | gradeList: [ 8 | { 9 | grade: gradeList[0].value, 10 | subject: [{ name: '' }], 11 | _id: '' 12 | }, 13 | ] 14 | }) 15 | 16 | 17 | const resetForm = () => { 18 | formData.gradeList = [ 19 | { 20 | grade: gradeList[0].value, 21 | subject: [{ name: '' }], 22 | _id: '' 23 | }, 24 | ] 25 | } 26 | 27 | const addGrade = (form: GradeForm) => { 28 | form.push({ 29 | grade: null, 30 | subject: [{ name: '' }] 31 | }) 32 | } 33 | const deleteGrade = (form: GradeForm, index: number) => { 34 | form.splice(index, 1) 35 | } 36 | 37 | const isFirstGrade = computed(() => { 38 | return (index: number) => { 39 | return index != 0 40 | } 41 | }) 42 | 43 | const isLastGrade = computed(() => { 44 | return (index: number, Len: number) => { 45 | return index == Len 46 | } 47 | }) 48 | 49 | //选过的就不能再选 50 | const disableGrade = computed(() => { 51 | return formData.gradeList.map((item) => { 52 | return item.grade 53 | }).filter((item) => { 54 | return item 55 | }) 56 | }) 57 | 58 | return { 59 | disableGrade, 60 | formData, 61 | addGrade, 62 | deleteGrade, 63 | isFirstGrade, 64 | isLastGrade, 65 | resetForm, 66 | } 67 | } -------------------------------------------------------------------------------- /backmanger/src/views/subject/hooks/subjectHooks.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue' 2 | import { gradeList, type SubList } from '@questionbank/config/grade/index.ts' 3 | //只负责科目的逻辑 4 | export const useSubject = () => { 5 | 6 | const isLast = computed(() => { 7 | return (index: number, len: number) => { 8 | return index == len 9 | } 10 | }) 11 | const addSubject = (subList: SubList) => { 12 | subList.push({ name: '' }) 13 | } 14 | 15 | const deleteSubject = (subList: SubList, index: number) => { 16 | subList.splice(index, 1) 17 | } 18 | return { 19 | isLast, 20 | addSubject, 21 | deleteSubject 22 | } 23 | } -------------------------------------------------------------------------------- /backmanger/src/views/user/create.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 118 | 119 | -------------------------------------------------------------------------------- /backmanger/src/views/user/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import { getUserList } from '@/apis/user/index' 2 | import { ref } from 'vue' 3 | import type { UserList } from '@questionbank/config/user/index.ts' 4 | import { userLevalList } from '@questionbank/config/user/index.ts' 5 | export const useUser = () => { 6 | let data = ref([]) 7 | const getList = async () => { 8 | const res = await getUserList<{ data: UserList[] }>() 9 | res.data.forEach((item: UserList) => { 10 | item.showpopover = false 11 | }) 12 | data.value = res.data 13 | } 14 | const getLeval = (id: number) => { 15 | return userLevalList.find((v) => v.id === id)?.name ?? '' 16 | } 17 | getList() 18 | return { 19 | getList, 20 | getLeval, 21 | data 22 | } 23 | } -------------------------------------------------------------------------------- /backmanger/src/views/user/index.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 82 | 83 | -------------------------------------------------------------------------------- /backmanger/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "exclude": ["src/**/__tests__/*"], 5 | "compilerOptions": { 6 | "strict": false, 7 | "composite": true, 8 | "noEmit": true, 9 | "allowImportingTsExtensions": true, 10 | "baseUrl": ".", 11 | "types": ["element-plus/global"], 12 | "paths": { 13 | "@/*": ["./src/*"] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backmanger/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /backmanger/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18/tsconfig.json", 3 | "include": [ 4 | "vite.config.*", 5 | "vitest.config.*", 6 | "cypress.config.*", 7 | "nightwatch.conf.*", 8 | "playwright.config.*" 9 | ], 10 | "compilerOptions": { 11 | "strict": false, 12 | "composite": true, 13 | "noEmit": true, 14 | "module": "ESNext", 15 | "moduleResolution": "Bundler", 16 | "types": ["node"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /backmanger/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import * as port from '@questionbank/config' 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | vue(), 10 | ], 11 | server: { 12 | port: port.backmangerPort, 13 | proxy: { 14 | '/api': { 15 | target: 'http://localhost:3000', 16 | changeOrigin: true, 17 | rewrite: (path) => path.replace(/^\/api/, '') 18 | }, 19 | '/fs': { 20 | target: 'http://localhost:3001', 21 | changeOrigin: true, 22 | rewrite: (path) => path.replace(/^\/fs/, '') 23 | } 24 | } 25 | }, 26 | resolve: { 27 | alias: { 28 | '@': fileURLToPath(new URL('./src', import.meta.url)) 29 | } 30 | } 31 | }) 32 | -------------------------------------------------------------------------------- /config/course/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /config/course/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface CourseCodeItem { 3 | name: string 4 | desc: string 5 | _id?: string 6 | other?: string 7 | createTime: Date 8 | updateTime: Date 9 | uuid: string 10 | showpopover: boolean 11 | } 12 | 13 | export type CourseCodeList = CourseCodeItem[] 14 | -------------------------------------------------------------------------------- /config/database/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.MongodbUrl = void 0; 4 | exports.MongodbUrl = `mongodb://localhost:27017/questionbank`; 5 | -------------------------------------------------------------------------------- /config/database/index.ts: -------------------------------------------------------------------------------- 1 | export const MongodbUrl = `mongodb://localhost:27017/questionbank` -------------------------------------------------------------------------------- /config/grade/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.gradeList = void 0; 4 | exports.gradeList = [ 5 | { 6 | label: '小学', 7 | value: 1 8 | }, 9 | { 10 | label: '初中', 11 | value: 2 12 | }, 13 | { 14 | label: '高中', 15 | value: 3 16 | }, 17 | { 18 | label: '中专', 19 | value: 4 20 | }, 21 | { 22 | label: '大学', 23 | value: 5 24 | }, 25 | { 26 | label: '大专', 27 | value: 6 28 | }, 29 | { 30 | label: '硕士', 31 | value: 7 32 | }, 33 | { 34 | label: '博士', 35 | value: 8 36 | } 37 | ]; 38 | -------------------------------------------------------------------------------- /config/grade/index.ts: -------------------------------------------------------------------------------- 1 | export const gradeList = [ 2 | { 3 | label: '小学', 4 | value: 1 5 | }, 6 | { 7 | label: '初中', 8 | value: 2 9 | }, 10 | { 11 | label: '高中', 12 | value: 3 13 | }, 14 | { 15 | label: '中专', 16 | value: 4 17 | }, 18 | { 19 | label: '大学', 20 | value: 5 21 | }, 22 | { 23 | label: '大专', 24 | value: 6 25 | }, 26 | { 27 | label: '硕士', 28 | value: 7 29 | }, 30 | { 31 | label: '博士', 32 | value: 8 33 | } 34 | ] 35 | 36 | 37 | type GradeList = typeof gradeList 38 | 39 | type GradeValue = GradeList[number]['value'] 40 | 41 | export interface Subject { 42 | name: string 43 | id?: string 44 | } 45 | 46 | export interface KnowledgeSubject extends Subject { 47 | id: string 48 | } 49 | 50 | export type SubList = Subject[] 51 | export type KnowledgeSublist = KnowledgeSubject[] 52 | export interface Grade { 53 | grade: GradeValue | undefined | null 54 | subject: SubList 55 | _id?: string 56 | showpopover?: boolean 57 | } 58 | 59 | export interface Knowledge { 60 | grade: GradeValue | undefined | null 61 | subject: KnowledgeSublist 62 | _id: string 63 | } 64 | 65 | export type KnowledgeForm = Knowledge[] 66 | 67 | export type GradeForm = Grade[] -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __exportStar = (this && this.__exportStar) || function(m, exports) { 14 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); 15 | }; 16 | Object.defineProperty(exports, "__esModule", { value: true }); 17 | __exportStar(require("./port/index"), exports); 18 | -------------------------------------------------------------------------------- /config/index.ts: -------------------------------------------------------------------------------- 1 | export * from './port/index' -------------------------------------------------------------------------------- /config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@questionbank/config", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "tsc --watch" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | -------------------------------------------------------------------------------- /config/port/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.fsPort = exports.serverPort = exports.backmangerPort = exports.appPort = void 0; 4 | exports.appPort = 9001; // h5 page port 5 | exports.backmangerPort = 9002; // backmanger port 6 | exports.serverPort = 3000; //server port 7 | exports.fsPort = 3001; //fs port 8 | -------------------------------------------------------------------------------- /config/port/index.ts: -------------------------------------------------------------------------------- 1 | export const appPort = 9001 // h5 page port 2 | export const backmangerPort = 9002 // backmanger port 3 | export const serverPort = 3000 //server port 4 | export const fsPort = 3001 //fs port 5 | -------------------------------------------------------------------------------- /config/secret/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.projectSecret = void 0; 4 | exports.projectSecret = '%^HUYBSDBLHUBHBDB?LU%^&*UXMZS*&&BUBEDEUB'; 5 | -------------------------------------------------------------------------------- /config/secret/index.ts: -------------------------------------------------------------------------------- 1 | export const projectSecret = '%^HUYBSDBLHUBHBDB?LU%^&*UXMZS*&&BUBEDEUB' -------------------------------------------------------------------------------- /config/subject/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Awnsers = exports.Content = exports.difficulty = exports.categoryList = exports.subjecetList = void 0; 4 | exports.subjecetList = [ 5 | { 6 | value: 1, 7 | label: '单选题' 8 | }, 9 | { 10 | value: 2, 11 | label: '多选题' 12 | }, 13 | { 14 | value: 3, 15 | label: '判断题' 16 | }, 17 | { 18 | value: 4, 19 | label: '填空题' 20 | }, 21 | { 22 | value: 5, 23 | label: '简答题' 24 | }, 25 | { 26 | value: 6, 27 | label: '阅读题' 28 | } 29 | ]; 30 | exports.categoryList = [ 31 | { 32 | value: 1, 33 | label: '语文' 34 | }, 35 | { 36 | value: 2, 37 | label: '数学' 38 | }, 39 | { 40 | value: 3, 41 | label: '英语' 42 | }, 43 | { 44 | value: 4, 45 | label: '物理' 46 | }, 47 | { 48 | value: 5, 49 | label: '化学' 50 | }, 51 | { 52 | value: 6, 53 | label: '生物' 54 | }, 55 | { 56 | value: 7, 57 | label: '地理' 58 | }, 59 | { 60 | value: 8, 61 | label: '历史' 62 | }, 63 | { 64 | value: 9, 65 | label: '政治' 66 | }, 67 | { 68 | value: 10, 69 | label: '体育' 70 | }, 71 | { 72 | value: 11, 73 | label: '美术' 74 | }, 75 | { 76 | value: 12, 77 | label: '音乐' 78 | }, 79 | { 80 | value: 13, 81 | label: '其他' 82 | } 83 | ]; 84 | exports.difficulty = [ 85 | { 86 | label: 'd1', 87 | value: 1 88 | }, 89 | { 90 | label: 'd2', 91 | value: 2 92 | }, 93 | { 94 | label: 'd3', 95 | value: 3 96 | }, 97 | { 98 | label: 'o1', 99 | value: 4 100 | }, 101 | { 102 | label: 'o2', 103 | value: 5 104 | }, 105 | { 106 | label: 'o3', 107 | value: 6 108 | }, 109 | { 110 | label: 'p1', 111 | value: 7 112 | }, 113 | { 114 | label: 'p2', 115 | value: 8 116 | }, 117 | { 118 | label: 'p3', 119 | value: 9 120 | } 121 | ]; 122 | class Content { 123 | } 124 | exports.Content = Content; 125 | class Awnsers { 126 | } 127 | exports.Awnsers = Awnsers; 128 | -------------------------------------------------------------------------------- /config/subject/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export const subjecetList = [ 3 | { 4 | value: 1, 5 | label: '单选题' 6 | }, 7 | { 8 | value: 2, 9 | label: '多选题' 10 | }, 11 | { 12 | value: 3, 13 | label: '判断题' 14 | }, 15 | { 16 | value: 4, 17 | label: '填空题' 18 | }, 19 | { 20 | value: 5, 21 | label: '简答题' 22 | }, 23 | { 24 | value: 6, 25 | label: '阅读题' 26 | } 27 | ] as const 28 | 29 | export const categoryList = [ 30 | { 31 | value: 1, 32 | label: '语文' 33 | }, 34 | { 35 | value: 2, 36 | label: '数学' 37 | }, 38 | { 39 | value: 3, 40 | label: '英语' 41 | }, 42 | { 43 | value: 4, 44 | label: '物理' 45 | }, 46 | { 47 | value: 5, 48 | label: '化学' 49 | }, 50 | { 51 | value: 6, 52 | label: '生物' 53 | }, 54 | { 55 | value: 7, 56 | label: '地理' 57 | }, 58 | { 59 | value: 8, 60 | label: '历史' 61 | }, 62 | { 63 | value: 9, 64 | label: '政治' 65 | }, 66 | { 67 | value: 10, 68 | label: '体育' 69 | }, 70 | { 71 | value: 11, 72 | label: '美术' 73 | }, 74 | { 75 | value: 12, 76 | label: '音乐' 77 | }, 78 | { 79 | value: 13, 80 | label: '其他' 81 | } 82 | ] 83 | 84 | export const difficulty = [ 85 | { 86 | label: 'd1', 87 | value: 1 88 | }, 89 | { 90 | label: 'd2', 91 | value: 2 92 | }, 93 | { 94 | label: 'd3', 95 | value: 3 96 | }, 97 | { 98 | label: 'o1', 99 | value: 4 100 | }, 101 | { 102 | label: 'o2', 103 | value: 5 104 | }, 105 | { 106 | label: 'o3', 107 | value: 6 108 | }, 109 | { 110 | label: 'p1', 111 | value: 7 112 | }, 113 | { 114 | label: 'p2', 115 | value: 8 116 | }, 117 | { 118 | label: 'p3', 119 | value: 9 120 | } 121 | ] 122 | 123 | 124 | 125 | export class Content { 126 | text: string 127 | value: string 128 | isAnswer: boolean 129 | } 130 | 131 | export type SubjectType = typeof subjecetList[number]['value'] 132 | 133 | export type AwnsersType = string | number | Content[] 134 | 135 | export class Awnsers { 136 | type: SubjectType 137 | content: AwnsersType 138 | } 139 | 140 | -------------------------------------------------------------------------------- /config/user/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.userLevalList = exports.UserLeval = void 0; 4 | var UserLeval; 5 | (function (UserLeval) { 6 | UserLeval[UserLeval["admin"] = 1] = "admin"; 7 | UserLeval[UserLeval["director"] = 2] = "director"; 8 | UserLeval[UserLeval["teacher"] = 3] = "teacher"; 9 | UserLeval[UserLeval["student"] = 4] = "student"; 10 | UserLeval[UserLeval["guest"] = 5] = "guest"; //游客 客人 11 | })(UserLeval || (exports.UserLeval = UserLeval = {})); 12 | exports.userLevalList = [ 13 | { id: UserLeval.admin, name: '管理员' }, 14 | { id: UserLeval.director, name: '主任' }, 15 | { id: UserLeval.teacher, name: '教师' }, 16 | { id: UserLeval.student, name: '学生' }, 17 | { id: UserLeval.guest, name: '游客' } 18 | ]; 19 | -------------------------------------------------------------------------------- /config/user/index.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | username: string 3 | account: string 4 | password: string 5 | age?: number 6 | role: number 7 | leval: UserLeval 8 | uuid: string 9 | } 10 | 11 | export interface UserList extends User { 12 | _id: string 13 | uuid: string 14 | showpopover?: boolean 15 | } 16 | 17 | export enum UserLeval { 18 | admin = 1, //管理员 19 | director = 2, //校长 主任 20 | teacher = 3, //教师 21 | student = 4, //学生 22 | guest = 5 //游客 客人 23 | } 24 | 25 | export const userLevalList = [ 26 | { id: UserLeval.admin, name: '管理员' }, 27 | { id: UserLeval.director, name: '主任' }, 28 | { id: UserLeval.teacher, name: '教师' }, 29 | { id: UserLeval.student, name: '学生' }, 30 | { id: UserLeval.guest, name: '游客' } 31 | ] 32 | -------------------------------------------------------------------------------- /fs/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /fs/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | 38 | # dotenv environment variable files 39 | .env 40 | .env.development.local 41 | .env.test.local 42 | .env.production.local 43 | .env.local 44 | 45 | # temp directory 46 | .temp 47 | .tmp 48 | 49 | # Runtime data 50 | pids 51 | *.pid 52 | *.seed 53 | *.pid.lock 54 | 55 | # Diagnostic reports (https://nodejs.org/api/report.html) 56 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 57 | -------------------------------------------------------------------------------- /fs/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /fs/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 6 | [circleci-url]: https://circleci.com/gh/nestjs/nest 7 | 8 |

A progressive Node.js framework for building efficient and scalable server-side applications.

9 |

10 | NPM Version 11 | Package License 12 | NPM Downloads 13 | CircleCI 14 | Coverage 15 | Discord 16 | Backers on Open Collective 17 | Sponsors on Open Collective 18 | 19 | Support us 20 | 21 |

22 | 24 | 25 | ## Description 26 | 27 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 28 | 29 | ## Installation 30 | 31 | ```bash 32 | $ pnpm install 33 | ``` 34 | 35 | ## Running the app 36 | 37 | ```bash 38 | # development 39 | $ pnpm run start 40 | 41 | # watch mode 42 | $ pnpm run start:dev 43 | 44 | # production mode 45 | $ pnpm run start:prod 46 | ``` 47 | 48 | ## Test 49 | 50 | ```bash 51 | # unit tests 52 | $ pnpm run test 53 | 54 | # e2e tests 55 | $ pnpm run test:e2e 56 | 57 | # test coverage 58 | $ pnpm run test:cov 59 | ``` 60 | 61 | ## Support 62 | 63 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 64 | 65 | ## Stay in touch 66 | 67 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 68 | - Website - [https://nestjs.com](https://nestjs.com/) 69 | - Twitter - [@nestframework](https://twitter.com/nestframework) 70 | 71 | ## License 72 | 73 | Nest is [MIT licensed](LICENSE). 74 | -------------------------------------------------------------------------------- /fs/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /fs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@questionbank/fs", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "nest start", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@nestjs/common": "^10.0.0", 24 | "@nestjs/core": "^10.0.0", 25 | "@nestjs/mapped-types": "*", 26 | "@nestjs/mongoose": "^10.0.6", 27 | "@nestjs/platform-express": "^10.0.0", 28 | "@questionbank/config": "workspace:^", 29 | "exceljs": "^4.4.0", 30 | "mongoose": "^8.4.1", 31 | "multer": "1.4.5-lts.1", 32 | "reflect-metadata": "^0.2.0", 33 | "rxjs": "^7.8.1" 34 | }, 35 | "devDependencies": { 36 | "@nestjs/cli": "^10.0.0", 37 | "@nestjs/schematics": "^10.0.0", 38 | "@nestjs/testing": "^10.0.0", 39 | "@types/express": "^4.17.17", 40 | "@types/jest": "^29.5.2", 41 | "@types/multer": "^1.4.11", 42 | "@types/node": "^20.3.1", 43 | "@types/supertest": "^6.0.0", 44 | "@typescript-eslint/eslint-plugin": "^6.0.0", 45 | "@typescript-eslint/parser": "^6.0.0", 46 | "eslint": "^8.42.0", 47 | "eslint-config-prettier": "^9.0.0", 48 | "eslint-plugin-prettier": "^5.0.0", 49 | "jest": "^29.5.0", 50 | "prettier": "^3.0.0", 51 | "source-map-support": "^0.5.21", 52 | "supertest": "^6.3.3", 53 | "ts-jest": "^29.1.0", 54 | "ts-loader": "^9.4.3", 55 | "ts-node": "^10.9.1", 56 | "tsconfig-paths": "^4.2.0", 57 | "typescript": "^5.1.3" 58 | }, 59 | "jest": { 60 | "moduleFileExtensions": [ 61 | "js", 62 | "json", 63 | "ts" 64 | ], 65 | "rootDir": "src", 66 | "testRegex": ".*\\.spec\\.ts$", 67 | "transform": { 68 | "^.+\\.(t|j)s$": "ts-jest" 69 | }, 70 | "collectCoverageFrom": [ 71 | "**/*.(t|j)s" 72 | ], 73 | "coverageDirectory": "../coverage", 74 | "testEnvironment": "node" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /fs/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /fs/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | import { CourseModule } from './course/course.module'; 5 | import { MongooseModule } from '@nestjs/mongoose' 6 | import { MongodbUrl } from '@questionbank/config/database'; 7 | @Module({ 8 | imports: [MongooseModule.forRoot(MongodbUrl),CourseModule], 9 | controllers: [AppController], 10 | providers: [AppService], 11 | }) 12 | export class AppModule {} 13 | -------------------------------------------------------------------------------- /fs/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /fs/src/course/course.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Patch, Param, Delete, UseInterceptors, UploadedFile } from '@nestjs/common'; 2 | import { CourseService } from './course.service'; 3 | import { CreateCourseDto } from './dto/create-course.dto'; 4 | import { UpdateCourseDto } from './dto/update-course.dto'; 5 | import { FileInterceptor } from '@nestjs/platform-express'; 6 | 7 | @Controller('course') 8 | export class CourseController { 9 | constructor(private readonly courseService: CourseService) { } 10 | 11 | @Post('upload/excel') 12 | @UseInterceptors(FileInterceptor('file')) 13 | async importExcel(@UploadedFile() file: Express.Multer.File) { 14 | return await this.courseService.handlerExcel(file); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /fs/src/course/course.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CourseService } from './course.service'; 3 | import { CourseController } from './course.controller'; 4 | import { MongooseModule } from '@nestjs/mongoose' 5 | import { TableName, CourseSchema } from './entities/course.entity'; 6 | @Module({ 7 | imports: [MongooseModule.forFeature([{ name: TableName, schema: CourseSchema }])], 8 | controllers: [CourseController], 9 | providers: [CourseService], 10 | }) 11 | export class CourseModule { } 12 | -------------------------------------------------------------------------------- /fs/src/course/course.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CreateCourseDto } from './dto/create-course.dto'; 3 | import { UpdateCourseDto } from './dto/update-course.dto'; 4 | import * as Exceljs from 'exceljs'; 5 | import { InjectModel } from '@nestjs/mongoose' 6 | import { Model } from 'mongoose'; 7 | import { TableName, CourseDocument } from './entities/course.entity'; 8 | @Injectable() 9 | export class CourseService { 10 | constructor(@InjectModel(TableName) private readonly course: Model) { 11 | 12 | } 13 | async handlerExcel(file: Express.Multer.File) { 14 | if (!file) return '请上传文件' 15 | const workbook = new Exceljs.Workbook(); 16 | await workbook.xlsx.load(file.buffer); 17 | const worksheet = workbook.worksheets[0]; 18 | const rows = [] 19 | worksheet.eachRow((row) => { 20 | const rowData = [] 21 | row.eachCell((cell) => { 22 | rowData.push(cell.value) 23 | }) 24 | rows.push(rowData) 25 | }) 26 | const columns = rows[1] 27 | rows.shift() //删除中文 28 | rows.shift() //删除key 29 | //剩下的就是值了 key-value映射 30 | const data = [] 31 | const isObject = (data) => Object.prototype.toString.call(data) === '[object Object]' 32 | rows.forEach((item) => { 33 | const obj = {} 34 | item.forEach((value, index) => { 35 | obj[columns[index]] = isObject(value) ? Number(value.result ? value.result : 0) : value 36 | }) 37 | data.push(obj) 38 | }) 39 | try { 40 | await this.course.insertMany(data) 41 | return '导入成功'; 42 | } 43 | catch (e) { 44 | console.log(e) 45 | return '导入失败' + e 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /fs/src/course/dto/create-course.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreateCourseDto {} 2 | -------------------------------------------------------------------------------- /fs/src/course/dto/update-course.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { CreateCourseDto } from './create-course.dto'; 3 | 4 | export class UpdateCourseDto extends PartialType(CreateCourseDto) {} 5 | -------------------------------------------------------------------------------- /fs/src/course/entities/course.entity.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose' 2 | import { Document } from 'mongoose' 3 | 4 | @Schema() 5 | export class Course { 6 | 7 | @Prop({ required: true }) 8 | stage: number //阶段 9 | 10 | @Prop({ required: true }) 11 | semester: number //学期 12 | 13 | @Prop({ required: true }) 14 | unit: number //单元 15 | 16 | @Prop({ required: true }) 17 | chapter: number //章节 18 | 19 | @Prop({ required: true, default: Date.now() }) 20 | createTime: string //创建时间 21 | 22 | @Prop({ required: true, default: Date.now() }) 23 | updateTime: string //更新时间 24 | 25 | @Prop({ required: true }) 26 | courseId: number //课程编号 27 | 28 | @Prop({ required: true }) 29 | courseNumberd: number //课号 30 | 31 | @Prop({ required: true }) 32 | courseName: string // 课程名称 33 | 34 | @Prop({ required: true }) 35 | author: string // 课程作者 36 | 37 | @Prop({ required: true }) 38 | courseCategories: string // 课程分类 39 | 40 | @Prop({ required: true }) 41 | lessonType: string // 课程类型 42 | 43 | @Prop({ required: true }) 44 | writingStyle: string //文体 45 | 46 | @Prop({ required: true }) 47 | languageStyle: string //语体 48 | } 49 | 50 | export const CourseSchema = SchemaFactory.createForClass(Course) 51 | export const TableName = 'Course' 52 | export type CourseDocument = Course & Document 53 | -------------------------------------------------------------------------------- /fs/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import * as port from '@questionbank/config' 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule); 6 | await app.listen(port.fsPort); 7 | } 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /fs/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /fs/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /fs/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /fs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@questionbank/index", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start:server": "pnpm -C server start:dev", 8 | "start:app": "pnpm -C app dev", 9 | "start:back": "pnpm -C backmanger dev", 10 | "start:ts": "pnpm -C config dev", 11 | "start:fs": "pnpm -C fs start:dev" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "@types/md5": "^2.3.5", 18 | "md5": "^2.3.0" 19 | } 20 | } -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | # all packages in direct subdirs of packages/ 3 | - 'server' 4 | - 'config' 5 | - 'app' 6 | - 'backmanger' 7 | - 'fs' -------------------------------------------------------------------------------- /server/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | 38 | # dotenv environment variable files 39 | .env 40 | .env.development.local 41 | .env.test.local 42 | .env.production.local 43 | .env.local 44 | 45 | # temp directory 46 | .temp 47 | .tmp 48 | 49 | # Runtime data 50 | pids 51 | *.pid 52 | *.seed 53 | *.pid.lock 54 | 55 | # Diagnostic reports (https://nodejs.org/api/report.html) 56 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 57 | -------------------------------------------------------------------------------- /server/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 6 | [circleci-url]: https://circleci.com/gh/nestjs/nest 7 | 8 |

A progressive Node.js framework for building efficient and scalable server-side applications.

9 |

10 | NPM Version 11 | Package License 12 | NPM Downloads 13 | CircleCI 14 | Coverage 15 | Discord 16 | Backers on Open Collective 17 | Sponsors on Open Collective 18 | 19 | Support us 20 | 21 |

22 | 24 | 25 | ## Description 26 | 27 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 28 | 29 | ## Installation 30 | 31 | ```bash 32 | $ npm install 33 | ``` 34 | 35 | ## Running the app 36 | 37 | ```bash 38 | # development 39 | $ npm run start 40 | 41 | # watch mode 42 | $ npm run start:dev 43 | 44 | # production mode 45 | $ npm run start:prod 46 | ``` 47 | 48 | ## Test 49 | 50 | ```bash 51 | # unit tests 52 | $ npm run test 53 | 54 | # e2e tests 55 | $ npm run test:e2e 56 | 57 | # test coverage 58 | $ npm run test:cov 59 | ``` 60 | 61 | ## Support 62 | 63 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 64 | 65 | ## Stay in touch 66 | 67 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 68 | - Website - [https://nestjs.com](https://nestjs.com/) 69 | - Twitter - [@nestframework](https://twitter.com/nestframework) 70 | 71 | ## License 72 | 73 | Nest is [MIT licensed](LICENSE). 74 | -------------------------------------------------------------------------------- /server/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@questionbank/server", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "nest start", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@nestjs/common": "^10.0.0", 24 | "@nestjs/core": "^10.0.0", 25 | "@nestjs/jwt": "^10.2.0", 26 | "@nestjs/mapped-types": "^2.0.5", 27 | "@nestjs/mongoose": "^10.0.6", 28 | "@nestjs/passport": "^10.0.3", 29 | "@nestjs/platform-express": "^10.0.0", 30 | "@questionbank/config": "workspace:^", 31 | "cookie-parser": "^1.4.6", 32 | "express-session": "^1.18.0", 33 | "jsonwebtoken": "^9.0.2", 34 | "md5": "^2.3.0", 35 | "mongoose": "^8.4.1", 36 | "passport": "^0.7.0", 37 | "passport-jwt": "^4.0.1", 38 | "passport-local": "^1.0.0", 39 | "reflect-metadata": "^0.2.0", 40 | "rxjs": "^7.8.1", 41 | "svg-captcha": "^1.4.0", 42 | "uuid": "^10.0.0" 43 | }, 44 | "devDependencies": { 45 | "@nestjs/cli": "^10.0.0", 46 | "@nestjs/schematics": "^10.0.0", 47 | "@nestjs/testing": "^10.0.0", 48 | "@types/express": "^4.17.17", 49 | "@types/jest": "^29.5.2", 50 | "@types/node": "^20.3.1", 51 | "@types/passport-jwt": "^4.0.1", 52 | "@types/supertest": "^6.0.0", 53 | "@typescript-eslint/eslint-plugin": "^6.0.0", 54 | "@typescript-eslint/parser": "^6.0.0", 55 | "eslint": "^8.42.0", 56 | "eslint-config-prettier": "^9.0.0", 57 | "eslint-plugin-prettier": "^5.0.0", 58 | "jest": "^29.5.0", 59 | "prettier": "^3.0.0", 60 | "source-map-support": "^0.5.21", 61 | "supertest": "^6.3.3", 62 | "ts-jest": "^29.1.0", 63 | "ts-loader": "^9.4.3", 64 | "ts-node": "^10.9.1", 65 | "tsconfig-paths": "^4.2.0", 66 | "typescript": "^5.1.3" 67 | }, 68 | "jest": { 69 | "moduleFileExtensions": [ 70 | "js", 71 | "json", 72 | "ts" 73 | ], 74 | "rootDir": "src", 75 | "testRegex": ".*\\.spec\\.ts$", 76 | "transform": { 77 | "^.+\\.(t|j)s$": "ts-jest" 78 | }, 79 | "collectCoverageFrom": [ 80 | "**/*.(t|j)s" 81 | ], 82 | "coverageDirectory": "../coverage", 83 | "testEnvironment": "node" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /server/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /server/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | import { DbModule } from './db/db.module'; 5 | import { UserModule } from './user/user.module'; 6 | import { SubjectModule } from './subject/subject.module'; 7 | import { CourseModule } from './course/course.module'; 8 | import { AuthModule } from './auth/auth.module'; 9 | import { RouteModule } from './route/route.module'; 10 | import { KnowledgeModule } from './knowledge/knowledge.module'; 11 | import { GradeModule } from './grade/grade.module'; 12 | import { CourseCodeModule } from './course-code/course-code.module'; 13 | 14 | @Module({ 15 | imports: [DbModule, UserModule, SubjectModule, CourseModule, AuthModule, RouteModule, KnowledgeModule, GradeModule, CourseCodeModule], 16 | controllers: [AppController], 17 | providers: [AppService], 18 | }) 19 | export class AppModule {} 20 | -------------------------------------------------------------------------------- /server/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /server/src/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Patch, Param, Delete, Session, UseGuards } from '@nestjs/common'; 2 | import { AuthService } from './auth.service'; 3 | import { JwtAuthGuard } from './guards/jwt-auth.guard'; 4 | 5 | @Controller('auth') 6 | export class AuthController { 7 | constructor(private readonly authService: AuthService) { } 8 | 9 | @UseGuards(JwtAuthGuard) 10 | @Post('login') 11 | async login(@Body() body, @Session() session) { 12 | return 213123 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /server/src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AuthService } from './auth.service'; 3 | import { JwtModule } from "@nestjs/jwt"; 4 | import { projectSecret } from '@questionbank/config/secret'; 5 | import { MongooseModule } from '@nestjs/mongoose' 6 | import { UserSchema, TableName } from '../user/entities/user.entity'; 7 | import { JwtStrategy } from './strategies/jwt.strategy'; 8 | import { LocalStrategy } from './strategies/local.strategy'; 9 | import { PassportModule } from '@nestjs/passport'; 10 | import { AuthController } from './auth.controller'; 11 | @Module({ 12 | imports: [ 13 | PassportModule, 14 | JwtModule.register({ secret: projectSecret, signOptions: { expiresIn: '30d' } }), //注意token有效期为30天 15 | MongooseModule.forFeature([{ name: TableName, schema: UserSchema }]), 16 | ], 17 | providers: [AuthService,LocalStrategy,JwtStrategy,], 18 | controllers: [AuthController], 19 | exports: [AuthService,LocalStrategy,JwtStrategy], 20 | }) 21 | export class AuthModule { } 22 | -------------------------------------------------------------------------------- /server/src/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { JwtService } from '@nestjs/jwt'; 3 | import { InjectModel } from '@nestjs/mongoose' 4 | import { Model } from 'mongoose'; 5 | import { TableName as UserTableName, type UserDocument } from '../user/entities/user.entity'; 6 | import type { User } from '@questionbank/config/user'; 7 | @Injectable() 8 | export class AuthService { 9 | constructor( 10 | @InjectModel(UserTableName) private readonly User: Model, 11 | private readonly jwt: JwtService 12 | ) { } 13 | 14 | 15 | async validateUser({ account, password }) { 16 | const user = await this.User.findOne({ account, password }) 17 | return user 18 | } 19 | 20 | 21 | createToken({ uuid, role, username, }: User) { 22 | return this.jwt.sign({ uuid, role, username }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/src/auth/guards/jwt-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { AuthGuard } from '@nestjs/passport'; 2 | import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; 3 | import { Observable } from 'rxjs'; 4 | @Injectable() 5 | export class JwtAuthGuard extends AuthGuard('jwt') {} 6 | 7 | -------------------------------------------------------------------------------- /server/src/auth/guards/local-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { AuthGuard } from '@nestjs/passport'; 2 | import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; 3 | import { Observable } from 'rxjs'; 4 | @Injectable() 5 | export class LocalAuthGuard extends AuthGuard('local') {} 6 | 7 | -------------------------------------------------------------------------------- /server/src/auth/strategies/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { ExtractJwt, Strategy } from 'passport-jwt'; 4 | import { projectSecret } from '@questionbank/config/secret'; 5 | @Injectable() 6 | export class JwtStrategy extends PassportStrategy(Strategy) { 7 | constructor() { 8 | super({ 9 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 10 | ignoreExpiration: false, 11 | secretOrKey: projectSecret, 12 | }); 13 | } 14 | 15 | 16 | async validate(payload) { 17 | console.log(payload) 18 | return payload; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/src/auth/strategies/local.strategy.ts: -------------------------------------------------------------------------------- 1 | import { AuthService } from '../auth.service'; 2 | import { Strategy } from 'passport-local'; 3 | import { PassportStrategy } from '@nestjs/passport'; 4 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 5 | 6 | @Injectable() 7 | export class LocalStrategy extends PassportStrategy(Strategy) { 8 | constructor(private readonly authService: AuthService) { 9 | super({ 10 | usernameField: 'account', 11 | passwordField: 'password', 12 | }); 13 | } 14 | 15 | // 重写validate方法 16 | async validate(account, password) { 17 | // 调用在服务层验证的方法 18 | const user = await this.authService.validateUser({ account, password }); 19 | if (!user) { 20 | throw new UnauthorizedException({ 21 | data: { 22 | message: '用户名或密码错误' 23 | }, 24 | code: 401 25 | }); 26 | } 27 | return user; 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /server/src/course-code/course-code.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Req } from '@nestjs/common'; 2 | import { CourseCodeService } from './course-code.service'; 3 | import { CreateCourseCodeDto } from './dto/create-course-code.dto'; 4 | import { UpdateCourseCodeDto } from './dto/update-course-code.dto'; 5 | import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'; 6 | import { Request } from 'express'; 7 | @Controller('course-code') 8 | export class CourseCodeController { 9 | constructor(private readonly courseCodeService: CourseCodeService) { } 10 | 11 | @UseGuards(JwtAuthGuard) 12 | @Post('/create') 13 | create(@Req() req: Request) { 14 | return this.courseCodeService.create(req); 15 | } 16 | 17 | @Get('/list') 18 | findAll() { 19 | return this.courseCodeService.findAll(); 20 | } 21 | 22 | 23 | @Patch('/update/:id') 24 | update(@Param('id') id: string, @Body() updateCourseCodeDto: UpdateCourseCodeDto) { 25 | return this.courseCodeService.update(id, updateCourseCodeDto); 26 | } 27 | 28 | @UseGuards(JwtAuthGuard) 29 | @Delete('/delete/:id') 30 | remove(@Param('id') id: string) { 31 | return this.courseCodeService.remove(id); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /server/src/course-code/course-code.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CourseCodeService } from './course-code.service'; 3 | import { CourseCodeController } from './course-code.controller'; 4 | import { MongooseModule } from '@nestjs/mongoose'; 5 | import { CourseCodeSchema, TableName } from './entities/course-code.entity'; 6 | @Module({ 7 | imports: [MongooseModule.forFeature([{ name: TableName, schema: CourseCodeSchema }])], 8 | controllers: [CourseCodeController], 9 | providers: [CourseCodeService], 10 | }) 11 | export class CourseCodeModule { } 12 | -------------------------------------------------------------------------------- /server/src/course-code/course-code.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CreateCourseCodeDto } from './dto/create-course-code.dto'; 3 | import { UpdateCourseCodeDto } from './dto/update-course-code.dto'; 4 | import { InjectModel } from '@nestjs/mongoose' 5 | import { Model } from 'mongoose'; 6 | import { TableName, type CourseCodeDocument } from './entities/course-code.entity'; 7 | import { Request } from 'express'; 8 | @Injectable() 9 | export class CourseCodeService { 10 | constructor(@InjectModel(TableName) private readonly courseCode: Model) { } 11 | create(req: Request) { 12 | const body = { ...req.body, uuid: req.user.uuid } 13 | return this.courseCode.create(body); 14 | } 15 | 16 | findAll() { 17 | return this.courseCode.find() 18 | } 19 | 20 | 21 | update(id: string, updateCourseCodeDto: UpdateCourseCodeDto) { 22 | return this.courseCode.updateOne({ _id: id }, updateCourseCodeDto) 23 | } 24 | 25 | remove(id: string) { 26 | return this.courseCode.findByIdAndDelete(id) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/src/course-code/dto/create-course-code.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreateCourseCodeDto { 2 | name: string 3 | desc: string 4 | other?: string 5 | } 6 | -------------------------------------------------------------------------------- /server/src/course-code/dto/update-course-code.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { CreateCourseCodeDto } from './create-course-code.dto'; 3 | 4 | export class UpdateCourseCodeDto extends PartialType(CreateCourseCodeDto) {} 5 | -------------------------------------------------------------------------------- /server/src/course-code/entities/course-code.entity.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose' 2 | import { Document } from 'mongoose' 3 | 4 | @Schema() 5 | export class CourseCode { 6 | @Prop({ required: true }) 7 | name: string 8 | 9 | @Prop({ required: true }) 10 | desc: string 11 | 12 | @Prop({ required: false, default: "" }) 13 | other: string 14 | 15 | @Prop({required: true, default: Date.now()}) 16 | createTime: string 17 | 18 | @Prop({required: true, default: Date.now()}) 19 | updateTime: string 20 | 21 | @Prop({required: true, default: 0}) 22 | uuid: string 23 | } 24 | 25 | export const CourseCodeSchema = SchemaFactory.createForClass(CourseCode) 26 | export const TableName = 'CourseCode' 27 | export type CourseCodeDocument = CourseCode & Document -------------------------------------------------------------------------------- /server/src/course/course.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common'; 2 | import { CourseService } from './course.service'; 3 | import { CreateCourseDto, Query as Params } from './dto/create-course.dto'; 4 | import { UpdateCourseDto } from './dto/update-course.dto'; 5 | 6 | @Controller('course') 7 | export class CourseController { 8 | constructor(private readonly courseService: CourseService) { } 9 | 10 | @Post('create') 11 | create(@Body() createCourseDto: CreateCourseDto) { 12 | return this.courseService.create(createCourseDto); 13 | } 14 | 15 | @Get('list') 16 | async findAll(@Query() query: Params) { 17 | return await this.courseService.findAll(query); 18 | } 19 | 20 | 21 | @Patch('/update/:id') 22 | update(@Param('id') id: string, @Body() updateCourseDto: UpdateCourseDto) { 23 | return this.courseService.update(id, updateCourseDto); 24 | } 25 | 26 | @Delete('delete/:id') 27 | remove(@Param('id') id: string) { 28 | return this.courseService.remove(id); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /server/src/course/course.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CourseService } from './course.service'; 3 | import { CourseController } from './course.controller'; 4 | import { MongooseModule } from '@nestjs/mongoose'; 5 | import { CourseSchema, TableName } from './entities/course.entity'; 6 | @Module({ 7 | imports: [MongooseModule.forFeature([{ name: TableName, schema: CourseSchema }])], 8 | controllers: [CourseController], 9 | providers: [CourseService], 10 | }) 11 | export class CourseModule { } 12 | -------------------------------------------------------------------------------- /server/src/course/course.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CreateCourseDto } from './dto/create-course.dto'; 3 | import { UpdateCourseDto } from './dto/update-course.dto'; 4 | import { InjectModel } from '@nestjs/mongoose' 5 | import { Model } from 'mongoose'; 6 | import { TableName, CourseDocument } from './entities/course.entity'; 7 | import { Query } from './dto/create-course.dto'; 8 | @Injectable() 9 | export class CourseService { 10 | constructor(@InjectModel(TableName) private readonly course: Model) { } 11 | create(createCourseDto: CreateCourseDto) { 12 | const course = new this.course(createCourseDto) 13 | return course.save() 14 | } 15 | 16 | async findAll(query: Query) { 17 | const { pageNo = 1, pageSize = 10 } = query 18 | const skip = (pageNo - 1) * pageSize 19 | const search = { courseName: new RegExp(query.keyWord, 'i') } 20 | const total = await this.course.countDocuments(search) 21 | const data = await this.course.find(search).sort({ _id: -1 }).skip(skip).limit(Number(pageSize)).exec() 22 | return { 23 | data, 24 | total 25 | } 26 | } 27 | 28 | 29 | update(id: string, updateCourseDto: UpdateCourseDto) { 30 | const course = this.course.findByIdAndUpdate(id, updateCourseDto) 31 | return course 32 | } 33 | 34 | remove(_id: string) { 35 | const course = this.course.findByIdAndDelete(_id) 36 | return course 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /server/src/course/dto/create-course.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreateCourseDto { 2 | stage: number //阶段 3 | 4 | semester: number //学期 5 | 6 | unit: number //单元 7 | 8 | chapter: number //章节 9 | 10 | createTime: string //创建时间 11 | 12 | updateTime: string //更新时间 13 | 14 | courseId: number //课程编号 15 | 16 | courseNumberd: number //课号 17 | 18 | courseName: string // 课程名称 19 | 20 | author: string // 课程作者 21 | 22 | courseCategories: string // 课程分类 23 | 24 | lessonType: string // 课程类型 25 | 26 | writingStyle: string //文体 27 | 28 | languageStyle: string //语体 29 | } 30 | 31 | 32 | export interface Page { 33 | pageNo: number 34 | pageSize: number 35 | } 36 | 37 | export interface Query extends Page { 38 | keyWord:string 39 | } -------------------------------------------------------------------------------- /server/src/course/dto/update-course.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { CreateCourseDto } from './create-course.dto'; 3 | 4 | export class UpdateCourseDto extends PartialType(CreateCourseDto) {} 5 | -------------------------------------------------------------------------------- /server/src/course/entities/course.entity.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose' 2 | import { Document } from 'mongoose' 3 | 4 | @Schema() 5 | export class Course { 6 | 7 | @Prop({ required: true }) 8 | stage: number //阶段 9 | 10 | @Prop({ required: true }) 11 | semester: number //学期 12 | 13 | @Prop({ required: true }) 14 | unit: number //单元 15 | 16 | @Prop({ required: true }) 17 | chapter: number //章节 18 | 19 | @Prop({ required: true, default: Date.now() }) 20 | createTime: string //创建时间 21 | 22 | @Prop({ required: true, default: Date.now() }) 23 | updateTime: string //更新时间 24 | 25 | @Prop({ required: true }) 26 | courseId: number //课程编号 27 | 28 | @Prop({ required: true }) 29 | courseNumberd: number //课号 30 | 31 | @Prop({ required: true }) 32 | courseName: string // 课程名称 33 | 34 | @Prop({ required: true }) 35 | author: string // 课程作者 36 | 37 | @Prop({ required: true }) 38 | courseCategories: string // 课程分类 39 | 40 | @Prop({ required: true }) 41 | lessonType: string // 课程类型 42 | 43 | @Prop({ required: true }) 44 | writingStyle: string //文体 45 | 46 | @Prop({ required: true }) 47 | languageStyle: string //语体 48 | } 49 | 50 | export const CourseSchema = SchemaFactory.createForClass(Course) 51 | export const TableName = 'Course' 52 | export type CourseDocument = Course & Document -------------------------------------------------------------------------------- /server/src/db/db.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose' 3 | import { MongodbUrl } from '@questionbank/config/database'; 4 | 5 | @Module({ 6 | imports: [MongooseModule.forRoot(MongodbUrl)], 7 | controllers: [], 8 | providers: [], 9 | }) 10 | export class DbModule { } 11 | -------------------------------------------------------------------------------- /server/src/enum/authority.ts: -------------------------------------------------------------------------------- 1 | export enum Authority { 2 | ADMIN = 1, //管理员 3 | USER = 2, //普通用户 4 | GUEST = 3 //游客 5 | } 6 | 7 | -------------------------------------------------------------------------------- /server/src/env.d.ts: -------------------------------------------------------------------------------- 1 | declare module Express { 2 | interface User { 3 | uuid: string 4 | role: number 5 | username: string 6 | } 7 | export interface Request { 8 | user: User 9 | } 10 | } -------------------------------------------------------------------------------- /server/src/grade/dto/create-grade.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreateGradeDto {} 2 | 3 | 4 | export interface Page { 5 | pageNo: number 6 | pageSize: number 7 | } 8 | 9 | export interface Query extends Page { 10 | keyWord:string 11 | } -------------------------------------------------------------------------------- /server/src/grade/dto/update-grade.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { CreateGradeDto } from './create-grade.dto'; 3 | 4 | export class UpdateGradeDto extends PartialType(CreateGradeDto) {} 5 | -------------------------------------------------------------------------------- /server/src/grade/entities/grade.entity.ts: -------------------------------------------------------------------------------- 1 | import { Schema, SchemaFactory, Prop } from '@nestjs/mongoose' 2 | import type { Document } from 'mongoose' 3 | import { SubList } from '@questionbank/config/grade' 4 | 5 | 6 | @Schema() 7 | export class Grade { 8 | @Prop({ required: true }) 9 | grade: number 10 | 11 | @Prop({ type: Object }) 12 | subject: SubList 13 | 14 | @Prop({ default: Date.now() }) 15 | createTime: string 16 | 17 | @Prop({ default: Date.now() }) 18 | updateTime: string 19 | } 20 | 21 | export const GradeSchema = SchemaFactory.createForClass(Grade) //存储数据库方法 22 | export const TableName = 'Grade' //数据库的名字 23 | 24 | export type GradeDocument = Grade & Document //数据库类型 25 | -------------------------------------------------------------------------------- /server/src/grade/grade.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common'; 2 | import { GradeService } from './grade.service'; 3 | import { CreateGradeDto, type Query as Params } from './dto/create-grade.dto'; 4 | import { UpdateGradeDto } from './dto/update-grade.dto'; 5 | 6 | @Controller('grade') 7 | export class GradeController { 8 | constructor(private readonly gradeService: GradeService) { } 9 | 10 | @Post('create') 11 | async create(@Body() createGradeDto: CreateGradeDto) { 12 | return await this.gradeService.create(createGradeDto); 13 | } 14 | 15 | @Get('list') 16 | findAll(@Query() query: Params) { 17 | return this.gradeService.findAll(query); 18 | } 19 | 20 | 21 | @Patch('/update/:id') 22 | update(@Param('id') id: string, @Body() updateGradeDto: UpdateGradeDto) { 23 | return this.gradeService.update(id, updateGradeDto); 24 | } 25 | 26 | @Delete('/delete/:id') 27 | remove(@Param('id') id: string) { 28 | return this.gradeService.remove(id); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /server/src/grade/grade.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { GradeService } from './grade.service'; 3 | import { GradeController } from './grade.controller'; 4 | import { MongooseModule } from '@nestjs/mongoose' 5 | import { TableName, GradeSchema } from './entities/grade.entity' 6 | @Module({ 7 | imports: [ 8 | MongooseModule.forFeature([{ name: TableName, schema: GradeSchema }]) 9 | ], 10 | controllers: [GradeController], 11 | providers: [GradeService], 12 | }) 13 | export class GradeModule { } 14 | -------------------------------------------------------------------------------- /server/src/grade/grade.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CreateGradeDto } from './dto/create-grade.dto'; 3 | import { UpdateGradeDto } from './dto/update-grade.dto'; 4 | import { InjectModel } from '@nestjs/mongoose' 5 | import { TableName, GradeDocument } from './entities/grade.entity' 6 | import type { Model } from 'mongoose' 7 | import type { Query } from './dto/create-grade.dto'; 8 | @Injectable() 9 | export class GradeService { 10 | constructor(@InjectModel(TableName) private readonly Grade: Model) { 11 | 12 | } 13 | async create(createGradeDto: CreateGradeDto) { 14 | return await this.Grade.create(createGradeDto) 15 | } 16 | 17 | async findAll(query: Query) { 18 | const { pageNo = 1, pageSize = 10 } = query 19 | const skip = (pageNo - 1) * pageSize 20 | const search = { grade: query.keyWord } 21 | const queryKeyWord = query.keyWord ? search : {} 22 | const total = await this.Grade.countDocuments(queryKeyWord) 23 | const data = await this.Grade.find(queryKeyWord).skip(skip).limit(Number(pageSize)).exec() 24 | return { 25 | data, 26 | total 27 | } 28 | } 29 | 30 | update(id: string, updateGradeDto: UpdateGradeDto) { 31 | return this.Grade.findByIdAndUpdate(id, updateGradeDto) 32 | } 33 | 34 | remove(id: string) { 35 | return this.Grade.findByIdAndDelete(id) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /server/src/interceptor/interceptor.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { map } from 'rxjs/operators' 4 | 5 | 6 | 7 | interface data { 8 | data: T 9 | } 10 | 11 | @Injectable() 12 | export class InterceptorInterceptor implements NestInterceptor { 13 | intercept(context: ExecutionContext, next: CallHandler): Observable> { 14 | return next.handle().pipe(map(data => ({ 15 | data, 16 | code: 200, 17 | success: true, 18 | message: '请求成功' 19 | }))) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /server/src/knowledge/dto/create-knowledge.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreateKnowledgeDto {} 2 | -------------------------------------------------------------------------------- /server/src/knowledge/dto/update-knowledge.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { CreateKnowledgeDto } from './create-knowledge.dto'; 3 | 4 | export class UpdateKnowledgeDto extends PartialType(CreateKnowledgeDto) {} 5 | -------------------------------------------------------------------------------- /server/src/knowledge/entities/knowledge.entity.ts: -------------------------------------------------------------------------------- 1 | import { Schema, SchemaFactory, Prop } from '@nestjs/mongoose' 2 | import { Document } from 'mongoose' 3 | interface Tree { 4 | id: string 5 | label: string 6 | children: Tree[] 7 | } 8 | 9 | @Schema() 10 | export class Knowledge { 11 | @Prop() 12 | subjectId: string 13 | 14 | @Prop() 15 | gradeId: string 16 | 17 | @Prop() 18 | gradeName: string 19 | 20 | @Prop({ type: Object }) 21 | TreeKnowledge: Tree 22 | } 23 | 24 | export const KnowledgeSchema = SchemaFactory.createForClass(Knowledge) 25 | export const tableName = 'Knowledge' 26 | export type KnowledgeDocument = Knowledge & Document -------------------------------------------------------------------------------- /server/src/knowledge/knowledge.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; 2 | import { KnowledgeService } from './knowledge.service'; 3 | import { CreateKnowledgeDto } from './dto/create-knowledge.dto'; 4 | import { UpdateKnowledgeDto } from './dto/update-knowledge.dto'; 5 | 6 | @Controller('knowledge') 7 | export class KnowledgeController { 8 | constructor(private readonly knowledgeService: KnowledgeService) {} 9 | 10 | @Post('/create') 11 | create(@Body() createKnowledgeDto: CreateKnowledgeDto) { 12 | return this.knowledgeService.create(createKnowledgeDto); 13 | } 14 | 15 | @Get('/list') 16 | findAll() { 17 | return this.knowledgeService.findAll(); 18 | } 19 | 20 | @Get('/list/:id') 21 | findOne(@Param('id') id: string) { 22 | return this.knowledgeService.findOne(id); 23 | } 24 | 25 | @Patch('/update/:id') 26 | update(@Param('id') id: string, @Body() updateKnowledgeDto: UpdateKnowledgeDto) { 27 | return this.knowledgeService.update(id, updateKnowledgeDto); 28 | } 29 | 30 | @Delete(':id') 31 | remove(@Param('id') id: string) { 32 | return this.knowledgeService.remove(+id); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /server/src/knowledge/knowledge.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { KnowledgeService } from './knowledge.service'; 3 | import { KnowledgeController } from './knowledge.controller'; 4 | import { MongooseModule } from '@nestjs/mongoose'; 5 | import { KnowledgeSchema, tableName } from './entities/knowledge.entity'; 6 | @Module({ 7 | imports: [MongooseModule.forFeature([{ name: tableName, schema: KnowledgeSchema }])], 8 | controllers: [KnowledgeController], 9 | providers: [KnowledgeService], 10 | }) 11 | export class KnowledgeModule { } 12 | -------------------------------------------------------------------------------- /server/src/knowledge/knowledge.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CreateKnowledgeDto } from './dto/create-knowledge.dto'; 3 | import { UpdateKnowledgeDto } from './dto/update-knowledge.dto'; 4 | import { InjectModel } from '@nestjs/mongoose' 5 | import type { Model } from 'mongoose' 6 | import { tableName, type KnowledgeDocument } from './entities/knowledge.entity' 7 | @Injectable() 8 | export class KnowledgeService { 9 | constructor(@InjectModel(tableName) private readonly knowledge: Model) { } 10 | create(createKnowledgeDto: CreateKnowledgeDto) { 11 | return this.knowledge.create(createKnowledgeDto) 12 | } 13 | 14 | findAll() { 15 | return this.knowledge.find() 16 | } 17 | 18 | findOne(id: string) { 19 | return this.knowledge.findOne({ subjectId: id }) 20 | } 21 | 22 | update(id: string, updateKnowledgeDto: UpdateKnowledgeDto) { 23 | return this.knowledge.updateOne({ subjectId: id }, updateKnowledgeDto) 24 | } 25 | 26 | remove(id: number) { 27 | return `This action removes a #${id} knowledge`; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /server/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import * as port from '@questionbank/config/index' 4 | import { InterceptorInterceptor } from './interceptor/interceptor.interceptor'; 5 | async function bootstrap() { 6 | const app = await NestFactory.create(AppModule); 7 | app.enableCors({ 8 | credentials: true, // 允许cookie跨域 9 | }); 10 | app.useGlobalInterceptors(new InterceptorInterceptor()); 11 | await app.listen(port.serverPort); 12 | } 13 | bootstrap(); 14 | -------------------------------------------------------------------------------- /server/src/route/entities/route.children.ts: -------------------------------------------------------------------------------- 1 | import { Document } from 'mongoose' 2 | import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; 3 | 4 | 5 | @Schema() 6 | export class RouteChildren extends Document { 7 | 8 | @Prop({ required: true }) 9 | title: string 10 | 11 | @Prop({ type: String }) 12 | name: string 13 | 14 | @Prop({ type: String }) 15 | path: string 16 | 17 | @Prop({ type: String }) 18 | icon: string 19 | 20 | @Prop({ required: true }) 21 | componentUrl: string; 22 | 23 | @Prop({ required: false }) 24 | children: RouteChildren[] 25 | 26 | @Prop({ required: true }) 27 | id: number; 28 | 29 | @Prop({ required: true, default: null }) 30 | parentId: number | null; 31 | } -------------------------------------------------------------------------------- /server/src/route/entities/route.entity.ts: -------------------------------------------------------------------------------- 1 | import { Document } from 'mongoose' 2 | import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; 3 | import { Authority } from '../../enum/authority'; 4 | import { RouteChildren } from './route.children'; 5 | @Schema() 6 | export class Route { 7 | 8 | @Prop({ required: true }) 9 | title: string; 10 | 11 | @Prop({ required: false }) 12 | order: number; 13 | 14 | @Prop({ required: false }) 15 | icon: string; 16 | 17 | @Prop({ required: false }) 18 | type: number; 19 | 20 | @Prop({ required: false, enum: Authority, default: Authority.GUEST }) 21 | authority: Authority; 22 | 23 | @Prop({ required: false }) 24 | children: RouteChildren[]; 25 | 26 | @Prop({ required: true }) 27 | id: number; 28 | } 29 | 30 | export const tableName = 'Route' 31 | export const RouteSchema = SchemaFactory.createForClass(Route) 32 | 33 | export type RouteDocument = Route & Document -------------------------------------------------------------------------------- /server/src/route/route.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller,Get, Req, UseGuards } from '@nestjs/common'; 2 | import { RouteService } from './route.service'; 3 | import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; 4 | @Controller('route') 5 | export class RouteController { 6 | 7 | constructor(private readonly routeService: RouteService) {} 8 | 9 | @UseGuards(JwtAuthGuard) 10 | @Get('/list') 11 | async getRoute(@Req() req) { 12 | return await this.routeService.getRoute(req) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /server/src/route/route.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { RouteService } from './route.service'; 3 | import { MongooseModule } from '@nestjs/mongoose' 4 | import { RouteSchema,tableName } from './entities/route.entity'; 5 | import { RouteController } from './route.controller'; 6 | @Module({ 7 | imports: [MongooseModule.forFeature([{ name: tableName, schema: RouteSchema }])], 8 | controllers: [RouteController], 9 | providers: [RouteService], 10 | }) 11 | export class RouteModule {} 12 | -------------------------------------------------------------------------------- /server/src/route/route.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnModuleInit } from '@nestjs/common'; 2 | import { InjectModel } from '@nestjs/mongoose'; 3 | import { Model } from 'mongoose'; 4 | import { tableName } from './entities/route.entity'; 5 | import { type RouteDocument } from './entities/route.entity'; 6 | import { Router } from './router'; 7 | import { Request } from 'express'; 8 | @Injectable() 9 | export class RouteService implements OnModuleInit { 10 | constructor(@InjectModel(tableName) private readonly route: Model) { 11 | 12 | } 13 | async getRoute(req: Request) { 14 | return await this.route.find({ authority: req.user.role }) 15 | } 16 | async onModuleInit() { 17 | //默认塞入一些路由 并且不会重复塞入 18 | for await (let routeItem of Router) { 19 | const exist = await this.route.findOne({ title: routeItem.title }).exec() 20 | if (!exist) { 21 | const newRoute = new this.route(routeItem) 22 | await newRoute.save() 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/src/route/router/index.ts: -------------------------------------------------------------------------------- 1 | export const Router = [ 2 | { 3 | title: '首页', 4 | authority: 1, 5 | icon: 'PieChart', 6 | id: 1, 7 | children: [ 8 | { 9 | title: '驾驶舱', 10 | path: '/page/home', 11 | name: 'home', 12 | componentUrl: '@/views/home/index.vue', 13 | id: 2, 14 | icon: 'home', 15 | order: 1, 16 | type: 1, 17 | authority: 1, 18 | parentId: 1 19 | } 20 | ] 21 | }, 22 | { 23 | title: '课程管理', 24 | authority: 1, 25 | icon: 'Reading', 26 | id: 3, 27 | children: [ 28 | { 29 | title: '课程列表', 30 | path: '/page/course', 31 | name: 'course', 32 | componentUrl: '@/views/course/index.vue', 33 | id: 4, 34 | icon: 'book', 35 | order: 2, 36 | type: 1, 37 | parentId: 3, 38 | authority: 1, 39 | }, 40 | { 41 | title: '课程代码', 42 | path: '/page/course/code', 43 | name: 'courseAdd', 44 | componentUrl: '@/views/course/code/index.vue', 45 | id: 41, 46 | icon: 'edit', 47 | order: 2, 48 | type: 1, 49 | parentId: 3, 50 | authority: 1, 51 | } 52 | ] 53 | }, 54 | { 55 | title: '题库管理', 56 | authority: 1, 57 | icon: 'Tickets', 58 | id: 5, 59 | children: [ 60 | { 61 | title: '题库列表', 62 | path: '/page/question', 63 | name: 'question', 64 | componentUrl: '@/views/question/index.vue', 65 | id: 6, 66 | icon: 'tickets', 67 | order: 3, 68 | type: 1, 69 | parentId: 5, 70 | authority: 1, 71 | }, 72 | ] 73 | }, 74 | { 75 | title: '知识点管理', 76 | authority: 1, 77 | icon: 'Star', 78 | id: 7, 79 | children: [ 80 | { 81 | title: '知识点列表', 82 | path: '/page/knowledge', 83 | name: 'knowledge', 84 | componentUrl: '@/views/knowledge/index.vue', 85 | id: 8, 86 | icon: 'tickets', 87 | order: 4, 88 | type: 1, 89 | parentId: 7, 90 | authority: 1, 91 | } 92 | ] 93 | }, 94 | { 95 | title: '学科管理', 96 | authority: 1, 97 | icon: 'Notebook', 98 | id: 9, 99 | children: [ 100 | { 101 | title: '学科列表', 102 | path: '/page/subject', 103 | name: 'subject', 104 | componentUrl: '@/views/subject/index.vue', 105 | id: 10, 106 | order: 4, 107 | type: 1, 108 | parentId: 9, 109 | authority: 1, 110 | } 111 | ] 112 | }, 113 | { 114 | title: '用户管理', 115 | authority: 1, 116 | icon: 'User', 117 | id: 11, 118 | children: [ 119 | { 120 | title: '用户列表', 121 | path: '/page/user', 122 | name: 'user', 123 | componentUrl: '@/views/user/index.vue', 124 | id: 12, 125 | order: 5, 126 | type: 1, 127 | parentId: 11, 128 | authority: 1, 129 | } 130 | ] 131 | } 132 | ] -------------------------------------------------------------------------------- /server/src/subject/dto/create-subject.dto.ts: -------------------------------------------------------------------------------- 1 | import type { SubjectType, AwnsersType, Awnsers } from '@questionbank/config/subject/index.ts' 2 | export class CreateSubjectDto { 3 | title: string 4 | type: SubjectType 5 | awnsers: Awnsers 6 | analysis: string 7 | difficulty: number 8 | category: number 9 | source: Array 10 | } [] 11 | 12 | export interface Page { 13 | pageNo: number 14 | pageSize: number 15 | } -------------------------------------------------------------------------------- /server/src/subject/dto/update-subject.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { CreateSubjectDto } from './create-subject.dto'; 3 | 4 | export class UpdateSubjectDto extends PartialType(CreateSubjectDto) {} 5 | -------------------------------------------------------------------------------- /server/src/subject/entities/subject.entity.ts: -------------------------------------------------------------------------------- 1 | import { Document } from 'mongoose' 2 | import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; 3 | 4 | import { SubjectType, Awnsers } from '@questionbank/config/subject' 5 | @Schema() 6 | export class Subject { 7 | 8 | @Prop() 9 | testSetNumber: string //试卷编号 10 | 11 | @Prop() 12 | questionSetName: string //试卷名称 13 | 14 | @Prop() 15 | subjectCode: string //学科 16 | 17 | @Prop() 18 | type: SubjectType //类型 19 | 20 | @Prop({ type: Array }) 21 | knowledgeId: number[] //关联知识点 22 | 23 | @Prop() 24 | courseCode: string //课程 25 | 26 | @Prop({ default: Date.now() }) 27 | createTime: string //创建时间 28 | 29 | @Prop({ default: Date.now() }) 30 | updateTime: string //更新时间 31 | 32 | @Prop() 33 | score: number //分值 34 | 35 | @Prop() 36 | originalScore: number //原始分数 37 | 38 | @Prop() 39 | source: number //终分数 40 | 41 | @Prop() 42 | fastestSpeed: number //最快速度 43 | 44 | @Prop() 45 | slowestSpeed: number //最慢速度 46 | 47 | @Prop() 48 | readingTime: number //阅读时间 49 | 50 | @Prop() 51 | difficultyLevel: string //难度等级 52 | 53 | @Prop() 54 | degree: number //难度 55 | 56 | @Prop() 57 | uuid: string //uuid 58 | 59 | @Prop({ default: 1 }) 60 | version: number 61 | 62 | @Prop() 63 | username: string //用户名 64 | 65 | @Prop() 66 | role: number //角色 67 | 68 | @Prop() 69 | content: string //题目内容 70 | 71 | @Prop({ type: Array }) 72 | data: any[] 73 | 74 | 75 | } 76 | 77 | export const SubjectSchema = SchemaFactory.createForClass(Subject) 78 | export const TableName = 'Subject' 79 | 80 | export type SubjectDocument = Subject & Document -------------------------------------------------------------------------------- /server/src/subject/subject.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Req } from '@nestjs/common'; 2 | import { SubjectService } from './subject.service'; 3 | import { CreateSubjectDto } from './dto/create-subject.dto'; 4 | import { UpdateSubjectDto } from './dto/update-subject.dto'; 5 | import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'; 6 | import { Request } from 'express'; 7 | @Controller('subject') 8 | export class SubjectController { 9 | constructor(private readonly subjectService: SubjectService) { } 10 | 11 | @UseGuards(JwtAuthGuard) 12 | @Post('create') 13 | create(@Req() createSubjectDto: Request) { 14 | return this.subjectService.create(createSubjectDto); 15 | } 16 | 17 | @Get('list') 18 | findAll() { 19 | return this.subjectService.findAll(); 20 | } 21 | 22 | @Get(':id') 23 | findOne(@Param('id') id: string) { 24 | return this.subjectService.findOne(+id); 25 | } 26 | 27 | @Patch(':id') 28 | update(@Param('id') id: string, @Body() updateSubjectDto: UpdateSubjectDto) { 29 | return this.subjectService.update(+id, updateSubjectDto); 30 | } 31 | 32 | @Delete(':id') 33 | remove(@Param('id') id: string) { 34 | return this.subjectService.remove(+id); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /server/src/subject/subject.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { SubjectService } from './subject.service'; 3 | import { SubjectController } from './subject.controller'; 4 | import { MongooseModule } from '@nestjs/mongoose' 5 | import {TableName,SubjectSchema} from './entities/subject.entity' 6 | @Module({ 7 | imports: [MongooseModule.forFeature([{ name: TableName, schema: SubjectSchema }])], 8 | controllers: [SubjectController], 9 | providers: [SubjectService], 10 | }) 11 | export class SubjectModule { } 12 | -------------------------------------------------------------------------------- /server/src/subject/subject.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CreateSubjectDto } from './dto/create-subject.dto'; 3 | import { UpdateSubjectDto } from './dto/update-subject.dto'; 4 | import { type SubjectDocument, TableName } from './entities/subject.entity' 5 | import { Model } from 'mongoose' 6 | import { InjectModel } from '@nestjs/mongoose' 7 | import { Request } from 'express'; 8 | @Injectable() 9 | export class SubjectService { 10 | 11 | constructor(@InjectModel(TableName) private readonly subject: Model) { } 12 | 13 | create(req: Request) { 14 | if (req.body.length == 0) { 15 | return { 16 | code: 400, 17 | msg: "请传入数据" 18 | } 19 | } else { 20 | const body = { 21 | ...req.body, 22 | uuid: req.user.uuid, 23 | username: req.user.username, 24 | role: req.user.role 25 | } 26 | return this.subject.create(body) 27 | } 28 | 29 | } 30 | 31 | findAll() { 32 | //查询所有 33 | return this.subject.find().limit(10).sort({ _id: -1 }).exec() 34 | } 35 | 36 | findOne(id: number) { 37 | return `This action returns a #${id} subject`; 38 | } 39 | 40 | update(id: number, updateSubjectDto: UpdateSubjectDto) { 41 | return `This action updates a #${id} subject`; 42 | } 43 | 44 | remove(id: number) { 45 | return `This action removes a #${id} subject`; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /server/src/user/dto/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreateUserDto { 2 | username: string 3 | account: string 4 | password: string 5 | age?: number 6 | role: number 7 | } 8 | -------------------------------------------------------------------------------- /server/src/user/dto/update-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { CreateUserDto } from './create-user.dto'; 3 | 4 | export class UpdateUserDto extends PartialType(CreateUserDto) {} 5 | -------------------------------------------------------------------------------- /server/src/user/entities/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose' 2 | import { Document } from 'mongoose' 3 | import { Authority } from '../../enum/authority'; 4 | import { UserLeval } from '@questionbank/config/user' 5 | @Schema() 6 | export class User { 7 | 8 | @Prop({ required: true }) 9 | uuid: string 10 | 11 | @Prop({ required: true }) 12 | username: string; 13 | 14 | @Prop({ required: true, unique: true }) 15 | account: string; 16 | 17 | @Prop({ required: true }) 18 | password: string; 19 | 20 | @Prop({ required: true, default: Date.now }) 21 | createTime: string; 22 | 23 | @Prop({ required: true, default: Date.now }) 24 | updateTime: string; 25 | 26 | @Prop({ required: true, enum:UserLeval, default: UserLeval.guest }) 27 | leval: UserLeval; 28 | 29 | @Prop({ required: false }) 30 | age: number; 31 | 32 | @Prop({ required: true, enum: Authority, default: Authority.USER }) 33 | role: Authority; 34 | } 35 | //schema 36 | export const UserSchema = SchemaFactory.createForClass(User) 37 | export const TableName = 'User' 38 | //type 39 | export type UserDocument = User & Document -------------------------------------------------------------------------------- /server/src/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Patch, Param, Delete, Session, Req, Res, UseGuards, Query } from '@nestjs/common'; 2 | import { UserService } from './user.service'; 3 | // import { CreateUserDto } from './dto/create-user.dto'; 4 | import { type User } from '@questionbank/config/user' 5 | import { UpdateUserDto } from './dto/update-user.dto'; 6 | import { LocalAuthGuard } from '../auth/guards/local-auth.guard'; 7 | import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; 8 | @Controller('user') 9 | export class UserController { 10 | constructor(private readonly userService: UserService) { } 11 | // 创建用户 12 | @Post('/create') 13 | create(@Body() createUserDto: User) { 14 | return this.userService.create(createUserDto); 15 | } 16 | // 获取验证码 17 | @Get('/code') 18 | createCode(@Req() req, @Res() res) { 19 | return this.userService.createCode(req, res); 20 | } 21 | // 登录 22 | @UseGuards(LocalAuthGuard) 23 | @Post('/login') 24 | login(@Body() body, @Session() session) { 25 | return this.userService.login(body, session); 26 | } 27 | // 获取用户信息 28 | @UseGuards(JwtAuthGuard) 29 | @Get('/profile') 30 | getInfo(@Req() req) { 31 | return this.userService.getInfo(req); 32 | } 33 | // 获取用户列表 34 | @UseGuards(JwtAuthGuard) 35 | @Get('/list') 36 | getUserList() { 37 | return this.userService.getUserList() 38 | } 39 | // 获取用户账号 40 | @Get('/account') 41 | getUserAccount(@Query() query: { keyWord: string }) { 42 | return this.userService.getUserAccount(query) 43 | } 44 | // 删除用户 45 | @UseGuards(JwtAuthGuard) 46 | @Delete('/delete/:id') 47 | removUser(@Param('id') id: string) { 48 | return this.userService.removUser(id); 49 | } 50 | 51 | @Patch('/update/:id') 52 | updateUser(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { 53 | return this.userService.updateUser(id, updateUserDto); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /server/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { type MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; 2 | import { projectSecret } from '@questionbank/config/secret'; 3 | import { UserService } from './user.service'; 4 | import { UserController } from './user.controller'; 5 | import { MongooseModule } from '@nestjs/mongoose' 6 | import { UserSchema, TableName } from './entities/user.entity'; 7 | import { AuthModule } from '../auth/auth.module'; 8 | import * as cookieParser from 'cookie-parser'; 9 | import * as session from 'express-session'; 10 | @Module({ 11 | imports: [ 12 | AuthModule, 13 | MongooseModule.forFeature([{ name: TableName, schema: UserSchema }]), 14 | ], 15 | controllers: [UserController], 16 | providers: [UserService], 17 | }) 18 | export class UserModule implements NestModule { 19 | configure(consumer: MiddlewareConsumer) { 20 | consumer.apply(cookieParser(), session({ 21 | secret: projectSecret, 22 | resave: false, 23 | saveUninitialized: false, 24 | })).forRoutes(UserController); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/src/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | // import { CreateUserDto } from './dto/create-user.dto'; 3 | import { type User } from '@questionbank/config/user' 4 | import { UpdateUserDto } from './dto/update-user.dto'; 5 | import { InjectModel } from '@nestjs/mongoose' 6 | import { Model } from 'mongoose'; 7 | import { TableName, type UserDocument } from './entities/user.entity'; 8 | import * as svgCaptcha from 'svg-captcha'; 9 | import * as uuid from 'uuid' 10 | import type { Request, Response } from 'express'; 11 | import { AuthService } from '../auth/auth.service'; 12 | @Injectable() 13 | export class UserService { 14 | constructor( 15 | @InjectModel(TableName) private readonly user: Model, 16 | private readonly jwt: AuthService 17 | ) { } 18 | create(createUserDto: User) { 19 | const createUser = new this.user({ ...createUserDto, uuid: uuid.v4(), password: createUserDto.password }); 20 | return createUser.save(); 21 | } 22 | 23 | 24 | createCode(req, res: Response) { 25 | const captcha = svgCaptcha.createMathExpr({ 26 | size: 4, 27 | ignoreChars: '0o1i', 28 | color: true, 29 | noise: 2, 30 | width: 100, 31 | height: 40, 32 | fontSize: 50, 33 | background: '#cc9966' 34 | }) 35 | 36 | req.session['captcha'] = captcha.text 37 | res.type('svg') 38 | res.send(captcha.data) 39 | } 40 | 41 | async login(data, session) { 42 | const { code } = data 43 | const captcha = session['captcha'] 44 | const result = { 45 | message: '', 46 | token: '' 47 | } 48 | if (code.toLowerCase() !== captcha.toLowerCase()) { 49 | result.message = '验证码错误' 50 | return result //优化 如果验证错误就不要查询数据库了 51 | } 52 | const user = await this.user.findOne({ account: data.account, password: data.password }) 53 | if (user) { 54 | result.message = '登录成功' 55 | result.token = this.jwt.createToken(user) 56 | } else { 57 | result.message = '用户名或密码错误' 58 | } 59 | return result 60 | } 61 | 62 | getInfo(req) { 63 | return req.user 64 | } 65 | 66 | getUserList() { 67 | return this.user.find() 68 | } 69 | 70 | async getUserAccount(queryString: { keyWord: string }) { 71 | const user = await this.user.findOne({ account: queryString.keyWord }) 72 | if (user) { 73 | return { 74 | code: 400, 75 | message: '该账号已存在' 76 | } 77 | } else { 78 | return { 79 | code: 200, 80 | message: '账号可用' 81 | } 82 | } 83 | } 84 | 85 | 86 | removUser(id: string) { 87 | return this.user.findByIdAndDelete({ _id: id }) 88 | } 89 | 90 | updateUser(id: string, updateUserDto: UpdateUserDto) { 91 | return this.user.findByIdAndUpdate({ _id: id }, updateUserDto) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /server/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /server/test/index.http: -------------------------------------------------------------------------------- 1 | # POST http://localhost:3000/user/create HTTP/1.1 2 | # Host: localhost:3000 3 | # Content-Type: application/json 4 | 5 | # { 6 | # "username": "Admin", 7 | # "account": "admin", 8 | # "password": "admin", 9 | # "role": 1 10 | # } 11 | 12 | # GET http://localhost:3000/user/profile HTTP/1.1 13 | # Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiYmQ0YWQ0Y2MtMjdkNC00ZDM4LWJjYTAtMmI0MTQ4NDRlZTI3IiwiaWF0IjoxNzE4MjM4ODkxLCJleHAiOjE3MTgyMzg5NTF9.meeeXDAZpKwLYXew6yev8zb2Q6w2VRTbX-847xFRey8 14 | 15 | 16 | # POST http://localhost:3000/user/create HTTP/1.1 17 | # Host: localhost:3000 18 | # Content-Type: application/json 19 | 20 | # { 21 | 22 | # "account": "admin", 23 | # "password": "admin", 24 | # "username": "Admin", 25 | # "role":1 26 | # } 27 | 28 | POST http://47.109.198.147:3000/user/create HTTP/1.1 29 | Host: http://47.109.198.147 30 | Content-Type: application/json 31 | 32 | { 33 | 34 | "account": "admin", 35 | "password": "21232f297a57a5a743894a0e4a801fc3", 36 | "username": "Admin", 37 | "role":1 38 | } 39 | 40 | 41 | -------------------------------------------------------------------------------- /server/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /server/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | --------------------------------------------------------------------------------