From 0d4d9f57683faa6b5e4d767773b1856665687e0e Mon Sep 17 00:00:00 2001 From: zhonghua Date: Sat, 28 Feb 2026 06:44:56 +0800 Subject: [PATCH] =?UTF-8?q?=E8=8F=9C=E5=8D=95=E9=A1=B6=E9=83=A8=E6=A0=8F?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- reading-platform-backend/.env | 2 +- reading-platform-backend/package.json | 4 +- reading-platform-backend/src/main.ts | 40 +++++------ reading-platform-backend/start-backend.sh | 18 ----- reading-platform-frontend/package.json | 5 +- .../playwright.config.ts | 27 ++++++++ reading-platform-frontend/src/components.d.ts | 30 ++++++++ .../src/views/admin/LayoutView.vue | 60 +++++++++++++--- .../src/views/auth/LoginView.vue | 34 +++++++++ .../src/views/parent/LayoutView.vue | 68 +++++++++++++++--- .../src/views/school/LayoutView.vue | 62 ++++++++++++++--- .../src/views/teacher/LayoutView.vue | 60 +++++++++++++--- reading-platform-frontend/start-frontend.sh | 18 ----- .../tests/e2e-login-flows.spec.ts | 69 +++++++++++++++++++ 14 files changed, 400 insertions(+), 97 deletions(-) delete mode 100644 reading-platform-backend/start-backend.sh create mode 100644 reading-platform-frontend/playwright.config.ts delete mode 100644 reading-platform-frontend/start-frontend.sh create mode 100644 reading-platform-frontend/tests/e2e-login-flows.spec.ts diff --git a/reading-platform-backend/.env b/reading-platform-backend/.env index 2d55981..84403a0 100644 --- a/reading-platform-backend/.env +++ b/reading-platform-backend/.env @@ -1,4 +1,4 @@ -DATABASE_URL="file:/Users/retirado/ccProgram/reading-platform-backend/dev.db" +DATABASE_URL="file:./dev.db" NODE_ENV=development PORT=3000 JWT_SECRET="your-super-secret-jwt-key" diff --git a/reading-platform-backend/package.json b/reading-platform-backend/package.json index e7767b4..c80c8af 100644 --- a/reading-platform-backend/package.json +++ b/reading-platform-backend/package.json @@ -24,7 +24,7 @@ "@nestjs/platform-express": "^10.4.22", "@nestjs/schedule": "^6.1.1", "@nestjs/throttler": "^5.2.0", - "@prisma/client": "^5.8.0", + "@prisma/client": "^5.22.0", "@types/multer": "^2.0.0", "ali-oss": "^6.18.1", "bcrypt": "^5.1.1", @@ -54,7 +54,7 @@ "@typescript-eslint/parser": "^6.18.0", "eslint": "^8.56.0", "jest": "^29.7.0", - "prisma": "^5.8.0", + "prisma": "^5.22.0", "source-map-support": "^0.5.21", "ts-jest": "^29.1.1", "ts-loader": "^9.5.1", diff --git a/reading-platform-backend/src/main.ts b/reading-platform-backend/src/main.ts index 3ca479a..a3e9977 100644 --- a/reading-platform-backend/src/main.ts +++ b/reading-platform-backend/src/main.ts @@ -1,21 +1,21 @@ -import { NestFactory } from '@nestjs/core'; -import { ValidationPipe, Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { NestExpressApplication } from '@nestjs/platform-express'; -import { join } from 'path'; -import { AppModule } from './app.module'; -import * as compression from 'compression'; -import { HttpExceptionFilter } from './common/filters/http-exception.filter'; +import { NestFactory } from "@nestjs/core"; +import { ValidationPipe, Logger } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { NestExpressApplication } from "@nestjs/platform-express"; +import { join } from "path"; +import { AppModule } from "./app.module"; +import * as compression from "compression"; +import { HttpExceptionFilter } from "./common/filters/http-exception.filter"; async function bootstrap() { const app = await NestFactory.create(AppModule, { - logger: ['error', 'warn', 'log', 'debug', 'verbose'], + logger: ["error", "warn", "log", "debug", "verbose"], }); - + console.log("bootstrap"); // 增加请求体大小限制(支持上传大文件 base64) // 1GB 文件编码后约 1.33GB,加上其他字段,设置为 1500mb - app.useBodyParser('json', { limit: '1500mb' }); - app.useBodyParser('urlencoded', { limit: '1500mb', extended: true }); + app.useBodyParser("json", { limit: "1500mb" }); + app.useBodyParser("urlencoded", { limit: "1500mb", extended: true }); const configService = app.get(ConfigService); @@ -28,7 +28,7 @@ async function bootstrap() { transformOptions: { enableImplicitConversion: true, }, - }), + }) ); // 全局异常过滤器 @@ -39,23 +39,23 @@ async function bootstrap() { // CORS app.enableCors({ - origin: configService.get('FRONTEND_URL') || 'http://localhost:5173', + origin: configService.get("FRONTEND_URL") || "http://localhost:5173", credentials: true, - methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', - allowedHeaders: 'Content-Type, Accept, Authorization', + methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS", + allowedHeaders: "Content-Type, Accept, Authorization", }); // 配置静态文件服务(用于访问上传的文件) // 使用绝对路径确保在编译后也能正确找到 uploads 目录 - const uploadsPath = join(__dirname, '..', '..', 'uploads'); + const uploadsPath = join(__dirname, "..", "..", "uploads"); app.useStaticAssets(uploadsPath, { - prefix: '/uploads/', + prefix: "/uploads/", }); // API前缀 - app.setGlobalPrefix('api/v1'); + app.setGlobalPrefix("api/v1"); - const port = configService.get('PORT') || 3000; + const port = configService.get("PORT") || 3000; await app.listen(port); console.log(` diff --git a/reading-platform-backend/start-backend.sh b/reading-platform-backend/start-backend.sh deleted file mode 100644 index 21ef2f8..0000000 --- a/reading-platform-backend/start-backend.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -# 后端启动脚本 -# 确保从正确目录启动 - -cd "$(dirname "$0")" - -echo "🚀 正在启动后端服务..." -echo "📂 工作目录: $(pwd)" - -# 检查 node_modules 是否存在 -if [ ! -d "node_modules" ]; then - echo "📦 正在安装依赖..." - npm install -fi - -# 启动后端 -npm run start:dev diff --git a/reading-platform-frontend/package.json b/reading-platform-frontend/package.json index d490b31..f93b892 100644 --- a/reading-platform-frontend/package.json +++ b/reading-platform-frontend/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "vue-tsc && vite build", "preview": "vite preview", - "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "test:e2e": "playwright test" }, "dependencies": { "@ant-design/icons-vue": "^7.0.1", @@ -27,10 +28,12 @@ "vue-router": "^4.3.0" }, "devDependencies": { + "@playwright/test": "^1.58.2", "@types/lodash-es": "^4.17.12", "@types/node": "^20.11.28", "@vitejs/plugin-vue": "^5.0.4", "@vue/tsconfig": "^0.5.1", + "@playwright/test": "^1.50.0", "sass-embedded": "^1.97.3", "typescript": "~5.4.0", "unplugin-auto-import": "^0.17.5", diff --git a/reading-platform-frontend/playwright.config.ts b/reading-platform-frontend/playwright.config.ts new file mode 100644 index 0000000..f04b0cc --- /dev/null +++ b/reading-platform-frontend/playwright.config.ts @@ -0,0 +1,27 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './tests', + timeout: 60 * 1000, + expect: { + timeout: 10 * 1000, + }, + use: { + baseURL: 'http://localhost:5173', + headless: true, + trace: 'on-first-retry', + }, + webServer: { + command: 'npm run dev', + port: 5173, + reuseExistingServer: true, + timeout: 120 * 1000, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}); + diff --git a/reading-platform-frontend/src/components.d.ts b/reading-platform-frontend/src/components.d.ts index bdd3aa4..49eaa1b 100644 --- a/reading-platform-frontend/src/components.d.ts +++ b/reading-platform-frontend/src/components.d.ts @@ -11,9 +11,14 @@ declare module 'vue' { AAvatar: typeof import('ant-design-vue/es')['Avatar'] ABadge: typeof import('ant-design-vue/es')['Badge'] AButton: typeof import('ant-design-vue/es')['Button'] + AButtonGroup: typeof import('ant-design-vue/es')['ButtonGroup'] ACard: typeof import('ant-design-vue/es')['Card'] ACheckbox: typeof import('ant-design-vue/es')['Checkbox'] + ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup'] ACol: typeof import('ant-design-vue/es')['Col'] + ACollapse: typeof import('ant-design-vue/es')['Collapse'] + ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel'] + ADatePicker: typeof import('ant-design-vue/es')['DatePicker'] ADescriptions: typeof import('ant-design-vue/es')['Descriptions'] ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem'] ADivider: typeof import('ant-design-vue/es')['Divider'] @@ -22,6 +27,8 @@ declare module 'vue' { AEmpty: typeof import('ant-design-vue/es')['Empty'] AForm: typeof import('ant-design-vue/es')['Form'] AFormItem: typeof import('ant-design-vue/es')['FormItem'] + AImage: typeof import('ant-design-vue/es')['Image'] + AImagePreviewGroup: typeof import('ant-design-vue/es')['ImagePreviewGroup'] AInput: typeof import('ant-design-vue/es')['Input'] AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] @@ -30,24 +37,47 @@ declare module 'vue' { ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent'] ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader'] ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider'] + AList: typeof import('ant-design-vue/es')['List'] + AListItem: typeof import('ant-design-vue/es')['ListItem'] + AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta'] AMenu: typeof import('ant-design-vue/es')['Menu'] AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider'] AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] AModal: typeof import('ant-design-vue/es')['Modal'] APageHeader: typeof import('ant-design-vue/es')['PageHeader'] + APagination: typeof import('ant-design-vue/es')['Pagination'] APopconfirm: typeof import('ant-design-vue/es')['Popconfirm'] + AProgress: typeof import('ant-design-vue/es')['Progress'] + ARadio: typeof import('ant-design-vue/es')['Radio'] + ARadioButton: typeof import('ant-design-vue/es')['RadioButton'] + ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] ARangePicker: typeof import('ant-design-vue/es')['RangePicker'] + ARate: typeof import('ant-design-vue/es')['Rate'] + AResult: typeof import('ant-design-vue/es')['Result'] ARow: typeof import('ant-design-vue/es')['Row'] ASelect: typeof import('ant-design-vue/es')['Select'] + ASelectOptGroup: typeof import('ant-design-vue/es')['SelectOptGroup'] ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] ASkeleton: typeof import('ant-design-vue/es')['Skeleton'] ASpace: typeof import('ant-design-vue/es')['Space'] + ASpin: typeof import('ant-design-vue/es')['Spin'] AStatistic: typeof import('ant-design-vue/es')['Statistic'] + AStep: typeof import('ant-design-vue/es')['Step'] + ASteps: typeof import('ant-design-vue/es')['Steps'] + ASubMenu: typeof import('ant-design-vue/es')['SubMenu'] + ASwitch: typeof import('ant-design-vue/es')['Switch'] ATable: typeof import('ant-design-vue/es')['Table'] + ATabPane: typeof import('ant-design-vue/es')['TabPane'] + ATabs: typeof import('ant-design-vue/es')['Tabs'] ATag: typeof import('ant-design-vue/es')['Tag'] ATextarea: typeof import('ant-design-vue/es')['Textarea'] + ATimeline: typeof import('ant-design-vue/es')['Timeline'] + ATimelineItem: typeof import('ant-design-vue/es')['TimelineItem'] + ATimeRangePicker: typeof import('ant-design-vue/es')['TimeRangePicker'] ATooltip: typeof import('ant-design-vue/es')['Tooltip'] + ATypographyText: typeof import('ant-design-vue/es')['TypographyText'] AUpload: typeof import('ant-design-vue/es')['Upload'] + AUploadDragger: typeof import('ant-design-vue/es')['UploadDragger'] FilePreviewModal: typeof import('./components/FilePreviewModal.vue')['default'] NotificationBell: typeof import('./components/NotificationBell.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] diff --git a/reading-platform-frontend/src/views/admin/LayoutView.vue b/reading-platform-frontend/src/views/admin/LayoutView.vue index bd89aee..4a8afd2 100644 --- a/reading-platform-frontend/src/views/admin/LayoutView.vue +++ b/reading-platform-frontend/src/views/admin/LayoutView.vue @@ -1,5 +1,5 @@