create-bubbles 0.1.25 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +3 -9
- package/package.json +1 -1
- package/template-nextjs-vinext-eslint/Dockerfile +4 -4
- package/template-nextjs-vinext-eslint/README.md +6 -2
- package/template-nextjs-vinext-eslint/docker-compose.yaml +4 -4
- package/template-nextjs-vinext-eslint/eslint.config.js +2 -0
- package/template-nextjs-vinext-eslint/package.json +28 -26
- package/template-nextjs-vinext-eslint/pnpm-workspace.yaml +13 -1
- package/template-nextjs-vinext-eslint/src/utils/request/core/index.ts +63 -45
- package/template-nextjs-vinext-eslint/src/utils/request/core/utils.ts +12 -6
- package/template-nextjs-vinext-eslint/vite.config.ts +1 -1
- package/template-react-rsbuild-biome/.agents/rules/SVG/344/275/277/347/224/250.md +8 -0
- package/template-react-rsbuild-biome/AGENTS.md +11 -0
- package/template-react-rsbuild-biome/index.html +2 -2
- package/template-react-rsbuild-biome/package.json +25 -24
- package/template-react-rsbuild-biome/pnpm-workspace.yaml +5 -0
- package/template-react-rsbuild-biome/rsbuild.config.ts +1 -1
- package/template-react-rsbuild-biome/src/assets/icon/logo.svg +1 -1
- package/template-react-rsbuild-biome/src/assets/svg/draft.svg +4 -0
- package/template-react-rsbuild-biome/src/components/Icon/svg-icon/README.md +37 -0
- package/template-react-rsbuild-biome/src/components/Icon/svg-icon/SvgIcon.tsx +27 -0
- package/template-react-rsbuild-biome/src/env.d.ts +7 -0
- package/template-react-rsbuild-biome/src/utils/request/index.ts +3 -4
- package/template-react-rsbuild-biome/tsconfig.json +1 -2
- package/template-taro-react-oxc/.oxlintrc.json +0 -1
- package/template-taro-react-oxc/package.json +53 -52
- package/template-taro-react-oxc/pnpm-workspace.yaml +46 -0
- package/template-taro-vue-eslint/package.json +56 -56
- package/template-vp-monorepo-react-hono/apps/web/.agents/rules/SVG/344/275/277/347/224/250.md +8 -0
- package/template-vp-monorepo-react-hono/apps/web/AGENTS.md +11 -0
- package/template-vp-monorepo-react-hono/apps/web/package.json +1 -0
- package/template-vp-monorepo-react-hono/apps/web/src/assets/svg/draft.svg +4 -0
- package/template-vp-monorepo-react-hono/apps/web/src/components/Icon/svg-icon/README.md +37 -0
- package/template-vp-monorepo-react-hono/apps/web/src/components/Icon/svg-icon/SvgIcon.tsx +27 -0
- package/template-vp-monorepo-react-hono/apps/web/tsconfig.json +1 -1
- package/template-vp-monorepo-react-hono/apps/web/vite.config.ts +2 -0
- package/template-vp-monorepo-react-hono/pnpm-workspace.yaml +1 -0
- package/template-vp-monorepo-react-nestjs/.agents/rules//344/273/243/347/240/201/350/256/276/350/256/241.md +4 -0
- package/template-vp-monorepo-react-nestjs/.agents/rules//345/205/261/344/272/253/344/273/243/347/240/201.md +4 -0
- package/template-vp-monorepo-react-nestjs/.spaces/01./344/274/201/344/270/232/347/272/247/351/241/271/347/233/256/347/272/247//351/234/200/346/261/202/346/226/207/346/241/243.md +177 -0
- package/template-vp-monorepo-react-nestjs/.spaces/server/04.upload.md +4504 -0
- package/template-vp-monorepo-react-nestjs/.spaces/server/05./344/270/212/344/274/240/346/224/271/351/200/240/346/226/271/346/241/210/README.md +858 -0
- package/template-vp-monorepo-react-nestjs/.spaces/server/06./345/205/254/345/205/261/351/230/237/345/210/227/346/216/245/345/205/245/346/226/271/346/241/210/README.md +1890 -0
- package/template-vp-monorepo-react-nestjs/.spaces/server/06./345/205/254/345/205/261/351/230/237/345/210/227/346/216/245/345/205/245/346/226/271/346/241/210//344/270/212/344/274/240/346/270/205/347/220/206.md +1465 -0
- package/template-vp-monorepo-react-nestjs/AGENTS.md +8 -12
- package/template-vp-monorepo-react-nestjs/apps/server/.agents/rules/error-handling.md +84 -0
- package/template-vp-monorepo-react-nestjs/apps/server/.env.development +60 -0
- package/template-vp-monorepo-react-nestjs/apps/server/.env.production +16 -0
- package/template-vp-monorepo-react-nestjs/apps/server/AGENTS.md +9 -0
- package/template-vp-monorepo-react-nestjs/apps/server/drizzle/0000_military_colonel_america.sql +11 -0
- package/template-vp-monorepo-react-nestjs/apps/server/drizzle/0001_loud_siren.sql +27 -0
- package/template-vp-monorepo-react-nestjs/apps/server/drizzle/meta/0000_snapshot.json +97 -0
- package/template-vp-monorepo-react-nestjs/apps/server/drizzle/meta/0001_snapshot.json +354 -0
- package/template-vp-monorepo-react-nestjs/apps/server/drizzle/meta/_journal.json +20 -0
- package/template-vp-monorepo-react-nestjs/apps/server/package.json +4 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/app.module.ts +14 -2
- package/template-vp-monorepo-react-nestjs/apps/server/src/common/adapters/fastify.adapter.ts +6 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/config/index.ts +2 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/config/queue.config.ts +105 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/config/storage.config.ts +33 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/database/schema.ts +57 -1
- package/template-vp-monorepo-react-nestjs/apps/server/src/main.ts +1 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.service.ts +1 -3
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/dto/auth-repsponse.dto.ts +2 -2
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/queue-worker.bootstrap.ts +24 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/task-queue.constants.ts +24 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/task-queue.contracts.ts +35 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/task-queue.module.ts +56 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/task-queue.processor.ts +62 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/task-queue/task-queue.service.ts +58 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/test/error-catelog.spec.ts +34 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/dto/initiate-mutipart-upload.dto.ts +26 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/dto/upload-params.dto.ts +15 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/storage/exact-size.transform.ts +66 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/storage/minio-storage.adapter.ts +239 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/storage/storage.port.ts +61 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.constants.ts +23 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.controller.ts +109 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.errors.ts +80 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.module.ts +20 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.reponsitory.ts +166 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/upload.service.ts +696 -0
- package/template-vp-monorepo-react-nestjs/apps/server/src/modules/upload/utils/object-key.ts +10 -0
- package/template-vp-monorepo-react-nestjs/apps/server/test/task-queue.integration.spec.ts +120 -0
- package/template-vp-monorepo-react-nestjs/apps/server/test/task-queue.processor.spec.ts +63 -0
- package/template-vp-monorepo-react-nestjs/apps/server/test/task-queue.service.spec.ts +106 -0
- package/template-vp-monorepo-react-nestjs/apps/web/.agents/rules/SVG/344/275/277/347/224/250.md +8 -0
- package/template-vp-monorepo-react-nestjs/apps/web/.agents/rules//345/274/271/347/252/227/347/273/204/344/273/266.md +4 -0
- package/template-vp-monorepo-react-nestjs/apps/web/.agents/rules//346/226/207/344/273/266/346/213/206/345/210/206.md +4 -0
- package/template-vp-monorepo-react-nestjs/apps/web/.agents/rules//347/273/204/344/273/266/345/221/275/345/220/215.md +3 -0
- package/template-vp-monorepo-react-nestjs/apps/web/.agents/rules//350/241/250/346/240/274/351/241/265/351/235/242.md +11 -0
- package/template-vp-monorepo-react-nestjs/apps/web/.agents/rules//351/241/265/351/235/242/346/216/245/345/217/243.md +3 -0
- package/template-vp-monorepo-react-nestjs/apps/web/.env.dev +1 -1
- package/template-vp-monorepo-react-nestjs/apps/web/AGENTS.md +16 -0
- package/template-vp-monorepo-react-nestjs/apps/web/README.MD +153 -0
- package/template-vp-monorepo-react-nestjs/apps/web/index.html +3 -2
- package/template-vp-monorepo-react-nestjs/apps/web/package.json +6 -1
- package/template-vp-monorepo-react-nestjs/apps/web/public/favicon.svg +9 -1
- package/template-vp-monorepo-react-nestjs/apps/web/src/App.module.css +5 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/App.tsx +16 -9
- package/template-vp-monorepo-react-nestjs/apps/web/src/api/auth.ts +9 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/assets/svg/draft.svg +4 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/DraftProTable/DraftProTable.module.css +78 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/DraftProTable/DraftProTable.tsx +99 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/DraftProTable/README.md +34 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/FullHeightProTable/FullHeightProTable.module.css +54 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/FullHeightProTable/FullHeightProTable.tsx +41 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/Icon/svg-icon/README.md +37 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/Icon/svg-icon/SvgIcon.tsx +27 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/Loading/PageLoading.module.css +7 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/components/Loading/PageLoading.tsx +2 -1
- package/template-vp-monorepo-react-nestjs/apps/web/src/config/theme.ts +20 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/layouts/BasicLayout.module.css +17 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/layouts/BasicLayout.tsx +106 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/i18n/index.module.css +23 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/i18n/index.tsx +53 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/components/ProjectFormDialog.tsx +132 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/config/columns.tsx +121 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/config/index.ts +77 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/components/DraftProjectFormDialog.tsx +140 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/config/columns.tsx +127 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/config/index.ts +39 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/config/projects.ts +48 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/index.module.css +49 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/draft/index.tsx +163 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/index.module.css +53 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/examples/pro-table/index.tsx +135 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/home/index.module.css +23 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/home/index.tsx +26 -43
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/login/api.ts +11 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/login/index.tsx +183 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/pages/login/login.css +164 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/router/index.tsx +36 -12
- package/template-vp-monorepo-react-nestjs/apps/web/src/styles/index.css +11 -4
- package/template-vp-monorepo-react-nestjs/apps/web/src/utils/request/core/index.ts +55 -47
- package/template-vp-monorepo-react-nestjs/apps/web/src/utils/request/index.ts +10 -5
- package/template-vp-monorepo-react-nestjs/apps/web/src/utils/storage/cookie.ts +50 -0
- package/template-vp-monorepo-react-nestjs/apps/web/src/utils/storage/session.ts +33 -0
- package/template-vp-monorepo-react-nestjs/apps/web/test/draft-projects.spec.ts +89 -0
- package/template-vp-monorepo-react-nestjs/apps/web/test/request.spec.ts +145 -0
- package/template-vp-monorepo-react-nestjs/apps/web/test/storage.spec.ts +108 -0
- package/template-vp-monorepo-react-nestjs/apps/web/test/tsconfig.json +7 -0
- package/template-vp-monorepo-react-nestjs/apps/web/tsconfig.json +1 -1
- package/template-vp-monorepo-react-nestjs/apps/web/vite.config.ts +7 -0
- package/template-vp-monorepo-react-nestjs/apps/web-vue/tsconfig.tsbuildinfo +1 -0
- package/template-vp-monorepo-react-nestjs/docker-compose.yml +19 -0
- package/template-vp-monorepo-react-nestjs/mise.toml +1 -1
- package/template-vp-monorepo-react-nestjs/package.json +1 -6
- package/template-vp-monorepo-react-nestjs/pnpm-workspace.yaml +9 -1
- package/template-vp-react/.agents/rules/SVG/344/275/277/347/224/250.md +8 -0
- package/template-vp-react/AGENTS.md +11 -0
- package/template-vp-react/commitlint.config.js +0 -0
- package/template-vp-react/package.json +21 -26
- package/template-vp-react/pnpm-workspace.yaml +6 -0
- package/template-vp-react/src/assets/svg/draft.svg +4 -0
- package/template-vp-react/src/components/Icon/svg-icon/README.md +37 -0
- package/template-vp-react/src/components/Icon/svg-icon/SvgIcon.tsx +27 -0
- package/template-vp-react/src/utils/request/index.ts +18 -5
- package/template-vp-react/tsconfig.json +2 -3
- package/template-vp-react/vite.config.ts +2 -0
- package/template-vp-react-shadcn/.agents/rules/SVG/344/275/277/347/224/250.md +8 -0
- package/template-vp-react-shadcn/.vscode/settings.json +2 -2
- package/template-vp-react-shadcn/{agents.md → AGENTS.md} +12 -2
- package/template-vp-react-shadcn/README.md +41 -0
- package/template-vp-react-shadcn/commitlint.config.js +1 -1
- package/template-vp-react-shadcn/components.json +1 -1
- package/template-vp-react-shadcn/package.json +25 -32
- package/template-vp-react-shadcn/pnpm-workspace.yaml +10 -0
- package/template-vp-react-shadcn/src/App.tsx +10 -10
- package/template-vp-react-shadcn/src/assets/svg/draft.svg +4 -0
- package/template-vp-react-shadcn/src/components/Icon/svg-icon/README.md +37 -0
- package/template-vp-react-shadcn/src/components/Icon/svg-icon/SvgIcon.tsx +27 -0
- package/template-vp-react-shadcn/src/components/Loading/PageLoading.tsx +4 -4
- package/template-vp-react-shadcn/src/components/Player/VideoJS/index.tsx +94 -78
- package/template-vp-react-shadcn/src/components/Player/index.ts +1 -1
- package/template-vp-react-shadcn/src/components/ui/button.tsx +25 -32
- package/template-vp-react-shadcn/src/components/ui/spinner.tsx +8 -7
- package/template-vp-react-shadcn/src/components/ui/toast.tsx +229 -0
- package/template-vp-react-shadcn/src/lib/utils.ts +3 -3
- package/template-vp-react-shadcn/src/main.tsx +5 -4
- package/template-vp-react-shadcn/src/pages/home/index.tsx +43 -35
- package/template-vp-react-shadcn/src/router/index.tsx +15 -15
- package/template-vp-react-shadcn/src/styles/index.css +4 -4
- package/template-vp-react-shadcn/src/styles/shadcn.css +2 -2
- package/template-vp-react-shadcn/src/utils/env/index.tsx +2 -2
- package/template-vp-react-shadcn/src/utils/request/alova-core/index.ts +102 -30
- package/template-vp-react-shadcn/src/utils/request/alova-core/utils.ts +5 -2
- package/template-vp-react-shadcn/src/utils/request/index.ts +32 -19
- package/template-vp-react-shadcn/src/utils/request/readme.md +41 -38
- package/template-vp-react-shadcn/tsconfig.json +2 -3
- package/template-vp-react-shadcn/vite.config.ts +29 -15
- package/template-vp-vue-eslint-vapor/package.json +26 -33
- package/template-vp-vue-eslint-vapor/pnpm-workspace.yaml +29 -1
- package/template-vue-vp-eslint/README.md +3 -10
- package/template-vue-vp-eslint/package.json +27 -34
- package/template-vue-vp-eslint/pnpm-workspace.yaml +27 -2
- package/template-vue-vp-eslint/src/layout/default/index.vue +4 -3
- package/template-vp-monorepo-react-nestjs/.agents/auth.config.ts +0 -0
- package/template-vp-monorepo-react-nestjs/.spaces/server/todo.md +0 -991
- package/template-vp-monorepo-react-nestjs/apps/server/README.md +0 -3
- package/template-vp-react-shadcn/.oxfmtrc.jsonc +0 -8
- package/template-vp-react-shadcn/.oxlintrc.jsonc +0 -3
- package/template-vp-react-shadcn/src/components/ui/sonner.tsx +0 -43
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import { getQueueToken } from '@nestjs/bullmq'
|
|
3
|
+
import { ConfigModule } from '@nestjs/config'
|
|
4
|
+
import { Test, type TestingModule } from '@nestjs/testing'
|
|
5
|
+
import { type Queue, QueueEvents } from 'bullmq'
|
|
6
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vite-plus/test'
|
|
7
|
+
import queueConfig from '@/config/queue.config'
|
|
8
|
+
import { TaskQueueModule } from '@/modules/task-queue/task-queue.module'
|
|
9
|
+
import { TaskQueueService } from '@/modules/task-queue/task-queue.service'
|
|
10
|
+
import { TASK_JOB_NAMES, TASK_QUEUE_NAME } from '@/modules/task-queue/task-queue.constants'
|
|
11
|
+
import { ENV_ARR } from '@/utils/env-arr'
|
|
12
|
+
|
|
13
|
+
const describeQueueIntegration =
|
|
14
|
+
process.env.RUN_QUEUE_INTEGRATION === 'true' ? describe : describe.skip
|
|
15
|
+
|
|
16
|
+
describeQueueIntegration('TaskQueue Redis integration', () => {
|
|
17
|
+
let testingModule: TestingModule | undefined
|
|
18
|
+
let queue: Queue | undefined
|
|
19
|
+
let queueEvents: QueueEvents | undefined
|
|
20
|
+
let taskQueueService: TaskQueueService | undefined
|
|
21
|
+
|
|
22
|
+
const previousPrefix = process.env.QUEUE_PREFIX
|
|
23
|
+
const previousWorkerEnabled = process.env.QUEUE_WORKER_ENABLED
|
|
24
|
+
const uniquePrefix = 'bubbles:test:' + randomUUID() + ':queue'
|
|
25
|
+
|
|
26
|
+
beforeAll(async () => {
|
|
27
|
+
if (process.env.NODE_ENV === 'production') {
|
|
28
|
+
throw new Error('Queue integration test must not run in production')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
process.env.QUEUE_PREFIX = uniquePrefix
|
|
32
|
+
process.env.QUEUE_WORKER_ENABLED = 'true'
|
|
33
|
+
|
|
34
|
+
testingModule = await Test.createTestingModule({
|
|
35
|
+
imports: [
|
|
36
|
+
ConfigModule.forRoot({
|
|
37
|
+
isGlobal: true,
|
|
38
|
+
envFilePath: ENV_ARR,
|
|
39
|
+
load: [queueConfig],
|
|
40
|
+
}),
|
|
41
|
+
TaskQueueModule,
|
|
42
|
+
],
|
|
43
|
+
}).compile()
|
|
44
|
+
|
|
45
|
+
// 必须调用。compile() 本身不会触发
|
|
46
|
+
// QueueWorkerBootstrap.onApplicationBootstrap()
|
|
47
|
+
await testingModule.init()
|
|
48
|
+
|
|
49
|
+
queue = testingModule.get<Queue>(getQueueToken(TASK_QUEUE_NAME))
|
|
50
|
+
taskQueueService = testingModule.get(TaskQueueService)
|
|
51
|
+
|
|
52
|
+
// QueueEvents 必须与 Queue 使用完全相同的
|
|
53
|
+
// connection、DB 和 prefix。
|
|
54
|
+
queueEvents = new QueueEvents(TASK_QUEUE_NAME, {
|
|
55
|
+
connection: queue.opts.connection,
|
|
56
|
+
prefix: queue.opts.prefix,
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// 必须先 ready,再投递,避免错过完成事件。
|
|
60
|
+
await queueEvents.waitUntilReady()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('completes a Job through real Redis', async () => {
|
|
64
|
+
if (!queue || !queueEvents || !taskQueueService) {
|
|
65
|
+
throw new Error('Queue integration test was not initialized')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const queued = await taskQueueService.enqueue(
|
|
69
|
+
TASK_JOB_NAMES.QUEUE_SMOKE,
|
|
70
|
+
{
|
|
71
|
+
marker: 'redis-smoke',
|
|
72
|
+
enqueuedAt: new Date().toISOString(),
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
jobId: 'queue-smoke-' + randomUUID(),
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
const job = await queue.getJob(queued.jobId)
|
|
80
|
+
|
|
81
|
+
if (!job) {
|
|
82
|
+
throw new Error('Smoke Job was not found')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const result = await job.waitUntilFinished(queueEvents, 10_000)
|
|
86
|
+
|
|
87
|
+
expect(result.marker).toBe('redis-smoke')
|
|
88
|
+
}, 15_000)
|
|
89
|
+
|
|
90
|
+
afterAll(async () => {
|
|
91
|
+
try {
|
|
92
|
+
await queueEvents?.close()
|
|
93
|
+
|
|
94
|
+
if (queue) {
|
|
95
|
+
if (queue.opts.prefix !== uniquePrefix) {
|
|
96
|
+
throw new Error('Refusing to clean a non-test Queue prefix')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 这里只清理由随机 Prefix 创建的测试队列。
|
|
100
|
+
await queue.obliterate({ force: true })
|
|
101
|
+
}
|
|
102
|
+
} finally {
|
|
103
|
+
try {
|
|
104
|
+
await testingModule?.close()
|
|
105
|
+
} finally {
|
|
106
|
+
if (previousPrefix === undefined) {
|
|
107
|
+
delete process.env.QUEUE_PREFIX
|
|
108
|
+
} else {
|
|
109
|
+
process.env.QUEUE_PREFIX = previousPrefix
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (previousWorkerEnabled === undefined) {
|
|
113
|
+
delete process.env.QUEUE_WORKER_ENABLED
|
|
114
|
+
} else {
|
|
115
|
+
process.env.QUEUE_WORKER_ENABLED = previousWorkerEnabled
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Job } from 'bullmq'
|
|
2
|
+
import { UnrecoverableError } from 'bullmq'
|
|
3
|
+
import { describe, expect, it, vi } from 'vite-plus/test'
|
|
4
|
+
import { TASK_JOB_NAMES } from '@/modules/task-queue/task-queue.constants'
|
|
5
|
+
import { TaskQueueProcessor } from '@/modules/task-queue/task-queue.processor'
|
|
6
|
+
|
|
7
|
+
function createJob(name: string, data: unknown): Job {
|
|
8
|
+
return { name, data } as unknown as Job
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('TaskQueueProcessor', () => {
|
|
12
|
+
it('completes a smoke task with marker and processedAt', async () => {
|
|
13
|
+
const processor = new TaskQueueProcessor()
|
|
14
|
+
|
|
15
|
+
const result = await processor.process(
|
|
16
|
+
createJob(TASK_JOB_NAMES.QUEUE_SMOKE, {
|
|
17
|
+
marker: 'smoke-marker',
|
|
18
|
+
enqueuedAt: new Date().toISOString(),
|
|
19
|
+
}),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
expect(result).toMatchObject({ marker: 'smoke-marker' })
|
|
23
|
+
expect(typeof (result as { processedAt: string }).processedAt).toBe('string')
|
|
24
|
+
expect(Number.isNaN(Date.parse((result as { processedAt: string }).processedAt))).toBe(false)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('throws UnrecoverableError for invalid payload', async () => {
|
|
28
|
+
const processor = new TaskQueueProcessor()
|
|
29
|
+
|
|
30
|
+
await expect(
|
|
31
|
+
processor.process(
|
|
32
|
+
createJob(TASK_JOB_NAMES.QUEUE_SMOKE, {
|
|
33
|
+
marker: '',
|
|
34
|
+
enqueuedAt: 'not-a-datetime',
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
37
|
+
).rejects.toBeInstanceOf(UnrecoverableError)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('throws UnrecoverableError for unknown task names', async () => {
|
|
41
|
+
const processor = new TaskQueueProcessor()
|
|
42
|
+
|
|
43
|
+
await expect(processor.process(createJob('unknown.task.v1', { any: 'data' }))).rejects.toThrow(
|
|
44
|
+
'Unsupported task name',
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('does not swallow ordinary handler errors', async () => {
|
|
49
|
+
const processor = new TaskQueueProcessor()
|
|
50
|
+
const toISOString = vi.spyOn(Date.prototype, 'toISOString').mockImplementation(() => {
|
|
51
|
+
throw new Error('clock failure')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
await expect(
|
|
55
|
+
processor.process(
|
|
56
|
+
createJob(TASK_JOB_NAMES.QUEUE_SMOKE, {
|
|
57
|
+
marker: 'smoke-marker',
|
|
58
|
+
enqueuedAt: '2026-01-01T00:00:00.000Z',
|
|
59
|
+
}),
|
|
60
|
+
),
|
|
61
|
+
).rejects.toThrow('clock failure')
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { Queue } from 'bullmq'
|
|
2
|
+
import { describe, expect, it, vi } from 'vite-plus/test'
|
|
3
|
+
import { TASK_JOB_NAMES } from '@/modules/task-queue/task-queue.constants'
|
|
4
|
+
import { TaskQueueService } from '@/modules/task-queue/task-queue.service'
|
|
5
|
+
|
|
6
|
+
function createQueue(add: ReturnType<typeof vi.fn> = vi.fn()) {
|
|
7
|
+
return { add } as unknown as Queue
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function validPayload() {
|
|
11
|
+
return {
|
|
12
|
+
marker: 'test',
|
|
13
|
+
enqueuedAt: new Date().toISOString(),
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('TaskQueueService', () => {
|
|
18
|
+
it('enqueues a typed task and converts jobId and delayMs', async () => {
|
|
19
|
+
const add = vi.fn().mockResolvedValue({ id: 'queue-smoke-1' })
|
|
20
|
+
const service = new TaskQueueService(createQueue(add))
|
|
21
|
+
|
|
22
|
+
const payload = validPayload()
|
|
23
|
+
|
|
24
|
+
const result = await service.enqueue(TASK_JOB_NAMES.QUEUE_SMOKE, payload, {
|
|
25
|
+
jobId: 'queue-smoke-1',
|
|
26
|
+
delayMs: 100,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
expect(add).toHaveBeenCalledOnce()
|
|
30
|
+
expect(add).toHaveBeenCalledWith(TASK_JOB_NAMES.QUEUE_SMOKE, payload, {
|
|
31
|
+
jobId: 'queue-smoke-1',
|
|
32
|
+
delay: 100,
|
|
33
|
+
})
|
|
34
|
+
expect(result).toEqual({
|
|
35
|
+
queueName: 'server-tasks',
|
|
36
|
+
jobId: 'queue-smoke-1',
|
|
37
|
+
name: TASK_JOB_NAMES.QUEUE_SMOKE,
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('omits jobId and delay options when not provided', async () => {
|
|
42
|
+
const add = vi.fn().mockResolvedValue({ id: 'queue-smoke-2' })
|
|
43
|
+
const service = new TaskQueueService(createQueue(add))
|
|
44
|
+
const payload = validPayload()
|
|
45
|
+
|
|
46
|
+
await service.enqueue(TASK_JOB_NAMES.QUEUE_SMOKE, payload)
|
|
47
|
+
|
|
48
|
+
expect(add).toHaveBeenCalledWith(TASK_JOB_NAMES.QUEUE_SMOKE, payload, {})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('rejects jobId containing a colon', async () => {
|
|
52
|
+
const add = vi.fn()
|
|
53
|
+
const service = new TaskQueueService(createQueue(add))
|
|
54
|
+
|
|
55
|
+
await expect(
|
|
56
|
+
service.enqueue(TASK_JOB_NAMES.QUEUE_SMOKE, validPayload(), { jobId: 'bad:id' }),
|
|
57
|
+
).rejects.toThrow('colon')
|
|
58
|
+
|
|
59
|
+
expect(add).not.toHaveBeenCalled()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('rejects negative delayMs', async () => {
|
|
63
|
+
const add = vi.fn()
|
|
64
|
+
const service = new TaskQueueService(createQueue(add))
|
|
65
|
+
|
|
66
|
+
await expect(
|
|
67
|
+
service.enqueue(TASK_JOB_NAMES.QUEUE_SMOKE, validPayload(), { delayMs: -1 }),
|
|
68
|
+
).rejects.toThrow('non-negative')
|
|
69
|
+
|
|
70
|
+
expect(add).not.toHaveBeenCalled()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('rejects non-safe-integer delayMs', async () => {
|
|
74
|
+
const add = vi.fn()
|
|
75
|
+
const service = new TaskQueueService(createQueue(add))
|
|
76
|
+
|
|
77
|
+
await expect(
|
|
78
|
+
service.enqueue(TASK_JOB_NAMES.QUEUE_SMOKE, validPayload(), { delayMs: 1.5 }),
|
|
79
|
+
).rejects.toThrow('non-negative')
|
|
80
|
+
|
|
81
|
+
expect(add).not.toHaveBeenCalled()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('rejects invalid payload before reaching the queue', async () => {
|
|
85
|
+
const add = vi.fn()
|
|
86
|
+
const service = new TaskQueueService(createQueue(add))
|
|
87
|
+
|
|
88
|
+
await expect(
|
|
89
|
+
service.enqueue(TASK_JOB_NAMES.QUEUE_SMOKE, {
|
|
90
|
+
marker: '',
|
|
91
|
+
enqueuedAt: 'not-a-datetime',
|
|
92
|
+
}),
|
|
93
|
+
).rejects.toThrow()
|
|
94
|
+
|
|
95
|
+
expect(add).not.toHaveBeenCalled()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('throws when BullMQ returns a Job without an id', async () => {
|
|
99
|
+
const add = vi.fn().mockResolvedValue({ id: undefined })
|
|
100
|
+
const service = new TaskQueueService(createQueue(add))
|
|
101
|
+
|
|
102
|
+
await expect(service.enqueue(TASK_JOB_NAMES.QUEUE_SMOKE, validPayload())).rejects.toThrow(
|
|
103
|
+
'without an id',
|
|
104
|
+
)
|
|
105
|
+
})
|
|
106
|
+
})
|
package/template-vp-monorepo-react-nestjs/apps/web/.agents/rules/SVG/344/275/277/347/224/250.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# SVG 使用
|
|
2
|
+
|
|
3
|
+
- 图标通过 `*.svg?react` 导入,使用 `@/components/Icon/svg-icon/SvgIcon` 渲染;用 `size`、`color` 控制大小和颜色,默认继承父级。
|
|
4
|
+
- 公共图标放 `src/assets/svg/`,页面专用图标放所属页面的 `assets/svg/`。
|
|
5
|
+
- 保留 `viewBox`;单色图标用 `currentColor`,保留 `none`、多色和渐变。
|
|
6
|
+
- 图片或背景直接导入 `*.svg` 获取 URL。
|
|
7
|
+
|
|
8
|
+
参数、无障碍设置及示例见 [SvgIcon 使用说明](../../src/components/Icon/svg-icon/README.md)。
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# 表格页面
|
|
2
|
+
|
|
3
|
+
适用于以表格为主体,包含查询、新增、编辑、删除等操作的列表页面。
|
|
4
|
+
|
|
5
|
+
- 普通表格类增删改查页面统一使用 [FullHeightProTable](../../src/components/FullHeightProTable/FullHeightProTable.tsx)。包含草稿或草稿箱功能时统一使用 [DraftProTable](../../src/components/DraftProTable/DraftProTable.tsx),使用前阅读其 [组件说明](../../src/components/DraftProTable/README.md)。业务页面复用现有封装,不直接使用原生 `ProTable` 重复实现同类布局和草稿入口。
|
|
6
|
+
- 沿用组件的固定表头、表体独立滚动、分页贴底布局。父容器需有明确高度,位于 flex 布局时按需设置 `min-h-0`;组件内部已包含 `w-full h-full p-[16px]`,页面不要重复添加同一层间距或改成整个表格外层滚动。
|
|
7
|
+
- `columns`、`request`、`params`、`actionRef`、`pagination`、`toolBarRender` 等沿用 ProTable 参数,并保留行数据和查询参数的类型。查询、新增、编辑、删除、表单及权限等业务逻辑由页面负责,不放进通用表格组件。
|
|
8
|
+
- 使用 `DraftProTable` 时,由页面受控维护 `view: 'list' | 'draft'`,默认显示 `list`,通过 `onViewChange` 切换。草稿箱入口放在刷新、列设置旁,点击选中并显示草稿,再次点击取消选中、返回列表。
|
|
9
|
+
- 通过 `draftCount` 传入草稿总数,使用组件自带的右上角数字角标;0 条或未传入时隐藏,超过 99 显示 `99+`,不使用 Tooltip 展示数量。
|
|
10
|
+
- 使用 `request` 时,通过 `params` 传递当前列表或草稿视图对应的查询条件。切换视图时由页面重置分页和行选择,并按业务需要重置筛选条件。草稿保存、继续编辑、提交、数据刷新和数量更新由页面及其接口处理。
|
|
11
|
+
- 基础用法参考 [基础示例](../../src/pages/examples/pro-table/index.tsx),草稿用法参考 [草稿示例](../../src/pages/examples/pro-table/draft/index.tsx)。示例中的本地存储和固定演示用户仅用于 Demo,实际业务按项目接口和权限规则接入。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
VITE_API_URL = http://localhost:
|
|
1
|
+
VITE_API_URL = http://localhost:13000
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
## 适用范围
|
|
2
|
+
|
|
3
|
+
本文件适用于 `apps/web` 及其子目录,同时遵循[根目录 AGENTS.md](../../AGENTS.md)中的通用规则。
|
|
4
|
+
|
|
5
|
+
## 按需规则
|
|
6
|
+
|
|
7
|
+
根据当前任务匹配下表,在执行相关操作前读取对应规则;命中多项时分别读取。不要预先加载整个 `.agents/rules/` 目录;任务范围变化时补读新命中的规则。
|
|
8
|
+
|
|
9
|
+
| 适用条件 | 规则文件 |
|
|
10
|
+
| ------------------------------------------------------------------ | ------------------------------------- |
|
|
11
|
+
| 当前修改的 Web 代码文件已超过或将超过 300 行 | [文件拆分](.agents/rules/文件拆分.md) |
|
|
12
|
+
| 新增、修改或移动页面相关的 API 请求代码 | [页面接口](.agents/rules/页面接口.md) |
|
|
13
|
+
| 新增、修改、移动或重命名 `components` 下的组件文件、组件目录或组件 | [组件命名](.agents/rules/组件命名.md) |
|
|
14
|
+
| 新增、封装或修改弹窗组件,或调整其打开、关闭及传参方式 | [弹窗组件](.agents/rules/弹窗组件.md) |
|
|
15
|
+
| 新增、修改或重构表格类增删改查列表页面(含草稿功能) | [表格页面](.agents/rules/表格页面.md) |
|
|
16
|
+
| 新增、修改或使用 SVG | [SVG 使用](.agents/rules/SVG使用.md) |
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# 通用平台 Web
|
|
2
|
+
|
|
3
|
+
基于 React 19、React Router 和 Vite+,作为通用业务平台的前端入口,不限定行业或业务类型。
|
|
4
|
+
|
|
5
|
+
## 开发
|
|
6
|
+
|
|
7
|
+
在 Monorepo 根目录执行:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm --filter web dev
|
|
11
|
+
pnpm --filter web build
|
|
12
|
+
pnpm --filter web test --run
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
开发地址为 `http://localhost:9999`。`/api` 沿用现有代理,转发到 NestJS Server
|
|
16
|
+
(开发环境为 `http://localhost:13000`)。工作台提供 ProTable 项目管理演示入口,演示页无需启动后端。
|
|
17
|
+
|
|
18
|
+
## 组件库与版本
|
|
19
|
+
|
|
20
|
+
依赖版本统一在根目录 `pnpm-workspace.yaml` 的 `catalog` 管理:
|
|
21
|
+
|
|
22
|
+
| 依赖 | 接入版本 | 用途 |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| `antd` | `^6.6.2` | 基础组件、中文语言包与主题 |
|
|
25
|
+
| `@ant-design/pro-components` | `3.1.14-7`(固定 beta 版本) | ProLayout、PageContainer、ProCard,以及后续的 ProTable / ProForm |
|
|
26
|
+
| `@ant-design/icons` | `^6.3.4` | 平台导航图标 |
|
|
27
|
+
| `@ant-design/cssinjs` | `^2.1.2` | CSS 样式层级配置 |
|
|
28
|
+
|
|
29
|
+
接入时,ProComponents 的 `latest` 为 2.8.10,其 peerDependencies 仅声明支持 Ant Design 4/5;
|
|
30
|
+
`beta` 为 3.1.14-7,声明支持 Ant Design 6 和 React 18+。
|
|
31
|
+
因此固定使用兼容的 beta 版本,升级时需重新验证,不要直接安装 `@latest`。
|
|
32
|
+
ProComponents 3 的部分 API 已调整,例如 ProCard 使用 `variant="outlined"`,不再使用 `bordered`。
|
|
33
|
+
|
|
34
|
+
## 全局配置
|
|
35
|
+
|
|
36
|
+
- `src/App.tsx`:`StyleProvider → ConfigProvider → ProConfigProvider → AntdApp`。
|
|
37
|
+
Ant Design 使用 `zh_CN`,ProComponents 跟随 ConfigProvider 的语言;Day.js 使用 `zh-cn`。
|
|
38
|
+
- `src/config/theme.ts`:统一配置主题 token,Ant Design 与 ProComponents 共同使用。
|
|
39
|
+
- `src/styles/index.css`:声明 `theme → base → antd → antd-pro → components → utilities`
|
|
40
|
+
样式层级、引入基础重置并设置 `html / body / #root` 的全局基础样式,配合 `StyleProvider layer` 使用。
|
|
41
|
+
组件库样式在基础重置之后,业务样式和 Tailwind 工具类在组件库之后。
|
|
42
|
+
- 页面和组件的专属样式放在同目录的 `*.module.css`,通过 `styles.xxx` 引用;
|
|
43
|
+
`App.module.css` 管理应用容器,`layouts/BasicLayout.module.css` 管理布局品牌,
|
|
44
|
+
首页和国际化示例各自使用页面目录中的 `index.module.css`。
|
|
45
|
+
模块样式及其响应式规则统一放在 `@layer components` 中;层名控制覆盖顺序,CSS Modules 负责类名隔离。
|
|
46
|
+
- React 19 可直接使用 Ant Design 6,无需 Ant Design 5 的 React 19 兼容补丁。
|
|
47
|
+
|
|
48
|
+
需要消息提示、通知或确认弹窗时,在 AntdApp 子组件中使用上下文实例:
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { App, Button } from 'antd'
|
|
52
|
+
|
|
53
|
+
export default function MessageExample() {
|
|
54
|
+
const { message } = App.useApp()
|
|
55
|
+
return <Button onClick={() => void message.success('操作成功')}>显示提示</Button>
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
组件内通过上下文实例继承主题与语言设置;请求配置沿用直接调用 `message.success()` / `message.error()` 的简单写法。
|
|
60
|
+
|
|
61
|
+
## 布局与页面
|
|
62
|
+
|
|
63
|
+
- `src/layouts/BasicLayout.tsx`:ProLayout 基础布局、菜单与 React Router 的 Outlet。
|
|
64
|
+
- `/login`:独立登录页,使用 LoginForm / ProFormText,支持账号校验、密码显隐、提交状态与接口错误提示。
|
|
65
|
+
- `/home`:工作台,使用 PageContainer 与 ProCard,提供项目管理演示入口。
|
|
66
|
+
- `/examples/pro-table`:带菜单的 ProTable 项目管理演示,包含 18 条模拟数据,支持名称或编号搜索、
|
|
67
|
+
状态/负责人/优先级/日期筛选、排序、分页、列设置、新增、编辑、删除和批量删除。
|
|
68
|
+
数据仅保存在页面内存中,刷新或离开页面后恢复初始状态。新增和编辑使用 ModalForm,
|
|
69
|
+
通过组件 ref 的 `show(project?)` / `hide()` 打开和关闭,不请求后端接口。
|
|
70
|
+
- `/examples/i18n`:保留原有的独立语言作用域示例。
|
|
71
|
+
- 未匹配的地址显示 404,并可返回工作台。
|
|
72
|
+
|
|
73
|
+
新增页面时,在 `src/pages` 创建组件,在 `src/router/index.tsx` 注册懒加载子路由,
|
|
74
|
+
并在 BasicLayout 的 `menuRoutes` 添加菜单项。页面内使用 PageContainer,复用外层布局。
|
|
75
|
+
|
|
76
|
+
后续接入 NestJS 接口时,前后端接口类型和返回类型统一从 `shared/types` 导入
|
|
77
|
+
(源码为 `packages/shared/src/types`),公用方法从 `shared/utils` 导入
|
|
78
|
+
(源码为 `packages/shared/src/utils`)。ProTable 的 `request` 需要返回
|
|
79
|
+
`{ data, success, total }`,应在前端适配业务 API 的统一响应。
|
|
80
|
+
|
|
81
|
+
## 浏览器存储
|
|
82
|
+
|
|
83
|
+
- `src/utils/storage/cookie.ts`:`cookie.get / set / remove`,用于 Cookie 的读取、写入、更新和删除。
|
|
84
|
+
默认 `Path=/`、`SameSite=Lax`,HTTPS 下自动设置 `Secure`;支持过期时间、有效秒数和域名。
|
|
85
|
+
删除自定义作用域的 Cookie 时,传入与写入时相同的 `path`、`domain`。
|
|
86
|
+
- `src/utils/storage/session.ts`:`local` 对应 localStorage,`session` 对应 sessionStorage,
|
|
87
|
+
均提供 `get / set / remove / clear`。值自动转换为 JSON;不存在或 JSON 无效时读取返回 `null`。
|
|
88
|
+
`set` 同时用于新增和更新,浏览器存储失败时将错误交给调用方处理。
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { cookie } from '@/utils/storage/cookie'
|
|
92
|
+
import { local, session } from '@/utils/storage/session'
|
|
93
|
+
|
|
94
|
+
cookie.set('preference', 'compact')
|
|
95
|
+
cookie.get('preference')
|
|
96
|
+
cookie.remove('preference')
|
|
97
|
+
|
|
98
|
+
local.set('settings', { theme: 'light' })
|
|
99
|
+
local.get<{ theme: string }>('settings')
|
|
100
|
+
session.set('draft', { title: '草稿' })
|
|
101
|
+
session.remove('draft')
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 登录与会话
|
|
105
|
+
|
|
106
|
+
`src/pages/login/api.ts` 定义登录请求,`src/api/auth.ts` 定义布局使用的退出请求,
|
|
107
|
+
分别复用共享包中的 LoginRequest、LoginResult 和 LogoutResult:
|
|
108
|
+
|
|
109
|
+
| 接口 | 用途 |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| `POST /auth/login` | 使用 `account`、`password` 登录,返回不透明 Session Token |
|
|
112
|
+
| `POST /auth/logout` | 撤销当前 Session |
|
|
113
|
+
|
|
114
|
+
浏览器通过 `/api` 代理访问以上接口。Server 成功时直接返回业务数据,失败时返回 HTTP 错误状态和
|
|
115
|
+
`{ code, message, details? }`,因此 Web 请求默认 `isWrapped: false`。后续接其他包装格式的接口时,
|
|
116
|
+
可以在 method 的 `meta` 中覆盖 `isWrapped`。请求 core 默认使用 Axios,所有 2xx 响应进入
|
|
117
|
+
`responded.onSuccess`,仅解析数据及检查包装响应的业务 `code`;不再配置 `statusMap.success`。
|
|
118
|
+
非 2xx 错误进入 `responded.onError`,使用原生 `Error` 承载错误消息,并附加 `status` 和 `code`,
|
|
119
|
+
并通过 `statusMap.unAuthorized` 处理 HTTP 401。业务检查抛出的错误直接传给调用方,不会再次进入
|
|
120
|
+
全局 `onError`。自定义适配器须遵守相同的 HTTP 错误拒绝约定,并在 `error.response` 中保留响应。
|
|
121
|
+
页面自己展示错误时设置 `isShowErrorMessage: false`,避免重复提示。
|
|
122
|
+
|
|
123
|
+
- 登录令牌通过 `cookie` 工具保存在 `token` Cookie 中,过期时间使用接口返回的
|
|
124
|
+
`absoluteExpiresAt`;请求从 Cookie 读取令牌并携带 `Authorization: Bearer <token>`。
|
|
125
|
+
登录、退出和 401 清理统一使用 Cookie,不在 localStorage 或 sessionStorage 中保存令牌。
|
|
126
|
+
- 登录成功后跳转 `/home`;路由直接渲染页面,不进行前置会话校验或登录回跳。
|
|
127
|
+
- 顶部根据令牌是否存在显示登录或退出入口;退出时先请求 Server 撤销会话,成功后清除令牌并跳转登录页。
|
|
128
|
+
- 登录和退出请求关闭缓存与请求共享。
|
|
129
|
+
- 请求成功和失败提示直接写在 `successMessageFunc`、`errorMessageFunc` 中;
|
|
130
|
+
`unAuthorizedResponseFunc` 清除令牌、提示并跳转登录页。
|
|
131
|
+
- 请求配置不设置客户端超时,统一由 Nginx 控制。
|
|
132
|
+
|
|
133
|
+
当前 Server 使用 PostgreSQL 保存用户及 Argon2 密码哈希,Redis 保存 Session 并处理过期与撤销,
|
|
134
|
+
不是 JWT。接口访问控制由 NestJS 的 SessionAuthGuard 完成。
|
|
135
|
+
令牌在浏览器内可由 JavaScript 读取,部署时需使用 HTTPS 并防范 XSS;如改为 HttpOnly Cookie,
|
|
136
|
+
必须同时调整 Server 的认证协议与 CSRF 防护。
|
|
137
|
+
|
|
138
|
+
真实联调需要先启动 Docker Desktop,再在仓库根目录启动 PostgreSQL、Redis 和 Server:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
docker compose up -d postgres redis
|
|
142
|
+
pnpm --filter server dev
|
|
143
|
+
pnpm --filter web dev
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
后两条命令各在一个终端运行。数据库需要具备现有迁移定义的表。当前没有内置默认账号,
|
|
147
|
+
可使用已有账号,或通过 Server Swagger 的 `POST /auth/register` 创建开发账号。
|
|
148
|
+
开发接口文档为 `http://localhost:13000/api-docs`。
|
|
149
|
+
|
|
150
|
+
`test/storage.spec.ts` 验证 Cookie 编解码、作用域与过期属性,以及 local/session 的数据读写与隔离。
|
|
151
|
+
`test/request.spec.ts` 使用临时 HTTP 服务验证 Axios 响应适配、Bearer 请求头、2xx 成功、
|
|
152
|
+
401 / 422 / 503 错误及 HTTP 200 下的业务失败,
|
|
153
|
+
这些测试不依赖 PostgreSQL 或 Redis,也不代替真实数据库认证联调。
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html lang="
|
|
2
|
+
<html lang="zh-CN">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
<
|
|
7
|
+
<meta name="description" content="通用平台,为不同业务提供统一的工作空间。" />
|
|
8
|
+
<title>通用平台</title>
|
|
8
9
|
</head>
|
|
9
10
|
<body>
|
|
10
11
|
<div id="root"></div>
|
|
@@ -6,10 +6,14 @@
|
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "vp dev --mode dev",
|
|
8
8
|
"build": "tsc && vp build --mode dev",
|
|
9
|
-
"preview": "vp preview"
|
|
9
|
+
"preview": "vp preview",
|
|
10
|
+
"test": "vp test"
|
|
10
11
|
},
|
|
11
12
|
"dependencies": {
|
|
12
13
|
"@alova/adapter-axios": "catalog:",
|
|
14
|
+
"@ant-design/cssinjs": "catalog:",
|
|
15
|
+
"@ant-design/icons": "catalog:",
|
|
16
|
+
"@ant-design/pro-components": "catalog:",
|
|
13
17
|
"@bubblesjs/i18n-core": "workspace:*",
|
|
14
18
|
"@bubblesjs/i18n-react": "workspace:*",
|
|
15
19
|
"@tailwindcss/vite": "catalog:",
|
|
@@ -31,6 +35,7 @@
|
|
|
31
35
|
"typescript": "catalog:",
|
|
32
36
|
"unplugin-auto-import": "catalog:",
|
|
33
37
|
"vite": "catalog:",
|
|
38
|
+
"vite-plugin-svgr": "catalog:",
|
|
34
39
|
"vite-plus": "catalog:"
|
|
35
40
|
}
|
|
36
41
|
}
|
|
@@ -1 +1,9 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg"
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
|
2
|
+
<rect width="32" height="32" rx="6" fill="#176f88"/>
|
|
3
|
+
<g stroke="white" stroke-width="1.6">
|
|
4
|
+
<rect x="7" y="7" width="7" height="7" rx="1.5"/>
|
|
5
|
+
<rect x="18" y="7" width="7" height="7" rx="1.5"/>
|
|
6
|
+
<rect x="7" y="18" width="7" height="7" rx="1.5"/>
|
|
7
|
+
<rect x="18" y="18" width="7" height="7" rx="1.5"/>
|
|
8
|
+
</g>
|
|
9
|
+
</svg>
|
|
@@ -1,24 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StyleProvider } from '@ant-design/cssinjs'
|
|
2
|
+
import { ProConfigProvider } from '@ant-design/pro-components'
|
|
3
|
+
import { App as AntdApp, ConfigProvider } from 'antd'
|
|
4
|
+
import zhCN from 'antd/locale/zh_CN'
|
|
2
5
|
import dayjs from 'dayjs'
|
|
3
6
|
import 'dayjs/locale/zh-cn'
|
|
4
7
|
import { Suspense } from 'react'
|
|
5
8
|
import { RouterProvider } from 'react-router'
|
|
6
9
|
import PageLoading from '@/components/Loading/PageLoading'
|
|
10
|
+
import { appTheme } from '@/config/theme'
|
|
11
|
+
import styles from './App.module.css'
|
|
7
12
|
import { router } from './router'
|
|
8
13
|
|
|
9
14
|
dayjs.locale('zh-cn')
|
|
10
15
|
|
|
11
|
-
// const i18nStore = await i18n.init()
|
|
12
|
-
|
|
13
16
|
function App() {
|
|
14
17
|
return (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
<StyleProvider layer>
|
|
19
|
+
<ConfigProvider locale={zhCN} theme={appTheme}>
|
|
20
|
+
<ProConfigProvider>
|
|
21
|
+
<AntdApp className={styles.app}>
|
|
22
|
+
<Suspense fallback={<PageLoading />}>
|
|
23
|
+
<RouterProvider router={router} />
|
|
24
|
+
</Suspense>
|
|
25
|
+
</AntdApp>
|
|
26
|
+
</ProConfigProvider>
|
|
19
27
|
</ConfigProvider>
|
|
20
|
-
</
|
|
21
|
-
// </I18nProvider>
|
|
28
|
+
</StyleProvider>
|
|
22
29
|
)
|
|
23
30
|
}
|
|
24
31
|
|