feat(GOV-009): 建立 Vue 前端工程基线
This commit is contained in:
@@ -3,6 +3,8 @@ node_modules/
|
|||||||
dist/
|
dist/
|
||||||
coverage/
|
coverage/
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
playwright-report/
|
||||||
|
test-results/
|
||||||
|
|
||||||
# 本地环境与密钥
|
# 本地环境与密钥
|
||||||
.env
|
.env
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
engine-strict=true
|
engine-strict=true
|
||||||
save-exact=true
|
save-exact=true
|
||||||
strict-peer-dependencies=true
|
strict-peer-dependencies=true
|
||||||
|
only-built-dependencies[]=esbuild
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<!-- gitnexus:start -->
|
<!-- gitnexus:start -->
|
||||||
# GitNexus — Code Intelligence
|
# GitNexus — Code Intelligence
|
||||||
|
|
||||||
This project is indexed by GitNexus as **a-share-analysis** (1031 symbols, 1213 relationships, 21 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
This project is indexed by GitNexus as **a-share-analysis** (1076 symbols, 1271 relationships, 22 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||||
|
|
||||||
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
||||||
|
|
||||||
|
|||||||
@@ -93,3 +93,7 @@ Server 数据访问使用 Nest 官方 `@nestjs/typeorm` 集成、TypeORM 和 `my
|
|||||||
## 回退原则
|
## 回退原则
|
||||||
|
|
||||||
GOV-009 不迁移业务数据。回退时恢复上一 Git 标签即可移除脚手架;本地 `*.env.local` 和基础设施数据不随 Git 回退。若 Schema 工具变化失败,恢复旧工具后仍保留已发布基线,禁止改写历史目录。
|
GOV-009 不迁移业务数据。回退时恢复上一 Git 标签即可移除脚手架;本地 `*.env.local` 和基础设施数据不随 Git 回退。若 Schema 工具变化失败,恢复旧工具后仍保留已发布基线,禁止改写历史目录。
|
||||||
|
|
||||||
|
## 实施后架构论证门禁
|
||||||
|
|
||||||
|
本文档描述的是实施前目标架构。完成代码、测试、构建和 Review 后,必须根据 GitNexus 实际依赖图、Web/Server 目录、三个 Server 进程、OpenAPI 生成链路和部署镜像另写 `architecture-review.md`。复核需要说明实际实现与目标的偏差、当前选择优于替代方案的理由、何时应拆分或调整。用户再次确认前不发布 `v0.0.8`,也不把本功能视为彻底完成。
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
**Architecture:** 根 pnpm workspace 只编排 `web` 与 `server`。HTTP 契约由 Server 的 DTO 和 OpenAPI 单向发布,Web 生成自己的客户端;RocketMQ 消息契约只在 Server 内共享。Schema 安全门禁继续固定开发环境与数据库,只增加代码内固定远程主机和 `captured_empty` 状态。
|
**Architecture:** 根 pnpm workspace 只编排 `web` 与 `server`。HTTP 契约由 Server 的 DTO 和 OpenAPI 单向发布,Web 生成自己的客户端;RocketMQ 消息契约只在 Server 内共享。Schema 安全门禁继续固定开发环境与数据库,只增加代码内固定远程主机和 `captured_empty` 状态。
|
||||||
|
|
||||||
**Tech Stack:** Node.js 22.x、pnpm 10.25.0、TypeScript 5.9.3、Vue 3.5.41、Vite 7.3.6、Naive UI 2.44.1、Dart Sass 1.102.0、Vitest 3.2.7、Playwright 1.62.1、NestJS 11.1.29、TypeORM 0.3.31、mysql2 3.23.3、ioredis 5.11.1、MySQL 5.7、Redis、RocketMQ 5.x。
|
**Tech Stack:** Node.js 22.x、pnpm 10.25.0、TypeScript 5.9.3、Vue 3.5.41、Vite 6.4.3、Naive UI 2.44.1、Dart Sass 1.102.0、Vitest 3.2.7、Playwright 1.62.1、NestJS 11.1.29、TypeORM 0.3.31、mysql2 3.23.3、ioredis 5.11.1、MySQL 5.7、Redis、RocketMQ 5.x。
|
||||||
|
|
||||||
## Global Constraints
|
## Global Constraints
|
||||||
|
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
### Task 1: 修正 Schema 开发配置与空库基线
|
### Task 1: 修正 Schema 开发配置与空库基线
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
|
|
||||||
- Modify: `tools/database/schema-baseline.mjs`
|
- Modify: `tools/database/schema-baseline.mjs`
|
||||||
- Modify: `tools/database/schema-baseline.test.mjs`
|
- Modify: `tools/database/schema-baseline.test.mjs`
|
||||||
- Modify: `docs/governance/development-workflow.md`
|
- Modify: `docs/governance/development-workflow.md`
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
- Modify: `docs/deployment/local-development.md`
|
- Modify: `docs/deployment/local-development.md`
|
||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
|
|
||||||
- Produces: `resolveFixedDevelopmentEnv(repositoryRoot): string`,优先返回 `deploy/env/development/api.env.local`,缺失时退回模板。
|
- Produces: `resolveFixedDevelopmentEnv(repositoryRoot): string`,优先返回 `deploy/env/development/api.env.local`,缺失时退回模板。
|
||||||
- Produces: `assertSafeDevelopmentConfiguration(config): Readonly<SchemaConfig>`,只接受回环地址或 `112.192.20.11`。
|
- Produces: `assertSafeDevelopmentConfiguration(config): Readonly<SchemaConfig>`,只接受回环地址或 `112.192.20.11`。
|
||||||
- Produces: `buildCapturedEmptyArtifacts(metadata): BaselineArtifacts`,状态为 `captured_empty`、format v2,只保存结构化清单、`object_counts` 和空结构 SHA-256,不创建 `schema.sql`。
|
- Produces: `buildCapturedEmptyArtifacts(metadata): BaselineArtifacts`,状态为 `captured_empty`、format v2,只保存结构化清单、`object_counts` 和空结构 SHA-256,不创建 `schema.sql`。
|
||||||
@@ -69,12 +71,7 @@ Expected: FAIL,原因分别为 `api.env.local` 未被选择、固定远程地
|
|||||||
在代码内使用不可变集合,不读取动态白名单变量:
|
在代码内使用不可变集合,不读取动态白名单变量:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const DEVELOPMENT_HOSTS = new Set([
|
const DEVELOPMENT_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '112.192.20.11']);
|
||||||
'127.0.0.1',
|
|
||||||
'localhost',
|
|
||||||
'::1',
|
|
||||||
'112.192.20.11',
|
|
||||||
]);
|
|
||||||
|
|
||||||
export function resolveFixedDevelopmentEnv(repositoryRoot) {
|
export function resolveFixedDevelopmentEnv(repositoryRoot) {
|
||||||
const localEnv = path.join(repositoryRoot, 'deploy', 'env', 'development', 'api.env.local');
|
const localEnv = path.join(repositoryRoot, 'deploy', 'env', 'development', 'api.env.local');
|
||||||
@@ -146,6 +143,7 @@ Push: `git push origin "$(git branch --show-current)"`
|
|||||||
### Task 2: 建立 Node 22 与 pnpm workspace 质量基线
|
### Task 2: 建立 Node 22 与 pnpm workspace 质量基线
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
|
|
||||||
- Modify: `package.json`
|
- Modify: `package.json`
|
||||||
- Create: `pnpm-workspace.yaml`
|
- Create: `pnpm-workspace.yaml`
|
||||||
- Create: `pnpm-lock.yaml`
|
- Create: `pnpm-lock.yaml`
|
||||||
@@ -156,6 +154,7 @@ Push: `git push origin "$(git branch --show-current)"`
|
|||||||
- Create: `tools/runtime/verify-runtime.test.mjs`
|
- Create: `tools/runtime/verify-runtime.test.mjs`
|
||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
|
|
||||||
- Produces: `assertSupportedRuntime({ nodeVersion, pnpmVersion })`。
|
- Produces: `assertSupportedRuntime({ nodeVersion, pnpmVersion })`。
|
||||||
- Produces root scripts: `check:runtime`, `lint`, `format:check`, `typecheck`, `test`, `build`, `verify`。
|
- Produces root scripts: `check:runtime`, `lint`, `format:check`, `typecheck`, `test`, `build`, `verify`。
|
||||||
|
|
||||||
@@ -211,6 +210,7 @@ Push current branch and verify local/remote SHA equality.
|
|||||||
### Task 3: 建立 Vue 3 Web 工程
|
### Task 3: 建立 Vue 3 Web 工程
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
|
|
||||||
- Delete: `web/.gitkeep`
|
- Delete: `web/.gitkeep`
|
||||||
- Create: `web/package.json`
|
- Create: `web/package.json`
|
||||||
- Create: `web/tsconfig.json`
|
- Create: `web/tsconfig.json`
|
||||||
@@ -229,6 +229,7 @@ Push current branch and verify local/remote SHA equality.
|
|||||||
- Create: `web/tests/smoke.spec.ts`
|
- Create: `web/tests/smoke.spec.ts`
|
||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
|
|
||||||
- Produces: `createAppRouter()` 和 `installAppProviders(app)`。
|
- Produces: `createAppRouter()` 和 `installAppProviders(app)`。
|
||||||
- Consumes: only `VITE_APP_ENV`, `VITE_API_BASE_URL`, `VITE_APP_TITLE` from `web.env.local`。
|
- Consumes: only `VITE_APP_ENV`, `VITE_API_BASE_URL`, `VITE_APP_TITLE` from `web.env.local`。
|
||||||
- Preserves: FEAT-MO-001 将在 `src/features/market-overview/` 添加首屏,不需要重构应用入口。
|
- Preserves: FEAT-MO-001 将在 `src/features/market-overview/` 添加首屏,不需要重构应用入口。
|
||||||
@@ -245,7 +246,7 @@ Expected: FAIL,因为 Web 包和应用入口尚不存在。
|
|||||||
|
|
||||||
- [ ] **Step 3: 创建精确依赖和最小应用外壳**
|
- [ ] **Step 3: 创建精确依赖和最小应用外壳**
|
||||||
|
|
||||||
Web dependencies 精确锁定 Vue 3.5.41、Vue Router 4.6.4、Pinia 3.0.4、Naive UI 2.44.1;dev dependencies 锁定 Vite 7.3.6、plugin-vue 6.0.8、TypeScript 5.9.3、vue-tsc 3.3.9、Sass 1.102.0、Vitest 3.2.7、Vue Test Utils 2.4.11、jsdom 26.1.0、Playwright 1.62.1。
|
Web dependencies 精确锁定 Vue 3.5.41、Vue Router 4.6.4、Pinia 3.0.4、Naive UI 2.44.1;dev dependencies 锁定 Vite 6.4.3、plugin-vue 5.2.4、TypeScript 5.9.3、vue-tsc 3.3.9、Sass 1.102.0、Vitest 3.2.7、Vue Test Utils 2.4.11、jsdom 26.1.0、Playwright 1.62.1。该组合兼容开发机 Node 22.11;Vite 7 要求 Node 22.12+,不在本基线采用。
|
||||||
|
|
||||||
`main.ts` 固定从项目外部加载开发 env:
|
`main.ts` 固定从项目外部加载开发 env:
|
||||||
|
|
||||||
@@ -284,6 +285,7 @@ Push current branch and verify remote SHA.
|
|||||||
### Task 4: 建立 NestJS 三进程与最小权限配置
|
### Task 4: 建立 NestJS 三进程与最小权限配置
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
|
|
||||||
- Delete: `server/.gitkeep`
|
- Delete: `server/.gitkeep`
|
||||||
- Create: `server/package.json`
|
- Create: `server/package.json`
|
||||||
- Create: `server/nest-cli.json`
|
- Create: `server/nest-cli.json`
|
||||||
@@ -301,6 +303,7 @@ Push current branch and verify remote SHA.
|
|||||||
- Create: `server/test/process-smoke.test.ts`
|
- Create: `server/test/process-smoke.test.ts`
|
||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
|
|
||||||
- Produces: `loadApiConfig`, `loadSchedulerConfig`, `loadWorkerConfig`,返回冻结且不含未知字段的配置。
|
- Produces: `loadApiConfig`, `loadSchedulerConfig`, `loadWorkerConfig`,返回冻结且不含未知字段的配置。
|
||||||
- Produces: `bootstrapApi`, `bootstrapScheduler`, `bootstrapWorker`,支持依赖注入端口和优雅关闭。
|
- Produces: `bootstrapApi`, `bootstrapScheduler`, `bootstrapWorker`,支持依赖注入端口和优雅关闭。
|
||||||
- Consumes: `deploy/env/development/<process>.env.local` only in development; production only consumes `process.env` injected by Compose/systemd.
|
- Consumes: `deploy/env/development/<process>.env.local` only in development; production only consumes `process.env` injected by Compose/systemd.
|
||||||
@@ -367,6 +370,7 @@ Commit: `feat(GOV-009): 建立 NestJS 三进程基线`
|
|||||||
### Task 5: 建立后端契约、基础设施与 API 健康检查
|
### Task 5: 建立后端契约、基础设施与 API 健康检查
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
|
|
||||||
- Create: `server/libs/http-contracts/src/health.dto.ts`
|
- Create: `server/libs/http-contracts/src/health.dto.ts`
|
||||||
- Create: `server/libs/message-contracts/src/message-envelope.ts`
|
- Create: `server/libs/message-contracts/src/message-envelope.ts`
|
||||||
- Create: `server/libs/message-contracts/src/message-envelope.test.ts`
|
- Create: `server/libs/message-contracts/src/message-envelope.test.ts`
|
||||||
@@ -387,6 +391,7 @@ Commit: `feat(GOV-009): 建立 NestJS 三进程基线`
|
|||||||
- Modify ignored local API/Worker env files to retain `MYSQL_TIMEZONE=+08:00` without staging them.
|
- Modify ignored local API/Worker env files to retain `MYSQL_TIMEZONE=+08:00` without staging them.
|
||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
|
|
||||||
- Produces: `GET /api/v1/health -> HealthResponseDto`。
|
- Produces: `GET /api/v1/health -> HealthResponseDto`。
|
||||||
- Produces: `MessageEnvelope<TPayload>` with `version`, `eventType`, `idempotencyKey`, `occurredAt`, `payload`。
|
- Produces: `MessageEnvelope<TPayload>` with `version`, `eventType`, `idempotencyKey`, `occurredAt`, `payload`。
|
||||||
- Produces: `MessageBusAdapter.publish/subscribe/close`,disabled adapter rejects publish/subscribe with a typed error。
|
- Produces: `MessageBusAdapter.publish/subscribe/close`,disabled adapter rejects publish/subscribe with a typed error。
|
||||||
@@ -454,6 +459,7 @@ Commit: `feat(GOV-009): 建立后端契约与健康检查`
|
|||||||
### Task 6: 生成 Web OpenAPI 客户端并强制边界
|
### Task 6: 生成 Web OpenAPI 客户端并强制边界
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
|
|
||||||
- Create: `web/src/api/generated/schema.ts`
|
- Create: `web/src/api/generated/schema.ts`
|
||||||
- Create: `web/src/api/client.ts`
|
- Create: `web/src/api/client.ts`
|
||||||
- Create: `web/src/api/adapters/health.adapter.ts`
|
- Create: `web/src/api/adapters/health.adapter.ts`
|
||||||
@@ -464,6 +470,7 @@ Commit: `feat(GOV-009): 建立后端契约与健康检查`
|
|||||||
- Modify: root `package.json`
|
- Modify: root `package.json`
|
||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
|
|
||||||
- Consumes: `server/openapi/openapi.json` as the only cross-project contract artifact.
|
- Consumes: `server/openapi/openapi.json` as the only cross-project contract artifact.
|
||||||
- Produces: `createApiClient(baseUrl)` using `openapi-fetch` and generated `paths` type.
|
- Produces: `createApiClient(baseUrl)` using `openapi-fetch` and generated `paths` type.
|
||||||
- Produces: `toHealthViewModel(dto): HealthViewModel`,页面模型不暴露后端枚举细节。
|
- Produces: `toHealthViewModel(dto): HealthViewModel`,页面模型不暴露后端枚举细节。
|
||||||
@@ -499,6 +506,7 @@ Commit: `feat(GOV-009): 建立前端 OpenAPI 客户端边界`
|
|||||||
### Task 7: 对齐 Docker、Compose、systemd 与源码开发命令
|
### Task 7: 对齐 Docker、Compose、systemd 与源码开发命令
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
|
|
||||||
- Create: `web/Dockerfile`
|
- Create: `web/Dockerfile`
|
||||||
- Create: `web/nginx.conf`
|
- Create: `web/nginx.conf`
|
||||||
- Create: `server/Dockerfile`
|
- Create: `server/Dockerfile`
|
||||||
@@ -514,6 +522,7 @@ Commit: `feat(GOV-009): 建立前端 OpenAPI 客户端边界`
|
|||||||
- Modify: `tools/governance/environment-baseline.test.mjs`
|
- Modify: `tools/governance/environment-baseline.test.mjs`
|
||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
|
|
||||||
- Produces immutable Web image serving `/` and proxying `/api/` according to deployment contract.
|
- Produces immutable Web image serving `/` and proxying `/api/` according to deployment contract.
|
||||||
- Produces one Server image with commands `dist/apps/api/main.js`, `dist/apps/scheduler/main.js`, `dist/apps/worker/main.js`.
|
- Produces one Server image with commands `dist/apps/api/main.js`, `dist/apps/scheduler/main.js`, `dist/apps/worker/main.js`.
|
||||||
|
|
||||||
@@ -542,6 +551,7 @@ Commit: `build(GOV-009): 对齐应用部署与运行入口`
|
|||||||
### Task 8: 完成全量验证、Review 与发布留痕
|
### Task 8: 完成全量验证、Review 与发布留痕
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
|
|
||||||
- Update: `docs/features/GOV-009-application-scaffold-baseline/manifest.yaml`
|
- Update: `docs/features/GOV-009-application-scaffold-baseline/manifest.yaml`
|
||||||
- Create: `docs/features/GOV-009-application-scaffold-baseline/gitnexus/detected-changes.md`
|
- Create: `docs/features/GOV-009-application-scaffold-baseline/gitnexus/detected-changes.md`
|
||||||
- Create: `docs/features/GOV-009-application-scaffold-baseline/gitnexus/after.md`
|
- Create: `docs/features/GOV-009-application-scaffold-baseline/gitnexus/after.md`
|
||||||
@@ -552,10 +562,12 @@ Commit: `build(GOV-009): 对齐应用部署与运行入口`
|
|||||||
- Create: `docs/features/GOV-009-application-scaffold-baseline/review/standards-review.md`
|
- Create: `docs/features/GOV-009-application-scaffold-baseline/review/standards-review.md`
|
||||||
- Create: `docs/features/GOV-009-application-scaffold-baseline/release-notes.md`
|
- Create: `docs/features/GOV-009-application-scaffold-baseline/release-notes.md`
|
||||||
- Create: `docs/features/GOV-009-application-scaffold-baseline/rollback.md`
|
- Create: `docs/features/GOV-009-application-scaffold-baseline/rollback.md`
|
||||||
|
- Create: `docs/features/GOV-009-application-scaffold-baseline/architecture-review.md`
|
||||||
- Modify: `VERSION`
|
- Modify: `VERSION`
|
||||||
- Modify: `CHANGELOG.md`
|
- Modify: `CHANGELOG.md`
|
||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
|
|
||||||
- Produces: reproducible evidence for tests, builds, browser screenshots, GitNexus changes, reviews, release and rollback.
|
- Produces: reproducible evidence for tests, builds, browser screenshots, GitNexus changes, reviews, release and rollback.
|
||||||
|
|
||||||
- [ ] **Step 1: 在固定 Node 22 环境运行完整验证**
|
- [ ] **Step 1: 在固定 Node 22 环境运行完整验证**
|
||||||
@@ -584,12 +596,16 @@ Run GitNexus `detect_changes`,核对新增 API、Scheduler、Worker、OpenAPI
|
|||||||
|
|
||||||
将版本提升到 `0.0.8`,说明工程能力而不声称市场概览已实现。回退说明不得删除远程数据库或 Redis 数据,只回退应用代码和镜像。
|
将版本提升到 `0.0.8`,说明工程能力而不声称市场概览已实现。回退说明不得删除远程数据库或 Redis 数据,只回退应用代码和镜像。
|
||||||
|
|
||||||
- [ ] **Step 6: 最终提交、推送与标签**
|
- [ ] **Step 6: 基于实际实现完成架构复核并向用户论证**
|
||||||
|
|
||||||
|
`architecture-review.md` 必须引用真实文件和 GitNexus 流程,说明 Web 分层、Server 三进程、HTTP 与消息契约方向、基础设施边界、部署产物、替代方案、已知限制和后续调整触发条件。将 manifest 保持 `reviewing` 和 `unreleased`,提交并推送复核材料,然后向用户展示论证;未获得用户再次确认时在此停止,不创建标签。
|
||||||
|
|
||||||
|
- [ ] **Step 7: 用户确认后执行最终提交、推送与标签**
|
||||||
|
|
||||||
Commit: `docs(GOV-009): 封存应用脚手架验证结果`
|
Commit: `docs(GOV-009): 封存应用脚手架验证结果`
|
||||||
|
|
||||||
Push current branch, compare local and remote SHA, create annotated tag `v0.0.8`, then run `git push origin v0.0.8`。禁止 `--tags` 和任何 force 选项。
|
Push current branch, compare local and remote SHA, create annotated tag `v0.0.8`, then run `git push origin v0.0.8`。禁止 `--tags` 和任何 force 选项。
|
||||||
|
|
||||||
- [ ] **Step 7: 刷新 GitNexus 并确认干净状态**
|
- [ ] **Step 8: 刷新 GitNexus 并确认干净状态**
|
||||||
|
|
||||||
Run `npx gitnexus status`; stale 时运行 analyze。最终要求工作区干净、`main` 与 `origin/main` 一致、远程 `v0.0.8^{}` 指向本地标签提交。
|
Run `npx gitnexus status`; stale 时运行 analyze。最终要求工作区干净、`main` 与 `origin/main` 一致、远程 `v0.0.8^{}` 指向本地标签提交。
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ test_plan: test-plan.md
|
|||||||
gitnexus_before: gitnexus/before.md
|
gitnexus_before: gitnexus/before.md
|
||||||
gitnexus_impact: gitnexus/impact-plan.md
|
gitnexus_impact: gitnexus/impact-plan.md
|
||||||
release: unreleased
|
release: unreleased
|
||||||
|
completion_gate: 实施完成后必须基于实际前后端架构向用户论证并再次确认
|
||||||
|
|||||||
@@ -56,4 +56,4 @@
|
|||||||
- Schema 工具只接受 `development`、`a_share_analysis_dev`、MySQL 5.7、`utf8mb4_general_ci` 和固定主机白名单;其他远程主机立即拒绝。
|
- Schema 工具只接受 `development`、`a_share_analysis_dev`、MySQL 5.7、`utf8mb4_general_ci` 和固定主机白名单;其他远程主机立即拒绝。
|
||||||
- 空开发库可以生成可验证的 captured Schema 基线,不用空 SQL 冒充结构,也不退回 `not_applicable`。
|
- 空开发库可以生成可验证的 captured Schema 基线,不用空 SQL 冒充结构,也不退回 `not_applicable`。
|
||||||
- 单元测试、契约测试、构建、开发启动冒烟和适用的浏览器验证全部通过。
|
- 单元测试、契约测试、构建、开发启动冒烟和适用的浏览器验证全部通过。
|
||||||
|
- 全部实施完成后必须基于真实代码和部署产物提交《实施架构复核》,向用户论证前后端职责、依赖方向、进程边界、替代方案和演进成本;获得用户再次确认前,GOV-009 不得标记完成或创建发布标签。
|
||||||
|
|||||||
@@ -49,3 +49,10 @@
|
|||||||
- 根级检查必须覆盖格式、静态检查、类型、单元测试、契约测试和构建。
|
- 根级检查必须覆盖格式、静态检查、类型、单元测试、契约测试和构建。
|
||||||
- 所有关键配置边界、契约生成和启动失败行为必须有自动化测试。
|
- 所有关键配置边界、契约生成和启动失败行为必须有自动化测试。
|
||||||
- Server 使用 `@nestjs/swagger` 输出稳定的 `openapi.json`;Web 使用 `openapi-typescript` 与 `openapi-fetch` 生成类型安全客户端。
|
- Server 使用 `@nestjs/swagger` 输出稳定的 `openapi.json`;Web 使用 `openapi-typescript` 与 `openapi-fetch` 生成类型安全客户端。
|
||||||
|
|
||||||
|
## R8 实施后架构复核
|
||||||
|
|
||||||
|
- 开发阶段允许按批准设计实施,但设计文档不能代替对实际代码结构的复核。
|
||||||
|
- 所有测试和 Review 通过后,必须新增 `architecture-review.md`,以实际文件、依赖图、进程入口、契约生成和部署产物为证据。
|
||||||
|
- 复核必须比较至少一个替代架构,解释当前方案的收益、限制、后续拆分条件和可能的技术债。
|
||||||
|
- 必须向用户展示并完成论证;用户确认前 manifest 保持 `reviewing`、版本保持 `unreleased`,不得创建 `v0.0.8` 标签。
|
||||||
|
|||||||
@@ -1,5 +1,28 @@
|
|||||||
|
import eslint from '@eslint/js';
|
||||||
|
import vue from 'eslint-plugin-vue';
|
||||||
|
import globals from 'globals';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
ignores: ['**/dist/**', '**/coverage/**', '**/node_modules/**', '**/.local/**'],
|
ignores: ['**/dist/**', '**/coverage/**', '**/node_modules/**', '**/.local/**'],
|
||||||
},
|
},
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
...vue.configs['flat/recommended'],
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,mjs,cjs,ts,tsx,vue}'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
...globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-console': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'vue/max-attributes-per-line': 'off',
|
||||||
|
'vue/multi-word-component-names': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
+6
-1
@@ -27,6 +27,11 @@
|
|||||||
"verify": "pnpm check:runtime && pnpm lint && pnpm format:check && pnpm typecheck && pnpm test && pnpm build"
|
"verify": "pnpm check:runtime && pnpm lint && pnpm format:check && pnpm typecheck && pnpm test && pnpm build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "3.9.6"
|
"@eslint/js": "9.39.5",
|
||||||
|
"eslint": "9.39.5",
|
||||||
|
"eslint-plugin-vue": "9.33.0",
|
||||||
|
"globals": "17.11.0",
|
||||||
|
"prettier": "3.9.6",
|
||||||
|
"typescript-eslint": "8.46.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+3486
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="color-scheme" content="light" />
|
||||||
|
<title>A 股市场分析工具</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "@a-share-analysis/web",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --host 127.0.0.1",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"typecheck": "vue-tsc --noEmit && tsc --noEmit -p tsconfig.node.json",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"test:e2e": "playwright test",
|
||||||
|
"build": "vue-tsc --noEmit && vite build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"naive-ui": "2.44.1",
|
||||||
|
"pinia": "3.0.4",
|
||||||
|
"vue": "3.5.41",
|
||||||
|
"vue-router": "4.6.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "1.62.1",
|
||||||
|
"@types/node": "22.20.0",
|
||||||
|
"@vitejs/plugin-vue": "5.2.4",
|
||||||
|
"@vue/test-utils": "2.4.11",
|
||||||
|
"dotenv": "16.6.1",
|
||||||
|
"jsdom": "26.1.0",
|
||||||
|
"katex": "0.18.4",
|
||||||
|
"sass": "1.102.0",
|
||||||
|
"typescript": "5.9.3",
|
||||||
|
"vite": "6.4.3",
|
||||||
|
"vitest": "3.2.7",
|
||||||
|
"vue-tsc": "3.3.9"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { defineConfig } from '@playwright/test';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './tests',
|
||||||
|
fullyParallel: false,
|
||||||
|
retries: 0,
|
||||||
|
reporter: 'list',
|
||||||
|
use: {
|
||||||
|
baseURL: 'http://127.0.0.1:5173',
|
||||||
|
trace: 'retain-on-failure',
|
||||||
|
},
|
||||||
|
webServer: {
|
||||||
|
command: 'env -u NO_COLOR pnpm dev',
|
||||||
|
url: 'http://127.0.0.1:5173',
|
||||||
|
reuseExistingServer: false,
|
||||||
|
timeout: 30_000,
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'desktop-chromium',
|
||||||
|
use: { browserName: 'chromium', viewport: { width: 1440, height: 900 } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mobile-chromium',
|
||||||
|
use: { browserName: 'chromium', viewport: { width: 390, height: 844 } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { createPinia } from 'pinia';
|
||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
import { createMemoryHistory } from 'vue-router';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import App from './App.vue';
|
||||||
|
import { createAppRouter } from './router';
|
||||||
|
|
||||||
|
describe('应用外壳', () => {
|
||||||
|
it('提供可访问的应用标题和主内容区域', async () => {
|
||||||
|
const router = createAppRouter(createMemoryHistory());
|
||||||
|
await router.push('/');
|
||||||
|
await router.isReady();
|
||||||
|
|
||||||
|
const wrapper = mount(App, {
|
||||||
|
global: {
|
||||||
|
plugins: [createPinia(), router],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.get('header').attributes('aria-label')).toBe('应用导航');
|
||||||
|
expect(wrapper.get('main').attributes('id')).toBe('main-content');
|
||||||
|
expect(wrapper.text()).toContain('A 股市场分析工具');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { NConfigProvider, NGlobalStyle, NMessageProvider } from 'naive-ui';
|
||||||
|
|
||||||
|
const appTitle = import.meta.env.VITE_APP_TITLE || 'A 股市场分析工具';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NConfigProvider>
|
||||||
|
<NGlobalStyle />
|
||||||
|
<NMessageProvider>
|
||||||
|
<div class="app-shell">
|
||||||
|
<header class="app-header" aria-label="应用导航">
|
||||||
|
<span class="app-mark" aria-hidden="true">A</span>
|
||||||
|
<strong>{{ appTitle }}</strong>
|
||||||
|
</header>
|
||||||
|
<main id="main-content" class="app-main">
|
||||||
|
<RouterView />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</NMessageProvider>
|
||||||
|
</NConfigProvider>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createPinia } from 'pinia';
|
||||||
|
import type { App } from 'vue';
|
||||||
|
|
||||||
|
import { createAppRouter } from './router';
|
||||||
|
|
||||||
|
export function installAppProviders(app: App) {
|
||||||
|
app.use(createPinia());
|
||||||
|
app.use(createAppRouter());
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import {
|
||||||
|
createRouter,
|
||||||
|
createWebHistory,
|
||||||
|
type RouterHistory,
|
||||||
|
type RouteRecordRaw,
|
||||||
|
} from 'vue-router';
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: {
|
||||||
|
template: '<section aria-label="应用就绪"><p>工程基线已就绪</p></section>',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function createAppRouter(history: RouterHistory = createWebHistory()) {
|
||||||
|
return createRouter({ history, routes });
|
||||||
|
}
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_APP_ENV?: string;
|
||||||
|
readonly VITE_API_BASE_URL?: string;
|
||||||
|
readonly VITE_APP_TITLE?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createApp } from 'vue';
|
||||||
|
|
||||||
|
import App from './app/App.vue';
|
||||||
|
import { installAppProviders } from './app/providers';
|
||||||
|
import './styles/index.scss';
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
installAppProviders(app);
|
||||||
|
app.mount('#app');
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
:root {
|
||||||
|
color: #171a1f;
|
||||||
|
background: #f4f6f8;
|
||||||
|
font-family:
|
||||||
|
Inter,
|
||||||
|
'PingFang SC',
|
||||||
|
'Microsoft YaHei',
|
||||||
|
system-ui,
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
sans-serif;
|
||||||
|
font-synthesis: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#app {
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
a,
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid #1677ff;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-shell {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
min-height: 52px;
|
||||||
|
padding: 0 20px;
|
||||||
|
color: #fff;
|
||||||
|
background: #1d2229;
|
||||||
|
border-bottom: 1px solid #343b44;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-mark {
|
||||||
|
display: inline-grid;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
place-items: center;
|
||||||
|
color: #1d2229;
|
||||||
|
font-weight: 700;
|
||||||
|
background: #50c878;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-main {
|
||||||
|
width: min(100%, 1600px);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.app-header,
|
||||||
|
.app-main {
|
||||||
|
padding-right: 14px;
|
||||||
|
padding-left: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
test('应用外壳在桌面和移动视口均可正常呈现', async ({ page }) => {
|
||||||
|
const runtimeErrors: string[] = [];
|
||||||
|
|
||||||
|
page.on('console', (message) => {
|
||||||
|
if (message.type() === 'error') runtimeErrors.push(message.text());
|
||||||
|
});
|
||||||
|
page.on('pageerror', (error) => runtimeErrors.push(error.message));
|
||||||
|
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
await expect(page.locator('main')).toBeVisible();
|
||||||
|
await expect(page.getByText('A 股市场分析工具')).toBeVisible();
|
||||||
|
|
||||||
|
const hasHorizontalOverflow = await page.evaluate(
|
||||||
|
() => document.documentElement.scrollWidth > document.documentElement.clientWidth,
|
||||||
|
);
|
||||||
|
expect(hasHorizontalOverflow).toBe(false);
|
||||||
|
expect(runtimeErrors).toEqual([]);
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"types": ["vitest/globals"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.vue", "src/**/*.d.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"types": ["node", "vite/client", "vitest/globals"]
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts", "vitest.config.ts", "playwright.config.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import { parse } from 'dotenv';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
const webDirectory = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const developmentEnvFile = path.resolve(webDirectory, '../deploy/env/development/web.env.local');
|
||||||
|
|
||||||
|
function readDevelopmentPublicEnv() {
|
||||||
|
if (process.env.NODE_ENV === 'production') return {};
|
||||||
|
try {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(parse(readFileSync(developmentEnvFile))).filter(([key]) =>
|
||||||
|
key.startsWith('VITE_'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return {};
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicEnv = readDevelopmentPublicEnv();
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
define: Object.fromEntries(
|
||||||
|
Object.entries(publicEnv).map(([key, value]) => [
|
||||||
|
`import.meta.env.${key}`,
|
||||||
|
JSON.stringify(value),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
server: {
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: 5173,
|
||||||
|
strictPort: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
include: ['src/**/*.test.ts'],
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user