├── .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 |
7 |
19 |
20 |
21 |
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 |
8 |
9 |
{{ msg }}
10 |
11 | You’ve successfully created a project with
12 | Vite +
13 | Vue 3 . What's next?
14 |
15 |
16 |
17 |
18 |
42 |
--------------------------------------------------------------------------------
/app/src/components/TheWelcome.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Documentation
16 |
17 | Vue’s
18 | official documentation
19 | provides you with all information you need to get started.
20 |
21 |
22 |
23 |
24 |
25 |
26 | Tooling
27 |
28 | This project is served and bundled with
29 | Vite . The
30 | recommended IDE setup is
31 | VSCode +
32 | Volar . If
33 | you need to test your components and web pages, check out
34 | Cypress and
35 | Cypress Component Testing .
38 |
39 |
40 |
41 | More instructions are available in README.md
.
42 |
43 |
44 |
45 |
46 |
47 |
48 | Ecosystem
49 |
50 | Get official tools and libraries for your project:
51 | Pinia ,
52 | Vue Router ,
53 | Vue Test Utils , and
54 | Vue Dev Tools . If
55 | you need more resources, we suggest paying
56 | Awesome Vue
57 | a visit.
58 |
59 |
60 |
61 |
62 |
63 |
64 | Community
65 |
66 | Got stuck? Ask your question on
67 | Vue Land , our official
68 | Discord server, or
69 | StackOverflow . You should also subscribe to
72 | our mailing list and follow
73 | the official
74 | @vuejs
75 | twitter account for latest news in the Vue world.
76 |
77 |
78 |
79 |
80 |
81 |
82 | Support Vue
83 |
84 | As an independent project, Vue relies on community backing for its sustainability. You can help
85 | us by
86 | becoming a sponsor .
87 |
88 |
89 |
--------------------------------------------------------------------------------
/app/src/components/WelcomeItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
88 |
--------------------------------------------------------------------------------
/app/src/components/icons/IconCommunity.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/components/icons/IconDocumentation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/components/icons/IconEcosystem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/components/icons/IconSupport.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/components/icons/IconTooling.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
18 |
19 |
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 |
2 |
3 |
This is an about page
4 |
5 |
6 |
7 |
16 |
--------------------------------------------------------------------------------
/app/src/views/HomeView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
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 |
2 |
3 |
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 |
2 |
3 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/backmanger/src/layout/Header/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
43 |
44 |
--------------------------------------------------------------------------------
/backmanger/src/layout/Menu/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
24 |
25 |
--------------------------------------------------------------------------------
/backmanger/src/layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
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 |
2 |
3 | 新增课程代码
4 |
5 |
6 |
7 |
8 |
9 | {{ app?.proxy?.$timeFormat(scope.row.createTime) }}
10 |
11 |
12 |
13 |
14 | {{ app?.proxy?.$timeFormat(scope.row.updateTime) }}
15 |
16 |
17 |
18 |
19 |
20 | 编辑
21 |
22 | 是否删除{{ scope.row.username }}
23 |
24 | 取消
25 |
26 | 确定
27 |
28 |
29 |
30 | 删除
32 |
33 |
34 |
35 |
36 |
37 |
38 |
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 |
2 |
3 |
4 |
5 |
8 |
9 |
--------------------------------------------------------------------------------
/backmanger/src/views/knowledge/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
11 |
12 |
13 | {{ isData ? '修改' : '保存' }}
14 |
15 |
16 |
17 |
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 |
2 |
3 |
4 |
5 |
6 | 添加题目{{ data.length ?
7 | `(${data.length})` : '' }}
8 |
10 |
12 | 提交
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 试题{{ index + 1 }}: 删除
21 |
22 |
23 |
24 | 正确答案
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
75 |
76 |
--------------------------------------------------------------------------------
/backmanger/src/views/question/components/MultipleChoice.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 添加题目{{ data.length ?
7 | `(${data.length})` : '' }}
8 |
10 |
12 | 提交
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | 题目{{ index + 1 }}: 删除
23 |
24 |
25 |
26 | 选项A
27 |
28 |
29 |
30 | 选项B
31 |
32 |
33 |
34 | 选项C
35 |
36 |
37 |
38 | 选项D
39 |
40 |
41 |
42 | 正确答案
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 答案解析A
52 |
53 |
54 |
55 | 答案解析B
56 |
57 |
58 |
59 | 答案解析C
60 |
61 |
62 |
63 | 答案解析D
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
124 |
125 |
--------------------------------------------------------------------------------
/backmanger/src/views/question/components/MultipleChoiceQuestions.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 添加题目{{ data.length ?
7 | `(${data.length})` : '' }}
8 |
10 |
12 | 提交
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | 题目{{ index + 1 }}: 删除
23 |
24 |
25 |
26 | 选项A
27 |
28 |
29 |
30 | 选项B
31 |
32 |
33 |
34 | 选项C
35 |
36 |
37 |
38 | 选项D
39 |
40 |
41 |
42 | 正确答案
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 答案解析A
52 |
53 |
54 |
55 | 答案解析B
56 |
57 |
58 |
59 | 答案解析C
60 |
61 |
62 |
63 | 答案解析D
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
118 |
119 |
--------------------------------------------------------------------------------
/backmanger/src/views/question/components/ReadingQuestion.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 添加题目{{ data.length ?
7 | `(${data.length})` : '' }}
8 |
10 |
12 | 提交
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 选文内容{{ index + 1 }}: 删除
24 |
25 |
26 |
27 | 问题描述
28 |
29 |
30 |
31 | 参考答案
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | 添加
60 | 删除
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
118 |
119 |
--------------------------------------------------------------------------------
/backmanger/src/views/question/components/ShortAnswer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 添加题目{{ data.length ?
7 | `(${data.length})` : '' }}
8 |
10 |
12 | 提交
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 题目{{ index + 1 }}: 删除
21 |
22 |
23 |
24 | 参考答案
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 添加
53 | 删除
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
109 |
110 |
--------------------------------------------------------------------------------
/backmanger/src/views/question/components/TrueOfFalse.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 添加题目{{ data.length ?
7 | `(${data.length})` : '' }}
8 |
10 |
12 | 提交
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 判断项{{ index + 1 }}: 删除
21 |
22 |
23 |
24 | 正确答案
25 |
26 |
27 |
28 |
29 |
30 |
31 | 答案解析
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
80 |
81 |
--------------------------------------------------------------------------------
/backmanger/src/views/question/components/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
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 |
2 |
3 | 新增用户
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{ getLeval(scope.row.leval) }}
12 |
13 |
14 |
15 |
16 | {{ app?.proxy?.$timeFormat(scope.row.createTime) }}
17 |
18 |
19 |
20 |
21 | {{ app?.proxy?.$timeFormat(scope.row.updateTime) }}
22 |
23 |
24 |
25 |
26 | 编辑
27 |
28 | 是否删除{{ scope.row.username }}
29 |
30 | 取消
31 |
32 | 确定
33 |
34 |
35 |
36 | 删除
38 |
39 |
40 |
41 |
42 |
43 |
44 |
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 |
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 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
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 |
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 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
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 |
--------------------------------------------------------------------------------