From 2fb1dfee141f2ecf7cf10453c1cf76662182e788 Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Sun, 2 Aug 2026 16:00:44 +0800 Subject: [PATCH] chore: seal Design Vision v1 and monorepo scaffold Archive the differentiated YuXinGu product docs, AI engineering system, design contract, and Go/Vue scaffold. Next execution prioritizes Cece-parity over early innovation (see .ai/product/STRATEGY.md). Co-authored-by: Cursor --- .ai/README.md | 67 + .ai/ROADMAP.md | 38 + .ai/adr/0001-use-golang.md | 20 + .ai/adr/0002-use-gin.md | 19 + .ai/adr/0003-rest-api.md | 20 + .ai/adr/0004-use-jwt.md | 19 + .ai/adr/0005-use-redis.md | 22 + .ai/adr/0006-monorepo-vue-h5.md | 21 + .ai/ai-contract.md | 36 + .ai/api.md | 49 + .ai/architecture.md | 51 + .ai/checklists/bugfix.md | 9 + .ai/checklists/feature.md | 16 + .ai/checklists/refactor.md | 8 + .ai/checklists/release.md | 10 + .ai/coding.md | 37 + .ai/commands.md | 67 + .ai/constitution.md | 34 + .ai/database.md | 39 + .ai/definition-of-done.md | 17 + .ai/deployment.md | 33 + .ai/design/README.md | 15 + .ai/design/component-catalog.md | 233 +++ .ai/design/design-system.md | 276 +++ .ai/design/platform/flutter.md | 38 + .ai/design/platform/h5.md | 47 + .ai/design/platform/mini-program.md | 47 + .ai/design/platform/website.md | 44 + .ai/domain.md | 65 + .ai/domain/README.md | 6 + .ai/domain/domain-map.md | 183 ++ .ai/examples/good-api.json | 25 + .ai/examples/good-handler.go | 54 + .ai/examples/good-service.go | 29 + .ai/examples/good-test.go | 39 + .ai/file-map.md | 93 + .ai/forbidden.md | 37 + .ai/patterns/crud.md | 14 + .ai/patterns/handler.md | 37 + .ai/patterns/jwt.md | 13 + .ai/patterns/pagination.md | 22 + .ai/patterns/repository.md | 24 + .ai/patterns/service.md | 31 + .ai/playbooks/add-api.md | 19 + .ai/playbooks/login.md | 14 + .ai/playbooks/new-page.md | 18 + .ai/playbooks/new-table.md | 14 + .ai/playbooks/payment.md | 14 + .ai/product/README.md | 12 + .ai/product/STRATEGY.md | 32 + .ai/product/cece-feature-map.md | 307 +++ .ai/product/feature-map.md | 212 +++ .ai/prompts/bugfix.md | 17 + .ai/prompts/new-feature.md | 18 + .ai/prompts/refactor.md | 16 + .ai/review.md | 40 + .ai/security.md | 26 + .ai/tech-stack.md | 42 + .ai/testing.md | 22 + .ai/ui.md | 64 + .ai/workflow.md | 33 + .cursor/README.md | 13 + .cursor/commands/project-commands.md | 1 + .cursor/commands/refactor.md | 1 + .cursor/commands/review.md | 1 + .cursor/rules/ai-contract.md | 1 + .cursor/rules/api.md | 1 + .cursor/rules/architecture.md | 1 + .cursor/rules/coding.md | 1 + .cursor/rules/constitution.md | 1 + .cursor/rules/database.md | 1 + .cursor/rules/design-system.md | 1 + .cursor/rules/domain-map.md | 1 + .cursor/rules/domain.md | 1 + .cursor/rules/feature-map.md | 1 + .cursor/rules/forbidden.md | 1 + .cursor/rules/review.md | 1 + .cursor/templates/feature-checklist.md | 1 + .cursor/templates/feature.md | 1 + .gitignore | 20 + AGENTS.md | 44 + ARCHITECTURE.md | 9 + CLAUDE.md | 85 +- CONTRIBUTING.md | 31 + LEGACY.md | 9 + README.md | 48 + apps/admin-h5/README.md | 3 + apps/admin-h5/package.json | 6 + apps/api/cmd/server/main.go | 39 + apps/api/go.mod | 34 + apps/api/go.sum | 89 + apps/api/internal/config/config.go | 27 + apps/api/internal/handler/health.go | 25 + apps/api/internal/middleware/request_id.go | 29 + apps/api/pkg/response/response.go | 25 + apps/docs/README.md | 27 + apps/docs/adr/0001-monorepo-go-h5.md | 23 + apps/docs/adr/README.md | 7 + apps/docs/analysis/cece-teardown.md | 79 + apps/docs/business-model.md | 90 + apps/docs/prd-mvp.md | 128 ++ apps/docs/product-roadmap.md | 78 + apps/docs/standards/01-git.md | 37 + apps/docs/standards/02-go.md | 66 + apps/docs/standards/03-api.md | 60 + apps/docs/standards/04-database.md | 38 + apps/docs/standards/05-h5-vue.md | 41 + apps/docs/standards/06-mini-program.md | 33 + apps/docs/standards/07-typescript.md | 25 + apps/docs/standards/08-testing.md | 21 + apps/docs/standards/09-cicd.md | 29 + apps/docs/standards/10-ai-collab.md | 25 + apps/docs/standards/11-security.md | 25 + apps/docs/standards/12-docs.md | 26 + apps/docs/standards/README.md | 22 + apps/mini-program/README.md | 23 + apps/mini-program/app.js | 5 + apps/mini-program/app.json | 8 + apps/mini-program/pages/home/index.js | 1 + apps/mini-program/pages/home/index.json | 3 + apps/mini-program/pages/home/index.wxml | 1 + apps/mini-program/pages/home/index.wxss | 1 + apps/user-h5/index.html | 12 + apps/user-h5/package.json | 27 + apps/user-h5/src/App.vue | 15 + apps/user-h5/src/api/client.ts | 7 + apps/user-h5/src/components/TabBar.vue | 53 + apps/user-h5/src/main.ts | 8 + apps/user-h5/src/pages/AskPage.vue | 14 + apps/user-h5/src/pages/CompanionPage.vue | 14 + apps/user-h5/src/pages/DecodePage.vue | 32 + apps/user-h5/src/pages/ExplorePage.vue | 18 + apps/user-h5/src/pages/HomePage.vue | 115 ++ apps/user-h5/src/pages/MinePage.vue | 14 + apps/user-h5/src/router/index.ts | 15 + apps/user-h5/src/styles/app.css | 12 + apps/user-h5/src/vite-env.d.ts | 7 + apps/user-h5/tsconfig.json | 24 + apps/user-h5/vite.config.ts | 21 + .../l2_LINK_20260629_171642_12h_data.json | 0 .../l2_LINK_kline_data.json | 0 .../l2_LINK_signals.json | 0 .../link_regime_chart.html | 0 manual.html => archive/manual.html | 0 xingjing.html => archive/xingjing.html | 0 yuxingu_v1.html => archive/yuxingu_v1.html | 0 css/app.css | 72 + css/decode.css | 110 ++ css/health.css | 124 ++ css/home.css | 213 +++ css/hub.css | 26 + css/mindmap.css | 107 ++ css/scale-test.css | 58 + css/scales.css | 48 + css/shuzi.css | 78 + css/xingjing.css | 221 +++ deploy/.env.example | 4 + deploy/docker-compose.yml | 15 + index.html | 83 +- common.js => js/common.js | 2 +- jiema.html => js/decode.js | 161 +- yangsheng.html => js/health.js | 197 -- js/home.js | 14 + mindmap.html => js/mindmap.js | 167 -- scale_test.html => js/scale-test.js | 80 +- js/scales.js | 53 + js/shell.js | 60 + shuzi.html => js/shuzi.js | 178 -- xingjing_app.html => js/xingjing.js | 452 +---- package-lock.json | 1687 +++++++++++++++++ package.json | 14 + packages/sdk/package.json | 14 + packages/sdk/src/index.ts | 75 + packages/types/package.json | 11 + packages/types/src/index.ts | 25 + packages/ui/package.json | 10 + packages/ui/src/tokens.css | 79 + packages/utils/package.json | 11 + packages/utils/src/index.ts | 20 + pages/activity.html | 33 + pages/decode.html | 61 + pages/health.html | 75 + pages/know.html | 60 + pages/mindmap.html | 62 + pages/mine.html | 45 + pages/scale-test.html | 27 + pages/scales.html | 34 + pages/shuzi.html | 103 + pages/xingjing.html | 237 +++ proto/openapi.yaml | 37 + scales.html | 124 -- yuxingu.html | 530 ++---- 立项文档.md | 1 + 193 files changed, 8854 insertions(+), 1851 deletions(-) create mode 100644 .ai/README.md create mode 100644 .ai/ROADMAP.md create mode 100644 .ai/adr/0001-use-golang.md create mode 100644 .ai/adr/0002-use-gin.md create mode 100644 .ai/adr/0003-rest-api.md create mode 100644 .ai/adr/0004-use-jwt.md create mode 100644 .ai/adr/0005-use-redis.md create mode 100644 .ai/adr/0006-monorepo-vue-h5.md create mode 100644 .ai/ai-contract.md create mode 100644 .ai/api.md create mode 100644 .ai/architecture.md create mode 100644 .ai/checklists/bugfix.md create mode 100644 .ai/checklists/feature.md create mode 100644 .ai/checklists/refactor.md create mode 100644 .ai/checklists/release.md create mode 100644 .ai/coding.md create mode 100644 .ai/commands.md create mode 100644 .ai/constitution.md create mode 100644 .ai/database.md create mode 100644 .ai/definition-of-done.md create mode 100644 .ai/deployment.md create mode 100644 .ai/design/README.md create mode 100644 .ai/design/component-catalog.md create mode 100644 .ai/design/design-system.md create mode 100644 .ai/design/platform/flutter.md create mode 100644 .ai/design/platform/h5.md create mode 100644 .ai/design/platform/mini-program.md create mode 100644 .ai/design/platform/website.md create mode 100644 .ai/domain.md create mode 100644 .ai/domain/README.md create mode 100644 .ai/domain/domain-map.md create mode 100644 .ai/examples/good-api.json create mode 100644 .ai/examples/good-handler.go create mode 100644 .ai/examples/good-service.go create mode 100644 .ai/examples/good-test.go create mode 100644 .ai/file-map.md create mode 100644 .ai/forbidden.md create mode 100644 .ai/patterns/crud.md create mode 100644 .ai/patterns/handler.md create mode 100644 .ai/patterns/jwt.md create mode 100644 .ai/patterns/pagination.md create mode 100644 .ai/patterns/repository.md create mode 100644 .ai/patterns/service.md create mode 100644 .ai/playbooks/add-api.md create mode 100644 .ai/playbooks/login.md create mode 100644 .ai/playbooks/new-page.md create mode 100644 .ai/playbooks/new-table.md create mode 100644 .ai/playbooks/payment.md create mode 100644 .ai/product/README.md create mode 100644 .ai/product/STRATEGY.md create mode 100644 .ai/product/cece-feature-map.md create mode 100644 .ai/product/feature-map.md create mode 100644 .ai/prompts/bugfix.md create mode 100644 .ai/prompts/new-feature.md create mode 100644 .ai/prompts/refactor.md create mode 100644 .ai/review.md create mode 100644 .ai/security.md create mode 100644 .ai/tech-stack.md create mode 100644 .ai/testing.md create mode 100644 .ai/ui.md create mode 100644 .ai/workflow.md create mode 100644 .cursor/README.md create mode 120000 .cursor/commands/project-commands.md create mode 120000 .cursor/commands/refactor.md create mode 120000 .cursor/commands/review.md create mode 120000 .cursor/rules/ai-contract.md create mode 120000 .cursor/rules/api.md create mode 120000 .cursor/rules/architecture.md create mode 120000 .cursor/rules/coding.md create mode 120000 .cursor/rules/constitution.md create mode 120000 .cursor/rules/database.md create mode 120000 .cursor/rules/design-system.md create mode 120000 .cursor/rules/domain-map.md create mode 120000 .cursor/rules/domain.md create mode 120000 .cursor/rules/feature-map.md create mode 120000 .cursor/rules/forbidden.md create mode 120000 .cursor/rules/review.md create mode 120000 .cursor/templates/feature-checklist.md create mode 120000 .cursor/templates/feature.md create mode 100644 AGENTS.md create mode 100644 ARCHITECTURE.md create mode 100644 CONTRIBUTING.md create mode 100644 LEGACY.md create mode 100644 README.md create mode 100644 apps/admin-h5/README.md create mode 100644 apps/admin-h5/package.json create mode 100644 apps/api/cmd/server/main.go create mode 100644 apps/api/go.mod create mode 100644 apps/api/go.sum create mode 100644 apps/api/internal/config/config.go create mode 100644 apps/api/internal/handler/health.go create mode 100644 apps/api/internal/middleware/request_id.go create mode 100644 apps/api/pkg/response/response.go create mode 100644 apps/docs/README.md create mode 100644 apps/docs/adr/0001-monorepo-go-h5.md create mode 100644 apps/docs/adr/README.md create mode 100644 apps/docs/analysis/cece-teardown.md create mode 100644 apps/docs/business-model.md create mode 100644 apps/docs/prd-mvp.md create mode 100644 apps/docs/product-roadmap.md create mode 100644 apps/docs/standards/01-git.md create mode 100644 apps/docs/standards/02-go.md create mode 100644 apps/docs/standards/03-api.md create mode 100644 apps/docs/standards/04-database.md create mode 100644 apps/docs/standards/05-h5-vue.md create mode 100644 apps/docs/standards/06-mini-program.md create mode 100644 apps/docs/standards/07-typescript.md create mode 100644 apps/docs/standards/08-testing.md create mode 100644 apps/docs/standards/09-cicd.md create mode 100644 apps/docs/standards/10-ai-collab.md create mode 100644 apps/docs/standards/11-security.md create mode 100644 apps/docs/standards/12-docs.md create mode 100644 apps/docs/standards/README.md create mode 100644 apps/mini-program/README.md create mode 100644 apps/mini-program/app.js create mode 100644 apps/mini-program/app.json create mode 100644 apps/mini-program/pages/home/index.js create mode 100644 apps/mini-program/pages/home/index.json create mode 100644 apps/mini-program/pages/home/index.wxml create mode 100644 apps/mini-program/pages/home/index.wxss create mode 100644 apps/user-h5/index.html create mode 100644 apps/user-h5/package.json create mode 100644 apps/user-h5/src/App.vue create mode 100644 apps/user-h5/src/api/client.ts create mode 100644 apps/user-h5/src/components/TabBar.vue create mode 100644 apps/user-h5/src/main.ts create mode 100644 apps/user-h5/src/pages/AskPage.vue create mode 100644 apps/user-h5/src/pages/CompanionPage.vue create mode 100644 apps/user-h5/src/pages/DecodePage.vue create mode 100644 apps/user-h5/src/pages/ExplorePage.vue create mode 100644 apps/user-h5/src/pages/HomePage.vue create mode 100644 apps/user-h5/src/pages/MinePage.vue create mode 100644 apps/user-h5/src/router/index.ts create mode 100644 apps/user-h5/src/styles/app.css create mode 100644 apps/user-h5/src/vite-env.d.ts create mode 100644 apps/user-h5/tsconfig.json create mode 100644 apps/user-h5/vite.config.ts rename l2_LINK_20260629_171642_12h_data.json => archive/l2_LINK_20260629_171642_12h_data.json (100%) rename l2_LINK_kline_data.json => archive/l2_LINK_kline_data.json (100%) rename l2_LINK_signals.json => archive/l2_LINK_signals.json (100%) rename link_regime_chart.html => archive/link_regime_chart.html (100%) rename manual.html => archive/manual.html (100%) rename xingjing.html => archive/xingjing.html (100%) rename yuxingu_v1.html => archive/yuxingu_v1.html (100%) create mode 100644 css/app.css create mode 100644 css/decode.css create mode 100644 css/health.css create mode 100644 css/home.css create mode 100644 css/hub.css create mode 100644 css/mindmap.css create mode 100644 css/scale-test.css create mode 100644 css/scales.css create mode 100644 css/shuzi.css create mode 100644 css/xingjing.css create mode 100644 deploy/.env.example create mode 100644 deploy/docker-compose.yml rename common.js => js/common.js (99%) rename jiema.html => js/decode.js (54%) rename yangsheng.html => js/health.js (79%) create mode 100644 js/home.js rename mindmap.html => js/mindmap.js (68%) rename scale_test.html => js/scale-test.js (75%) create mode 100644 js/scales.js create mode 100644 js/shell.js rename shuzi.html => js/shuzi.js (71%) rename xingjing_app.html => js/xingjing.js (78%) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 packages/sdk/package.json create mode 100644 packages/sdk/src/index.ts create mode 100644 packages/types/package.json create mode 100644 packages/types/src/index.ts create mode 100644 packages/ui/package.json create mode 100644 packages/ui/src/tokens.css create mode 100644 packages/utils/package.json create mode 100644 packages/utils/src/index.ts create mode 100644 pages/activity.html create mode 100644 pages/decode.html create mode 100644 pages/health.html create mode 100644 pages/know.html create mode 100644 pages/mindmap.html create mode 100644 pages/mine.html create mode 100644 pages/scale-test.html create mode 100644 pages/scales.html create mode 100644 pages/shuzi.html create mode 100644 pages/xingjing.html create mode 100644 proto/openapi.yaml delete mode 100644 scales.html create mode 100644 立项文档.md diff --git a/.ai/README.md b/.ai/README.md new file mode 100644 index 0000000..5563a37 --- /dev/null +++ b/.ai/README.md @@ -0,0 +1,67 @@ +# AI Engineering System + +Machine-readable rules for Cursor / Claude Code / other agents. +**Source of truth for engineering constraints.** Prompts are helpers, not the core. + +## Priority (what AI depends on most) + +1. `constitution.md` — laws +2. `architecture.md` — layers +3. `domain.md` — ubiquitous language +4. `patterns/` — how to implement +5. `examples/` — copy shapes +6. `playbooks/` — step flows +7. `review.md` + `definition-of-done.md` + `checklists/` +8. `prompts/` — optional task templates + +Also always: `ai-contract.md`, `forbidden.md`, `file-map.md`, `workflow.md`, `commands.md`, `adr/`. + +## Tree + +``` +.ai/ +├── constitution.md +├── architecture.md +├── tech-stack.md +├── domain.md +├── domain/ +│ └── domain-map.md # DDD contexts / aggregates +├── product/ +│ ├── feature-map.md # 愈心谷 Feature Tree +│ └── cece-feature-map.md +├── file-map.md +├── workflow.md +├── ai-contract.md +├── coding.md +├── api.md +├── database.md +├── ui.md # IA pointer → design/ +├── design/ # AI Design System Contract +│ ├── design-system.md +│ ├── component-catalog.md +│ └── platform/ +├── deployment.md +├── testing.md +├── security.md +├── review.md +├── definition-of-done.md +├── forbidden.md +├── commands.md +├── adr/ +├── patterns/ +├── playbooks/ +├── checklists/ +├── examples/ +└── prompts/ # auxiliary +``` + +## Cursor + +`.cursor/rules|templates|commands` are **symlinks** into this directory. +Truth = `.ai/` only. See `../.cursor/README.md`. + +## Evolution + +See `ROADMAP.md` — Phase 2/3 (anti-patterns, metrics, gates, memory, graph, mcp, skills) are deferred on purpose. + +Root entry: `AGENTS.md` · `CLAUDE.md` diff --git a/.ai/ROADMAP.md b/.ai/ROADMAP.md new file mode 100644 index 0000000..ccfd7ec --- /dev/null +++ b/.ai/ROADMAP.md @@ -0,0 +1,38 @@ +# AI System Evolution Roadmap + +Do **not** build everything at once. Rules have maintenance cost. + +## Phase 1 — Now (shipped) + +- Rules: constitution, architecture, domain, coding, api, database, ui, … +- ADR, patterns, examples, playbooks +- Review / DoD / checklists / forbidden / commands / workflow / file-map +- `ai-contract.md` +- Cursor runtime: `.cursor/*` → symlink → `.ai/` (no content copy) + +**Invest next engineering time in:** polishing `patterns/`, `examples/`, `playbooks/` while building real features — not more rule files. + +## Phase 2 — When the codebase grows + +Add only when pain appears: + +| Item | Why wait | +|---|---| +| `anti-patterns/` | Useful after real mistakes accumulate | +| `metrics.md` | Numbers matter once CI exists | +| `quality-gates.md` | Wire when lint/test gates are real | +| `memory/` (known-bugs, tech-debt, todo) | Fill from actual incidents | + +## Phase 3 — Multi-agent / heavy automation + +| Item | Why wait | +|---|---| +| `graph/domain.yaml` | Knowledge graph for relation reasoning | +| `mcp/*.json` | Structured context servers | +| `skills//` | Packaged feature kits (login, payment, …) | + +Markdown remains human-readable; JSON/YAML for machines — introduce when agents > 1 or retrieval latency hurts. + +## Rule of thumb + +If adding a rule file does not prevent a repeated AI failure this week, **do not add it**. diff --git a/.ai/adr/0001-use-golang.md b/.ai/adr/0001-use-golang.md new file mode 100644 index 0000000..13c8665 --- /dev/null +++ b/.ai/adr/0001-use-golang.md @@ -0,0 +1,20 @@ +# ADR-0001 Use Golang for API + +## Status + +Accepted + +## Decision + +Backend is Go only (`apps/api`). + +## Reason + +1. Fits small team / AI-assisted CRUD + rule engines +2. Simple deploy (single binary) +3. Strong concurrency for future AI/proxy workloads + +## Never + +Never replace the API language with Node/Java/Python without a new ADR and explicit approval. +Legacy `server.py` is temporary and must not grow new product APIs. diff --git a/.ai/adr/0002-use-gin.md b/.ai/adr/0002-use-gin.md new file mode 100644 index 0000000..3a4effe --- /dev/null +++ b/.ai/adr/0002-use-gin.md @@ -0,0 +1,19 @@ +# ADR-0002 Use gin HTTP framework + +## Status + +Accepted + +## Decision + +Use `github.com/gin-gonic/gin` for HTTP routing and middleware. + +## Reason + +1. Widely known; good AI completion quality +2. Enough features for REST MVP +3. Low ceremony + +## Never + +Never switch to echo/fiber/chi “for fun” mid-feature. Framework change needs a new ADR. diff --git a/.ai/adr/0003-rest-api.md b/.ai/adr/0003-rest-api.md new file mode 100644 index 0000000..1fcf814 --- /dev/null +++ b/.ai/adr/0003-rest-api.md @@ -0,0 +1,20 @@ +# ADR-0003 REST API (not GraphQL) + +## Status + +Accepted + +## Decision + +Public HTTP API is REST under `/api/v1` with envelope `{code,message,data}`. + +## Reason + +1. Simpler mental model for H5 + mini-program +2. Better Cursor/Claude reliability than GraphQL schemas mid-flight +3. Easier debugging with curl + +## Never + +Never replace with GraphQL without approval + new ADR. +Internal gRPC later is allowed only behind a new ADR; public clients stay REST. diff --git a/.ai/adr/0004-use-jwt.md b/.ai/adr/0004-use-jwt.md new file mode 100644 index 0000000..48eb7e6 --- /dev/null +++ b/.ai/adr/0004-use-jwt.md @@ -0,0 +1,19 @@ +# ADR-0004 JWT (or opaque Bearer) for auth + +## Status + +Accepted (direction) + +## Decision + +Clients send `Authorization: Bearer `. Prefer JWT for stateless MVP; server may later move to opaque tokens + session store via new ADR. + +## Reason + +1. Works for H5 and future mini-program +2. Simple middleware story + +## Never + +Never invent parallel auth headers (`X-User-Id` as sole auth). +Never trust client-only “isVip” flags. diff --git a/.ai/adr/0005-use-redis.md b/.ai/adr/0005-use-redis.md new file mode 100644 index 0000000..068e8f4 --- /dev/null +++ b/.ai/adr/0005-use-redis.md @@ -0,0 +1,22 @@ +# ADR-0005 Redis — deferred + +## Status + +Deferred (not accepted for MVP) + +## Decision + +MVP does **not** require Redis. Use PostgreSQL + process memory only when safe. + +## Reason + +1. Reduce moving parts for first ship +2. Membership/session can start in Postgres + +## When to revisit + +Rate limiting, hot session cache, or distributed locks → write ADR-0005b Accepted before adding Redis. + +## Never + +Never add Redis “just in case” without an Accepted ADR update. diff --git a/.ai/adr/0006-monorepo-vue-h5.md b/.ai/adr/0006-monorepo-vue-h5.md new file mode 100644 index 0000000..454b60f --- /dev/null +++ b/.ai/adr/0006-monorepo-vue-h5.md @@ -0,0 +1,21 @@ +# ADR-0006 Monorepo + Vue3 user-h5 + +## Status + +Accepted + +## Decision + +- npm workspaces Monorepo: `apps/*` + `packages/*` +- Primary client: Vue 3 + TypeScript + Vite (`apps/user-h5`) +- Shared network: `packages/sdk` + +## Reason + +1. Multi-platform path to mini-program via SDK adapters +2. AI-friendly explicit boundaries +3. Retire root Legacy HTML gradually + +## Never + +Never start a second unrelated frontend framework for user-h5 without ADR. diff --git a/.ai/ai-contract.md b/.ai/ai-contract.md new file mode 100644 index 0000000..20fa8df --- /dev/null +++ b/.ai/ai-contract.md @@ -0,0 +1,36 @@ +# AI Contract + +Highest operational contract for every agent session. + +## Before coding + +MUST read: + +1. `.ai/constitution.md` +2. `.ai/architecture.md` +3. `.ai/domain.md` (+ `domain/domain-map.md` when touching models/API) +4. `.ai/coding.md` +5. Task-relevant: `api.md` / `database.md` / `ui.md` / `security.md` / `product/feature-map.md` +6. UI tasks: `design/design-system.md` + `design/component-catalog.md` + `design/platform/*` +7. Relevant ADR under `.ai/adr/` if changing stack or style + +## After coding + +MUST: + +1. Run / follow `.ai/review.md` and print Review block +2. Verify `.ai/definition-of-done.md` +3. Use matching checklist under `.ai/checklists/` +4. Prefer verifying with `.ai/commands.md` (build/test/health) + +## Never + +- Guess API shape +- Guess DB schema +- Guess product requirement +- Invent endpoints, tables, or domain words not in `.ai/domain.md` / OpenAPI / task +- Silently reverse an Accepted ADR + +## If unclear + +**ASK FIRST.** Do not invent. Do not “assume and continue”. diff --git a/.ai/api.md b/.ai/api.md new file mode 100644 index 0000000..eb6c64f --- /dev/null +++ b/.ai/api.md @@ -0,0 +1,49 @@ +# API — Golden Rules + +## Style + +- REST only for public HTTP. +- Never use POST for pure queries (use GET). +- Verbs: GET / POST / PUT / DELETE. +- Prefix: `/api/v1`. + +## Envelope (mandatory — never invent alternatives) + +Success: + +```json +{ "code": 0, "message": "success", "data": {} } +``` + +Error: + +```json +{ "code": 10001, "message": "user not found" } +``` + +Forbidden shapes: `{ success: true }`, `{ ok: true }`, bare arrays as root. + +## Error codes + +- Start business codes at **10000**. +- 0 = success. +- Ranges: 1xxxx general/auth, 2xxxx profile, 3xxxx report/scale, 4xxxx order/membership, 5xxxx AI/companion. + +## Pagination + +Query: `page`, `page_size` +Data: + +```json +{ "list": [], "total": 0, "page": 1, "page_size": 20 } +``` + +## Auth + +- Bearer token in `Authorization` header when required. +- Resource ownership checked in service layer. + +## Docs + +- Public route changes update `proto/openapi.yaml` in the same change set. +- Do not invent endpoints that are not in OpenAPI / task spec. diff --git a/.ai/architecture.md b/.ai/architecture.md new file mode 100644 index 0000000..37b5472 --- /dev/null +++ b/.ai/architecture.md @@ -0,0 +1,51 @@ +# Architecture + +## Allowed call graph + +``` +UI (user-h5 / mini-program) + → packages/sdk + → API Handler + → Service + → Repository + → Database +``` + +## Forbidden + +- Handler → Database (skip Service/Repository) +- UI → Database +- UI direct `fetch` to raw URLs (must use `@yuxingu/sdk` or `src/api` thin wrapper) +- Service importing Handler +- Circular package imports +- Giant `utils` / `common` dump packages + +## Monorepo map + +| Path | Role | +|---|---| +| `apps/api` | Only backend | +| `apps/user-h5` | Primary client (Vue3+TS) | +| `apps/mini-program` | Scaffold only until tasked | +| `apps/admin-h5` | Deferred | +| `packages/sdk` | Multi-platform HTTP client | +| `packages/types` | Shared TS types | +| `packages/utils` | Pure helpers | +| `packages/ui` | Design tokens | +| Root HTML / `server.py` | Legacy — do not extend | + +## Feature flow (mandatory when adding a feature) + +``` +API route + handler + → service + → repository (if persistence) + → migration (if schema change) + → SDK / types update + → UI page or component + → test for money/scoring/auth paths + → OpenAPI touch if public API changed + → self-review +``` + +Missing migration or skipping service layer = incomplete. diff --git a/.ai/checklists/bugfix.md b/.ai/checklists/bugfix.md new file mode 100644 index 0000000..8705dec --- /dev/null +++ b/.ai/checklists/bugfix.md @@ -0,0 +1,9 @@ +# Checklist: Bugfix + +- [ ] Failure mode stated +- [ ] Root cause located (file/func) +- [ ] Minimal fix only +- [ ] Regression test if scoring/auth/payment +- [ ] No unrelated refactors +- [ ] Build/tests green +- [ ] Review: Architecture / Security / Test / Scope diff --git a/.ai/checklists/feature.md b/.ai/checklists/feature.md new file mode 100644 index 0000000..26f886f --- /dev/null +++ b/.ai/checklists/feature.md @@ -0,0 +1,16 @@ +# Checklist: Feature + +- [ ] Architecture layers respected +- [ ] Domain words match `.ai/domain.md` +- [ ] API (+ OpenAPI if public) +- [ ] Service +- [ ] Repository (if persistence) +- [ ] Migration (if schema) +- [ ] SDK/types (if client needs) +- [ ] UI (if user-facing) +- [ ] Test or smoke steps +- [ ] Docs touched if behavior user-visible +- [ ] `go test` / `npm run build:h5` as applicable +- [ ] Health still OK +- [ ] Review block printed +- [ ] DoD satisfied diff --git a/.ai/checklists/refactor.md b/.ai/checklists/refactor.md new file mode 100644 index 0000000..81d9499 --- /dev/null +++ b/.ai/checklists/refactor.md @@ -0,0 +1,8 @@ +# Checklist: Refactor + +- [ ] Goal / non-goals stated (behavior unchanged unless asked) +- [ ] No ADR silently reversed +- [ ] Layers still clean +- [ ] File/function size improved toward limits +- [ ] Tests still pass; added if extracting unscored logic +- [ ] Review: Architecture / Readability / Test / Scope diff --git a/.ai/checklists/release.md b/.ai/checklists/release.md new file mode 100644 index 0000000..27047e4 --- /dev/null +++ b/.ai/checklists/release.md @@ -0,0 +1,10 @@ +# Checklist: Release + +- [ ] main green: `go test ./...`, `npm run build:h5` +- [ ] Migrations applied plan documented +- [ ] Health endpoint verified +- [ ] No secrets in artifact +- [ ] Image tag ≠ `latest` (prod) +- [ ] OpenAPI matches deployed routes +- [ ] Changelog / version tag if shipping +- [ ] Rollback path known diff --git a/.ai/coding.md b/.ai/coding.md new file mode 100644 index 0000000..80cd260 --- /dev/null +++ b/.ai/coding.md @@ -0,0 +1,37 @@ +# Coding — Golden Rules + +## Size + +- One responsibility per file. +- One responsibility per function. +- No function longer than **50** lines. +- No file longer than **400** lines (hard preference; split earlier if possible). +- No circular dependency. + +## Go + +- Business packages by domain: `auth`, `profile`, `report`, `order`, `payment` — not `utils`. +- Interface defined at **caller** (usually handler), implemented by service. +- First parameter of I/O methods: `context.Context`. +- Errors: `return fmt.Errorf("create user: %w", err)`. +- Never `panic` in request path. +- Never use package-level mutable globals for request state. +- Export comments required on public funcs (what / in / out / side effects). + +## TypeScript / Vue + +- `strict: true`. No casual `any`. +- Components: `PascalCase.vue` (`UserCard.vue`). +- Pages under `src/pages/`, composables under `src/hooks/`. +- No direct `fetch` in pages — use SDK. +- Shared domain types go to `packages/types`. + +## Naming + +- Consistent domain words: profile, report, scale, membership, order. +- Prefer boring clear names over clever short names. + +## Comments + +- Public APIs: always document. +- Do not narrate obvious code. Document intent, invariants, side effects. diff --git a/.ai/commands.md b/.ai/commands.md new file mode 100644 index 0000000..65b5087 --- /dev/null +++ b/.ai/commands.md @@ -0,0 +1,67 @@ +# Commands — Do Not Guess + +Run from repo root unless noted. + +## Go API (`apps/api`) + +```bash +export GOPROXY=https://goproxy.cn,direct # if download timeout + +cd apps/api +go mod tidy +go build ./... +go test ./... +go run ./cmd/server +``` + +Health: + +```bash +curl -s http://127.0.0.1:8080/api/v1/healthz +curl -s http://127.0.0.1:8080/api/v1/ping +``` + +## User H5 + +```bash +npm install +npm run dev:h5 +npm run build:h5 +``` + +## Docker / DB + +```bash +docker compose -f deploy/docker-compose.yml up -d +docker compose -f deploy/docker-compose.yml down +``` + +## Lint (when configured) + +```bash +# Go — after golangci-lint is added: +# cd apps/api && golangci-lint run + +# H5 — after eslint is added: +# npm run lint -w @yuxingu/user-h5 +``` + +## Migrations (when make targets exist) + +```bash +# Preferred once Makefile lands: +# make migrate +# make migrate-down + +# Until then: document the goose/migrate command used in the PR. +``` + +## Dev all (local) + +```bash +# terminal 1 +cd apps/api && go run ./cmd/server + +# terminal 2 +npm run dev:h5 +``` diff --git a/.ai/constitution.md b/.ai/constitution.md new file mode 100644 index 0000000..badd1cd --- /dev/null +++ b/.ai/constitution.md @@ -0,0 +1,34 @@ +# Project Constitution — 愈心谷 + +最高原则。与本文件冲突时,以本文件为准。 + +## Absolute Laws + +1. Architecture never broken. Layers: UI → API → Service → Repository → Database. No skipping. +2. Readability > Cleverness. +3. Deterministic > Magic. +4. Simple > Complex. +5. Explicit > Implicit. +6. No hidden global state. +7. Every module independently testable. +8. Every feature deployable. +9. Every API documented (OpenAPI or apps/docs). +10. Every database change versioned (migration required). + +## Product Laws (YuXinGu) + +1. Never invent medical efficacy claims. +2. Never use fortune-telling / 吉凶祸福 wording. +3. Birthday and profile data are sensitive — minimize, authorize, allow delete. +4. Billing and unlock decisions happen on the server, never trust the client alone. +5. New product code lives in `apps/` and `packages/` only. Legacy root HTML is read-only unless the task is explicit migration. + +## AI Laws + +1. Obey `.ai/ai-contract.md` every session. +2. Load constitution → architecture → domain before writing code. +3. Never invent APIs/DB/domain words; never reverse Accepted ADRs silently. +4. Never modify files outside the current task scope. +5. Prefer patterns + examples + playbooks over prompts. +6. Prefer small diffs. One feature per change set. +7. Think → architecture check → code → commands → review → DoD. diff --git a/.ai/database.md b/.ai/database.md new file mode 100644 index 0000000..0664502 --- /dev/null +++ b/.ai/database.md @@ -0,0 +1,39 @@ +# Database — Golden Rules + +## Naming + +- `snake_case` only for tables and columns. +- Good: `user_profile`, `payment_order`, `user_session`. +- Forbidden prefixes: `tbl_`, `t_`. + +## Required columns on business tables + +``` +id +created_at +updated_at +deleted_at +``` + +## Types + +- Never `varchar(5000)` as a habit. +- Prefer bounded `varchar(n)`. +- `TEXT` only when necessary (long content bodies). +- Timestamps: `timestamptz`. + +## Keys & indexes + +- Every foreign key indexed. +- Unique business keys enforced with UNIQUE. + +## Migrations + +- Schema change without migration = incomplete feature. +- Migrations live in `apps/api/migrations/`. +- Provide Up and Down when tool supports it. + +## Soft delete + +- Default: set `deleted_at`, do not hard delete user content in MVP. +- Queries must filter `deleted_at IS NULL` unless explicitly including deleted. diff --git a/.ai/definition-of-done.md b/.ai/definition-of-done.md new file mode 100644 index 0000000..7b7800a --- /dev/null +++ b/.ai/definition-of-done.md @@ -0,0 +1,17 @@ +# Definition of Done + +Code finished ≠ Done. + +A feature is Done only when applicable items PASS: + +- [ ] Code (compiles / typechecks) +- [ ] API (handler + service + repository as needed) +- [ ] Test (unit or listed smoke for critical paths) +- [ ] Migration (if schema changed) +- [ ] Docker / deploy notes (if new service or env) +- [ ] Docs (OpenAPI / short PRD note if user-visible) +- [ ] Review (`.ai/review.md` checklist output) +- [ ] Build (`go test ./...` and/or `npm run build:h5` for touched side) +- [ ] Health check still green (`/api/v1/healthz`) + +If an item is N/A, state why. Silent skip = not Done. diff --git a/.ai/deployment.md b/.ai/deployment.md new file mode 100644 index 0000000..bee3573 --- /dev/null +++ b/.ai/deployment.md @@ -0,0 +1,33 @@ +# Deployment — Golden Rules + +## Containers + +- Every runnable service that ships to prod must have a Dockerfile. +- Never use image tag `latest` in production. +- Always semantic version tags: `v0.1.0` or git SHA. + +## Health + +- Liveness: `GET /api/v1/healthz` required. +- When DB is required for traffic: add readiness endpoint (e.g. `/api/v1/readyz`) that checks DB. +- Alias names `/health` `/readiness` `/liveness` may map to the above; keep one canonical path documented in OpenAPI. + +## Config + +- Secrets via env only. Commit `.env.example`, never `.env`. +- Required: `APP_ENV`, `HTTP_ADDR`, `DATABASE_URL`. + +## CI + +- No manual prod deploy as the happy path — CI builds and tags. +- Minimum gates: `go test ./...` (api), `npm run build:h5`. + +## Local + +```bash +docker compose -f deploy/docker-compose.yml up -d +cd apps/api && go run ./cmd/server +npm run dev:h5 +``` + +If Go module download times out in CN: `export GOPROXY=https://goproxy.cn,direct`. diff --git a/.ai/design/README.md b/.ai/design/README.md new file mode 100644 index 0000000..69a0204 --- /dev/null +++ b/.ai/design/README.md @@ -0,0 +1,15 @@ +# AI Design System + +Machine-readable **UI/UX/VI contract** for agents generating H5 / Mini Program / Web / App UI. + +| File | Purpose | +|---|---| +| [design-system.md](design-system.md) | Foundation: tokens, color, type, spacing, motion, voice | +| [component-catalog.md](component-catalog.md) | Button / Card / Chat / Report / Membership … | +| [platform/h5.md](platform/h5.md) | Mobile Web implementation limits | +| [platform/mini-program.md](platform/mini-program.md) | WeChat constraints | +| [platform/website.md](platform/website.md) | Desktop / SEO / admin | +| [platform/flutter.md](platform/flutter.md) | Future native | + +Runtime tokens: `packages/ui/src/tokens.css` +Product chrome / tabs: `../ui.md` diff --git a/.ai/design/component-catalog.md b/.ai/design/component-catalog.md new file mode 100644 index 0000000..30bf1d4 --- /dev/null +++ b/.ai/design/component-catalog.md @@ -0,0 +1,233 @@ +# Component Catalog — AI Contract + +Agents MUST check this file + platform `components/` before creating UI. + +Naming: PascalCase; domain words from `.ai/domain.md`. +Visual tokens: [design-system.md](design-system.md). +Ship path today: `apps/user-h5/src/components/` (Vue). Mini Program / Flutter map 1:1 by name. + +Status: `exists` | `required` (build when first needed) | `future` + +--- + +## Primitives + +### Button + +Status: **required** + +Types ONLY: `Primary` · `Secondary` · `Ghost` · `Danger` · `Loading` · `Disabled` + +| Prop | Notes | +|---|---| +| variant | primary / secondary / ghost / danger | +| loading | shows progress; blocks double submit | +| disabled | uses `--color-primary-disabled` or neutral | +| block | full width on H5 forms | + +MUST NOT: `SpecialButton`, `CustomButton2`, one-off gradient buttons outside Primary. + +Primary visual: pill + brand CTA gradient from design-system. + +### IconTile + +Status: **required** + +Square/rounded tile for Explore / Home grid. Soft accent bg + symbol (△ ◆ ☯ 问), not emoji. + +### Avatar + +Status: **required** + +Profile face placeholder (initials or soft color). Sizes: sm 32 / md 40 / lg 56. + +### Tag / Chip + +Status: **required** + +Filter chips, Soft labels. Active = white surface + primary text + light shadow. + +### Divider + +Status: **required** — `--color-border`, 1px. + +### Skeleton + +Status: **required** — list/card placeholders; calm pulse ≤300ms feel, no flashy shimmer rainbow. + +### Toast / Banner + +Status: **required** — success / error / info. Short copy; no fear wording. + +--- + +## Layout chrome + +### AppShell + +Status: **exists** (TabBar + max-width column) + +User column `max-width: var(--layout-max-user)`. Safe-area bottom padding. + +### TabBar + +Status: **exists** + +Tabs: 首页 / 探索 / **问** / 陪伴 / 我的. Center Ask may use primary circle. Active = primary. + +### TopBar + +Status: **required** + +Secondary pages: back · title · optional action. Soft wash or white; not heavy app bar. + +### ContentSheet + +Status: **required** + +White rounded sheet over peach wash (home feed pattern). + +--- + +## Forms + +### TextField / DateField + +Status: **required** + +Label required · error · disabled · focus border soft primary. Birthday Decode uses compact year/month/day fields. + +### FormSection + +Status: **required** — label + control + helper/error. + +Rules: Label, error, loading, disabled states always. + +--- + +## Cards + +### Card (base) + +Status: **required** + +Structure: Container → Padding (`--spacing-md`) → Content → Optional Action. +Surface white, `--radius-lg`, `--shadow-card`. No random card skins. + +### FeatureCard + +Status: **required** — Explore entry (icon + title + one line). + +### ReportCard + +Status: **required** + +Decode / Match summary. Shows free conclusion; locked sections use PaywallLock. Footer disclaimer slot. + +### MoodCard + +Status: **required** — Companion daily mood entry/result. Soft, never clinical. + +### SolarTermCard + +Status: **required** — Today’s SolarTerm title + short lifestyle tip (no 吉凶). + +### SubscriptionCard + +Status: **required** + +Membership plans. Clear price · period · benefits. **MUST NOT** fake countdown / dark patterns. CTA = Primary Button. + +### UnlockCard + +Status: **required** — single Report / Match Unlock alternative to VIP. + +--- + +## AI / Ask + +### ChatThread + +Status: **required** — scrollable Ask history; Profile context chip at top (Self / Other switch). + +### ChatBubble + +Status: **required** + +User vs AI. AI bubble: soft surface, calm radius; no neon bot skins. Streaming = typing/skeleton, not decorative bounce spam. + +### AIMessage + +Status: **required** + +Structured Ask answer: short answer · optional bullets · disclaimer when health-adjacent. Prefer structured blocks over wall of text. + +### PromptChip + +Status: **required** — suggested follow-ups under Ask input; path-aware (性格 / 关系 / 养生), never 运势. + +### AskInputBar + +Status: **required** — bottom composer; ≥44px hit; send loading state. + +--- + +## Profile & social + +### ProfileSwitcher + +Status: **required** — Self / Other list; “帮 TA 测” entry. + +### ShareCard + +Status: **required** — Decode / ScaleResult share visual; brand + one conclusion line; no medical claims. + +### ScaleQuestion / ScaleResultView + +Status: **required** — progress · options · result type + CTA to Decode/Ask. + +--- + +## Commerce + +### PaywallLock + +Status: **required** — blurred/locked reason+plan; CTA Unlock or Membership. + +### OrderSummary + +Status: **required** — plan/report line items before pay-mock. + +--- + +## States + +### EmptyState + +Status: **required** — one sentence + one action. + +### ErrorState + +Status: **required** — message + retry/back. + +### LoadingBlock + +Status: **required** — page/section skeleton wrapper. + +--- + +## Forbidden inventions + +Without approval / catalog update: + +- Parallel button systems +- “Glassmorphism” chat skins +- Emoji-only navigation +- New layout shells beside AppShell +- Countdown / guilt paywalls + +--- + +## Extension rule + +Need a new component → add a row here (name, status, props, do/don’t) in the **same PR** as the first implementation. Then build under platform `components/`. diff --git a/.ai/design/design-system.md b/.ai/design/design-system.md new file mode 100644 index 0000000..183e74a --- /dev/null +++ b/.ai/design/design-system.md @@ -0,0 +1,276 @@ +# AI Design System Contract — 愈心谷 + +**Mandatory.** Agents MUST read this before creating or modifying frontend UI +(`apps/user-h5`, `apps/mini-program`, `apps/admin-h5`, future website / Flutter). + +| File | Role | +|---|---| +| [component-catalog.md](component-catalog.md) | Reusable components — check before inventing | +| [platform/h5.md](platform/h5.md) | Mobile Web (primary) | +| [platform/mini-program.md](platform/mini-program.md) | WeChat Mini Program | +| [platform/website.md](platform/website.md) | Desktop / marketing Web | +| [platform/flutter.md](platform/flutter.md) | Native app (future) | + +Tokens: `packages/ui/src/tokens.css` +IA / tabs: `.ai/ui.md` · Domain: `.ai/domain.md` + +--- + +## 0. Purpose + +This is an **AI contract**, not a traditional design deck. + +- Consistent brand across H5 / Mini Program / Web / App +- Stop random UI decisions +- Reuse components before inventing +- Shared language here; platform limits in `platform/*` + +When uncertain: **DO NOT invent. ASK FIRST.** Consistency > creativity. + +--- + +## 1. Design Philosophy + +### Core values + +| Value | UI implication | +|---|---| +| Simple | One job per section; short copy | +| Calm | Warm soft surfaces; low motion; no neon | +| Trustworthy | Clear hierarchy; disclaimer on reports | +| Professional | Stable tokens; no gaming chrome | +| Human-centered | Soft language; easy exit from paywalls | +| AI-first | Ask / chat are first-class patterns | + +### Domain (psychology / wellness) — MUST + +- Gentle self-understanding — not clinical hospital UI, not fortune-telling casino. +- Atmosphere: warm peach / coral wash → white content sheet. +- Unicode symbols (△ ☯ ◈) or SVG over emoji in chrome. +- Copy: friendly, clear, respectful, encouraging. +- **MUST NOT:** fear, guilt, fake urgency, medical cure claims, 吉凶祸福. + +### MUST NOT create + +- Excessive multi-stop gradients on chrome +- Neon, glow, dark-mode-by-default, purple-on-white “AI slop” +- Gaming HUD, particle spam, continuous decorative animation +- Random illustrations / emoji clusters +- Inconsistent spacing / one-off hex colors +- Dashboard clutter in home first viewport + +--- + +## 2. Design Architecture + +``` +Foundation (tokens) → Components (catalog) → Platform implementation +``` + +AI MUST follow this order. Never invent page magic numbers or duplicate components. + +--- + +## 3. Foundation — Design Tokens + +All UI values MUST use tokens from `packages/ui` (or platform mapping of the same names). + +Forbidden: `padding:17px; color:#2563eb; border-radius:13px;` +Required: `padding:var(--spacing-md); color:var(--color-primary); border-radius:var(--radius-md);` + +If a token is missing: **ASK** or add it to `packages/ui/src/tokens.css` in the same change — do not hardcode. + +Legacy `--yxg-*` remains valid; new code SHOULD prefer semantic names below. + +--- + +## 4. Color System + +Use only these semantic colors. Do not invent brand hues. + +### Primary + +| Token | Value | Use | +|---|---|---| +| `--color-primary` | `#E54D42` | CTA, brand, active tab | +| `--color-primary-hover` | `#D44338` | Hover / pressed | +| `--color-primary-soft` | `#FFE4E4` | Soft chip / Ask surface | +| `--color-primary-disabled` | `#F5B5B0` | Disabled primary | + +Brand CTA gradient (only this one): +`linear-gradient(135deg, #FF7A6E, var(--color-primary))` + +### Atmosphere + +| Token | Value | +|---|---| +| `--color-bg-start` | `#FFD1C7` | +| `--color-bg-end` | `#FFC8B5` | +| `--color-bg-sheet` | `#FFF9F7` | + +Do not replace with flat gray or purple gradients. + +### Neutral + +| Token | Value | Use | +|---|---|---| +| `--color-surface` | `#FFFFFF` | Cards, sheets, nav | +| `--color-text-primary` | `#333333` | Titles / body | +| `--color-text-secondary` | `#999999` | Subcopy | +| `--color-text-tertiary` | `#BBBBBB` | Meta | +| `--color-border` | `#F0F0F0` | Dividers | +| `--color-border-strong` | `#EEEEEE` | Inputs | +| `--color-input-bg` | `#FDFAF8` | Form fields | + +### Domain accents (tint only — not global CTA) + +| Token | Value | Feature | +|---|---|---| +| `--color-accent-gold` | `#C8923A` | Decode | +| `--color-accent-gold-soft` | `#FFF3D6` | Decode tile | +| `--color-accent-blue` | `#4A90E2` | Scale | +| `--color-accent-blue-soft` | `#E3EEFF` | Scale tile | +| `--color-accent-green` | `#5CB85C` | Companion / SolarTerm | +| `--color-accent-green-soft` | `#E0F6E4` | Companion tile | +| `--color-accent-orange` | `#E8985A` | Warm secondary | +| `--color-accent-purple` | `#8B5CF6` | Sparse only — never page theme | + +### Status (only) + +`success` · `warning` · `error` · `info` +Map: success≈green, warning≈orange/gold, error≈primary family, info≈blue. + +### Forbidden + +Neon / pure-black full-bleed / random Tailwind blue as brand / purple global theme. + +--- + +## 5. Typography + +Stack: `-apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", "Helvetica Neue", sans-serif` +Do not add Inter/Roboto/display fonts on product UI without ADR. + +| Level | Size | Weight | Use | +|---|---|---|---| +| Display | 28–32px | 700 | Brand 愈心谷 only | +| Heading 1 | 20–22px | 700 | Page hero | +| Heading 2 | 16–17px | 600–700 | Section | +| Heading 3 | 15px | 600 | Card title | +| Body | 14px | 400 | Default | +| Body Small | 12–13px | 400 | Secondary | +| Caption | 10–11px | 400 | Tab / disclaimer | + +No arbitrary sizes. Body line-height ≈ 1.6–1.7. + +--- + +## 6. Spacing + +Scale: **4 · 8 · 12 · 16 · 24 · 32 · 48 · 64** + +| Token | px | +|---|---| +| `--spacing-2xs` | 4 | +| `--spacing-xs` | 8 | +| `--spacing-sm` | 12 | +| `--spacing-md` | 16 | +| `--spacing-lg` | 24 | +| `--spacing-xl` | 32 | +| `--spacing-2xl` | 48 | +| `--spacing-3xl` | 64 | + +Forbidden: 13 / 18 / 27 / 35. Page pad default: 16. + +--- + +## 7. Radius, shadow, width + +| Token | Value | Use | +|---|---|---| +| `--radius-sm` | 10px | Back / small controls | +| `--radius-md` | 12px | Inputs / tiles | +| `--radius-lg` | 16px | Cards | +| `--radius-xl` | 18–20px | Large sheets | +| `--radius-pill` | 999px | CTA capsule | + +Shadows: `--shadow-card: 0 2px 10px rgba(0,0,0,.04)`; `--shadow-nav: 0 -2px 8px rgba(0,0,0,.03)`. No glow. + +`--layout-max-user: 430px` · `--layout-nav-h: 56px` + +--- + +## 8. Components + +MUST reuse. Before creating: catalog → `src/components/` → extend. +No `SpecialButton` / `CustomButton2` / `NewCard`. + +--- + +## 9. Layout Patterns + +| Pattern | Screens | +|---|---| +| HomeWash | Brand + Decode + white sheet | +| List | Explore, orders | +| Detail | Report / Match | +| Form | Decode birthday, edit Profile | +| Profile | Mine / switch Profile | +| Settings | Account, privacy | +| AI Chat | Ask | +| Paywall | Membership / Unlock | +| Companion | SolarTerm + Mood | + +Home first viewport: **brand · headline · Decode form · CTA**. No stats/promo clutter. + +--- + +## 10. Interaction States + +Network UI MUST have: Loading (skeleton) · Empty · Error (+ recovery) · Success · Disabled. +Paywall: free conclusion, lock reason/plan; no fake countdown. + +--- + +## 11. Motion + +Durations: **150 / 200 / 300ms**. Feedback only. +No continuous loops or decorative scroll animation. + +--- + +## 12. Accessibility + +Contrast for body text; touch ≥ 44px; no hover-only essentials; label interactive controls. + +--- + +## 13. Brand Voice + +温和、清晰、尊重。禁止恐吓 / 道德绑架 / 虚假倒计时. +Report/Ask: lifestyle disclaimer(非医疗、非算命). Labels follow `.ai/domain.md`. + +--- + +## 14. Platforms + +Shared language = this file. Limits = `platform/*`. +Priority: **H5 → Mini Program → Website → Flutter**. + +--- + +## 15. Before Coding Checklist + +- [ ] Catalog + components checked +- [ ] Correct `platform/*.md` read +- [ ] Tokens only +- [ ] Loading / empty / error +- [ ] Compliance copy if Report / Ask / Membership +- [ ] A11y (touch / contrast) +- [ ] No emoji chrome; no medical / 吉凶 wording + +--- + +## 16. Final Rule + +Uncertain → **ASK**. Do not invent colors, components, or layouts. diff --git a/.ai/design/platform/flutter.md b/.ai/design/platform/flutter.md new file mode 100644 index 0000000..c15cd34 --- /dev/null +++ b/.ai/design/platform/flutter.md @@ -0,0 +1,38 @@ +# Platform Contract — Flutter / Native App + +Future client. Not in MVP scope unless task explicitly opens it. +Design language: [../design-system.md](../design-system.md) · Catalog names stay the same. + +--- + +## Priority + +**Performance > Native experience > Visual decoration** + +--- + +## MUST + +- Map tokens to `ThemeData` / design token classes — same hex and spacing scale. +- Native navigation patterns (Material/Cupertino as decided by ADR). +- Platform permissions (camera, photos, notifications) only with clear UX rationale. +- 60fps interactions; avoid heavy blur/shadow on low-end devices. +- Reuse catalog component names (`Button`, `ChatBubble`, `SubscriptionCard`…). +- Same compliance and domain copy rules. + +--- + +## MUST NOT + +- Invent a dark-neon “AI app” skin. +- Bypass API unlock/membership rules with local flags. +- Port Web DOM assumptions. + +--- + +## Checklist + +- [ ] Token theme 1:1 with `packages/ui` +- [ ] Catalog parity for screens in scope +- [ ] Permission + performance considered +- [ ] ADR if introducing Flutter stack officially diff --git a/.ai/design/platform/h5.md b/.ai/design/platform/h5.md new file mode 100644 index 0000000..045594f --- /dev/null +++ b/.ai/design/platform/h5.md @@ -0,0 +1,47 @@ +# Platform Contract — H5 (Mobile Web) + +Primary shipping client: `apps/user-h5` (Vue 3 + Vite). +Design language: [../design-system.md](../design-system.md) · Components: [../component-catalog.md](../component-catalog.md) + +--- + +## MUST + +- Mobile-first; content column `max-width: var(--layout-max-user)` (430px), centered on large screens. +- Touch targets ≥ 44×44px. +- Prefer bottom actions (TabBar, AskInputBar, sticky CTA). +- Safe area: `env(safe-area-inset-bottom)` on TabBar / fixed footers. +- `user-scalable` / viewport per existing app shell; no desktop-only hover for essentials. +- Import tokens from `@yuxingu/ui` / `packages/ui` — no page-local brand hex. +- Data via `src/api` → `@yuxingu/sdk` only (no raw `fetch` in pages). +- Structure: `pages/` · `components/` · `layouts/` · `stores/` · `router/`. +- Loading / empty / error on every network view. +- Report / Decode / Ask: compliance disclaimer. + +--- + +## MUST NOT + +- Rely on hover tooltips for critical info. +- Use `window` APIs that break iOS WeChat/browser without fallback. +- Invent a second tab IA; tabs = Home / Explore / Ask / Companion / Mine. +- Grow Legacy root HTML (`yuxingu.html`, `pages/`) for new UI. + +--- + +## Layout notes + +- HomeWash: peach gradient body + Decode form + white ContentSheet. +- Secondary: TopBar + padded content. +- Keyboard: inputs must not hide primary CTA (scroll / visualViewport aware when needed). + +--- + +## Checklist before Done + +- [ ] Tokens only +- [ ] Catalog component reused or catalog updated +- [ ] 44px touch +- [ ] Safe area +- [ ] States + compliance if needed +- [ ] `npm run build:h5` diff --git a/.ai/design/platform/mini-program.md b/.ai/design/platform/mini-program.md new file mode 100644 index 0000000..e4a8c86 --- /dev/null +++ b/.ai/design/platform/mini-program.md @@ -0,0 +1,47 @@ +# Platform Contract — WeChat Mini Program + +Client: `apps/mini-program`. +Design language shared with H5; **implementation is native mini-program**, not Vue SFC reuse. + +Tokens: map `packages/ui` values into `app.wxss` / CSS variables supported by the base library — do not fork a second palette. + +--- + +## MUST + +- Use Mini Program lifecycle (`onLoad` / `onShow` / …) and `wx` APIs. +- Native navigation (`wx.navigateTo`, tabBar in `app.json`). +- Components under `components/`; pages call `services/*` only (same idea as H5 sdk). +- Support: share (`onShareAppMessage`), user authorization, payment flow when enabled. +- Touch ≥ 44px; rpx spacing aligned to 4/8/12/16… scale. +- Same IA tabs and domain copy rules as H5. +- Paywall / Membership decisions still from API — never local unlock only. + +--- + +## MUST NOT + +- `window` / `document` / DOM browser APIs. +- Import Vue SFCs from `user-h5` as runtime. +- Duplicate brand colors with new hex values. +- Build UGC广场 or Consult marketplace in MVP. + +--- + +## Mapping + +| H5 | Mini Program | +|---|---| +| Vue page | `pages/*` | +| Pinia | global / store pattern approved in playbook | +| `@yuxingu/sdk` | same package or thin `services` wrapper | +| CSS modules | `wxss` + shared token sheet | + +--- + +## Checklist + +- [ ] No browser-only APIs +- [ ] Tokens mapped 1:1 +- [ ] Share / auth / pay paths considered +- [ ] Catalog names reused diff --git a/.ai/design/platform/website.md b/.ai/design/platform/website.md new file mode 100644 index 0000000..18fbea3 --- /dev/null +++ b/.ai/design/platform/website.md @@ -0,0 +1,44 @@ +# Platform Contract — Website (Desktop / Marketing Web) + +Future or secondary surfaces (landing, SEO pages, admin beyond mobile shell). +Brand tokens identical to [../design-system.md](../design-system.md). + +--- + +## MUST + +- SEO basics: title / description / semantic headings on marketing pages. +- Keyboard navigation and visible focus for interactive controls. +- Hover states allowed as **enhancement**; core actions still work without hover. +- Large screens: may use sidebar, table, multi-column — do not ship desktop-only flows that leave mobile broken if the same app is responsive. +- Accessibility: contrast, labels, skip/focus order. +- Admin (`apps/admin-h5`): denser List/Table OK; still use tokens (primary coral, not random admin blue). + +--- + +## MUST NOT + +- Purple SaaS gradient landing that ignores 愈心谷 peach/coral brand. +- Heavy dashboard chrome on consumer marketing hero (see design-system home budget). +- Medical / 吉凶 claims in SEO copy. + +--- + +## Breakpoints + +| Name | Guide | +|---|---| +| Mobile | < 768px — follow H5 patterns | +| Tablet | 768–1024px — adapt columns, keep touch sizes | +| Desktop | > 1024px — sidebar / multi-column allowed | + +Do not merely scale desktop UI down to mobile. + +--- + +## Checklist + +- [ ] Tokens + brand voice +- [ ] Keyboard / focus +- [ ] Responsive behavior explicit +- [ ] SEO for public pages diff --git a/.ai/domain.md b/.ai/domain.md new file mode 100644 index 0000000..05998e1 --- /dev/null +++ b/.ai/domain.md @@ -0,0 +1,65 @@ +# Domain Language — Ubiquitous Terms + +Use ONLY these words in code, API, UI copy (Chinese labels noted). +Do not invent synonyms (Customer / Client / Account) unless added here via ADR. + +**Bounded contexts / aggregates / ER:** [domain/domain-map.md](domain/domain-map.md) +**Product feature tree:** [product/feature-map.md](product/feature-map.md) + +## Identity & access + +| Term | Meaning | Not | +|---|---|---| +| **Visitor** | 未登录访客;可有匿名 device id | Guest(勿混用) | +| **User** | 已注册主体(有 user id) | Customer / Client | +| **Member** | 同 User:已注册用户的业务称呼;代码字段优先 `user` | — | +| **VIP** | 付费会员期内的 User(membership active) | Member(勿把所有注册用户叫 VIP) | +| **Session** | 登录态 / token 会话 | — | + +## Commerce + +| Term | Meaning | +|---|---| +| **Subscription** | 会员订阅计划(月/季/年) | +| **Membership** | 用户当前会员权益状态(是否 VIP、到期时间、额度) | +| **Order** | 支付订单(报告解锁或订阅) | +| **Payment** | 一笔支付尝试 / 渠道回调结果 | +| **Refund** | 退款记录 | +| **Unlock** | 对某 Report 的购买解锁(非 VIP 也可单次解锁) | + +## Profile & content + +| Term | Meaning | +|---|---| +| **Profile** | 档案:自己或 TA 的生日等输入 | +| **Self Profile** | relation = self | +| **Other Profile** | relation = other(TA) | +| **Decode** | 愈心解码报告(数字性格 + 体质建议) | +| **Report** | 统称:Decode / Match / 尊享报告等可交付物 | +| **Match** | 双人/家庭契合度报告 | +| **Scale** | 心理量表 | +| **ScaleResult** | 量表作答与计分结果 | + +## Retention & AI + +| Term | Meaning | +|---|---| +| **Companion** | 陪伴域:节气、心情 | +| **SolarTerm** | 二十四节气内容单元 | +| **Mood** | 每日心情打卡 | +| **Ask** | 「问」能力:基于 Profile 的解读对话 | +| **Consult** | 真人顾问咨询(后置) | + +## Platform + +| Term | Meaning | +|---|---| +| **user-h5** | 用户 H5 客户端 | +| **mini-program** | 微信小程序客户端 | +| **api** | Go 后端唯一服务 | +| **Legacy** | 根目录旧静态原型,只读 | + +## Naming in code + +- Go packages / JSON: `user`, `profile`, `report`, `order`, `membership`, `scale` +- Never: `customer`, `client` (except HTTP client), `account` (unless wallet later + ADR) diff --git a/.ai/domain/README.md b/.ai/domain/README.md new file mode 100644 index 0000000..5344512 --- /dev/null +++ b/.ai/domain/README.md @@ -0,0 +1,6 @@ +# Domain docs + +| File | Purpose | +|---|---| +| [../domain.md](../domain.md) | Ubiquitous language — terms only | +| [domain-map.md](domain-map.md) | Bounded contexts, aggregates, ER sketch | diff --git a/.ai/domain/domain-map.md b/.ai/domain/domain-map.md new file mode 100644 index 0000000..8fbe472 --- /dev/null +++ b/.ai/domain/domain-map.md @@ -0,0 +1,183 @@ +# Domain Map — 愈心谷(DDD 视角) + +给后端 / DB / OpenAPI / SDK 用的**能力域与实体图**。 +词汇以 [../domain.md](../domain.md) 为准;产品范围以 [../product/feature-map.md](../product/feature-map.md) 为准。 +竞品能力对照:[../product/cece-feature-map.md](../product/cece-feature-map.md)。 + +AI 生成表结构或 API 时:先落本图中的 Context / Aggregate,再写 migration;禁止发明未列出的聚合根名。 + +--- + +## 1. Bounded Contexts + +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Identity │──▶│ Profile │──▶│ Discovery │ +│ (User) │ │ Life Archive│ │ Decode/Scale│ +└─────────────┘ └──────┬───────┘ └──────┬──────┘ + │ │ + ▼ ▼ + ┌──────────────┐ ┌─────────────┐ + │ Relationship │ │ Ask │ + │ Match │ │ Companion │ + └──────┬───────┘ └──────┬──────┘ + │ │ + ▼ ▼ + ┌─────────────────────────────────┐ + │ Commerce │ + │ Subscription / Order / Unlock │ + └─────────────────────────────────┘ + │ + ┌──────────────┼──────────────┐ + ▼ ▼ ▼ + ContentFeed GrowthShare Consult(*) + (* later) +``` + +| Context | 职责 | MVP | +|---|---|---| +| **Identity** | Visitor/User/Session | Yes | +| **Profile** | Self/Other 生命档案 | Yes | +| **Discovery** | Decode、Scale、Report 生成 | Yes | +| **Relationship** | Match | Yes | +| **Ask** | 档案上下文对话、额度 | Skeleton | +| **Companion** | SolarTerm、Mood | Skeleton | +| **Commerce** | Subscription、Membership、Order、Unlock、Payment | Yes (mock) | +| **Content** | Feed 配置 | Static OK | +| **Growth** | Share 卡片元数据 | Minimal | +| **Consult** | 真人顾问 Marketplace | No | +| **Recommendation** | 推荐 | V1 rules | + +--- + +## 2. Aggregates & Entities(命名强制) + +### Identity + +| Type | Name | Notes | +|---|---|---| +| Aggregate | **User** | 注册主体 | +| Entity | Session | token 会话 | +| Entity | DeviceIdentity | Visitor 匿名 | + +### Profile(生命档案) + +| Type | Name | Notes | +|---|---|---| +| Aggregate | **Profile** | `relation`: self \| other | +| VO | BirthInput | date;time/place optional later | +| VO | RelationType | partner/family/friend/… | + +### Discovery + +| Type | Name | Notes | +|---|---|---| +| Aggregate | **Report** | type: decode \| match \| … | +| Domain Svc | DecodeEngine | 可复算;结果入 Report | +| Aggregate | **Scale** | 量表定义 | +| Entity | ScaleQuestion | | +| Aggregate | **ScaleResult** | 作答 + 计分 | + +### Relationship + +| Type | Name | Notes | +|---|---|---| +| Aggregate | **Match** | 引用两个 Profile;产出 Report 或嵌入 | + +### Ask / Companion + +| Type | Name | Notes | +|---|---|---| +| Aggregate | **AskThread** | 挂 profile_id | +| Entity | AskMessage | role user/assistant | +| Entity | AskQuota | 与 Membership 联动 | +| Aggregate | **Mood** | 日维度打卡 | +| Read Model | **SolarTerm** | 日历内容;可配置表 | + +### Commerce + +| Type | Name | Notes | +|---|---|---| +| Aggregate | **Subscription** | 计划 SKU | +| Aggregate | **Membership** | 用户权益状态 | +| Aggregate | **Order** | 订阅或 Unlock | +| Entity | Payment | mock / 渠道 | +| Entity | Unlock | order → report 解锁 | + +### Content / Growth(薄) + +| Type | Name | Notes | +|---|---|---| +| Entity | FeedItem | 运营配置 | +| VO | SharePayload | 分享卡字段 | + +### Consult(Later) + +| Type | Name | Notes | +|---|---|---| +| Aggregate | Consultant | | +| Aggregate | ConsultOrder | | +| Entity | ConsultSession | | + +--- + +## 3. 关键关系(ER 草图) + +``` +User 1──* Profile +Profile 1──* Report +Profile 1──* AskThread +User 1──* ScaleResult +User 1──0..1 Membership +User 1──* Order +Order 0..1── Unlock ──▶ Report +Profile ── Match ── Profile +User 1──* Mood +``` + +权益规则:**Membership / Unlock 只由服务端判定**;客户端不可信任。 + +--- + +## 4. Context → 代码落点 + +| Context | Go | H5 | +|---|---|---| +| Identity | `internal/service/user` | login / mine | +| Profile | `service/profile` | decode form, mine | +| Discovery | `service/report`, `service/scale` | decode, explore, report | +| Relationship | `service/match` | match | +| Ask | `service/ask` | ask tab | +| Companion | `service/companion` | companion tab | +| Commerce | `service/order`, `membership` | paywall, mine | + +包名只用 `domain.md` 词汇:`user` `profile` `report` `order` `membership` `scale` `ask` `mood`。 + +--- + +## 5. 测测域 → 愈心谷域 映射 + +| 测测概念 | 愈心谷 | +|---|---| +| 星盘档案 | **Profile**(生日+体质相关输入) | +| 测测 AI | **Ask** | +| 真人 1v1 | **Consult**(后置) | +| MBTI 等 | **Scale** | +| 缘分合盘 | **Match**(合规叙事) | +| 今日运势 | **SolarTerm** + 生活建议(替换) | +| 会员 | **Subscription / Membership** | +| AI 玩法广场 | **[No] MVP** | +| 心情打卡 | **Mood** | +| 沙盘/心情小镇 | **[No]** | + +--- + +## 6. 生成顺序(给 AI) + +1. 改范围 → 更新 `product/feature-map.md` MVP 标记 +2. 新实体 → 本文件 + `domain.md` 词条(若新词) +3. migration → `playbooks/new-table.md` +4. API → `playbooks/add-api.md` + OpenAPI +5. 页面 → `playbooks/new-page.md` + design contract + +下一步可选:单独 `apps/docs/erd.md` 或 migrations 初稿(本任务不强制)。 diff --git a/.ai/examples/good-api.json b/.ai/examples/good-api.json new file mode 100644 index 0000000..7c94468 --- /dev/null +++ b/.ai/examples/good-api.json @@ -0,0 +1,25 @@ +{ + "success_example": { + "code": 0, + "message": "success", + "data": { + "summary": "简版结论占位", + "profile_id": "p_123" + } + }, + "error_example": { + "code": 30001, + "message": "invalid birth date" + }, + "list_example": { + "code": 0, + "message": "success", + "data": { + "list": [{ "slug": "mbti", "name": "MBTI" }], + "total": 1, + "page": 1, + "page_size": 20 + } + }, + "_comment": "Never use {success:true} or {ok:true} as root." +} diff --git a/.ai/examples/good-handler.go b/.ai/examples/good-handler.go new file mode 100644 index 0000000..5846359 --- /dev/null +++ b/.ai/examples/good-handler.go @@ -0,0 +1,54 @@ +//go:build ignore + +// EXAMPLE — reference shape for AI. Not compiled into apps/api. +package examples + +import ( + "context" + "net/http" + + "github.com/gin-gonic/gin" +) + +type apiBody struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} + +type DecodeInput struct { + Year int + Month int + Day int +} + +type DecodeOutput struct { + Summary string `json:"summary"` +} + +type DecodeService interface { + Decode(ctx context.Context, in DecodeInput) (*DecodeOutput, error) +} + +type DecodeHandler struct { + Svc DecodeService +} + +// Decode handles POST /api/v1/reports/decode +func (h *DecodeHandler) Decode(c *gin.Context) { + var req struct { + Year int `json:"year" binding:"required"` + Month int `json:"month" binding:"required"` + Day int `json:"day" binding:"required"` + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, apiBody{Code: 10000, Message: "invalid request"}) + return + } + out, err := h.Svc.Decode(c.Request.Context(), DecodeInput(req)) + if err != nil { + c.JSON(http.StatusBadRequest, apiBody{Code: 30001, Message: err.Error()}) + return + } + c.JSON(http.StatusOK, apiBody{Code: 0, Message: "success", Data: out}) +} diff --git a/.ai/examples/good-service.go b/.ai/examples/good-service.go new file mode 100644 index 0000000..ac32ebc --- /dev/null +++ b/.ai/examples/good-service.go @@ -0,0 +1,29 @@ +//go:build ignore + +// EXAMPLE — reference shape for AI. Not compiled into apps/api. +package examples + +import ( + "context" + "fmt" +) + +type DecodeRepo interface { + SaveDecode(ctx context.Context, userID string, summary string) error +} + +type DecodeServiceImpl struct { + Repo DecodeRepo +} + +func (s *DecodeServiceImpl) Decode(ctx context.Context, in DecodeInput) (*DecodeOutput, error) { + if in.Year < 1900 || in.Month < 1 || in.Month > 12 || in.Day < 1 || in.Day > 31 { + return nil, fmt.Errorf("invalid birth date") + } + // engine would run here + summary := "简版结论占位" + if err := s.Repo.SaveDecode(ctx, "user-from-ctx", summary); err != nil { + return nil, fmt.Errorf("save decode: %w", err) + } + return &DecodeOutput{Summary: summary}, nil +} diff --git a/.ai/examples/good-test.go b/.ai/examples/good-test.go new file mode 100644 index 0000000..4465f39 --- /dev/null +++ b/.ai/examples/good-test.go @@ -0,0 +1,39 @@ +//go:build ignore + +// EXAMPLE — table-driven unit test shape. +package examples + +import "testing" + +func TestClampBirthMonth(t *testing.T) { + tests := []struct { + name string + month int + wantErr bool + }{ + {name: "ok", month: 6, wantErr: false}, + {name: "zero", month: 0, wantErr: true}, + {name: "thirteen", month: 13, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateMonth(tt.month) + if (err != nil) != tt.wantErr { + t.Fatalf("validateMonth(%d) err=%v wantErr=%v", tt.month, err, tt.wantErr) + } + }) + } +} + +func validateMonth(m int) error { + if m < 1 || m > 12 { + return errInvalid + } + return nil +} + +var errInvalid = errString("invalid") + +type errString string + +func (e errString) Error() string { return string(e) } diff --git a/.ai/file-map.md b/.ai/file-map.md new file mode 100644 index 0000000..2217ac3 --- /dev/null +++ b/.ai/file-map.md @@ -0,0 +1,93 @@ +# File Map — Who Owns What + +## apps/api + +``` +apps/api/ + cmd/server/ # main only: wire deps, start HTTP + internal/ + handler/ # HTTP bind/unbind, validate input, call service interfaces + service// # business rules, transactions orchestration + repository/ # SQL / DB access only + model/ # DB structs / domain structs (no HTTP types leakage preferred) + middleware/ # auth, request id, recovery helpers + config/ # env config + pkg/ # small reusable libs (e.g. response envelope) + migrations/ # schema versions +``` + +### Forbidden placements + +| Wrong | Right | +|---|---| +| SQL in `handler/` | `repository/` | +| gin.Context in `repository/` | keep HTTP out of repo | +| Business unlock rules only in UI | `service/` + persist | +| Fat `pkg/utils` | domain package or focused pkg | + +## apps/user-h5 + +``` +src/ + pages/ # route screens + components/ # reusable UI + api/ # thin wrappers → @yuxingu/sdk + stores/ # pinia + hooks/ # composables + router/ + layouts/ + assets/ +``` + +No raw `fetch` in `pages/`. + +## packages + +| Package | Owns | +|---|---| +| `sdk` | HTTP client + adapters | +| `types` | Shared TS domain types | +| `utils` | Pure helpers | +| `ui` | CSS tokens | + +## .ai/ + +Rules, ADR, patterns, examples, playbooks, checklists. Not runtime code. + +### .ai/product/ & .ai/domain/ + +| Path | Owns | +|---|---| +| `product/feature-map.md` | 愈心谷 L0/L1/L2 + MVP 标记 | +| `product/cece-feature-map.md` | 测测竞品合并 Feature Tree | +| `domain/domain-map.md` | Bounded contexts / aggregates / ER | +| `domain.md` | Ubiquitous terms only | + +### .ai/design/ + +| Path | Owns | +|---|---| +| `design-system.md` | Tokens, color, type, spacing, motion, voice | +| `component-catalog.md` | Allowed reusable UI components | +| `platform/*.md` | H5 / mini-program / website / Flutter limits | + +Runtime CSS tokens: `packages/ui/src/tokens.css` (must stay aligned with design-system). + +## apps/docs/ + +Product / business docs (not engineering rules): + +| Path | Owns | +|---|---| +| `README.md` | Reading order | +| `analysis/` | Competitor teardowns | +| `prd-mvp.md` | MVP scope & acceptance | +| `business-model.md` | Revenue layers | +| `product-roadmap.md` | Phased vertical slices | +| `adr/` | Optional business-facing ADRs | + +Engineering standards live in `.ai/` only; `apps/docs/standards/*` is deprecated. + +## Legacy (do not extend) + +Root `yuxingu.html`, `pages/`, `css/`, `js/`, `server.py`. diff --git a/.ai/forbidden.md b/.ai/forbidden.md new file mode 100644 index 0000000..37cd39b --- /dev/null +++ b/.ai/forbidden.md @@ -0,0 +1,37 @@ +# Forbidden — Never + +AI obeys NEVER rules strictly. + +## Code + +- Never use `panic` on request paths. +- Never ignore errors (`_ = err` on important paths). +- Never create circular dependencies. +- Never put SQL in handlers. +- Never put `gin.Context` in repositories. +- Never use package-level mutable globals for request state. +- Never use `SELECT *` in new SQL. +- Never introduce `any` casually in TypeScript. +- Never put `fetch` directly in Vue pages. + +## API / data + +- Never invent response shapes other than `{code,message,data}`. +- Never use POST for pure read/query. +- Never skip migration when schema changes. +- Never hard-delete user PII without explicit task (use soft delete). + +## Security / deploy + +- Never hardcode passwords, tokens, or secrets. +- Never commit `.env` or private keys. +- Never use Docker image tag `latest` in production. +- Never log full birthday + answers payloads casually. + +## Product / process + +- Never invent medical efficacy or 吉凶祸福 copy. +- Never reverse an Accepted ADR without a new ADR + approval. +- Never modify files outside the current task. +- Never extend Legacy root HTML unless the task is migration. +- Never guess requirements — ASK FIRST. diff --git a/.ai/patterns/crud.md b/.ai/patterns/crud.md new file mode 100644 index 0000000..82e8f11 --- /dev/null +++ b/.ai/patterns/crud.md @@ -0,0 +1,14 @@ +# Pattern: CRUD slice + +For a resource `Foo`: + +1. Migration `foo` table (`id, created_at, updated_at, deleted_at`, …) +2. `model.Foo` +3. `repository` methods: Get / List / Create / Update / SoftDelete +4. `service` with authz + validation +5. `handler` REST routes +6. OpenAPI paths +7. SDK methods + types +8. UI only if user-facing + +List endpoints always paginate (`page`, `page_size`, `total`). diff --git a/.ai/patterns/handler.md b/.ai/patterns/handler.md new file mode 100644 index 0000000..315adf1 --- /dev/null +++ b/.ai/patterns/handler.md @@ -0,0 +1,37 @@ +# Pattern: Handler + +## Responsibility + +HTTP only: bind JSON/query → validate → call service interface → map errors to envelope. + +## Shape + +```go +type ReportService interface { + Decode(ctx context.Context, in DecodeInput) (*DecodeOutput, error) +} + +type ReportHandler struct { + svc ReportService +} + +func (h *ReportHandler) Decode(c *gin.Context) { + var req DecodeRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.Fail(c, 400, 10000, "invalid request") + return + } + out, err := h.svc.Decode(c.Request.Context(), toInput(req)) + if err != nil { + // map domain errors → codes + response.Fail(c, 400, 30001, err.Error()) + return + } + response.OK(c, out) +} +``` + +## Never + +- SQL here +- Business unlock math here (belongs service) diff --git a/.ai/patterns/jwt.md b/.ai/patterns/jwt.md new file mode 100644 index 0000000..6be8228 --- /dev/null +++ b/.ai/patterns/jwt.md @@ -0,0 +1,13 @@ +# Pattern: JWT / Bearer + +## Middleware + +1. Read `Authorization: Bearer ` +2. Parse/verify +3. Put `user_id` into context +4. Reject with code in 1xxxx if missing/invalid + +## Service + +Always take `userID` from context for mutating ops. +Never trust body `user_id` from client for ownership. diff --git a/.ai/patterns/pagination.md b/.ai/patterns/pagination.md new file mode 100644 index 0000000..89a31bc --- /dev/null +++ b/.ai/patterns/pagination.md @@ -0,0 +1,22 @@ +# Pattern: Pagination + +## Query + +`?page=1&page_size=20` + +Defaults: page=1, page_size=20, max page_size=100. + +## Response data + +```json +{ + "list": [], + "total": 0, + "page": 1, + "page_size": 20 +} +``` + +## SQL + +`LIMIT $n OFFSET $m` with bound params. Always return `total` via `COUNT(*)` (or approximate only with ADR). diff --git a/.ai/patterns/repository.md b/.ai/patterns/repository.md new file mode 100644 index 0000000..a0d61ca --- /dev/null +++ b/.ai/patterns/repository.md @@ -0,0 +1,24 @@ +# Pattern: Repository + +## Responsibility + +Persistence only. Parameterized SQL. Map rows ↔ model. + +## Shape + +```go +func (r *ReportRepository) SaveReport(ctx context.Context, m *model.Report) error { + const q = `INSERT INTO report (id, user_id, kind, payload, created_at, updated_at) + VALUES ($1,$2,$3,$4,now(),now())` + _, err := r.db.ExecContext(ctx, q, m.ID, m.UserID, m.Kind, m.Payload) + if err != nil { + return fmt.Errorf("insert report: %w", err) + } + return nil +} +``` + +## Never + +- `SELECT *` in new code +- Business policy (VIP checks) here — return data, let service decide diff --git a/.ai/patterns/service.md b/.ai/patterns/service.md new file mode 100644 index 0000000..ac6f375 --- /dev/null +++ b/.ai/patterns/service.md @@ -0,0 +1,31 @@ +# Pattern: Service + +## Responsibility + +Business rules, authorization, orchestration, transactions. + +## Shape + +```go +type ReportRepo interface { + SaveReport(ctx context.Context, r *model.Report) error +} + +type ReportService struct { + repo ReportRepo +} + +func (s *ReportService) Decode(ctx context.Context, in DecodeInput) (*DecodeOutput, error) { + if err := in.Validate(); err != nil { + return nil, fmt.Errorf("validate decode: %w", err) + } + // rules / engine + // persist via repo + return out, nil +} +``` + +## Never + +- Import `gin` +- Bypass repo with ad-hoc SQL drivers scattered around diff --git a/.ai/playbooks/add-api.md b/.ai/playbooks/add-api.md new file mode 100644 index 0000000..4c54870 --- /dev/null +++ b/.ai/playbooks/add-api.md @@ -0,0 +1,19 @@ +# Playbook: Add API + +Use when: “新增xxx接口” + +## Steps + +1. **OpenAPI** — add path to `proto/openapi.yaml` +2. **Migration** — if new/changed table (`apps/api/migrations/`) +3. **Model** — `internal/model` +4. **Repository** — SQL only +5. **Service** — business + authz +6. **Handler** — bind + envelope; interface at handler +7. **Wire** — register route in `cmd/server` or router file +8. **Test** — at least service/engine unit test if logic-heavy +9. **SDK/types** — update `packages/sdk` + `packages/types` if clients need it +10. **Commands** — `go test ./...`, hit with curl +11. **Review + DoD + checklist/feature.md** + +Follow patterns: `handler.md` `service.md` `repository.md` `pagination.md`. diff --git a/.ai/playbooks/login.md b/.ai/playbooks/login.md new file mode 100644 index 0000000..ae03f31 --- /dev/null +++ b/.ai/playbooks/login.md @@ -0,0 +1,14 @@ +# Playbook: Login / Auth + +## Steps + +1. Cite ADR-0004 +2. Define Visitor vs User in API behavior (`.ai/domain.md`) +3. Issue Bearer token; document in OpenAPI +4. Middleware extracts `user_id` +5. Persist session/user as designed (Postgres MVP; Redis deferred) +6. H5: store token via adapter (`localStorage` key `yxg_token`) +7. SDK `getToken` wired +8. Tests: invalid token / expired / ownership +9. Security review checklist +10. Never accept `X-User-Id` alone as auth diff --git a/.ai/playbooks/new-page.md b/.ai/playbooks/new-page.md new file mode 100644 index 0000000..b616e22 --- /dev/null +++ b/.ai/playbooks/new-page.md @@ -0,0 +1,18 @@ +# Playbook: New H5 Page + +Use when: “新增页面” + +## Steps + +1. Confirm IA tab vs secondary page (`.ai/ui.md`) +2. Read `.ai/design/design-system.md` + `component-catalog.md` + `design/platform/h5.md` +3. Reuse catalog components; update catalog if adding a new one +4. Add route in `apps/user-h5/src/router` +5. Add `pages/XxxPage.vue` (PascalCase); tokens only (no raw brand hex) +6. Extract components if file approaches 300–400 lines +7. Data via `src/api` → `@yuxingu/sdk` only +8. Use domain words from `.ai/domain.md` +9. Loading / empty / error states +10. Compliance copy if report-like +11. `npm run build:h5` +12. Review + checklist/feature.md diff --git a/.ai/playbooks/new-table.md b/.ai/playbooks/new-table.md new file mode 100644 index 0000000..89952d6 --- /dev/null +++ b/.ai/playbooks/new-table.md @@ -0,0 +1,14 @@ +# Playbook: New Table + +Use when: “新建表 / 加字段” + +## Steps + +1. Name with `snake_case` (`.ai/database.md` + `.ai/domain.md`) +2. Columns include `id, created_at, updated_at, deleted_at` +3. Write migration Up (+ Down) +4. Index FKs +5. Update `model` + repository +6. Never ship app code that queries columns not migrated +7. Document in PR +8. Review (Deployment + Docs items) diff --git a/.ai/playbooks/payment.md b/.ai/playbooks/payment.md new file mode 100644 index 0000000..cdf0a9e --- /dev/null +++ b/.ai/playbooks/payment.md @@ -0,0 +1,14 @@ +# Playbook: Payment / Unlock / Subscription + +## Steps + +1. Terms: Order, Payment, Unlock, Subscription, Membership, VIP (`.ai/domain.md`) +2. Server creates Order with amount/currency/product ref +3. Client never sets final price unilaterally +4. Payment callback verifies signature + idempotency +5. On success: grant Membership or Unlock in service transaction +6. Report access checks Membership/Unlock server-side every time +7. Migration for `payment_order` / related tables +8. Tests: double callback, wrong amount, replay +9. No secrets in frontend +10. Feature checklist + security review diff --git a/.ai/product/README.md b/.ai/product/README.md new file mode 100644 index 0000000..960f1c5 --- /dev/null +++ b/.ai/product/README.md @@ -0,0 +1,12 @@ +# Product docs for AI agents + +| File | Audience | Purpose | +|---|---|---| +| [STRATEGY.md](STRATEGY.md) | All agents | **执行优先级**:先测测 parity,Vision v1 已封存 | +| [feature-map.md](feature-map.md) | PRD / H5 / API | 愈心谷差异化能力树(Design Vision v1) | +| [cece-feature-map.md](cece-feature-map.md) | Competitor / parity | 测测 Feature Tree — 下一版对齐主参考 | +| [../domain/domain-map.md](../domain/domain-map.md) | Backend / DB / OpenAPI | Bounded contexts & entities | +| [../domain.md](../domain.md) | All code | Ubiquitous language (terms only) | + +Product narrative docs (human PRD): `apps/docs/`. +**冲突时:** `STRATEGY.md` 的 parity 执行 > Vision v1 创新项。 diff --git a/.ai/product/STRATEGY.md b/.ai/product/STRATEGY.md new file mode 100644 index 0000000..5d751bc --- /dev/null +++ b/.ai/product/STRATEGY.md @@ -0,0 +1,32 @@ +# Product Strategy Snapshot + +## Sealed design (this repo state) + +Documented vision: **愈心谷差异化** — 数字性格 + 中医体质 + 关系档案 + 节气,学测测飞轮但替换运势主叙事。 + +Canonical docs at seal time: + +- `product/feature-map.md` — 愈心谷能力树(含 MVP 标记) +- `product/cece-feature-map.md` — 测测竞品树 +- `domain/domain-map.md` — 领域模型 +- `apps/docs/prd-mvp.md` · `business-model.md` · `product-roadmap.md` +- `.ai/design/*` — Design System Contract + +Treat the above as **Design Vision v1(已封存)** — 可对照、可回退,不作为下一迭代的强制做完清单。 + +--- + +## Next build direction(执行优先) + +**先出一版和测测结构类似的产品,不要一上来就创新。** + +| 原则 | 含义 | +|---|---| +| IA 对齐测测 | 首页发现 · 问(AI)· 在线/咨询位 · 消息或可后置 · 我的 | +| 能力对齐测测漏斗 | 测评拉新 → 档案 → AI 问 → 会员 → 咨询占位 | +| 叙事可先「泛心理 + 星座/性格」 | 体质/节气差异化后置,不阻塞首版 | +| 不做冷启动重资产 | 不做达人双边冷启动、不做硬件、不做 UGC 玩法广场首版 | + +差异化 Vision v1 在首版跑通测测式漏斗后再渐进引入(Decode 体质、节气陪伴等)。 + +执行时以即将撰写的 **Cece-parity PRD / feature-map-v0** 为准;冲突时:**parity 执行 > Vision v1 创新项**。 diff --git a/.ai/product/cece-feature-map.md b/.ai/product/cece-feature-map.md new file mode 100644 index 0000000..7b821ef --- /dev/null +++ b/.ai/product/cece-feature-map.md @@ -0,0 +1,307 @@ +# 测测 App — Feature Map(竞品 · 合并视角) + +来源:[人人都是产品经理 · 测测深度体验](https://www.woshipm.com/evaluating/6391148.html) + 公开产品形态 / 行业常见能力。 +用途:竞品分析与 AI 产品设计对照。**不代表**测测未公开内部功能;版本会变。 + +视角说明: + +| 视角 | 回答的问题 | 本文件位置 | +|---|---|---| +| L0 Product IA | 用户打开 App 看到什么、怎么付费 | §1 L0 | +| L1 Capability | 系统背后有哪些能力域 | §2 挂在各入口下 / §3 横切 | +| 演进 | 工具 → AI 顾问 → Life OS | §4 | + +愈心谷落地地图见 [feature-map.md](feature-map.md)。领域实体见 [../domain/domain-map.md](../domain/domain-map.md)。 + +--- + +## §0 一级能力域总览(Domain 视角) + +``` +测测能力域 +├── 1. 用户体系 +├── 2. 首页与内容分发 +├── 3. AI 智能助手 +├── 4. 测试与测算 +├── 5. 星座体系 +├── 6. 命理体系 +├── 7. 塔罗体系 +├── 8. 情感关系分析 +├── 9. 心理与自我探索 +├── 10. 社区生态 +├── 11. 专家咨询 +├── 12. 内容体系 +├── 13. 会员与商业化 +├── 14. 用户增长 +└── 15. 数据与推荐系统 +``` + +演进增量(行业方向,非全部已上线):人生档案 / AI Agent / 情绪 OS / 梦境 / 冥想 / 中医体质 / 易经决策 / 多模态 / 长期成长模型。 + +--- + +## §1 L0 — App 信息架构(用户路径) + +``` +打开 App + → 首页发现与激活 + → 问(AI 战略入口) + → 在线(真人 Marketplace) + → 消息(关系与通知) + → 我的(资产 / 会员) +横切:增长运营 · 集团生态 +``` + +``` +测测 App +├── 首页 +├── 消息 +├── 问 +├── 在线 +├── 我的 +├── 增长运营 +└── 集团生态 +``` + +--- + +## §2 L0 × L1 × L2 — 合并功能树 + +### 1. 首页(发现与激活中心) + +#### 1.1 用户建档 → 生命档案系统 + +| 二级 | 三级 | +|---|---| +| 1.1.1 创建本人档案 | 出生日期 / 时间 / 地点 / 性别 | +| 1.1.2 创建关系档案 | 伴侣 / 家人 / 朋友 / 暗恋对象等 | +| 1.1.3 档案管理 | 编辑 / 删除 / 多档案切换 / AI 关联分析 | +| 1.1.4 兴趣与状态标签 | 爱情·事业·财富·健康·性格·家庭;情感/职业状态 | + +#### 1.2 工具入口宫格(前台)↔ 能力归类(后台) + +| 前台入口 | 归属能力域 | +|---|---| +| I人E人 / MBTI | 心理测评 / 测试系统 | +| 星座 / 星盘 | 星座体系 | +| 缘分合盘 | 情感关系 + 星座 | +| 沙盘 | 心理沉浸工具 | +| 陪伴小星 | AI 陪伴 | +| 倾诉 | 咨询 / AI 入口 | +| 商城 | 商业化 | +| AI 玩法广场 | 内容生态 · GPT Store 型 | +| 更多 | 收纳扩展 | + +#### 1.3 首页推荐与信息流 + +- 今日运势 / 热门测试 / AI 入口 / 专家推荐 / 热门文章 / 社区动态 +- 文章 · 视频 · 故事 · 达人内容 · AI 生成内容 · 个性推荐 + +#### 1.4 个性化推荐 + +- 按生日 / 兴趣 / 历史行为 / 付费意愿推荐 + +#### 1.5 AI 玩法广场(≈ GPT Store + 传播) + +- 玩法模板 → Prompt 应用 → 用户/达人创建 AI 小应用 → 热度榜 → 裂变 +- 示例形态:SBTI、答案之书、三生三世你和 Ta、灵魂伴侣等 + +#### 1.6 沉浸体验入口 + +- 3D 心理沙盘(投射分析) +- AI 心情小镇入口 + +--- + +### 2. 消息(互动与触达) + +| 二级 | 三级 | +|---|---| +| 2.1 AI 消息 | 会话提醒、未读 AI 对话 | +| 2.2 达人消息 | 咨询会话、达人回复 | +| 2.3 社区互动 | 赞评关私信通知 | +| 2.4 系统通知 | 公告、合规、功能 | +| 2.5 商业通知 | 会员到期、订单、活动 | + +--- + +### 3. 问(核心战略 · AI Life Assistant) + +实测:底部中央「问」Tab → 测测 AI;顶栏「测测AI ‖ 真人1v1」。 + +#### 3.1 AI 人格层 + +- 普通助手 / 情感陪伴 / 心理向疏导 / 星盘专家向 / 塔罗向 / 人生顾问 +- 报道中的灵犀 / 小智:更接近模式或子场景,非并列 Tab 级产品 + +#### 3.2 AI 聊天场景 + +- 一般聊天 · 情感倾诉 · 人生建议 · 性格分析 · 梦境解析 · 星座咨询 · 命理解读 + +#### 3.3 AI 上下文层(差异化) + +- 当前用户 → 生命档案 → 关系档案 → 历史对话 → 情绪轨迹 → 长期记忆 +- UI:星盘维度 · 对象切换(自己/TA)· 深度思考 · 灵魂伴侣子入口 + +#### 3.4 AI 能力层(底层) + +- LLM(心元等)· 心理知识库 · 星盘计算 · 测算模型 · 测试模型 · 推荐 + +#### 3.5 AI 报告生成 + +- 性格 / 爱情 / 职业 / 财富 / 综合人生报告 + +#### 3.6 AI 商业层 + +- 免费次数 → 会员扩容 → 高级模型/深度思考 → 转真人专家 + +#### 3.7 AI 心情小镇等沉浸 + +- 多虚拟倾诉师人设 · OCR/情感识别 · 角色陪伴 + +#### 3.8 已知能力缺口(公开评价) + +- 偏被动响应(少主动触达) +- 跨会话长期记忆不足 +- 引导问题偏运势,易被感知为「算命 AI」 + +--- + +### 4. 在线(真人 Marketplace) + +``` +达人供应 → 平台撮合 → 交易 → 评价 → 复购 / 分佣 +``` + +| 二级 | 三级 | +|---|---| +| 4.1 专家类型 | 星座专家 · 塔罗师 · 命理师 · 心理/情感达人 | +| 4.2 供给运营 | 达人资料 · 审核 · 培训激励(达人版工具) | +| 4.3 咨询流程 | 列表 → 预约 → 支付 → 文字/语音/连麦 → 评价 | +| 4.4 撮合与分佣 | 推荐排序 · 平台抽成 | + +--- + +### 5. 我的(用户资产 → Life Profile) + +| 二级 | 三级 | +|---|---| +| 5.1 账号 | 手机 / 微信 / Apple · 游客升级 | +| 5.2 生命档案资产 | 本人档案 · 关系档案 · 编辑切换 | +| 5.3 测试与报告资产 | 测试记录 · 报告库 · 收藏 | +| 5.4 AI 资产 | 对话历史 · AI 报告 | +| 5.5 会员与订单 | 订阅状态 · 解锁记录 · 咨询订单 | +| 5.6 行为画像沉淀 | 浏览 / 测试 / 收藏 / 咨询记录 | + +--- + +### 6. 增长运营(横切) + +| 二级 | 三级 | +|---|---| +| 6.1 分享裂变 | 测试结果卡 · 邀请好友 · 社交传播 | +| 6.2 裂变引擎(产品逻辑) | 结果分享 → 好友进入 → 建档 → 再传播 | +| 6.3 内容获客 | SEO / 搜索 → 测试 → 注册 → 会员 | +| 6.4 激励 | 签到 · 积分 · 等级 · 勋章 | +| 6.5 广告 | 曾有;主动压缩干扰广告 | +| 6.6 品牌 | 创始人/媒体露出;少大规模买量 | + +--- + +### 7. 集团生态(Business Layer) + +| 二级 | 三级 | +|---|---| +| 7.1 C 端 App | 测测主站 | +| 7.2 达人平台 | 测测达人版 | +| 7.3 教育延伸 | 快乐测测 | +| 7.4 B 端 | 企业 EAP · 校园心理健康 | +| 7.5 硬件 | 巴布家庭陪伴机器人(买断 + 模型协同) | +| 7.6 模型底座 | 心元大模型备案与训练数据飞轮 | + +--- + +## §3 能力域详表(便于对照数据库 / 模块) + +### A. 测试系统 + +- **性格**:MBTI · 九型 · 性格/优势/弱点画像 +- **情感**:恋爱人格 · 伴侣匹配 · 关系状态 · 分手/婚恋向 +- **心理**:情绪 · 压力 · 焦虑 · 自我探索 · 心理画像 +- **趣味**:灵魂动物 · 前世 · 小游戏 · 趣味问答 + +### B. 星座体系 + +- 十二星座 · 星盘 · 上升/月亮 · 行星分析 +- 匹配:恋爱/友情/婚姻指数 +- 日运/周运/月运/年运 + +### C. 命理体系 + +- 八字:四柱 · 五行 · 十神 · 格局 · 大运 · 流年 +- 紫微:命盘 · 十二宫 · 主星 · 流年 +- 姓名/五格 · 风水建议 + +### D. 塔罗体系 + +- 抽牌:单牌 · 三牌 · 爱情/事业阵 · 趋势 +- AI 解读:牌义 · 组合 · 行动建议 · 情绪支持 + +### E. 情感关系 + +- 恋爱分析:双人星盘 · 合婚 · 性格匹配 · 相处建议 +- 关系管理:记录伴侣 · 关系变化 · 重要日期 · 情绪记录 + +### F. 心理成长 + +- 自我探索:性格 · 原生家庭 · 价值 · 内在需求 +- 情绪管理:记录 · 分析 · 安慰陪伴 · 放松 +- 成长计划:目标 · 每日练习 · 打卡 · 成长报告 +- 沉浸:沙盘 · 心情小镇 + +### G. 社区生态 + +- 内容社区:发帖评论赞藏分享 +- 用户关系:关注粉丝私信群组 +- 话题:星座讨论 · 情感故事 · 测试分享 · AI 内容 + +### H. 内容体系 + +- 文章:星座/情感/心理/命理 +- 视频:短视频 · 直播 · 课程 + +### I. 商业化 + +- 会员:月/季/年;权益含完整报告、AI 次数、专属内容、去广告等 +- 增值:单次报告 · 真人咨询 · 课程 · 商城 + +### J. 数据与推荐 + +- 数据:行为 · 情绪 · 测试 · 对话 +- 推荐:内容 · 测试 · 专家 · 商品 + +--- + +## §4 产品演进阶梯(分析用) + +``` +测测 1.0 测试工具集合 + ↓ +测测 2.0 AI 人生顾问(档案化问答 + 订阅 + 咨询) + ↓ +AI Life OS 用户长期人生数据库(记忆 · Agent · 多模态 · 具身) +``` + +**第一梯队护城河(公开叙事)**:生命档案 · AI「问」· 关系系统 · AI 玩法广场 +**第二梯队获客**:MBTI · 星座星盘 · 塔罗 · 测评 +**第三梯队**:商城 · 广告 · 周边 + +--- + +## §5 与愈心谷对照(一句话) + +学:**档案化 AI、五 Tab 战略位、订阅为主、关系对象、分享裂变、克制广告。** +弃/降权:运势主叙事、塔罗/重玄学默认、UGC 玩法广场冷启动、达人双边、硬件、多智能体品牌矩阵。 +替换:数字性格 + 中医体质 + 节气陪伴 + 合规生活建议。 + +落地见 [feature-map.md](feature-map.md)。 diff --git a/.ai/product/feature-map.md b/.ai/product/feature-map.md new file mode 100644 index 0000000..988fdf6 --- /dev/null +++ b/.ai/product/feature-map.md @@ -0,0 +1,212 @@ +# 愈心谷 — Product Feature Map + +**权威产品能力树**(PRD / 路由 / 竖切范围以此为准)。 +竞品原文树:[cece-feature-map.md](cece-feature-map.md) +领域实体:[../domain/domain-map.md](../domain/domain-map.md) +词汇:[../domain.md](../domain.md) · PRD:`apps/docs/prd-mvp.md` + +标记:`[MVP]` `[V1]` `[Later]` `[No]` + +--- + +## L0 — App 信息架构 + +``` +愈心谷 +├── 首页 HomeWash:品牌 + Decode + Feed +├── 探索 Scale / Decode / 轻工具 +├── 问 Ask(战略中心 Tab) +├── 陪伴 SolarTerm + Mood +├── 我的 Life Profile 资产 +├── 增长运营(横切) +└── 平台生态(横切 · 后置) +``` + +路径:`发现 → 建档/测评 → 问/陪伴留存 → 会员·解锁 →(后)Consult` + +--- + +## L1 — 产品能力树(合并 IA × Domain) + +``` +愈心谷 Feature Tree +├── 1. Identity & 生命档案 +├── 2. Self Discovery(自我探索) +├── 3. Relationship(关系) +├── 4. AI Companion(问) +├── 5. Companion Rhythm(陪伴节奏) +├── 6. Human Expert(真人 · 后置) +├── 7. Content & Feed +├── 8. Growth +├── 9. Commerce +└── 10. Data & Recommendation(平台能力) +``` + +--- + +### 1. Identity & 生命档案 + +| 二级 | 三级 | 范围 | +|---|---|---| +| 1.1 注册登录 | 手机 / 微信 / 游客 device / 升级 User | [MVP] 游客+升级;微信 [V1] | +| 1.2 Self Profile | 生日(必填);时辰/地点(可选后置) | [MVP] 生日 | +| 1.3 Other Profile | 伴侣/家人/朋友等关系对象 | [MVP] 基础 Other | +| 1.4 档案管理 | 编辑 · 删除 · 多档案切换 · Ask 关联 | [MVP] | +| 1.5 兴趣标签 | 性格/关系/养生等(非运势标签) | [V1] | +| 1.6 行为资产 | 浏览 · Decode · Scale · Ask · Order 记录 | [MVP] 服务端最小集 | + +**[No]** 出生地理风水必填、吉凶标签画像。 + +--- + +### 2. Self Discovery(获客工具层) + +| 二级 | 三级 | 范围 | +|---|---|---| +| 2.1 Decode | 数字性格 + 体质倾向;简版结论 / 完整原因+方案 | **[MVP] 核心** | +| 2.2 Scale 性格 | MBTI 等热门量表 · 结果页 · 分享 | [MVP] ≥1 热门 | +| 2.3 Scale 情感/心理 | 恋爱人格、情绪压力等 | [V1] | +| 2.4 趣味 Scale | 轻传播向 | [V1] 克制 | +| 2.5 五行/八卦轻解读 | 挂在 Decode 或探索,非运势主叙事 | [V1] | +| 2.6 中医体质系统 | 体质倾向 · 生活建议 · 节气联动 | [MVP] 在 Decode 内;深化 [V1] | + +**[No]** 塔罗默认入口、紫微/抽签主路径、今日运势主 Feed。 +**[Later]** 易经决策系统(合规评审后)。 + +--- + +### 3. Relationship + +| 二级 | 三级 | 范围 | +|---|---|---| +| 3.1 Match | 双人契合分预览 · 解读锁定 | [MVP] | +| 3.2 相处建议 | 基于性格×体质,非合婚吉凶 | [MVP] 简版;[V1] 完整 | +| 3.3 关系管理 | 重要日期、关系笔记 | [Later] | + +**[No]** 八字合婚吉凶、缘分指数恐吓文案。 + +--- + +### 4. AI Companion(问 = AI Life Assistant) + +| 二级 | 三级 | 范围 | +|---|---|---| +| 4.1 Ask 对话 | 性格 / 关系 / 养生路径引导;非运势默认 | [MVP] 规则模板或薄 LLM | +| 4.2 上下文 | 当前 Profile(Self/Other)挂载 | **[MVP]** | +| 4.3 视角切换 | Ask 顶栏显式切换档案 | [MVP] | +| 4.4 结构化回答 | 短答 · 要点 · 免责声明 | [MVP] | +| 4.5 长期记忆 | 跨会话情绪/事件记忆 | [V1+] | +| 4.6 报告生成 | 从 Ask/Decode 生成可 Unlock Report | [V1] | +| 4.7 双轨入口 | Ask ‖ Consult 占位切换 | [MVP] 占位;履约 [Later] | + +**[No]** 多智能体并列品牌(灵犀/小智式)、算命引导语、AI 心情小镇级 3D。 + +人格模式(产品语义,非多 App):陪伴模式 / 解读模式 — [V1]。 + +--- + +### 5. Companion Rhythm(陪伴 Tab) + +| 二级 | 三级 | 范围 | +|---|---|---| +| 5.1 SolarTerm | 今日节气 · 生活方式建议 | [MVP] 可静态 | +| 5.2 Mood | 打卡 · 简史 | [MVP] 最小写 | +| 5.3 情绪分析/练习 | 安抚话术、放松练习 | [V1] | +| 5.4 成长计划 | 目标 · 打卡 · 成长报告 | [Later] | + +**[No]** 运势日历作为陪伴主轴。 + +--- + +### 6. Human Expert(Consult) + +| 二级 | 三级 | 范围 | +|---|---|---| +| 6.1 顾问类型 | 养生顾问 / 关系顾问(合规资质) | [Later] | +| 6.2 流程 | 列表 · 预约 · 支付 · 会话 · 评价 | [Later] | +| 6.3 Marketplace | 达人入驻 · 分佣 | [Later] 非冷启动优先 | + +**[MVP]** 仅「顾问预约」表单占位,不履约。 + +--- + +### 7. Content & Feed + +| 二级 | 三级 | 范围 | +|---|---|---| +| 7.1 首页 Feed | 官方卡片:热门 Scale、Decode、节气 | [MVP] 运营配置/静态 | +| 7.2 文章 | 性格 / 关系 / 养生知识 | [V1] | +| 7.3 社区 UGC | 发帖赞评关注 | **[No] MVP**;[Later] 审慎 | +| 7.4 AI 玩法广场 | UGC Prompt 集市 | **[No] MVP**;[Later] | + +--- + +### 8. Growth + +| 二级 | 三级 | 范围 | +|---|---|---| +| 8.1 分享卡 | Decode / ScaleResult / Match 预览 | [MVP] | +| 8.2 裂变 | 分享 → 建档 → 再测 | [V1] | +| 8.3 激励 | 签到积分勋章 | [Later] | +| 8.4 SEO/内容获客 | Web 落地 | [Later] website | + +**[No]** 干扰广告、虚假紧迫倒计时。 + +--- + +### 9. Commerce + +| 二级 | 三级 | 范围 | +|---|---|---| +| 9.1 Subscription | 月/季/年 | [MVP] mock 支付 | +| 9.2 Membership 权益 | 完整 Decode、Ask 次数、深度节气等 | [MVP] | +| 9.3 Unlock | 单次报告 / Match 解读 | [MVP] | +| 9.4 Order / Payment | 下单 · pay-mock ·(真支付) | [MVP] mock;真支付 [V1] | +| 9.5 课程/商城 | — | [Later] / 低优 | + +--- + +### 10. Data & Recommendation(平台) + +| 二级 | 三级 | 范围 | +|---|---|---| +| 10.1 用户数据 | 行为 · Mood · Scale · Ask(最小化、可删) | [MVP] 基础 | +| 10.2 推荐 | Feed / Scale 推荐 | [V1] 规则即可 | +| 10.3 专家/商品推荐 | — | [Later] | + +--- + +## L2 — 页面 / 路由映射(user-h5) + +| L0 | 路由建议 | 主要能力 | +|---|---|---| +| 首页 | `/` | Decode 入口、Feed | +| 探索 | `/explore` | Scale 列表、工具 | +| 问 | `/ask` | Ask + Consult 占位 | +| 陪伴 | `/companion` | SolarTerm、Mood | +| 我的 | `/mine` | Profiles、Membership、Orders | +| 二级 | `/decode` `/scales/:slug` `/match` `/report/:id` | Discovery / Relationship / Commerce | + +实现约束:`.ai/design/*` · playbook `new-page` / `add-api`。 + +--- + +## 护城河优先级(愈心谷) + +| 梯队 | 能力 | 说明 | +|---|---|---| +| ★★★★★ | 生命档案 Profile · Ask 挂档案 · Relationship Match · Decode(性格×体质) | Memory + 差异化叙事 | +| ★★★★ | Scale · SolarTerm/Mood · Membership/Unlock | 获客与留存与变现 | +| ★★ | Feed 运营 · 分享裂变 | 增长 | +| ★ / No | UGC 广场 · 达人市场 · 硬件 · 塔罗主路径 | 明确后置或不做 | + +--- + +## 竖切顺序(与 roadmap 对齐) + +1. **Phase A** — Identity + Decode + Commerce mock +2. **Phase B** — Scale + Match + Share +3. **Phase C** — Ask 骨架 + Companion +4. **Phase D** — 真支付 · 小程序 · Consult 试点 + +详见 `apps/docs/product-roadmap.md`。 diff --git a/.ai/prompts/bugfix.md b/.ai/prompts/bugfix.md new file mode 100644 index 0000000..84dfbd0 --- /dev/null +++ b/.ai/prompts/bugfix.md @@ -0,0 +1,17 @@ +# Prompt: Bugfix + +Load first: `.ai/constitution.md`, `.ai/coding.md`, `.ai/security.md`, `.ai/review.md`. + +## Process + +1. Reproduce or state the failure mode clearly. +2. Find root cause (file + function). Prefer evidence over guess. +3. Fix the smallest correct change. +4. Add a regression test when the bug is in scoring, auth, or payment. +5. Verify build/health. +6. Output Review block (at least Architecture, Security, Test, Scope). + +## Forbidden + +- Drive-by refactors unrelated to the bug. +- Swallowing errors to “make it pass”. diff --git a/.ai/prompts/new-feature.md b/.ai/prompts/new-feature.md new file mode 100644 index 0000000..6513272 --- /dev/null +++ b/.ai/prompts/new-feature.md @@ -0,0 +1,18 @@ +# Prompt: New Feature + +Load first: `.ai/constitution.md`, `.ai/architecture.md`, `.ai/coding.md`, `.ai/api.md`, `.ai/review.md`, `.ai/definition-of-done.md`. + +## Process + +1. Restate the feature in one sentence and list files you will touch. +2. Follow feature flow: API → Service → Repository → Migration → SDK/types → UI → Test → Docs. +3. Implement the smallest vertical slice that works end-to-end. +4. Run builds/tests for touched sides. +5. Output `.ai/review.md` Review block. +6. Confirm Definition of Done. + +## Forbidden + +- Inventing APIs not in the task / OpenAPI. +- Editing legacy root HTML unless migration is the task. +- Shipping UI-only unlock/payment without server checks. diff --git a/.ai/prompts/refactor.md b/.ai/prompts/refactor.md new file mode 100644 index 0000000..b10ba5f --- /dev/null +++ b/.ai/prompts/refactor.md @@ -0,0 +1,16 @@ +# Prompt: Refactor + +Load first: `.ai/constitution.md`, `.ai/architecture.md`, `.ai/coding.md`, `.ai/testing.md`, `.ai/review.md`. + +## Process + +1. State goal and non-goals. No behavior change unless asked. +2. Keep layers intact. Improve names, size, and boundaries. +3. Prefer move/extract over rewrite. +4. Keep tests green; add tests if extracting scorable logic with none. +5. Output Review block focusing on Architecture, Readability, Test, Scope. + +## Forbidden + +- Mixing refactor with feature work in one change set. +- Breaking public API envelope or URL contracts silently. diff --git a/.ai/review.md b/.ai/review.md new file mode 100644 index 0000000..6b47231 --- /dev/null +++ b/.ai/review.md @@ -0,0 +1,40 @@ +# AI Self-Review — mandatory before claiming Done + +After coding, check every item. Output a Review block. + +## Checklist + +| Area | Ask | +|---|---| +| Architecture | Layers respected? No Handler→DB? | +| Naming | Consistent domain terms? | +| Performance | Obvious N+1 / unbounded lists? | +| Security | AuthZ, validation, no secrets? | +| Readability | Files ≤400, funcs ≤50? | +| Error handling | Wrapped errors / user-safe messages? | +| Logging | Structured, no PII spam? | +| Test | Critical path covered or smoke listed? | +| Deployment | Migration / health / env noted if needed? | +| Docs | OpenAPI / PRD touch if public behavior changed? | +| Design | UI change? Tokens + catalog + platform contract followed? | +| Scope | No unrelated files modified? | + +## Output format (required) + +``` +## Review +- Architecture: PASS | FAIL — +- Naming: PASS | FAIL — +- Performance: PASS | FAIL — +- Security: PASS | FAIL — +- Readability: PASS | FAIL — +- Error handling: PASS | FAIL — +- Logging: PASS | FAIL — +- Test: PASS | FAIL — +- Deployment: PASS | FAIL — +- Docs: PASS | FAIL — +- Design: PASS | FAIL | N/A — +- Scope: PASS | FAIL — +``` + +Any FAIL → fix before finishing. Do not hide FAILs. diff --git a/.ai/security.md b/.ai/security.md new file mode 100644 index 0000000..9db59b0 --- /dev/null +++ b/.ai/security.md @@ -0,0 +1,26 @@ +# Security — Golden Rules + +## AuthZ + +- Authenticate before mutating user data. +- Authorize ownership: user A cannot read/write user B resources. + +## Input + +- Validate all external input at handler boundary. +- Parameterized SQL only. Never string-concatenate SQL. + +## Secrets + +- No secrets in repo, frontend bundles, or logs. +- Rotate via env / secret manager. + +## Privacy + +- Birthday / answers / reports are personal data. +- Soft-delete and future account deletion path required in design. +- Log request ids; avoid logging full PII payloads. + +## Content compliance + +- Reject generating 疗效 / 吉凶文案 in prompts and templates. diff --git a/.ai/tech-stack.md b/.ai/tech-stack.md new file mode 100644 index 0000000..8c89853 --- /dev/null +++ b/.ai/tech-stack.md @@ -0,0 +1,42 @@ +# Tech Stack — frozen unless ADR says otherwise + +## Backend + +- Language: Go 1.22+ +- HTTP: gin +- DB: PostgreSQL +- Migrations: goose (or golang-migrate) under `apps/api/migrations/` +- Log: structured (zap or slog wrapper). No `fmt.Println` in business paths. +- Module path: `github.com/yuxingu/digital-psychology/apps/api` + +## User H5 + +- Vue 3 + TypeScript (`strict: true`) + Vite +- Router: vue-router +- State: pinia +- Package: `@yuxingu/user-h5` + +## Shared + +- npm workspaces at repo root +- `@yuxingu/sdk` `createClient({ baseURL, adapters })` +- TS camelCase in app; SDK converts to/from API snake_case + +## Mini program + +- Native WeChat mini-program structure +- Same SDK via adapters (`wx.request`, storage) +- No shared Vue SFC with H5 + +## Deploy + +- Docker Compose for local Postgres: `deploy/docker-compose.yml` +- API health: `/api/v1/healthz` (liveness). Add readiness when DB is wired. +- Never deploy with image tag `latest` in prod. + +## Do not introduce without ADR + +- Another backend language +- GraphQL / gRPC as primary public API (REST is primary) +- ORM that hides SQL without team agreement +- New state manager besides Pinia on H5 diff --git a/.ai/testing.md b/.ai/testing.md new file mode 100644 index 0000000..803fb93 --- /dev/null +++ b/.ai/testing.md @@ -0,0 +1,22 @@ +# Testing — Golden Rules + +## Priority + +Test money, scoring, auth, and unlock paths first. + +## Go + +- Pure engines (decode / scale scoring) must have unit tests. +- `go test ./...` must pass before claiming API done. + +## H5 + +- Critical utils may use Vitest when introduced. +- Until then: PR must list manual smoke steps for touched flows. + +## Definition for AI + +Do not mark a feature Done if: + +- Scorable logic has zero tests, or +- Build is broken (`go test` / `npm run build:h5` fail). diff --git a/.ai/ui.md b/.ai/ui.md new file mode 100644 index 0000000..871a232 --- /dev/null +++ b/.ai/ui.md @@ -0,0 +1,64 @@ +# UI — Product chrome + pointer to Design Contract + +**Visual / component rules:** `.ai/design/design-system.md` (mandatory before UI work). +**Catalog:** `.ai/design/component-catalog.md` +**Platform:** `.ai/design/platform/h5.md` (default) · `mini-program.md` · `website.md` · `flutter.md` + +--- + +## Brand (summary) + +- Primary `#E54D42` → `--color-primary` / `--yxg-pri` +- Atmosphere: peach wash → white sheet +- Mobile-first; user column ~430px +- Unicode symbols over emoji in chrome + +--- + +## H5 structure + +``` +apps/user-h5/src/ + pages/ + components/ + layouts/ + hooks/ + api/ + stores/ + router/ + assets/ +``` + +No raw `fetch` in `pages/`. Tokens from `packages/ui`. + +--- + +## Tabs (product IA) + +Home / Explore / **Ask** / Companion / Mine. +Ask is the strategic center tab. + +--- + +## Copy / compliance + +- No medical cure claims +- No 吉凶祸福 +- Report screens include disclaimer +- Brand voice: see design-system §13 + +--- + +## Interaction + +- One job per section +- Home first viewport: brand, decode entry, then content sheet +- Loading / empty / error required for network views + +--- + +## Mini program + +- Own `components/`; share tokens from `packages/ui`, not Vue SFCs +- Pages → `services/*` only +- See `design/platform/mini-program.md` diff --git a/.ai/workflow.md b/.ai/workflow.md new file mode 100644 index 0000000..bb337f3 --- /dev/null +++ b/.ai/workflow.md @@ -0,0 +1,33 @@ +# Workflow — Default Development Loop + +``` +Receive task + ↓ +Read AI Contract + constitution + architecture + domain + ↓ +Architecture Check (ADR + file-map + layers) + ↓ +Choose playbook (add-api / new-page / new-table / …) + ↓ +Coding (follow patterns + examples) + ↓ +Commands (build / test / health) + ↓ +Review (.ai/review.md) + ↓ +DoD + checklist + ↓ +Commit (Conventional Commits, one concern) +``` + +## Architecture Check questions + +1. Does this change reverse an Accepted ADR? If yes → ASK / new ADR. +2. Correct layer? Handler / Service / Repository? +3. Domain words match `.ai/domain.md`? +4. API envelope still `{code,message,data}`? + +## Stop conditions + +- Unclear requirement → ASK FIRST +- Need GraphQL / new DB / new auth scheme → write or cite ADR before coding diff --git a/.cursor/README.md b/.cursor/README.md new file mode 100644 index 0000000..46875b1 --- /dev/null +++ b/.cursor/README.md @@ -0,0 +1,13 @@ +# Cursor as Agent Runtime + +`.cursor/` is **not** a second source of truth. + +Everything under `rules/`, `templates/`, `commands/` is a **symlink** into `.ai/`. + +| Cursor path | Points to | +|---|---| +| `rules/*.md` | `.ai/*.md` (constitution, architecture, coding, …) | +| `templates/feature.md` | `.ai/playbooks/add-api.md` | +| `commands/review.md` | `.ai/review.md` | + +Edit only `.ai/`. Never duplicate content here. diff --git a/.cursor/commands/project-commands.md b/.cursor/commands/project-commands.md new file mode 120000 index 0000000..3141df9 --- /dev/null +++ b/.cursor/commands/project-commands.md @@ -0,0 +1 @@ +../../.ai/commands.md \ No newline at end of file diff --git a/.cursor/commands/refactor.md b/.cursor/commands/refactor.md new file mode 120000 index 0000000..cff2049 --- /dev/null +++ b/.cursor/commands/refactor.md @@ -0,0 +1 @@ +../../.ai/prompts/refactor.md \ No newline at end of file diff --git a/.cursor/commands/review.md b/.cursor/commands/review.md new file mode 120000 index 0000000..de1dc6d --- /dev/null +++ b/.cursor/commands/review.md @@ -0,0 +1 @@ +../../.ai/review.md \ No newline at end of file diff --git a/.cursor/rules/ai-contract.md b/.cursor/rules/ai-contract.md new file mode 120000 index 0000000..ed0cc84 --- /dev/null +++ b/.cursor/rules/ai-contract.md @@ -0,0 +1 @@ +../../.ai/ai-contract.md \ No newline at end of file diff --git a/.cursor/rules/api.md b/.cursor/rules/api.md new file mode 120000 index 0000000..e9c7c0a --- /dev/null +++ b/.cursor/rules/api.md @@ -0,0 +1 @@ +../../.ai/api.md \ No newline at end of file diff --git a/.cursor/rules/architecture.md b/.cursor/rules/architecture.md new file mode 120000 index 0000000..206fd30 --- /dev/null +++ b/.cursor/rules/architecture.md @@ -0,0 +1 @@ +../../.ai/architecture.md \ No newline at end of file diff --git a/.cursor/rules/coding.md b/.cursor/rules/coding.md new file mode 120000 index 0000000..de30918 --- /dev/null +++ b/.cursor/rules/coding.md @@ -0,0 +1 @@ +../../.ai/coding.md \ No newline at end of file diff --git a/.cursor/rules/constitution.md b/.cursor/rules/constitution.md new file mode 120000 index 0000000..5880c9a --- /dev/null +++ b/.cursor/rules/constitution.md @@ -0,0 +1 @@ +../../.ai/constitution.md \ No newline at end of file diff --git a/.cursor/rules/database.md b/.cursor/rules/database.md new file mode 120000 index 0000000..1383f8d --- /dev/null +++ b/.cursor/rules/database.md @@ -0,0 +1 @@ +../../.ai/database.md \ No newline at end of file diff --git a/.cursor/rules/design-system.md b/.cursor/rules/design-system.md new file mode 120000 index 0000000..71a1776 --- /dev/null +++ b/.cursor/rules/design-system.md @@ -0,0 +1 @@ +../../.ai/design/design-system.md \ No newline at end of file diff --git a/.cursor/rules/domain-map.md b/.cursor/rules/domain-map.md new file mode 120000 index 0000000..db46966 --- /dev/null +++ b/.cursor/rules/domain-map.md @@ -0,0 +1 @@ +../../.ai/domain/domain-map.md \ No newline at end of file diff --git a/.cursor/rules/domain.md b/.cursor/rules/domain.md new file mode 120000 index 0000000..3840ce3 --- /dev/null +++ b/.cursor/rules/domain.md @@ -0,0 +1 @@ +../../.ai/domain.md \ No newline at end of file diff --git a/.cursor/rules/feature-map.md b/.cursor/rules/feature-map.md new file mode 120000 index 0000000..32480a1 --- /dev/null +++ b/.cursor/rules/feature-map.md @@ -0,0 +1 @@ +../../.ai/product/feature-map.md \ No newline at end of file diff --git a/.cursor/rules/forbidden.md b/.cursor/rules/forbidden.md new file mode 120000 index 0000000..c1c812d --- /dev/null +++ b/.cursor/rules/forbidden.md @@ -0,0 +1 @@ +../../.ai/forbidden.md \ No newline at end of file diff --git a/.cursor/rules/review.md b/.cursor/rules/review.md new file mode 120000 index 0000000..de1dc6d --- /dev/null +++ b/.cursor/rules/review.md @@ -0,0 +1 @@ +../../.ai/review.md \ No newline at end of file diff --git a/.cursor/templates/feature-checklist.md b/.cursor/templates/feature-checklist.md new file mode 120000 index 0000000..2183731 --- /dev/null +++ b/.cursor/templates/feature-checklist.md @@ -0,0 +1 @@ +../../.ai/checklists/feature.md \ No newline at end of file diff --git a/.cursor/templates/feature.md b/.cursor/templates/feature.md new file mode 120000 index 0000000..8c2e022 --- /dev/null +++ b/.cursor/templates/feature.md @@ -0,0 +1 @@ +../../.ai/playbooks/add-api.md \ No newline at end of file diff --git a/.gitignore b/.gitignore index 90539b9..ee362fd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,23 @@ __pycache__/ *.log .env .DS_Store + +# Node +node_modules/ +dist/ +*.local + +# Go +apps/api/bin/ +*.exe + +# IDE +.idea/ +.vscode/ + +# Data / local db +*.db +.scale_results.db + +# OS +Thumbs.db diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1d014ad --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,44 @@ +# AGENTS.md — AI System Entry + +You are the software engineer for **愈心谷 (YuXinGu)**. + +This file is for AI agents. + +## Load order (every task) + +1. `.ai/ai-contract.md` +2. `.ai/constitution.md` +3. `.ai/architecture.md` +4. `.ai/domain.md` (+ product/API tasks: `domain/domain-map.md`) +5. `.ai/forbidden.md` +6. `.ai/file-map.md` + `.ai/workflow.md` +7. Task rules: `.ai/coding.md` / `api.md` / `database.md` / `ui.md` / `security.md` / … +8. **Product scope / PRD slicing:** `.ai/product/feature-map.md` (竞品对照: `product/cece-feature-map.md`) +9. **Any UI work:** `.ai/design/design-system.md` + `component-catalog.md` + matching `design/platform/*.md` (H5 default) +10. Matching **ADR** in `.ai/adr/` before changing stack or API style +11. Prefer **patterns/** + **examples/** + **playbooks/** over inventing structure +12. Before Done: `.ai/review.md` + `.ai/definition-of-done.md` + `.ai/checklists/*` +13. Verify via `.ai/commands.md` +14. `prompts/` are optional helpers — not a substitute for rules above + +## Hard constraints + +- Never violate architecture or Accepted ADRs. +- Never invent APIs, DB tables, or domain synonyms. +- Never guess requirements — **ASK FIRST**. +- Never modify files outside the current task. +- New code only under `apps/` and `packages/` (unless legacy migration task). +- One concern per change set. Function ≤50 lines. File ≤400 lines. +- Print the Review block when a coding task finishes. + +## Cursor + +`.cursor/` is an Agent Runtime view of this system via symlinks. Edit `.ai/` only. + +## Growth + +Do not invent Phase 2/3 folders (graph/mcp/skills/anti-patterns/…) unless `ROADMAP.md` says so and the task asks. Prefer improving patterns/examples/playbooks. + +## Product one-liner + +Birthday → Profile (digital personality + constitution) → Companion / Ask retention → Membership & Report unlock → Consult later. Compliant lifestyle wording only. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..d34612a --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,9 @@ +# ARCHITECTURE.md + +Canonical architecture rules for AI and humans live in: + +**[.ai/architecture.md](.ai/architecture.md)** +**[.ai/tech-stack.md](.ai/tech-stack.md)** +**[.ai/constitution.md](.ai/constitution.md)** + +Start at [AGENTS.md](AGENTS.md). diff --git a/CLAUDE.md b/CLAUDE.md index 516cfaa..35aff36 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,64 +1,49 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +愈心谷 is an **AI-first** Monorepo. Treat `.ai/` as the executable engineering system. -## Project overview +## On every task -愈心谷 (YuXinGu) — a digital mental health platform. The codebase is a collection of standalone, mobile-first HTML pages with no build step, no framework, no package.json, and no JavaScript dependencies. +1. Read [AGENTS.md](AGENTS.md) (load order is authoritative). +2. Especially: `.ai/ai-contract.md`, `constitution.md`, `architecture.md`, `domain.md`, `forbidden.md`. +3. Prefer `patterns/` + `examples/` + `playbooks/` over free-form invention. +4. Finish with Review + DoD + matching checklist. -The product vision is detailed in `立项文档.md` (Chinese): combine digital psychology (numerology-based personality analysis) with TCM constitution theory (中医体质) to create a "test → report → subscribe → consult" funnel. Target audience is Chinese-speaking; all UI text is Chinese (zh-CN). +## Layout -## Running the app +| Path | Role | +|---|---| +| `.ai/` | AI Engineering System (source of truth for rules) | +| `apps/api` | Go API | +| `apps/user-h5` | Vue3 user H5 | +| `apps/mini-program` | Mini-program scaffold | +| `packages/*` | sdk / types / utils / ui | +| `apps/docs/` | Human PRD / business docs (not a substitute for `.ai/`) | + +## Commands ```bash -python3 server.py [port] # default port 8001 +export GOPROXY=https://goproxy.cn,direct # if needed +cd apps/api && go run ./cmd/server +npm install && npm run dev:h5 ``` -The server serves static files from the project root and exposes two JSON API endpoints: +## Legacy -- `GET /load` — returns mindmap data from `mindmap_data.json` -- `POST /save` — persists mindmap data to `mindmap_data.json` (validates JSON before writing) +Root `yuxingu.html`, `pages/`, `server.py` = prototype. Do not extend. See [LEGACY.md](LEGACY.md). -There are no tests, no linters, and no build pipeline. To verify changes, run the server and open the relevant HTML file in a browser. +## Skill routing -## File map +When the user's request matches an available skill, invoke it. When in doubt, invoke the skill. -| File | Purpose | -|---|---| -| `index.html` | Landing page linking to all sections | -| `yuxingu.html` | Main app shell — hero, service cards, activity center, bottom nav | -| `shuzi.html` | Digital psychology calculator — life-number triangle, bagua, liuren, zodiac, wuxing modules | -| `mindmap.html` | Canvas-based mind-map editor with multi-map management | -| `yangsheng.html` | AI Eastern wellness — bazi/wuxing constitution analysis, weekly/daily reports | -| `manual.html` | Static "life manual" poster with pyramid triangle charts rendered via inline SVG | -| `yuxingu_v1.html` | Earlier iteration of the main page (kept for reference, not linked from index) | -| `server.py` | Minimal HTTP server (Python stdlib, no dependencies) | -| `立项文档.md` | Product requirements doc — market analysis, business model, pricing, MVP scope | - -### Unrelated files - -These files are not part of the 愈心谷 app — they belong to a separate crypto trading visualization project that happens to share the repo: - -- `link_regime_chart.html` — LINK token HMM regime K-line chart -- `l2_LINK_*.json` (3 files) — market data and signals for the above - -## Architecture notes - -- **Every HTML file is self-contained.** All CSS and JS are embedded in ` diff --git a/apps/user-h5/src/main.ts b/apps/user-h5/src/main.ts new file mode 100644 index 0000000..b0c7d4d --- /dev/null +++ b/apps/user-h5/src/main.ts @@ -0,0 +1,8 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import App from './App.vue' +import router from './router' +import '@yuxingu/ui/tokens.css' +import './styles/app.css' + +createApp(App).use(createPinia()).use(router).mount('#app') diff --git a/apps/user-h5/src/pages/AskPage.vue b/apps/user-h5/src/pages/AskPage.vue new file mode 100644 index 0000000..71d6018 --- /dev/null +++ b/apps/user-h5/src/pages/AskPage.vue @@ -0,0 +1,14 @@ + + + diff --git a/apps/user-h5/src/pages/CompanionPage.vue b/apps/user-h5/src/pages/CompanionPage.vue new file mode 100644 index 0000000..d6d018b --- /dev/null +++ b/apps/user-h5/src/pages/CompanionPage.vue @@ -0,0 +1,14 @@ + + + diff --git a/apps/user-h5/src/pages/DecodePage.vue b/apps/user-h5/src/pages/DecodePage.vue new file mode 100644 index 0000000..bfe225f --- /dev/null +++ b/apps/user-h5/src/pages/DecodePage.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/apps/user-h5/src/pages/ExplorePage.vue b/apps/user-h5/src/pages/ExplorePage.vue new file mode 100644 index 0000000..25972ce --- /dev/null +++ b/apps/user-h5/src/pages/ExplorePage.vue @@ -0,0 +1,18 @@ + + + diff --git a/apps/user-h5/src/pages/HomePage.vue b/apps/user-h5/src/pages/HomePage.vue new file mode 100644 index 0000000..949a55b --- /dev/null +++ b/apps/user-h5/src/pages/HomePage.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/apps/user-h5/src/pages/MinePage.vue b/apps/user-h5/src/pages/MinePage.vue new file mode 100644 index 0000000..3b654c2 --- /dev/null +++ b/apps/user-h5/src/pages/MinePage.vue @@ -0,0 +1,14 @@ + + + diff --git a/apps/user-h5/src/router/index.ts b/apps/user-h5/src/router/index.ts new file mode 100644 index 0000000..a821e4e --- /dev/null +++ b/apps/user-h5/src/router/index.ts @@ -0,0 +1,15 @@ +import { createRouter, createWebHistory } from 'vue-router' + +const router = createRouter({ + history: createWebHistory(), + routes: [ + { path: '/', name: 'home', component: () => import('../pages/HomePage.vue'), meta: { tab: true } }, + { path: '/explore', name: 'explore', component: () => import('../pages/ExplorePage.vue'), meta: { tab: true } }, + { path: '/ask', name: 'ask', component: () => import('../pages/AskPage.vue'), meta: { tab: true } }, + { path: '/companion', name: 'companion', component: () => import('../pages/CompanionPage.vue'), meta: { tab: true } }, + { path: '/mine', name: 'mine', component: () => import('../pages/MinePage.vue'), meta: { tab: true } }, + { path: '/decode', name: 'decode', component: () => import('../pages/DecodePage.vue'), meta: { tab: false } }, + ], +}) + +export default router diff --git a/apps/user-h5/src/styles/app.css b/apps/user-h5/src/styles/app.css new file mode 100644 index 0000000..cc48f97 --- /dev/null +++ b/apps/user-h5/src/styles/app.css @@ -0,0 +1,12 @@ +*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent} +html,body,#app{min-height:100%} +body{ + font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif; + background:linear-gradient(180deg,var(--yxg-bg-start) 0%,var(--yxg-bg-end) 28%,#fff9f7 100%); + color:var(--yxg-text); +} +.app-shell{ + max-width:var(--yxg-max-w);margin:0 auto;min-height:100vh; + padding-bottom:calc(var(--yxg-nav-h) + env(safe-area-inset-bottom,0px) + 12px); +} +a{color:inherit;text-decoration:none} diff --git a/apps/user-h5/src/vite-env.d.ts b/apps/user-h5/src/vite-env.d.ts new file mode 100644 index 0000000..65c7311 --- /dev/null +++ b/apps/user-h5/src/vite-env.d.ts @@ -0,0 +1,7 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent + export default component +} diff --git a/apps/user-h5/tsconfig.json b/apps/user-h5/tsconfig.json new file mode 100644 index 0000000..b2e538d --- /dev/null +++ b/apps/user-h5/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "paths": { + "@/*": ["./src/*"] + }, + "baseUrl": "." + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/apps/user-h5/vite.config.ts b/apps/user-h5/vite.config.ts new file mode 100644 index 0000000..7f13ca1 --- /dev/null +++ b/apps/user-h5/vite.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { fileURLToPath, URL } from 'node:url' + +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + server: { + port: 5173, + proxy: { + '/api': { + target: 'http://127.0.0.1:8080', + changeOrigin: true, + }, + }, + }, +}) diff --git a/l2_LINK_20260629_171642_12h_data.json b/archive/l2_LINK_20260629_171642_12h_data.json similarity index 100% rename from l2_LINK_20260629_171642_12h_data.json rename to archive/l2_LINK_20260629_171642_12h_data.json diff --git a/l2_LINK_kline_data.json b/archive/l2_LINK_kline_data.json similarity index 100% rename from l2_LINK_kline_data.json rename to archive/l2_LINK_kline_data.json diff --git a/l2_LINK_signals.json b/archive/l2_LINK_signals.json similarity index 100% rename from l2_LINK_signals.json rename to archive/l2_LINK_signals.json diff --git a/link_regime_chart.html b/archive/link_regime_chart.html similarity index 100% rename from link_regime_chart.html rename to archive/link_regime_chart.html diff --git a/manual.html b/archive/manual.html similarity index 100% rename from manual.html rename to archive/manual.html diff --git a/xingjing.html b/archive/xingjing.html similarity index 100% rename from xingjing.html rename to archive/xingjing.html diff --git a/yuxingu_v1.html b/archive/yuxingu_v1.html similarity index 100% rename from yuxingu_v1.html rename to archive/yuxingu_v1.html diff --git a/css/app.css b/css/app.css new file mode 100644 index 0000000..ddd9a33 --- /dev/null +++ b/css/app.css @@ -0,0 +1,72 @@ +/* 愈心谷共享壳 —— 品牌色、布局、底栏、二级顶栏 */ +:root{ + --pri:#E54D42; --blue:#4A90E2; --green:#5CB85C; + --purple:#8B5CF6; --orange:#E8985A; --pink:#E0508C; + --gold:#C8923A; + --text:#333; --sub:#999; + --hero-start:#FF9F80; --hero-end:#FFB8A6; + --bg-start:#FFD1C7; --bg-end:#FFC8B5; + --nav-h:56px; +} +*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent} +html,body{min-height:100%} +body.app-body{ + font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei","Helvetica Neue",sans-serif; + background:linear-gradient(180deg,var(--bg-start) 0%,var(--bg-end) 30%,#fff5f0 100%); + color:var(--text);max-width:430px;margin:0 auto; + -webkit-font-smoothing:antialiased; + padding-bottom:calc(var(--nav-h) + env(safe-area-inset-bottom,0px) + 12px); +} +body.app-body.no-nav{padding-bottom:24px} + +a{color:inherit} + +/* ── 二级顶栏 ── */ +.app-topbar{ + display:flex;align-items:center;gap:10px; + padding:12px 14px;position:sticky;top:0;z-index:50; + background:linear-gradient(180deg,var(--bg-start) 0%,rgba(255,209,199,0.85) 100%); + -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px); +} +.app-topbar .back{ + width:36px;height:36px;border:none;background:rgba(255,255,255,0.7); + border-radius:10px;font-size:18px;color:#555;cursor:pointer; + display:flex;align-items:center;justify-content:center;text-decoration:none;flex-shrink:0; +} +.app-topbar .title{ + flex:1;font-size:16px;font-weight:600;color:var(--text); + white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:center; + padding-right:36px; +} + +/* ── 底栏 ── */ +.app-nav{ + position:fixed;bottom:0;left:50%;transform:translateX(-50%); + width:100%;max-width:430px;background:#fff; + display:flex;justify-content:space-around; + padding:6px 0 calc(10px + env(safe-area-inset-bottom,0)); + border-top:1px solid #f0f0f0;z-index:100; + box-shadow:0 -2px 8px rgba(0,0,0,0.03); +} +.app-nav a{ + display:flex;flex-direction:column;align-items:center;gap:2px; + font-size:10px;color:var(--sub);text-decoration:none;line-height:1; + flex:1;padding:2px 0; +} +.app-nav a.active{color:var(--pri)} +.app-nav .ni{ + width:20px;height:20px;display:flex;align-items:center;justify-content:center; + font-size:16px;line-height:1; +} + +/* ── 通用卡片 ── */ +.app-card{ + background:#fff;border-radius:16px;padding:18px 16px; + box-shadow:0 2px 10px rgba(0,0,0,0.04); +} +.app-section{padding:0 16px;margin-top:18px} +.app-sec-title{font-size:16px;font-weight:600;color:var(--text);margin-bottom:10px} + +.page-pad{padding:0 16px 20px} +.page-header{text-align:center;padding:20px 16px 12px} +.page-header .sub{font-size:13px;color:rgba(0,0,0,0.45);margin-top:4px} diff --git a/css/decode.css b/css/decode.css new file mode 100644 index 0000000..9d15a32 --- /dev/null +++ b/css/decode.css @@ -0,0 +1,110 @@ +:root{ + --pri:#E54D42; --num:#C8923A; --num-d:#8a6d3b; + --ys:#5CB85C; --ys-d:#3f8f45; + --text:#333; --sub:#999; + --bg-start:#FFD1C7; --bg-end:#FFC8B5; +} +*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent;} +/* legacy body → scoped */ +.app-page{font-family:-apple-system,"PingFang SC","Microsoft YaHei",sans-serif; + color:var(--text);background:linear-gradient(160deg,var(--bg-start),var(--bg-end)); + min-height:100vh;padding-bottom:80px;} +.wrap{max-width:430px;margin:0 auto;padding:0 14px;} +.top{text-align:center;padding:22px 0 10px;} +.top .t{font-size:24px;font-weight:800;letter-spacing:3px;color:#fff; + text-shadow:0 2px 8px rgba(0,0,0,.15);} +.top .s{font-size:12px;color:#fff;opacity:.9;margin-top:4px;} +.card{background:#fff;border-radius:18px;padding:20px;margin:14px 0; + box-shadow:0 8px 30px rgba(0,0,0,.08);} +.tag{display:inline-block;font-size:11px;padding:3px 10px;border-radius:20px; + color:#fff;font-weight:600;margin-bottom:10px;} +.tag-num{background:var(--num);} +.tag-ys{background:var(--ys);} + +/* Part1 数字 */ +.hex{width:76px;height:76px;margin:6px auto 8px;display:flex;align-items:center; + justify-content:center;font-size:34px;font-weight:800;color:#fff; + background:linear-gradient(135deg,var(--num),var(--num-d)); + clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);} +.p-title{text-align:center;font-size:19px;font-weight:700;color:var(--num-d);} +.p-oneline{text-align:center;font-size:13px;color:var(--sub);margin-top:4px;} +.pyr{text-align:center;margin:16px 0;} + +/* Part2 体质 */ +.ys-type{text-align:center;font-size:20px;font-weight:700;color:var(--ys-d);margin:4px 0;} +.ys-dir{text-align:center;font-size:13px;color:var(--sub);} +.bars{margin:16px 0 6px;} +.bar-row{display:flex;align-items:center;gap:8px;margin:7px 0;font-size:13px;} +.bar-row .lab{width:20px;text-align:center;font-weight:700;} +.bar-track{flex:1;height:14px;background:#f0f0f0;border-radius:8px;overflow:hidden;} +.bar-fill{height:100%;border-radius:8px;transition:width .6s;} +.bar-row .val{width:34px;text-align:right;color:var(--sub);} + +/* 付费墙 */ +.lock{position:relative;margin-top:14px;border-radius:14px;overflow:hidden; + border:1px solid #f2e6d0;} +.lock .blur{filter:blur(10px);opacity:0.35;pointer-events:none;user-select:none; + padding:16px;font-size:13px;line-height:1.9;color:#666;} +.lock .cover{position:absolute;inset:0;background:linear-gradient(180deg, + rgba(255,255,255,.4),rgba(255,255,255,.92));display:flex;flex-direction:column; + align-items:center;justify-content:center;text-align:center;padding:16px;} +.lock .cover .lk-t{font-size:15px;font-weight:700;color:var(--num-d);} +.lock .cover .lk-d{font-size:12px;color:var(--sub);margin:6px 0 12px;} +.unlock-btn{background:var(--pri);color:#fff;border:none;padding:11px 30px; + border-radius:24px;font-size:15px;font-weight:700;cursor:pointer; + box-shadow:0 4px 14px rgba(229,77,66,.35);} +.unlocked .cover{display:none;} +.unlocked .blur{filter:none;pointer-events:auto;user-select:auto;} + +/* Part3 今日锦囊 */ +.jn h4{font-size:14px;color:var(--ys-d);margin-bottom:8px;} +.jn .row{font-size:13px;line-height:2;color:#555;} +.jn .row b{color:var(--ys-d);} +.jn .st{font-size:12px;color:var(--sub);margin-bottom:6px;} + +/* 契合度 / 分享 */ +.mini-form{display:flex;gap:6px;align-items:center;justify-content:center; + flex-wrap:wrap;margin-top:10px;} +.mini-form input{width:56px;padding:9px 4px;border:1.5px solid #eee; + border-radius:9px;text-align:center;font-size:14px;} +.mini-form button,.share-btn{background:var(--pri);color:#fff;border:none; + padding:10px 20px;border-radius:22px;font-size:14px;font-weight:600;cursor:pointer;} +.share-btn{display:block;width:100%;margin-top:6px;} +.match-score{text-align:center;font-size:32px;font-weight:800;color:var(--pri);margin:8px 0 2px;} + +/* 深度入口 */ +.deep{display:flex;gap:10px;margin:16px 0;} +.deep a{flex:1;text-align:center;padding:13px 6px;border-radius:14px; + color:#fff;text-decoration:none;font-size:13px;font-weight:600;} +.deep .d-num{background:linear-gradient(135deg,var(--num),var(--num-d));} +.deep .d-ys{background:linear-gradient(135deg,var(--ys),var(--ys-d));} + +.disc{font-size:11px;color:#fff;opacity:.8;text-align:center;line-height:1.7; + padding:14px 8px;} +.err{text-align:center;color:#fff;padding:40px 20px;font-size:15px;} + +/* 分享卡片 canvas 弹层 */ +.modal{position:fixed;inset:0;background:rgba(0,0,0,.6);display:none; + align-items:center;justify-content:center;z-index:99;padding:20px;} +.modal.on{display:flex;} +.modal .m-box{background:#fff;border-radius:16px;padding:16px;max-width:340px;text-align:center;} +.modal canvas{width:100%;border-radius:10px;} +.modal .m-tip{font-size:12px;color:var(--sub);margin:10px 0;} +.modal .m-close{color:var(--pri);font-size:14px;cursor:pointer;} + +/* 支付弹窗 */ +.pay-modal{position:fixed;inset:0;background:rgba(0,0,0,.5);display:none; + align-items:center;justify-content:center;z-index:100;padding:20px;} +.pay-modal.on{display:flex;} +.pay-modal .pm-box{background:#fff;border-radius:20px;padding:28px 24px 22px; + max-width:300px;width:100%;text-align:center;box-shadow:0 16px 48px rgba(0,0,0,.15);} +.pay-modal .pm-icon{font-size:36px;margin-bottom:10px;} +.pay-modal .pm-title{font-size:18px;font-weight:700;color:#333;margin-bottom:4px;} +.pay-modal .pm-desc{font-size:13px;color:var(--sub);margin-bottom:6px;line-height:1.6;} +.pay-modal .pm-price{font-size:28px;font-weight:800;color:var(--pri);margin:8px 0;} +.pay-modal .pm-price small{font-size:14px;font-weight:400;color:var(--sub);} +.pay-modal .pm-features{font-size:12px;color:#888;margin:10px 0 14px;line-height:1.9;} +.pay-modal .pm-btn{display:block;width:100%;background:var(--pri);color:#fff;border:none; + padding:12px;border-radius:24px;font-size:15px;font-weight:700;cursor:pointer; + box-shadow:0 4px 14px rgba(229,77,66,.35);margin-bottom:8px;} +.pay-modal .pm-cancel{font-size:12px;color:#aaa;cursor:pointer;background:none;border:none;} diff --git a/css/health.css b/css/health.css new file mode 100644 index 0000000..9587bd3 --- /dev/null +++ b/css/health.css @@ -0,0 +1,124 @@ +:root{--pri:#E54D42;--gold:#C8923A;--wd:#5CB85C;--fr:#E54D42;--ea:#E8985A;--mt:#C8923A;--wt:#4A90E2;--bg0:#FFD1C7;--bg1:#FFC8B5;--bg2:#FFF8F0;--bg3:#fff5f0} +*{box-sizing:border-box;margin:0;padding:0} +/* legacy body → scoped */ +.app-page{ + font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif; + background:linear-gradient(180deg,var(--bg0) 0%,var(--bg1) 30%,var(--bg2) 60%,var(--bg3) 100%); + min-height:100vh;color:#333;-webkit-font-smoothing:antialiased; +} +.container{max-width:500px;margin:0 auto;padding:20px 14px 40px} +.header{text-align:center;padding:24px 0 16px} +.header .sub{font-size:12px;color:rgba(0,0,0,0.35);margin-top:4px} +.card{ + background:#fff;border-radius:16px;padding:20px;margin-bottom:14px; + box-shadow:0 2px 12px rgba(0,0,0,0.04); +} +.card h2{font-size:16px;font-weight:600;margin-bottom:12px;color:#333;display:flex;align-items:center;gap:8px} +.card h3{font-size:14px;font-weight:600;margin:14px 0 8px;color:#555} +.card h4{font-size:13px;font-weight:600;color:#333;margin-bottom:4px} +.card p.dim{font-size:12px;color:#aaa;margin-bottom:14px} + +/* Tabs */ +.tabs{display:flex;gap:0;margin-bottom:14px;background:#f5f0eb;border-radius:10px;padding:3px} +.tab{flex:1;text-align:center;padding:10px 0;border-radius:8px;font-size:13px;cursor:pointer;color:#888;transition:all 0.15s} +.tab.active{background:#fff;color:var(--pri);font-weight:600;box-shadow:0 1px 3px rgba(0,0,0,0.06)} + +/* Form */ +.fg{margin-bottom:12px} +.fg label{display:block;font-size:12px;color:#999;margin-bottom:4px;font-weight:500} +.fg input,.fg select{width:100%;padding:12px 14px;border:1.5px solid #e8e0d8;border-radius:10px;font-size:15px;outline:none;background:#fdfaf7} +.fg input:focus,.fg select:focus{border-color:var(--pri)} +.fr{display:flex;gap:10px}.fr .fg{flex:1} +.btn{width:100%;padding:14px;border:none;border-radius:12px;font-size:16px;font-weight:600;cursor:pointer} +.btn-pri{background:var(--pri);color:#fff}.btn-pri:active{opacity:0.85} + +/* Pentagram */ +.penta{display:flex;justify-content:center;gap:8px;margin:16px 0;flex-wrap:wrap} +.pb{text-align:center;min-width:44px} +.pb .f{height:120px;width:28px;border-radius:14px;margin:0 auto 6px;position:relative;background:#f0ebe5;overflow:hidden} +.pb .fi{position:absolute;bottom:0;left:0;right:0;border-radius:14px;transition:height 0.6s ease} +.pb .l{font-size:11px;color:#888} +.pb .v{font-size:13px;font-weight:700;margin-top:2px} + +/* Stats */ +.stats2{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:8px 0} +.st2{padding:14px 10px;border-radius:10px;background:#fdf8f5;text-align:center} +.st2 .v{font-size:20px;font-weight:700} +.st2 .l{font-size:11px;color:#999;margin-top:2px} +.st2 .d{font-size:11px;color:#bbb;margin-top:2px;line-height:1.5} + +/* Bazi table */ +.bzt{width:100%;border-collapse:collapse;font-size:13px;margin:10px 0} +.bzt th{background:#fdf8f5;padding:10px 6px;font-weight:600;font-size:11px;color:#999} +.bzt td{text-align:center;padding:10px 6px;border-bottom:1px solid #f5f0eb} +.bzt td strong{font-size:16px} +.bzt .el{font-size:11px;color:#aaa} + +/* Tags */ +.tags{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0} +.tag{padding:4px 12px;border-radius:20px;font-size:12px;font-weight:500} +.tg{background:#E6FFE8;color:#388E3C} +.tw{background:#FFECEC;color:var(--pri)} +.tb{background:#E6F0FF;color:var(--wt)} +.to{background:#FFF8E6;color:var(--ea)} + +/* Risk */ +.rk{padding:14px;border-radius:12px;margin-bottom:10px;border-left:4px solid} +.rk.r1{background:#FFF0ED;border-color:var(--fr)} +.rk.r2{background:#FFF8E6;border-color:var(--ea)} +.rk.r3{background:#E6F0FF;border-color:var(--wt)} +.rk h4{font-size:14px;margin-bottom:6px} +.rk p.x{font-size:12px;color:#888;margin-bottom:6px;line-height:1.6} +.rk ul{list-style:none;padding:0} +.rk li{font-size:12px;color:#666;padding:2px 0} +.rk li::before{content:'· ';color:var(--pri)} + +/* Suggest */ +.sr{display:flex;gap:8px;margin-bottom:10px} +.si{width:36px;height:36px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0} +.sb{flex:1} +.sb .t{font-size:14px;font-weight:600;margin-bottom:4px} +.sb .i{font-size:12px;color:#666;line-height:1.8} +.sb .r{font-size:11px;color:#aaa;margin-top:2px} +.sg{background:#E6FFE8}.sr2{background:#FFECEC}.sbl{background:#E6F0FF}.so{background:#FFF8E6} + +/* Score */ +.sc{display:flex;align-items:center;gap:10px;padding:8px 0} +.sl{font-size:13px;width:40px;text-align:right} +.sb2{flex:1;height:8px;border-radius:4px;background:#f0ebe5;overflow:hidden} +.sf{height:100%;border-radius:4px;transition:width 0.4s ease} +.sv{font-size:13px;font-weight:600;width:32px} + +/* Quote */ +.qt{background:linear-gradient(135deg,#FFF8F0,#FFECEC);border-radius:12px;padding:16px;text-align:center;margin:10px 0} +.qt .t{font-size:15px;font-weight:600;color:var(--pri);font-style:italic} +.qt .e{font-size:12px;color:#888;margin-top:6px;line-height:1.6} + +/* Organ stars */ +.or{display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid #f5f0eb} +.or:last-child{border-bottom:none} +.on{font-size:13px;width:30px} +.os2{color:var(--gold);font-size:14px;letter-spacing:2px} +.os2 .off{color:#ddd} + +/* Meridian */ +.mer{display:grid;grid-template-columns:1fr 1fr;gap:4px;margin:10px 0} +.mi{font-size:11px;padding:6px 8px;border-radius:8px;background:#fdf8f5;text-align:center} +.mi .t2{font-weight:600;font-size:12px} +.mi .o2{font-size:10px;color:#999;margin-top:1px} + +/* WYLQ table */ +.wlt{width:100%;border-collapse:collapse;font-size:12px;margin:8px 0} +.wlt td{padding:8px 6px;border-bottom:1px solid #f5f0eb} +.wlt td:first-child{color:#999;white-space:nowrap} +.wlt td:last-child{font-weight:500} + +/* Interaction */ +.inter{display:flex;gap:8px;flex-wrap:wrap;margin:8px 0} +.iv{padding:6px 12px;border-radius:8px;font-size:12px;font-weight:500} +.iv.good{background:#E6FFE8;color:#388E3C} +.iv.bad{background:#FFECEC;color:var(--pri)} +.iv.neut{background:#f5f0eb;color:#888} + +.hidden{display:none!important} +.tip{font-size:11px;color:#ccc;text-align:center;margin-top:8px} diff --git a/css/home.css b/css/home.css new file mode 100644 index 0000000..fde7dfe --- /dev/null +++ b/css/home.css @@ -0,0 +1,213 @@ +/* 首页 · 测测式信息流(愈心谷品牌色) */ + +body.app-body[data-nav="home"]{ + background: + radial-gradient(120% 80% at 80% -10%, rgba(255,180,160,.55) 0%, transparent 55%), + radial-gradient(90% 60% at 10% 8%, rgba(255,210,190,.7) 0%, transparent 50%), + linear-gradient(180deg, #FFD6CC 0%, #FFE8E0 28%, #FFF6F2 55%, #FFF9F7 100%); + overflow-x:hidden; +} + +/* ── 品牌顶区 ── */ +.home-brand{ + text-align:center;padding:18px 20px 6px;position:relative;z-index:2; +} +.home-brand img{height:28px;vertical-align:middle} +.home-brand .word{ + margin-top:10px; + font-family:"Noto Serif SC","Songti SC","PingFang SC",serif; + font-size:28px;font-weight:700;letter-spacing:0.18em; + color:#2a2a2a;line-height:1.2; +} +.home-brand .word em{ + font-style:normal;color:var(--pri); +} +.home-brand .tagline{ + margin-top:6px;font-size:12px;color:rgba(0,0,0,.38);letter-spacing:0.12em; +} + +/* 装饰光斑 */ +.home-glow{ + position:absolute;pointer-events:none;border-radius:50%;filter:blur(2px);z-index:0; +} +.home-glow.g1{width:140px;height:140px;top:40px;right:-30px;background:rgba(255,255,255,.45)} +.home-glow.g2{width:90px;height:90px;top:100px;left:-20px;background:rgba(255,200,180,.4)} + +/* ── 愈心解码(顶区主入口) ── */ +.decode-hero{ + margin:14px 16px 0;position:relative;z-index:2; + padding:18px 16px 16px;background:#fff;border-radius:20px; + box-shadow:0 8px 28px rgba(229,77,66,.12); + text-align:center; + animation:bdayIn .5s ease both; +} +.decode-hero .di-title{ + font-family:"Noto Serif SC","Songti SC",serif; + font-size:17px;font-weight:700;color:#C8923A;letter-spacing:0.1em; +} +.decode-hero .di-sub{font-size:12px;color:#aaa;margin:4px 0 14px} +.decode-hero .di-inputs{ + display:flex;gap:6px;align-items:center;justify-content:center;flex-wrap:wrap; +} +.decode-hero input{ + padding:11px 6px;border:1.5px solid #eee;border-radius:12px; + font-size:15px;text-align:center;outline:none;background:#fdfaf8; + transition:border-color .15s; +} +.decode-hero input:focus{border-color:#F0B8B0;background:#fff} +.decode-hero input.dc-y{width:64px} +.decode-hero input.dc-md{width:48px} +.decode-hero .dc-sep{font-size:12px;color:#bbb} +.decode-hero button{ + padding:11px 18px;background:linear-gradient(135deg,#FF7A6E,var(--pri)); + color:#fff;border:none;border-radius:22px;font-size:14px;font-weight:600; + cursor:pointer;box-shadow:0 6px 16px rgba(229,77,66,.28); +} +.decode-hero button:active{opacity:.9} +@keyframes bdayIn{ + from{opacity:0;transform:translateY(8px)} + to{opacity:1;transform:translateY(0)} +} + +/* ── 分类 chips ── */ +.chips{ + display:flex;gap:8px;padding:14px 16px 4px; + overflow-x:auto;-webkit-overflow-scrolling:touch; + scrollbar-width:none;position:relative;z-index:2; +} +.chips::-webkit-scrollbar{display:none} +.chip{ + flex-shrink:0;padding:7px 14px;border-radius:999px; + background:rgba(255,255,255,.72);border:1px solid rgba(255,255,255,.9); + font-size:12px;color:#666;text-decoration:none; + backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px); + transition:background .15s,color .15s; +} +.chip:active,.chip.on{background:#fff;color:var(--pri);font-weight:600;box-shadow:0 2px 8px rgba(0,0,0,.04)} + +/* ── 白色内容面板(测测式上浮 sheet) ── */ +.home-sheet{ + margin-top:14px;background:#fff; + border-radius:22px 22px 0 0; + padding:8px 0 28px; + box-shadow:0 -4px 24px rgba(180,100,80,.06); + position:relative;z-index:3; + animation:sheetUp .55s cubic-bezier(.22,.8,.28,1) both; +} +@keyframes sheetUp{ + from{opacity:0;transform:translateY(24px)} + to{opacity:1;transform:translateY(0)} +} + +.section{padding:0 16px;margin-top:20px} +.sec-head{ + display:flex;justify-content:space-between;align-items:baseline; + margin-bottom:12px; +} +.sec-head .title{ + font-size:17px;font-weight:700;color:#222;letter-spacing:0.02em; +} +.sec-head .more{ + font-size:12px;color:#bbb;text-decoration:none; +} + +/* ── 图标宫格 ── */ +.test-grid{ + display:grid;grid-template-columns:repeat(4,1fr);gap:14px 8px; + padding:4px 2px 2px; +} +.test-item{ + display:flex;flex-direction:column;align-items:center;gap:8px; + text-decoration:none;color:var(--text);position:relative; + transition:transform .15s; +} +.test-item:active{transform:scale(0.92)} +.test-item .icon{ + width:54px;height:54px;border-radius:18px; + display:flex;align-items:center;justify-content:center; + font-size:22px;flex-shrink:0; + box-shadow:inset 0 -2px 0 rgba(0,0,0,.03); +} +.test-item .label{ + font-size:11px;color:#555;text-align:center;line-height:1.25; + max-width:68px;word-break:keep-all; +} +.test-item .badge{ + position:absolute;top:-4px;right:6px; + font-size:9px;background:linear-gradient(135deg,#FF7A6E,var(--pri)); + color:#fff;padding:1px 5px;border-radius:6px;line-height:1.4; + box-shadow:0 2px 6px rgba(229,77,66,.3); +} + +.icon-pink{background:#FFE4E4;color:var(--pri)} +.icon-blue{background:#E3EEFF;color:var(--blue)} +.icon-purple{background:#EDE4FF;color:var(--purple)} +.icon-orange{background:#FFE8D6;color:var(--orange)} +.icon-green{background:#E0F6E4;color:var(--green)} +.icon-rose{background:#FFE0EC;color:var(--pink)} +.icon-teal{background:#DDF6EF;color:#2EA88A} +.icon-gold{background:#FFF3D6;color:#C8923A} +.icon-night{background:#E8E2F4;color:#6B5B95} +.icon-mute{background:#F3F3F3;color:#aaa} + +/* ── 热门信息流卡片(测测 feed) ── */ +.feed{display:flex;flex-direction:column;gap:12px} +.feed-card{ + display:flex;gap:14px;align-items:stretch; + padding:12px;background:#FAFAFA;border-radius:16px; + text-decoration:none;color:var(--text); + transition:transform .12s, background .12s; + position:relative;overflow:hidden; +} +.feed-card:active{transform:scale(0.985);background:#F5F5F5} +.feed-cover{ + width:72px;height:72px;border-radius:14px;flex-shrink:0; + display:flex;align-items:center;justify-content:center; + font-size:28px;position:relative;overflow:hidden; +} +.feed-cover::after{ + content:"";position:absolute;inset:0; + background:linear-gradient(160deg,rgba(255,255,255,.35),transparent 50%); +} +.feed-body{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;padding:2px 0} +.feed-body .name{font-size:15px;font-weight:650;color:#222;line-height:1.35} +.feed-body .meta{font-size:12px;color:#999;margin-top:4px;line-height:1.4} +.feed-body .stat{font-size:11px;color:#c4c4c4;margin-top:6px} +.feed-tag{ + position:absolute;top:10px;right:10px; + font-size:10px;color:#fff;background:var(--pri); + padding:2px 7px;border-radius:8px; +} + +.fc-a .feed-cover{background:linear-gradient(145deg,#FFD0C8,#FFB0A4);color:#C23B32} +.fc-b .feed-cover{background:linear-gradient(145deg,#E4D8F8,#C9B6F0);color:#5B4A8A} +.fc-c .feed-cover{background:linear-gradient(145deg,#D6E8FF,#B0D0F5);color:#3A6FB0} +.fc-d .feed-cover{background:linear-gradient(145deg,#D8F0DC,#B5E0BE);color:#3A8A4A} +.fc-e .feed-cover{background:linear-gradient(145deg,#FFE6C8,#F5C98A);color:#B07A28} + +/* ── 快捷入口横滑 ── */ +.scroll-row{ + display:flex;gap:10px;overflow-x:auto;padding:2px 0 4px; + -webkit-overflow-scrolling:touch;scrollbar-width:none; +} +.scroll-row::-webkit-scrollbar{display:none} +.mini-card{ + flex:0 0 148px;padding:14px 14px 16px;border-radius:16px; + text-decoration:none;color:#333;position:relative;overflow:hidden; + transition:transform .12s; +} +.mini-card:active{transform:scale(0.97)} +.mini-card .mi{font-size:22px;margin-bottom:8px} +.mini-card .mt{font-size:14px;font-weight:650} +.mini-card .md{font-size:11px;color:rgba(0,0,0,.4);margin-top:3px;line-height:1.35} +.mc-coral{background:linear-gradient(160deg,#FFE8E2,#FFD4CA)} +.mc-mint{background:linear-gradient(160deg,#E4F6EA,#D0EDD8)} +.mc-sky{background:linear-gradient(160deg,#E6F0FF,#D4E4FF)} +.mc-sand{background:linear-gradient(160deg,#FFF3E0,#FFE4C2)} + +@media(max-width:380px){ + .home-brand .word{font-size:24px} + .test-item .icon{width:48px;height:48px;font-size:20px;border-radius:16px} + .test-item .label{font-size:10px} + .feed-cover{width:64px;height:64px} +} diff --git a/css/hub.css b/css/hub.css new file mode 100644 index 0000000..04b4ea5 --- /dev/null +++ b/css/hub.css @@ -0,0 +1,26 @@ +/* 一级聚合页:认识自己 / 私域活动 / 我的 */ +.hub-list{display:flex;flex-direction:column;gap:10px;padding:0 16px 20px} +.hub-item{ + display:flex;align-items:center;gap:14px;padding:16px; + background:#fff;border-radius:14px;text-decoration:none;color:var(--text); + box-shadow:0 2px 10px rgba(0,0,0,0.04);transition:transform .12s; +} +.hub-item:active{transform:scale(0.98)} +.hub-item .hi{ + width:48px;height:48px;border-radius:12px; + display:flex;align-items:center;justify-content:center; + font-size:22px;flex-shrink:0; +} +.hub-item .ht{font-size:16px;font-weight:600} +.hub-item .hd{font-size:12px;color:var(--sub);margin-top:3px} +.hub-item .ha{margin-left:auto;color:#ccc;font-size:18px} +.hi-gold{background:#FFF8E6;color:#C8923A} +.hi-blue{background:#E6F0FF;color:#4A90E2} +.hi-purple{background:#F0E6FF;color:#8B5CF6} +.hi-green{background:#E6FFE8;color:#5CB85C} +.hi-rose{background:#FFECEC;color:#E54D42} +.hub-empty{ + margin:24px 16px;padding:28px 20px;text-align:center; + background:#fff;border-radius:16px;color:var(--sub);font-size:14px;line-height:1.7; + box-shadow:0 2px 10px rgba(0,0,0,0.04); +} diff --git a/css/mindmap.css b/css/mindmap.css new file mode 100644 index 0000000..2bb9a25 --- /dev/null +++ b/css/mindmap.css @@ -0,0 +1,107 @@ +:root{--pri:#E54D42;--hero-start:#FF9F80;--bg-start:#FFD1C7;--bg-end:#FFC8B5} +*{box-sizing:border-box;margin:0;padding:0} +/* legacy body → scoped */ +.app-page{ + font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif; + background:linear-gradient(180deg,var(--bg-start),var(--bg-end) 50%,#fff5f0); + height:100vh;display:flex;overflow:hidden; +} + +/* ── Sidebar ── */ +.sidebar{ + width:200px;background:#fff;border-right:1px solid #f0e0d8;display:flex;flex-direction:column; + flex-shrink:0;box-shadow:2px 0 8px rgba(0,0,0,0.03);z-index:10; +} +.sidebar .sh{ + padding:16px 14px 10px;font-size:15px;font-weight:600;color:#333; + border-bottom:1px solid #f5f0eb; +} +.sidebar .slist{flex:1;overflow-y:auto;padding:8px 0} +.sidebar .slist .item{ + padding:10px 14px;cursor:pointer;font-size:13px;color:#555; + display:flex;align-items:center;justify-content:space-between; + border-left:3px solid transparent;transition:background 0.1s; +} +.sidebar .slist .item:hover{background:#fdf8f5} +.sidebar .slist .item.active{background:#fff0ed;border-left-color:var(--pri);color:var(--pri);font-weight:600} +.sidebar .slist .item .del{font-size:11px;color:#ccc;visibility:hidden;cursor:pointer} +.sidebar .slist .item:hover .del{visibility:visible} +.sidebar .slist .item .del:hover{color:var(--pri)} +.sidebar .sb{display:flex;gap:6px;padding:10px 12px;border-top:1px solid #f5f0eb} +.sidebar .sb button{ + flex:1;padding:8px 0;border:1px solid #ddd;border-radius:6px; + background:#fff;font-size:12px;cursor:pointer;color:#555; +} +.sidebar .sb button.primary{background:var(--pri);color:#fff;border-color:var(--pri)} +.sidebar .sb button:active{opacity:0.8} + +/* ── Main ── */ +.main{flex:1;display:flex;flex-direction:column;overflow:hidden} +.toolbar{ + display:flex;align-items:center;gap:6px;padding:8px 12px; + background:rgba(255,255,255,0.9);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px); + border-bottom:1px solid #f0e0d8;flex-shrink:0;flex-wrap:wrap;z-index:10; +} +.toolbar button{ + padding:6px 12px;border:1px solid #ddd;border-radius:6px; + background:#fff;font-size:13px;cursor:pointer;color:#555; +} +.toolbar button:active{background:#f0ebe5} +.toolbar button.primary{background:var(--pri);color:#fff;border-color:var(--pri)} +.toolbar .sep{width:1px;height:20px;background:#ddd;margin:0 2px} +.toolbar .hint{font-size:11px;color:#aaa;margin-left:auto} +canvas{flex:1;display:block;cursor:grab} +canvas:active{cursor:grabbing} +#editor{position:absolute;display:none;z-index:20} +#editor input{ + padding:4px 8px;border:2px solid var(--pri);border-radius:4px; + font-size:13px;outline:none;min-width:80px;background:#fff; +} +/* Plus buttons */ +#plusChild,#plusSibling{ + position:absolute;display:none;z-index:25; + width:20px;height:20px;border-radius:50%; + color:#fff;font-size:14px;font-weight:bold;line-height:20px; + text-align:center;cursor:pointer;user-select:none; + pointer-events:auto; +} + +/* Mobile */ +@media(max-width:600px){ + .sidebar{ + position:fixed;left:0;top:0;bottom:0;width:240px;z-index:30; + transform:translateX(-100%);transition:transform 0.25s ease; + box-shadow:4px 0 20px rgba(0,0,0,0.1); + } + .sidebar.open{transform:translateX(0)} + .sidebar .sh{font-size:16px;padding:18px 16px 12px} + .sidebar .slist .item{font-size:14px;padding:12px 16px} + .sidebar .sb button{padding:10px 0;font-size:13px} + /* hamburger */ + .hamburger{ + display:flex!important;position:fixed;top:8px;left:8px;z-index:40; + width:36px;height:36px;align-items:center;justify-content:center; + background:rgba(255,255,255,0.9);border-radius:8px; + border:1px solid #e0d5cf;font-size:18px;cursor:pointer; + -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px); + } + /* overlay backdrop */ + .overlay{ + display:none;position:fixed;inset:0;z-index:25; + background:rgba(0,0,0,0.25); + } + .overlay.show{display:block} + /* toolbar compact */ + .toolbar{gap:3px;padding:6px 6px 6px 50px;min-height:42px} + .toolbar button{padding:6px 10px;font-size:12px;border-radius:8px} + .toolbar .sep{margin:0 1px} + .toolbar .hint{font-size:10px;display:none} + /* canvas */ + canvas{flex:1} + /* editor bigger for touch */ + #editor input{font-size:16px!important;padding:8px 12px;min-width:100px;border-radius:6px} + /* node hit area larger on mobile */ + .mobile .toolbar button{min-height:32px} +} +/* Desktop: hide hamburger */ +.hamburger{display:none} diff --git a/css/scale-test.css b/css/scale-test.css new file mode 100644 index 0000000..7534fca --- /dev/null +++ b/css/scale-test.css @@ -0,0 +1,58 @@ +:root{--pri:#E54D42;--bg-start:#FFD1C7;--bg-end:#FFC8B5} +*{box-sizing:border-box;margin:0;padding:0} +/* legacy body → scoped */ +.app-page{ + font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif; + background:linear-gradient(180deg,var(--bg-start) 0%,var(--bg-end) 50%,#fff5f0 100%); + min-height:100vh;-webkit-font-smoothing:antialiased; + display:flex;flex-direction:column; +} + +.top-bar{display:flex;align-items:center;padding:14px 16px;gap:12px;max-width:500px;margin:0 auto} +.top-bar .back{font-size:22px;text-decoration:none;color:#666;line-height:1} +.top-bar .title{flex:1;font-size:16px;font-weight:600;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:center} +.top-bar .qnum{font-size:13px;color:#999} +.progress{height:3px;background:rgba(0,0,0,0.08);max-width:500px;margin:0 auto} +.progress .bar{height:100%;background:var(--pri);transition:width 0.3s} + +.main{flex:1;display:flex;flex-direction:column;padding:20px;max-width:500px;width:100%;margin:0 auto} + +.qcard{background:#fff;border-radius:14px;padding:28px 22px;box-shadow:0 2px 10px rgba(0,0,0,0.04);flex:1} +.qcard .qtext{font-size:17px;color:#333;line-height:1.7;margin-bottom:24px} +.qcard .qtext .qidx{color:var(--pri);font-weight:600;margin-right:6px} + +.options{display:flex;flex-direction:column;gap:10px} +.option{ + padding:14px 18px;border-radius:10px;border:1.5px solid #eee; + font-size:15px;color:#444;cursor:pointer;transition:all 0.15s;text-align:left;background:#fafafa; +} +.option:hover{border-color:var(--pri);background:#fff5f3} +.option.selected{border-color:var(--pri);background:#fff0ed;color:var(--pri);font-weight:500} + +.btns{display:flex;gap:12px;padding:16px 0 30px;max-width:500px;width:100%;margin:0 auto} +.btns button{ + flex:1;padding:14px;border-radius:10px;border:none;font-size:15px;cursor:pointer; + font-weight:500;transition:all 0.15s; +} +.btn-prev{background:#f0f0f0;color:#666} +.btn-next{background:var(--pri);color:#fff} +.btn-submit{background:#4CAF50;color:#fff} +button:disabled{opacity:0.4;cursor:not-allowed} + +.submit-area{text-align:center;padding:40px 20px} +.submit-area p{font-size:14px;color:#888;margin-bottom:20px} +.submit-area button{ + padding:14px 40px;border-radius:10px;background:var(--pri);color:#fff; + border:none;font-size:16px;cursor:pointer;font-weight:500; +} + +.result-card{background:#fff;border-radius:14px;padding:32px 22px;box-shadow:0 2px 10px rgba(0,0,0,0.04);text-align:center;max-width:500px;width:100%;margin:0 auto} +.result-card .score{font-size:48px;font-weight:700;color:var(--pri);margin:12px 0} +.result-card .level{font-size:18px;color:#666;margin-bottom:20px} +.result-card .detail{font-size:14px;color:#999;line-height:1.6;text-align:left} +.result-card .back-btn{ + display:inline-block;margin-top:20px;padding:12px 30px;background:var(--pri);color:#fff; + border-radius:10px;text-decoration:none;font-size:15px; +} + +.loading{text-align:center;padding:80px 20px;color:#999} diff --git a/css/scales.css b/css/scales.css new file mode 100644 index 0000000..9adaaab --- /dev/null +++ b/css/scales.css @@ -0,0 +1,48 @@ +:root{--pri:#E54D42;--bg-start:#FFD1C7;--bg-end:#FFC8B5} +*{box-sizing:border-box;margin:0;padding:0} +/* legacy body → scoped */ +.app-page{ + font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif; + background:linear-gradient(180deg,var(--bg-start) 0%,var(--bg-end) 50%,#fff5f0 100%); + min-height:100vh;-webkit-font-smoothing:antialiased; +} +.header{padding:24px 20px 16px;text-align:center} +.logo img{height:36px;margin-bottom:4px} +.header .sub{font-size:13px;color:rgba(0,0,0,0.45);margin-top:4px} + +.categories{display:flex;flex-wrap:wrap;gap:8px;padding:0 20px 12px;justify-content:center;max-width:600px;margin:0 auto} +.cat-btn{ + padding:7px 16px;border-radius:20px;border:1px solid rgba(0,0,0,0.12); + background:#fff;font-size:13px;color:#555;cursor:pointer; + transition:all 0.2s;white-space:nowrap; +} +.cat-btn.active{background:var(--pri);color:#fff;border-color:var(--pri)} + +.scales{max-width:600px;margin:0 auto;padding:0 20px 40px} +.scale-card{ + display:flex;align-items:center;gap:12px;padding:16px; + background:#fff;border-radius:12px;margin-bottom:10px; + box-shadow:0 1px 6px rgba(0,0,0,0.05);cursor:pointer; + transition:transform 0.12s;text-decoration:none;color:inherit; +} +.scale-card:active{transform:scale(0.98)} +.scale-card .icon{width:44px;height:44px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0} +.scale-card .icon.cat-综合{background:#FFECEC;color:var(--pri)} +.scale-card .icon.cat-人格{background:#E6F0FF;color:#4A90E2} +.scale-card .icon.cat-抑郁{background:#E6E0FF;color:#7C5CF2} +.scale-card .icon.cat-焦虑{background:#FFF0E6;color:#E8985A} +.scale-card .icon.cat-强迫{background:#E6FFE8;color:#5CB85C} +.scale-card .icon.cat-躁狂{background:#FFF8E6;color:#E8985A} +.scale-card .icon.cat-双相{background:#FFE6F0;color:#E0508C} +.scale-card .icon.cat-恐惧{background:#E6F5FF;color:#4A90E2} +.scale-card .icon.cat-智商{background:#F3E8FF;color:#8B5CF6} +.scale-card .icon.cat-情商{background:#FFF5EC;color:#C8923A} +.scale-card .icon.cat-其他{background:#F0F0F0;color:#888} +.scale-card .info{flex:1;min-width:0} +.scale-card .info .name{font-size:15px;font-weight:600;color:#333} +.scale-card .info .desc{font-size:12px;color:#999;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.scale-card .count{font-size:12px;color:#aaa;flex-shrink:0} + +.loading{text-align:center;padding:60px 20px;color:#999} +.empty{text-align:center;padding:40px 20px;color:#ccc;font-size:14px} +.back{display:inline-block;padding:8px 16px;color:var(--pri);text-decoration:none;font-size:14px;margin:8px 0 0 20px} diff --git a/css/shuzi.css b/css/shuzi.css new file mode 100644 index 0000000..97e0b25 --- /dev/null +++ b/css/shuzi.css @@ -0,0 +1,78 @@ +@font-face { font-family:'TriangleDigits'; src:url(data:font/woff2;base64,d09GMgABAAAAAAUAAA4AAAAACHwAAASsAAIZmQAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbbBxIBmAANBEQCocshhEBNgIkAxoLGAAEIAWDIgcgG9QGKC4G7Ib6hm2MRajDYhGiDhrol2EQYF1VSXrYswJPkJb83g+eA7l2QeKDmZzBKY+S2Y6GNNvdREZSoL/sv5IIQxUu06If+FX4/z+X49/WsztrTAw5kRt099ubOm5NXEAFHuBcGvAGSjDAgLDn2bZk6fIVBBISV5n1GLNAADYApcVbCydXozYtkTdtbu8OvEOS0PEgPgIdEQfiY0OwiSCOBmAPsl4AYswwdCJQA1FoSBQqZkProGuMIFPAPhuoa8oSVYX8YAQSAZpDCmDZcIkm/ZAjI0LR3A1KzY56XmlDxUYMYNhtcBARzR51kKE7KkxKozJobFeXdNJlKY3czMCYMrUMa/83F2mmEHEHkFJnDXn9YBwBCSjAHYUA3NEIBjQ0BFDLHki2t3+KqVZYkbggAFQ26ho8RWcQ3tl/htql/UbHN70BAoXUbXpAnK4F+eoWL5vNxzpnKT0L1CrUld9zyZWVebLzdRVOGASMYohloBiiuziLHrm5uQI5VmbV7EqrcnVlszArq2tWV6c5C0+s7O5L1+3WqS7Q2737TCeTufqRH9o1O3uOtCpHMUc36sxupMOcNaesYEbpnGdwwlHmrs8VKBTmstgNaLvfBrx5PQ/wj1yzJd01+3LOm+/CzOY4zhHWtXYr5PbIyREYwJnHeFm4pXu2IcxByHzZVWJ3Er3bMQkX9snibM6Y1aTyc3rkXNUJPXpz8SR3SFeH84FFWYV9bzibJvHlL0bRde6KYc9cmiIKk5+FN+NThqtvc8aBGfnF6WzIqLk1ON06sNXy3bLFuIJ8DQT/QGhPY9JrsRk3RddPID33rzz9cLGnXZjvNNQR4659v7m7f0qW1qex/MqwciHDfdsqBnOLCispr6nWU0/DmAXZBdjkTA0M/4LXawb7VDhbjduWNPaWsM25jxyH1ENRUmejPPOk33dOKZ+v4BRLNZQeriawC/Nzxb0nr7/lPdzfGvGf3kgW0N6/tXYq1Si9RA/v58kV3DTY5DgaxwV1Uw5++lzxouLrgNYX82rdHtlWXSn61Di0UAxfhc2HeamcDnfrnVra3xecFNc61xtZhSxqUlFZFQ62rTQ+w3YS4S9Z4bNHHUB7y/fxoxt9pVAyqdOZ66cPKs7Oz8sYj5f2lIpvQPQfX3t4++1V80/rnFtRkIspHBjC5WXnpReTvuTiMVkJsZFp5SVhxARkdDKQhx19XV78vjqr1UAXlvCfnhUiz41CK/6P8dI6eIab2nlH17ap48+X3P/67iJUpWdlq6vNFAQABCB9dmU3fPxwgFPdb2YhMwAvzT++ByhOmZz+3/9vizVMBYAZOVthCvnfDK9OI6rfpSMQ1O8ljbVAg/Zm6S2UNDN4K6A9UySALjrjrgTjPKoUNdG7bdIswFkQgyCYswaJI28NikwKujXQYMBXOC8yEihiaSpGlAKsZbII4YSJENhVwgyBgJQEXSDaUphohpCxTCWZFJKhBY+AiHgRfBMjhobUGOI+OYREilhp4gmpjs63SFuCvHUricIo0nYoVERiSqyLCF+MnFdHwYlnbI0JjLBYonBsPGESCHYCTx5NXJ+7DhJpNTCDOrOPjGrLzlAAAAA) format('woff2'); } + * { box-sizing: border-box; margin: 0; padding: 0; } + /* legacy body → scoped */ +.app-page{ background:linear-gradient(160deg,#FFD1C7,#FFC8B5); font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif; display:flex; flex-direction:column; justify-content:center; align-items:center; min-height:100vh; color:#333; padding:10px 10px 20px; -webkit-user-select:none; user-select:none; -webkit-touch-callout:none; } + .container { position:relative; width:600px; max-width:95%; padding:12px 40px 40px; display:flex; flex-direction:column; align-items:center; background:#fff; border-radius:18px; box-shadow:0 8px 30px rgba(0,0,0,.08); margin-bottom:30px; } + .input-row { display:flex; gap:5px; align-items:center; margin-bottom:24px; font-family:sans-serif; } + .input-row input { width:56px; padding:6px 4px; border:1px solid #d9ccb6; border-radius:3px; font-size:14px; text-align:center; color:#6b5b4e; background:#fff; outline:none; } + .input-row input:focus { border-color:#a89580; } + .input-row .lbl { font-size:0.75em; color:#9b8a78; } + .btn-gen { padding:6px 12px; background:#8b7a68; color:#fff; border:none; border-radius:3px; font-size:0.75em; cursor:pointer; margin-left:2px; } + .btn-gen:active { opacity:0.8; } + .top-numbers { display:flex; flex-direction:column; align-items:center; margin-bottom:-20px; z-index:2; font-family:'TriangleDigits','Georgia','Times New Roman',serif; } + .top-numbers .num-top { font-size:2.2rem; font-weight:normal; } + .top-numbers .num-mid-row { display:flex; gap:60px; font-size:2.2rem; font-weight:normal; } + .triangle-wrapper { position:relative; width:300px; height:260px; margin:20px 0; display:flex; justify-content:center; align-items:center; overflow:visible; } + .triangle-svg { position:absolute; top:0; left:0; width:100%; height:100%; z-index:0; } + .triangle-path { fill:#f2ebdd; stroke:#d9ccb6; stroke-width:2; } + .divider-line { stroke:#d9ccb6; stroke-width:2; } + .inner-number { position:absolute; z-index:1; font-size:2.2rem; font-weight:bold; cursor:pointer; font-family:'TriangleDigits','Georgia','Times New Roman',serif; color:#5a4636; } + .red-num { color:#c47a78; top:25%; } + .mid-left { top:55%; left:30%; } .mid-right { top:55%; right:30%; } + .bot-1 { bottom:10%; left:18%; } .bot-2 { bottom:10%; left:38%; } + .bot-3 { bottom:10%; right:38%; } .bot-4 { bottom:10%; right:18%; } + .side-decorations { position:absolute; width:100%; height:100%; top:0; left:0; font-family:'TriangleDigits','Georgia','Times New Roman',serif; } + .side-num { position:absolute; font-size:2.2rem; opacity:0.65; cursor:pointer; font-family:'TriangleDigits','Georgia','Times New Roman',serif; font-weight:normal; color:#8b7e6e; } + .left-group { top:30%; left:-40px; text-align:right; line-height:1.5; } + .right-group { top:30%; right:-40px; text-align:left; line-height:1.5; } + .footer-date { margin-top:0; display:flex; gap:40px; font-size:1.8rem; letter-spacing:2px; color:#5a4636; font-family:'TriangleDigits','Georgia','Times New Roman',serif; } + .sections { width:100%; max-width:360px; margin-top:24px; font-family:sans-serif; font-size:1em; color:#6b5b4e; } + .divider { border:none; border-top:1px solid #e0d5c4; margin:12px 0; opacity:0.6; } + .codes .r { display:flex; justify-content:space-between; border-bottom:1px dotted rgba(168,144,112,0.15); padding:2px 4px; } + .codes .k { color:#9b8a78; } .codes .v { color:#6b5b4e; } + .joint-age { font-size:0.9em; color:#a89070; font-weight:600; text-align:center; margin:8px 0 2px; border-bottom:1px solid rgba(168,144,112,0.2); padding-bottom:2px; } + .joint-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:2px 10px; } + .joint-grid .jc { font-weight:700; color:#5a4636; } .joint-grid .jm { color:#9b8a78; } + .tooltip { position:fixed; padding:5px 10px; background:#6b5b4e; color:#fff; border-radius:4px; font-size:0.7em; pointer-events:none; opacity:0; transition:opacity 0.12s; z-index:999; max-width:200px; line-height:1.5; font-family:sans-serif; } + .tooltip.on { opacity:1; } + .footer { text-align:center; font-size:0.6em; color:#9b8a78; margin-top:10px; } + /* 付费墙 */ + .shuzi-lock { position:relative; border-radius:10px; overflow:hidden; border:1px solid #f2e6d0; } + .shuzi-lock > .blur { filter:blur(10px); opacity:0.35; pointer-events:none; user-select:none; } + .shuzi-lock .cover { position:absolute; inset:0; z-index:2; background:linear-gradient(180deg, rgba(250,247,242,.5), rgba(250,247,242,.95)); display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; padding:20px; } + .shuzi-lock .cover .lk-t { font-size:1.05em; font-weight:700; color:#c47a78; } + .shuzi-lock .cover .lk-d { font-size:0.75em; color:#9b8a78; margin:8px 0 14px; } + .shuzi-lock .cover .lk-btn { background:#c47a78; color:#fff; border:none; padding:10px 28px; border-radius:20px; font-size:0.85em; font-weight:600; cursor:pointer; } + .shuzi-lock.unlocked .cover { display:none; } + .shuzi-lock.unlocked .blur { filter:none; pointer-events:auto; user-select:auto; } + .scene-3d { position:relative; display:flex; justify-content:center; margin:8px 0; cursor:grab; } + .scene-3d:active { cursor:grabbing; } + .scene-3d canvas { display:block; touch-action:none; } + .scene-hint { position:absolute; bottom:2px; left:50%; transform:translateX(-50%); font-size:0.55em; color:#b8a898; pointer-events:none; opacity:0.7; } + .freq-label { text-align:center; font-size:0.7em; color:#6b5b4e; margin-top:2px; line-height:1.8; } + .tabs-nav { display:flex; flex-wrap:wrap; gap:4px; margin-bottom:18px; justify-content:center; padding:0 4px; } + .tab-btn { padding:5px 10px; border:1px solid #d9ccb6; border-radius:3px; background:#fff; color:#9b8a78; font-size:0.7em; cursor:pointer; font-family:sans-serif; transition:all 0.15s; white-space:nowrap; } + .tab-btn:hover { border-color:#c47a78; color:#c47a78; } + .tab-btn.active { background:#c47a78; color:#fff; border-color:#c47a78; } + .tab-panel { display:none; } + .tab-panel.active { display:block; } + .tab-panel .module-title { font-size:0.82em; font-weight:600; color:#5a4636; text-align:center; margin-bottom:8px; } + .tab-panel .module-desc { font-size:0.68em; color:#9b8a78; text-align:center; margin-bottom:10px; line-height:1.6; } + .tab-panel .module-section { font-size:0.72em; color:#6b5b4e; line-height:1.75; padding:0 4px; margin-bottom:10px; } + .tab-panel .module-section b { color:#5a4636; } + .bagua-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:6px; text-align:center; margin:8px 0; } + .bagua-cell { padding:6px 4px; border-radius:4px; font-size:0.68em; line-height:1.5; } + @media (max-width:600px) { + .triangle-wrapper { width:220px; height:190px; margin:10px auto 0; } + .triangle-wrapper svg { width:220px; height:190px; } + .inner-number { font-size:1.6rem; } + .red-num { top:22%; } + .mid-left { top:52%; left:28%; } .mid-right { top:52%; right:28%; } + .bot-1 { bottom:10%; left:14%; } .bot-2 { bottom:10%; left:35%; } + .bot-3 { bottom:10%; right:35%; } .bot-4 { bottom:10%; right:14%; } + .side-num { font-size:1.6rem; } + .left-group { left:-32px; top:26%; } .right-group { right:-32px; top:26%; } + .top-numbers .num-top { font-size:1.6rem; } + .top-numbers .num-mid-row { font-size:1.6rem; gap:40px; } + .footer-date { gap:14px; font-size:1.2rem; letter-spacing:0; } + } diff --git a/css/xingjing.css b/css/xingjing.css new file mode 100644 index 0000000..e4d87b1 --- /dev/null +++ b/css/xingjing.css @@ -0,0 +1,221 @@ +html{overflow-y:auto;-webkit-overflow-scrolling:touch} +:root{ + --pri:#E54D42; --blue:#4A90E2; --teal:#0D9488; --gold:#C8923A; + --text:#333; --sub:#999; --bg-start:#FFD1C7; --bg-end:#FFC8B5; --dark:#1a1a2e; +} +*{box-sizing:border-box;margin:0;padding:0} +/* legacy body → scoped */ +.app-page{ + font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei","Helvetica Neue",sans-serif; + background:linear-gradient(180deg,var(--bg-start) 0%,var(--bg-end) 40%,#fff5f0 100%); + color:var(--text);max-width:430px;margin:0 auto;min-height:100vh; + -webkit-font-smoothing:antialiased;overflow-x:hidden; + overflow-y:auto;-webkit-overflow-scrolling:touch; +} +.topbar{ + display:flex;align-items:center;justify-content:center;gap:8px; + padding:10px 16px 6px;position:sticky;top:0;z-index:20; + background:linear-gradient(180deg,var(--bg-start) 0%,rgba(255,209,199,0.7) 100%); + -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px); +} +.topbar .tbt{font-size:15px;font-weight:700;color:var(--text);flex:1;text-align:center} +.topbar .back-btn{ + width:32px;height:32px;border-radius:50%;border:none;background:rgba(255,255,255,0.6); + font-size:16px;cursor:pointer;display:none;align-items:center;justify-content:center; + flex-shrink:0;color:var(--text); +} +.topbar .back-btn.show{display:flex} +.topbar img{height:22px;flex-shrink:0} + +.page{display:none;padding:0 16px 85px;animation:fadeIn 0.25s} +.page.active{display:block} +@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}} + +.nav{ + position:fixed;bottom:0;left:50%;transform:translateX(-50%); + width:100%;max-width:430px;background:#fff;display:flex;justify-content:space-around; + padding:4px 0 calc(6px + env(safe-area-inset-bottom,0)); + border-top:1px solid #f0f0f0;z-index:100;box-shadow:0 -2px 8px rgba(0,0,0,0.03); +} +.nav-item{display:flex;flex-direction:column;align-items:center;gap:1px;font-size:9px;color:var(--sub);cursor:pointer;line-height:1;transition:color 0.15s} +.nav-item .ni{font-size:17px} +.nav-item.active{color:var(--pri)} + +.card{background:#fff;border-radius:14px;padding:16px;margin-bottom:10px;box-shadow:0 2px 8px rgba(0,0,0,0.04)} +.card-sm{padding:12px;border-radius:12px} +.card-title{font-size:15px;font-weight:700;margin-bottom:4px} +.card-sub{font-size:11px;color:var(--sub);line-height:1.5} + +.hero-card{ + background:linear-gradient(135deg,var(--dark),#16213e,#0f3460); + border-radius:18px;padding:24px 18px 20px;margin-bottom:12px;color:#fff;position:relative;overflow:hidden; +} +.hero-card .h-stars{position:absolute;inset:0;pointer-events:none} +.hero-card .h-stars span{position:absolute;background:#fff;border-radius:50%;animation:twinkle 3s ease-in-out infinite} +.hero-card .h-stars span:nth-child(1){width:3px;height:3px;top:12%;left:20%} +.hero-card .h-stars span:nth-child(2){width:2px;height:2px;top:25%;right:22%} +.hero-card .h-stars span:nth-child(3){width:3px;height:3px;top:50%;left:65%} +.hero-card .h-stars span:nth-child(4){width:2px;height:2px;bottom:30%;left:15%} +.hero-card .h-stars span:nth-child(5){width:3px;height:3px;bottom:20%;right:30%} +@keyframes twinkle{0%,100%{opacity:0.2}50%{opacity:1}} +.hero-card .hzodiac{margin-bottom:4px} +.hero-card .hlabel{font-size:11px;opacity:0.6;margin-bottom:2px} +/* Dunhuang zodiac badge */ +.zodiac-dh{display:inline-flex;align-items:center;justify-content:center;width:48px;height:48px;border-radius:50%;background:#F5E6D3;border:2px solid;font-size:22px;font-weight:700;line-height:1;box-shadow:0 2px 8px rgba(0,0,0,0.1);flex-shrink:0} +.hero-card .zodiac-dh{width:56px;height:56px;font-size:26px;border-width:2.5px;margin:0 auto} +.result-hero .zodiac-dh{width:64px;height:64px;font-size:30px;border-width:3px;margin:0 auto} +.zodiac-dh.fire{border-color:#C04030;color:#8B2020;background:#FDF2EC} +.zodiac-dh.earth{border-color:#A0855B;color:#6B4F3A;background:#F8F2EA} +.zodiac-dh.air{border-color:#5B8FA8;color:#3A6070;background:#F0F5F8} +.zodiac-dh.water{border-color:#6B5B95;color:#4A3570;background:#F2F0F8} +#pAvatar .zodiac-dh{width:56px;height:56px;font-size:26px;box-shadow:none} +.hist-item .zodiac-dh{width:40px;height:40px;font-size:18px} +.hero-card .hsign{font-size:24px;font-weight:800} +.hero-card .hdate{font-size:11px;opacity:0.5;margin-top:2px} +.hero-card .haction{display:inline-block;margin-top:12px;padding:8px 20px;background:rgba(255,255,255,0.15);color:#fff;border-radius:20px;font-size:13px;font-weight:600;cursor:pointer;transition:transform 0.15s} +.hero-card .haction:active{transform:scale(0.95)} + +.input-row{display:flex;gap:6px;align-items:center;margin-bottom:8px;flex-wrap:wrap} +.input-row input,.input-row select{padding:10px 8px;border:1.5px solid #e8e0d8;border-radius:10px;font-size:14px;outline:none;background:#fdfaf7;text-align:center;flex:1;min-width:0} +.input-row input:focus,.input-row select:focus{border-color:var(--pri)} +.input-row .lbl{font-size:12px;color:var(--sub);white-space:nowrap} + +.btn{display:inline-block;padding:10px 20px;border:none;border-radius:22px;font-size:13px;font-weight:600;cursor:pointer;transition:transform 0.15s} +.btn:active{transform:scale(0.96)} +.btn-pri{background:var(--pri);color:#fff;box-shadow:0 3px 10px rgba(229,77,66,.2)} +.btn-outline{background:#fff;color:var(--pri);border:1.5px solid var(--pri)} +.btn-sm{padding:6px 14px;font-size:11px} +.btn-block{display:block;width:100%;text-align:center} + +.quiz-q{margin-bottom:14px} +.quiz-q .q-num{font-size:10px;color:var(--sub);margin-bottom:2px} +.quiz-q .q-text{font-size:15px;font-weight:600;margin-bottom:8px;line-height:1.4} +.quiz-opt{display:block;width:100%;padding:12px 14px;margin-bottom:6px;background:#fafafa;border:1.5px solid #eee;border-radius:12px;font-size:13px;text-align:left;cursor:pointer;transition:all 0.15s;line-height:1.4} +.quiz-opt:active,.quiz-opt.sel{background:#FFF5F0;border-color:var(--pri);color:var(--pri)} + +.result-hero{background:linear-gradient(135deg,var(--dark),#16213e,#0f3460);border-radius:18px;padding:28px 20px;text-align:center;color:#fff;margin-bottom:12px} +.result-hero .rzodiac{margin-bottom:6px} +.result-hero .rsign{font-size:22px;font-weight:800;margin-bottom:2px} +.result-hero .rmbti{font-size:13px;opacity:0.7;margin-bottom:8px} +.result-tag{display:inline-block;padding:3px 10px;border-radius:20px;font-size:10px;margin:2px;background:rgba(255,255,255,0.12);color:rgba(255,255,255,0.85)} + +.elem-bar{display:flex;align-items:center;gap:6px;margin-bottom:4px;font-size:11px} +.elem-bar .el{width:40px;text-align:right;color:var(--sub)} +.elem-bar .eb{flex:1;height:8px;border-radius:4px;background:#eee;overflow:hidden} +.elem-bar .ebf{height:100%;border-radius:4px;transition:width 0.6s} +.elem-bar .ev{width:28px;text-align:right;font-weight:600;font-size:10px} + +.dim-row{display:flex;align-items:center;gap:8px;margin-bottom:6px;font-size:11px} +.dim-row .dl{width:24px;text-align:right;color:var(--sub);font-weight:600} +.dim-row .dbar{flex:1;height:18px;background:#f5f5f5;border-radius:9px;position:relative;overflow:hidden} +.dim-row .dfill{height:100%;border-radius:9px;transition:width 0.8s;display:flex;align-items:center;padding-left:8px;color:#fff;font-size:9px;font-weight:600} +.dim-row .dr{width:24px;font-weight:600;font-size:10px} + +#chartCanvas{display:block;width:100%;border-radius:16px;background:var(--dark);cursor:grab} +#chartCanvas:active{cursor:grabbing} +.chart-info{text-align:center;padding:10px;font-size:12px;color:var(--sub);background:#fff;border-radius:12px;margin-top:8px;box-shadow:0 2px 6px rgba(0,0,0,0.03)} +.chart-info .pn{font-weight:700;color:var(--pri);font-size:14px} + +.horo-card{background:linear-gradient(135deg,#fff8f0,#fff5f0);border-radius:14px;padding:14px;margin-bottom:10px;border:1px solid rgba(229,77,66,0.08)} +.horo-card .hday{font-size:11px;color:var(--sub)} +.horo-card .hmsg{font-size:13px;font-weight:600;color:var(--text);margin:4px 0;line-height:1.5} +.horo-card .hstars{color:#FFD700;font-size:12px} + +.quick-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px} +.quick-item{background:#fff;border-radius:12px;padding:16px 12px;text-align:center;box-shadow:0 2px 6px rgba(0,0,0,0.03);cursor:pointer;transition:transform 0.15s} +.quick-item:active{transform:scale(0.96)} +.quick-item .qi{font-size:26px;margin-bottom:4px} +.quick-item .qt{font-size:12px;font-weight:600;color:var(--text)} +.quick-item .qd{font-size:10px;color:var(--sub);margin-top:2px} + +.feed-tabs{display:flex;gap:8px;margin-bottom:10px;overflow-x:auto} +.feed-tab{padding:6px 14px;border-radius:20px;font-size:11px;white-space:nowrap;background:#fff;color:var(--sub);cursor:pointer;transition:all 0.15s;border:1px solid #eee} +.feed-tab.active{background:var(--pri);color:#fff;border-color:var(--pri)} +.feed-item{background:#fff;border-radius:14px;margin-bottom:10px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.04)} +.feed-item .f-thumb{position:relative;height:180px;display:flex;align-items:center;justify-content:center;cursor:pointer} +.feed-item .f-thumb .play-btn{width:44px;height:44px;border-radius:50%;background:rgba(255,255,255,0.2);display:flex;align-items:center;justify-content:center;font-size:16px;color:#fff} +.feed-item .f-thumb .f-dur{position:absolute;bottom:8px;right:8px;background:rgba(0,0,0,0.6);color:#fff;font-size:10px;padding:2px 6px;border-radius:4px} +.feed-item .f-avatar{position:absolute;bottom:8px;left:8px;width:28px;height:28px;border-radius:50%;background:rgba(255,255,255,0.3);display:flex;align-items:center;justify-content:center;font-size:14px} +.feed-item .f-info{padding:12px} +.feed-item .f-info .f-title{font-size:13px;font-weight:600;margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.feed-item .f-info .f-meta{font-size:10px;color:var(--sub);display:flex;gap:10px} +.feed-item .f-tags{display:flex;gap:5px;padding:0 12px 8px;flex-wrap:wrap} +.feed-tag{font-size:9px;padding:2px 7px;border-radius:10px;background:#FFF5F0;color:var(--pri)} + +.video-overlay{position:fixed;inset:0;z-index:200;background:#000;display:none;flex-direction:column;justify-content:center;align-items:center} +.video-overlay.on{display:flex} +.video-overlay .v-canvas{display:block;width:100%;max-width:430px} +.video-overlay .v-close{position:absolute;top:16px;right:16px;z-index:210;width:36px;height:36px;border-radius:50%;background:rgba(255,255,255,0.12);color:#fff;border:none;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center} +.video-overlay .v-progress{position:absolute;bottom:40px;left:50%;transform:translateX(-50%);width:70%;max-width:280px;height:3px;background:rgba(255,255,255,0.15);border-radius:3px;z-index:210} +.video-overlay .v-progress .v-bar{height:100%;background:var(--pri);border-radius:3px;transition:width 0.1s linear} +.video-overlay .v-watermark{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) rotate(-20deg);color:rgba(255,255,255,0.06);font-size:32px;font-weight:800;pointer-events:none;z-index:205;white-space:nowrap} + +.tier-card{background:#fff;border-radius:14px;padding:16px;margin-bottom:8px;box-shadow:0 2px 8px rgba(0,0,0,0.04);position:relative;overflow:hidden} +.tier-card.pro{border:2px solid var(--gold)} +.tier-card.pro::after{content:"推荐";position:absolute;top:0;right:0;background:var(--gold);color:#fff;font-size:10px;padding:3px 10px;border-radius:0 12px 0 12px;font-weight:600} +.tier-card .t-name{font-size:15px;font-weight:700;margin-bottom:2px} +.tier-card .t-price{font-size:20px;font-weight:800;color:var(--pri);margin-bottom:6px} +.tier-card .t-price .unit{font-size:12px;font-weight:400} +.tier-card .t-feat{font-size:11px;color:var(--sub);line-height:1.8} + +.badge-row{display:flex;gap:8px;overflow-x:auto;padding:4px 0;margin-bottom:6px} +.badge-item{flex-shrink:0;width:56px;text-align:center;font-size:9px;color:var(--sub)} +.badge-item .bi{width:44px;height:44px;border-radius:50%;margin:0 auto 4px;display:flex;align-items:center;justify-content:center;font-size:20px} +.badge-item .bi.locked{background:#f0f0f0;filter:grayscale(1);opacity:0.5} + +.hist-item{display:flex;align-items:center;gap:10px;padding:12px;background:#fff;border-radius:12px;margin-bottom:8px;box-shadow:0 1px 4px rgba(0,0,0,0.03);cursor:pointer} +.hist-item .hi{flex-shrink:0;display:flex;align-items:center} +.hist-item .hc{flex:1;min-width:0} +.hist-item .hc .hn{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.hist-item .hc .hd{font-size:10px;color:var(--sub)} + +.toast{position:fixed;top:60px;left:50%;transform:translateX(-50%);z-index:300;background:var(--dark);color:#fff;padding:10px 20px;border-radius:20px;font-size:13px;opacity:0;transition:opacity 0.3s;pointer-events:none} +.toast.show{opacity:1} + +/* Purchase modal - simple overlay kept for membership */ +.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;z-index:250;background:rgba(0,0,0,0.5);display:none;align-items:center;justify-content:center} +.modal-overlay.on{display:flex} +.modal-box{background:#fff;border-radius:16px;padding:24px;width:calc(100% - 48px);max-width:360px;text-align:center} +.modal-box .m-icon{font-size:40px;margin-bottom:6px} +.modal-box .m-title{font-size:17px;font-weight:700;margin-bottom:4px} +.modal-box .m-desc{font-size:12px;color:var(--sub);margin-bottom:14px;line-height:1.6} + +.sec-head{font-size:14px;font-weight:700;margin:14px 0 8px;display:flex;align-items:center;gap:6px} +.sec-head .dot{width:6px;height:6px;border-radius:50%;background:var(--pri);flex-shrink:0} +.scroll-x{display:flex;gap:10px;overflow-x:auto;padding-bottom:4px;-webkit-overflow-scrolling:touch} +.scroll-x::-webkit-scrollbar{display:none} + +/* Back button container */ +.back-row{display:flex;align-items:center;gap:6px;margin-bottom:10px} +.back-row .bk{cursor:pointer;font-size:13px;color:var(--pri);font-weight:600;display:flex;align-items:center;gap:2px} + +/* ═══ SCHULTE 3D CUBE ═══ */ +.cube-scene{perspective:900px;width:300px;height:300px;margin:20px auto;position:relative} +.cube-scene.paused .cube{animation-play-state:paused} +.cube{width:100%;height:100%;position:relative;transform-style:preserve-3d;transform:rotateX(-15deg) rotateY(25deg)} +.cube-face{position:absolute;width:300px;height:300px;border-radius:10px;backface-visibility:hidden} +.cube-front{transform:translateZ(150px);background:linear-gradient(135deg,#1a1a2e,#16213e)} +.cube-back{transform:rotateY(180deg) translateZ(150px);background:linear-gradient(135deg,#16213e,#0f3460)} +.cube-left{transform:rotateY(-90deg) translateZ(150px);background:linear-gradient(135deg,#0f3460,#1a1a2e)} +.cube-right{transform:rotateY(90deg) translateZ(150px);background:linear-gradient(135deg,#1a1a2e,#2d1b69)} +.cube-top{transform:rotateX(90deg) translateZ(150px);background:linear-gradient(135deg,#2d1b69,#16213e)} +.cube-bottom{transform:rotateX(-90deg) translateZ(150px);background:linear-gradient(135deg,#16213e,#1a1a2e)} +.cube-face .deco-face{font-size:32px;opacity:.2;text-align:center;line-height:1.3;pointer-events:none;position:absolute;inset:0;display:flex;align-items:center;justify-content:center} +.cube-grid{position:absolute;top:0;left:0;width:300px;height:300px;border-radius:10px;display:grid;grid-template-columns:repeat(5,1fr);grid-template-rows:repeat(5,1fr);gap:2px;padding:4px;z-index:10;background:rgba(26,26,46,.85);box-shadow:0 0 30px rgba(0,0,0,.4),inset 0 0 20px rgba(0,0,0,.2);perspective:400px} +.cube-grid .cs{display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:800;border-radius:6px;cursor:pointer;transition:transform .15s,box-shadow .15s;user-select:none;-webkit-tap-highlight-color:transparent;background:rgba(255,255,255,.92);color:var(--text);box-shadow:0 4px 14px rgba(0,0,0,.15);animation:cellFloat 3s ease-in-out infinite;transform-style:preserve-3d} +@keyframes cellFloat{0%,100%{transform:translateY(0) scale(1);box-shadow:0 4px 12px rgba(0,0,0,.18)}50%{transform:translateY(-12px) scale(1.06);box-shadow:0 18px 32px rgba(0,0,0,.42)}} +.cube-grid .cs:active{transform:scale(.9)!important;animation:none} +.cube-grid .cs.done{background:rgba(255,255,255,.12);color:transparent;box-shadow:none;pointer-events:none;opacity:0;animation:none} +.cube-grid .cs.next{animation:cellFloat 3s ease-in-out infinite,csPulse .8s ease-in-out infinite} +@keyframes csPulse{0%,100%{box-shadow:0 0 8px rgba(229,77,66,.3)}50%{box-shadow:0 0 24px rgba(229,77,66,.85)}} +.schulte-info{text-align:center;padding:8px 0;color:var(--sub);font-size:12px} +.schulte-timer{font-size:28px;font-weight:800;color:var(--pri);text-align:center;margin:8px 0} +.schulte-best{font-size:11px;color:var(--sub);text-align:center} +.schulte-cta{display:block;margin:10px auto;width:160px} +.schulte-result{text-align:center;padding:20px 0} +.schulte-result .sr-time{font-size:36px;font-weight:800;color:var(--pri)} +.schulte-result .sr-rank{font-size:13px;color:var(--sub);margin-top:4px} + +/* Synastry tabs */ +.syn-tab{flex:1;padding:8px 4px;border:none;border-radius:20px;background:#f0f0f0;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s;color:var(--sub)} +.syn-tab.active{background:var(--pri);color:#fff;box-shadow:0 2px 8px rgba(229,77,66,.2)} diff --git a/deploy/.env.example b/deploy/.env.example new file mode 100644 index 0000000..dda740a --- /dev/null +++ b/deploy/.env.example @@ -0,0 +1,4 @@ +APP_ENV=dev +HTTP_ADDR=:8080 +DATABASE_URL=postgres://yuxingu:yuxingu@127.0.0.1:5432/yuxingu?sslmode=disable +JWT_SECRET=change-me-in-production diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml new file mode 100644 index 0000000..87afb38 --- /dev/null +++ b/deploy/docker-compose.yml @@ -0,0 +1,15 @@ +services: + postgres: + image: postgres:16-alpine + container_name: yuxingu-postgres + environment: + POSTGRES_USER: yuxingu + POSTGRES_PASSWORD: yuxingu + POSTGRES_DB: yuxingu + ports: + - "5432:5432" + volumes: + - yuxingu_pg:/var/lib/postgresql/data + +volumes: + yuxingu_pg: diff --git a/index.html b/index.html index 74a0a27..285393a 100644 --- a/index.html +++ b/index.html @@ -1,84 +1,15 @@ - + - - - - - + + 愈心谷 - + - - -

专业的心理健康服务平台

- - - +

+ 进入愈心谷 +

diff --git a/common.js b/js/common.js similarity index 99% rename from common.js rename to js/common.js index d756ffc..2425b40 100644 --- a/common.js +++ b/js/common.js @@ -1,5 +1,5 @@ /* common.js —— 纯数据 + 纯函数,禁止任何 DOM 操作 - shared by: shuzi.html / yangsheng.html / jiema.html */ + shared by: pages/shuzi.html / pages/health.html / pages/decode.html */ // ===== from shuzi.html ===== function s1(n){while(n>=10)n=String(n).split('').reduce((a,b)=>a+ +b,0);return n} diff --git a/jiema.html b/js/decode.js similarity index 54% rename from jiema.html rename to js/decode.js index d1ca797..1e9d295 100644 --- a/jiema.html +++ b/js/decode.js @@ -1,159 +1,3 @@ - - - - - -愈心解码 · 一个生日读懂身与心 - - - -
-
- 愈心谷 -
愈心解码
-
一个生日,读懂你的身与心
-
-
- -
- - - - - -
-
-
🔮
-
解锁完整深度报告
-
一次解锁,永久查看所有解读
-
¥39.9
-
✨ 天赋优势深度解读
✨ 人生课题方向指引
✨ 12组联合码全解析
✨ 五行体质完整方案
- - -
-
- - - - - \ No newline at end of file diff --git a/yangsheng.html b/js/health.js similarity index 79% rename from yangsheng.html rename to js/health.js index 3349a0e..4d817f6 100644 --- a/yangsheng.html +++ b/js/health.js @@ -1,197 +1,3 @@ - - - - - - - - - -愈心谷 · AI东方养生顾问 - - - - -
- -
- 愈心谷 -
易经 × 黄帝内经 × 五运六气
-
- -
-
📋 先天档案
-
📊 本周报告
-
☀️ 今日养生
-
- - -
-
-

📝 建立先天健康档案

-

仅需填写一次,系统自动推算八字、五行、体质,生成终身健康画像

-
-
-
-
-
-
-
- -

数据仅存本地,不上传服务器

-
- -
- - - - - - - -
- - - - - diff --git a/js/home.js b/js/home.js new file mode 100644 index 0000000..8e64abc --- /dev/null +++ b/js/home.js @@ -0,0 +1,14 @@ +function goDecode(){ + var y=document.getElementById('dy').value.trim(); + var m=document.getElementById('dm').value.trim(); + var d=document.getElementById('dd').value.trim(); + if(!y||!m||!d){alert('请输入完整的出生年月日');return;} + location.href='pages/decode.html?y='+encodeURIComponent(y)+'&m='+encodeURIComponent(m)+'&d='+encodeURIComponent(d); +} +['dy','dm','dd'].forEach(function(id){ + var el=document.getElementById(id); + if(!el) return; + el.addEventListener('keydown',function(e){ + if(e.key==='Enter')goDecode(); + }); +}); diff --git a/mindmap.html b/js/mindmap.js similarity index 68% rename from mindmap.html rename to js/mindmap.js index 248e56d..363ff76 100644 --- a/mindmap.html +++ b/js/mindmap.js @@ -1,167 +1,3 @@ - - - - - - - - - -愈心谷 · 脑图编辑器 - - - - - - -
- - - - - -
-
- - - - - - - - - 💾 已自动保存 -
- -
- -
+
-
+
-
- - - - diff --git a/scale_test.html b/js/scale-test.js similarity index 75% rename from scale_test.html rename to js/scale-test.js index 8c95b98..98d9fba 100644 --- a/scale_test.html +++ b/js/scale-test.js @@ -1,74 +1,3 @@ - - - - - -量表测试 — 愈心谷 - - - - -
加载中…
- - - - diff --git a/js/scales.js b/js/scales.js new file mode 100644 index 0000000..f1519ff --- /dev/null +++ b/js/scales.js @@ -0,0 +1,53 @@ +const ICONS = {'心理测试综合':'📋','人格测试':'🎭','抑郁症测试':'🌧️','焦虑症测试':'😰','强迫症测试':'🔄','躁狂症测试':'🔥','双向情感障碍':'🌓','恐惧症测试':'😨','智商测试':'🧠','情商测试':'💝','其它测试':'📌'}; + +const CAT_KEYS = {'心理测试综合':'cat-综合','人格测试':'cat-人格','抑郁症测试':'cat-抑郁','焦虑症测试':'cat-焦虑','强迫症测试':'cat-强迫','躁狂症测试':'cat-躁狂','双向情感障碍':'cat-双相','恐惧症测试':'cat-恐惧','智商测试':'cat-智商','情商测试':'cat-情商','其它测试':'cat-其他'}; + +let scales = [], activeCat = '全部'; + +async function load() { + document.getElementById('content').innerHTML = '
加载中…
'; + try { + const r = await fetch('../api/scales'); + scales = await r.json(); + renderCats(); + renderScales(); + } catch(e) { + document.getElementById('content').innerHTML = '
加载失败,请刷新重试
'; + } +} + +function renderCats() { + const cats = [...new Set(scales.map(s=>s.category))]; + const html = ['']; + cats.forEach(c => { + html.push(``); + }); + document.getElementById('cats').innerHTML = html.join(''); +} + +function filter(cat) { + activeCat = cat; + document.querySelectorAll('.cat-btn').forEach(b => b.classList.toggle('active', b.textContent === cat)); + renderScales(); +} + +function renderScales() { + const filtered = activeCat === '全部' ? scales : scales.filter(s=>s.category===activeCat); + if (!filtered.length) { + document.getElementById('content').innerHTML = '
暂无数据
'; + return; + } + const html = filtered.map(s => ` + +
${ICONS[s.category]||'📌'}
+
+
${s.name}
+
${s.description||''}
+
+
${s.question_count}题
+
+ `).join(''); + document.getElementById('content').innerHTML = `
${html}
`; +} + +load(); diff --git a/js/shell.js b/js/shell.js new file mode 100644 index 0000000..ce5f300 --- /dev/null +++ b/js/shell.js @@ -0,0 +1,60 @@ +/* 愈心谷共享壳 —— 底栏注入、高亮、二级返回 */ +(function () { + var body = document.body; + if (!body) return; + + var navKey = body.getAttribute('data-nav') || 'none'; + var backTo = body.getAttribute('data-back') || ''; + var pageTitle = body.getAttribute('data-title') || document.title || ''; + var base = body.getAttribute('data-base') || ''; + + // 根据当前路径推断资源根:根目录页用 '',pages/ 下用 '../' + if (!base) { + base = /\/pages\//.test(location.pathname) ? '../' : ''; + } + + body.classList.add('app-body'); + if (navKey === 'none') body.classList.add('no-nav'); + + function abs(path) { + if (!path) return base; + if (/^(https?:|mailto:|tel:|#)/.test(path)) return path; + return base + path.replace(/^\.\//, ''); + } + + // 二级顶栏 + if (navKey === 'none' && (backTo || pageTitle)) { + var top = document.createElement('div'); + top.className = 'app-topbar'; + var backHref = abs(backTo || 'yuxingu.html'); + top.innerHTML = + '' + + '
'; + top.querySelector('.title').textContent = pageTitle; + body.insertBefore(top, body.firstChild); + } + + // 一级底栏 + if (navKey !== 'none') { + var items = [ + { key: 'home', href: 'yuxingu.html', icon: '⌂', label: '首页' }, + { key: 'know', href: 'pages/know.html', icon: '◉', label: '认识自己' }, + { key: 'scales', href: 'pages/scales.html', icon: '☰', label: '专业测评' }, + { key: 'activity', href: 'pages/activity.html', icon: '✦', label: '私域活动' }, + { key: 'health', href: 'pages/health.html', icon: '♡', label: '健康管理' }, + { key: 'mine', href: 'pages/mine.html', icon: '◍', label: '我的' } + ]; + var nav = document.createElement('nav'); + nav.className = 'app-nav'; + nav.setAttribute('aria-label', '主导航'); + nav.innerHTML = items.map(function (it) { + var cls = it.key === navKey ? ' active' : ''; + return ( + '' + + '' + it.icon + '' + it.label + + '' + ); + }).join(''); + body.appendChild(nav); + } +})(); diff --git a/shuzi.html b/js/shuzi.js similarity index 71% rename from shuzi.html rename to js/shuzi.js index 3573116..b161783 100644 --- a/shuzi.html +++ b/js/shuzi.js @@ -1,178 +1,3 @@ - - - - - - - - - - 愈心谷数字解构 - - - -
- 愈心谷 -
- -
-
-

愈心谷数字解构

-
✦ 输入公历出生年月日 ✦
-
-
- - - - -
-
-
3
-
48
-
-
- - - - - - -
7
-
1
-
6
-
8
-
2
-
1
-
5
-
-
-
3
-
93
-
-
-
9
-
72
-
-
-
- -
-
- - - - - - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
- - - - - \ No newline at end of file diff --git a/xingjing_app.html b/js/xingjing.js similarity index 78% rename from xingjing_app.html rename to js/xingjing.js index 9043a16..9feed22 100644 --- a/xingjing_app.html +++ b/js/xingjing.js @@ -1,448 +1,3 @@ - - - - - -星镜 - - - - -
- - 愈心谷星镜 -
-
- - -
-
-
-
今日星象
-
🔭
-
探索你的星盘
-
发现内在的宇宙
-
✦ 开始星盘测试
-
- -
-
★★★★☆
-
💛 幸运色: · 幸运数字:
-
- -
-
🔮
星盘测试
生辰排盘 · 性格解读
-
动态星盘
行星运转 · 实时动画
-
视频社区
达人解读 · 热门内容
-
🎬
我的报告
历史记录 · 视频回放
-
🧊
舒尔特训练
3D魔方 · 前额叶激活
-
💫
合盘配对
双人星盘 · 缘分契合度
-
- -
热门解读
-
-
- - -
-
← 返回首页
-
-
🔭
-
输入出生信息
-
解锁专属星盘与性格解读
-
-
-
- - - -
-
- - -
-
- - - -
- -
-
- - -
-
← 返回修改
-
- -
1 / 10
-
-
-
-
-
-
- - -
-
← 返回首页
-
-
- - -
-
点击行星查看详情
- -
- ◎ 实时天象 - 拖拽旋转 · 双指缩放 · 点击行星 -
-
- - -
-
测试历史
-
-
- - -
-
-
-
- - -
-
-
-
探索者
完成测试解锁完整档案
-
-
成就徽章
-
-
会员订阅
-
免费版
¥0 /月
✓ 基础排盘   ✓ 30秒带水印报告   ✓ 每日运势   ✓ 社区浏览
-
星镜会员
¥24 /月 首月¥9.9
✓ 无水印完整视频报告   ✓ 高清3D星盘   ✓ AI数字人多风格   ✓ 专属课程库   ✓ 不限次数生成
-
年度会员
¥168 /年 省 ¥120
✓ 包含全部月会员权益 + 年度专属深度报告
-
预约达人咨询
-
- - - -
-
- - -
-
← 返回首页
-
-
🧊
-
舒尔特前额叶训练
-
按顺序点击 1→25,训练专注力与反应速度
-
-
-
-

愈心谷
-

专注
前额叶
-

反应
速度
-

注意力
-

视觉
搜索
-

记忆
-
-
-
-
00:00.0
-
- -
拖拽旋转 · 点击数字按序消除
-
- - -
-
← 返回首页
-
-
💫
-
双人合盘 · 缘分配对
-
输入两人的出生信息,解锁星座契合度分析
-
-
- - - -
-
- - - - -
-
-
-
👤 你
-
-
-
-
- 👤 TA - -
-
-
-
- -
-
- - - - - -
- - -
-
星镜 · 预览版
-
- -
- - - - - diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f3d3f6b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1687 @@ +{ + "name": "yuxingu", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "yuxingu", + "workspaces": [ + "apps/user-h5", + "apps/admin-h5", + "packages/*" + ] + }, + "apps/admin-h5": { + "name": "@yuxingu/admin-h5", + "version": "0.1.0" + }, + "apps/user-h5": { + "name": "@yuxingu/user-h5", + "version": "0.1.0", + "dependencies": { + "@yuxingu/sdk": "0.1.0", + "@yuxingu/types": "0.1.0", + "@yuxingu/ui": "0.1.0", + "@yuxingu/utils": "0.1.0", + "pinia": "^2.3.0", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "typescript": "~5.7.2", + "vite": "^6.0.7", + "vue-tsc": "^2.2.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.15" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz", + "integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.40", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz", + "integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz", + "integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.40", + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-ssr": "3.5.40", + "@vue/shared": "3.5.40", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz", + "integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/language-core": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz", + "integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz", + "integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz", + "integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.40", + "@vue/runtime-core": "3.5.40", + "@vue/shared": "3.5.40", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz", + "integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz", + "integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==", + "license": "MIT" + }, + "node_modules/@yuxingu/admin-h5": { + "resolved": "apps/admin-h5", + "link": true + }, + "node_modules/@yuxingu/sdk": { + "resolved": "packages/sdk", + "link": true + }, + "node_modules/@yuxingu/types": { + "resolved": "packages/types", + "link": true + }, + "node_modules/@yuxingu/ui": { + "resolved": "packages/ui", + "link": true + }, + "node_modules/@yuxingu/user-h5": { + "resolved": "apps/user-h5", + "link": true + }, + "node_modules/@yuxingu/utils": { + "resolved": "packages/utils", + "link": true + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz", + "integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-sfc": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/server-renderer": "3.5.40", + "@vue/shared": "3.5.40" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-tsc": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "packages/sdk": { + "name": "@yuxingu/sdk", + "version": "0.1.0", + "dependencies": { + "@yuxingu/types": "0.1.0" + } + }, + "packages/types": { + "name": "@yuxingu/types", + "version": "0.1.0" + }, + "packages/ui": { + "name": "@yuxingu/ui", + "version": "0.1.0" + }, + "packages/utils": { + "name": "@yuxingu/utils", + "version": "0.1.0" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..14afa38 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "yuxingu", + "private": true, + "workspaces": [ + "apps/user-h5", + "apps/admin-h5", + "packages/*" + ], + "scripts": { + "dev:h5": "npm run dev -w @yuxingu/user-h5", + "build:h5": "npm run build -w @yuxingu/user-h5", + "dev:api": "cd apps/api && go run ./cmd/server" + } +} diff --git a/packages/sdk/package.json b/packages/sdk/package.json new file mode 100644 index 0000000..c2cc066 --- /dev/null +++ b/packages/sdk/package.json @@ -0,0 +1,14 @@ +{ + "name": "@yuxingu/sdk", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "dependencies": { + "@yuxingu/types": "0.1.0" + } +} diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts new file mode 100644 index 0000000..b7b954f --- /dev/null +++ b/packages/sdk/src/index.ts @@ -0,0 +1,75 @@ +import type { ApiResponse } from '@yuxingu/types' + +/** Platform adapters so H5 and mini-program share one client. */ +export interface RequestOptions { + method?: 'GET' | 'POST' | 'PUT' | 'DELETE' + path: string + body?: unknown + headers?: Record +} + +export interface ClientAdapters { + /** Perform HTTP and return parsed JSON envelope. */ + request: (opts: RequestOptions) => Promise> + getToken?: () => string | null | Promise +} + +export interface CreateClientOptions { + baseURL: string + adapters: ClientAdapters +} + +/** + * createClient builds a typed API facade. + * Side effect: network via adapters.request. + */ +export function createClient(opts: CreateClientOptions) { + const { baseURL, adapters } = opts + + async function call(path: string, init?: Omit): Promise { + const token = adapters.getToken ? await adapters.getToken() : null + const headers: Record = { ...(init?.headers || {}) } + if (token) headers.Authorization = `Bearer ${token}` + + const res = await adapters.request({ + method: init?.method || 'GET', + path: joinURL(baseURL, path), + body: init?.body, + headers, + }) + if (res.code !== 0) { + throw new Error(res.message || `api error ${res.code}`) + } + return res.data as T + } + + return { + healthz: () => call<{ status: string }>('/api/v1/healthz'), + ping: () => call<{ pong: boolean }>('/api/v1/ping'), + } +} + +/** Browser fetch adapter for user-h5. */ +export function createBrowserAdapters(): ClientAdapters { + return { + request: async ({ method = 'GET', path, body, headers }) => { + const res = await fetch(path, { + method, + headers: { + 'Content-Type': 'application/json', + ...(headers || {}), + }, + body: body === undefined ? undefined : JSON.stringify(body), + }) + return (await res.json()) as ApiResponse + }, + getToken: () => localStorage.getItem('yxg_token'), + } +} + +function joinURL(base: string, path: string): string { + if (path.startsWith('http')) return path + const b = base.replace(/\/$/, '') + const p = path.startsWith('/') ? path : `/${path}` + return `${b}${p}` +} diff --git a/packages/types/package.json b/packages/types/package.json new file mode 100644 index 0000000..31bec54 --- /dev/null +++ b/packages/types/package.json @@ -0,0 +1,11 @@ +{ + "name": "@yuxingu/types", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + } +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts new file mode 100644 index 0000000..8eec291 --- /dev/null +++ b/packages/types/src/index.ts @@ -0,0 +1,25 @@ +/** Unified API envelope from apps/api. */ +export interface ApiResponse { + code: number + message: string + data?: T +} + +/** User profile used by decode / relation features. */ +export interface Profile { + id: string + label: string + year: number + month: number + day: number + relation: 'self' | 'other' +} + +/** Minimal scale list item. */ +export interface ScaleSummary { + slug: string + name: string + category: string + questionCount: number + description?: string +} diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..f54fc30 --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,10 @@ +{ + "name": "@yuxingu/ui", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/tokens.css", + "exports": { + "./tokens.css": "./src/tokens.css" + } +} diff --git a/packages/ui/src/tokens.css b/packages/ui/src/tokens.css new file mode 100644 index 0000000..4336fc9 --- /dev/null +++ b/packages/ui/src/tokens.css @@ -0,0 +1,79 @@ +/* Shared design tokens — source of truth for H5 / mini-program / future clients + * Contract: .ai/design/design-system.md + */ +:root { + /* ── Semantic color (prefer in new UI) ── */ + --color-primary: #e54d42; + --color-primary-hover: #d44338; + --color-primary-soft: #ffe4e4; + --color-primary-disabled: #f5b5b0; + + --color-bg-start: #ffd1c7; + --color-bg-end: #ffc8b5; + --color-bg-sheet: #fff9f7; + + --color-surface: #ffffff; + --color-text-primary: #333333; + --color-text-secondary: #999999; + --color-text-tertiary: #bbbbbb; + --color-border: #f0f0f0; + --color-border-strong: #eeeeee; + --color-input-bg: #fdfaf8; + + --color-accent-gold: #c8923a; + --color-accent-gold-soft: #fff3d6; + --color-accent-blue: #4a90e2; + --color-accent-blue-soft: #e3eeff; + --color-accent-green: #5cb85c; + --color-accent-green-soft: #e0f6e4; + --color-accent-orange: #e8985a; + --color-accent-purple: #8b5cf6; + + --color-success: #5cb85c; + --color-warning: #e8985a; + --color-error: #e54d42; + --color-info: #4a90e2; + + /* ── Spacing ── */ + --spacing-2xs: 4px; + --spacing-xs: 8px; + --spacing-sm: 12px; + --spacing-md: 16px; + --spacing-lg: 24px; + --spacing-xl: 32px; + --spacing-2xl: 48px; + --spacing-3xl: 64px; + + /* ── Radius ── */ + --radius-sm: 10px; + --radius-md: 12px; + --radius-lg: 16px; + --radius-xl: 20px; + --radius-pill: 999px; + + /* ── Shadow / layout ── */ + --shadow-card: 0 2px 10px rgba(0, 0, 0, 0.04); + --shadow-nav: 0 -2px 8px rgba(0, 0, 0, 0.03); + --layout-max-user: 430px; + --layout-nav-h: 56px; + + /* ── Motion ── */ + --duration-fast: 150ms; + --duration-normal: 200ms; + --duration-slow: 300ms; + + /* ── Legacy aliases (do not remove; migrate gradually) ── */ + --yxg-pri: var(--color-primary); + --yxg-blue: var(--color-accent-blue); + --yxg-green: var(--color-accent-green); + --yxg-purple: var(--color-accent-purple); + --yxg-orange: var(--color-accent-orange); + --yxg-gold: var(--color-accent-gold); + --yxg-text: var(--color-text-primary); + --yxg-sub: var(--color-text-secondary); + --yxg-bg-start: var(--color-bg-start); + --yxg-bg-end: var(--color-bg-end); + --yxg-radius: var(--radius-lg); + --yxg-nav-h: var(--layout-nav-h); + --yxg-max-w: var(--layout-max-user); +} diff --git a/packages/utils/package.json b/packages/utils/package.json new file mode 100644 index 0000000..2639830 --- /dev/null +++ b/packages/utils/package.json @@ -0,0 +1,11 @@ +{ + "name": "@yuxingu/utils", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + } +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts new file mode 100644 index 0000000..eca1c25 --- /dev/null +++ b/packages/utils/src/index.ts @@ -0,0 +1,20 @@ +/** Clamp a number into [min, max]. */ +export function clamp(n: number, min: number, max: number): number { + return Math.min(max, Math.max(min, n)) +} + +/** Pad date part to 2 digits. */ +export function pad2(n: number): string { + return String(n).padStart(2, '0') +} + +/** + * Validate calendar-ish birth inputs. + * Side effect: none. Returns error message or null if ok. + */ +export function validateBirth(year: number, month: number, day: number): string | null { + if (!Number.isFinite(year) || year < 1900 || year > 2100) return '年份无效' + if (month < 1 || month > 12) return '月份无效' + if (day < 1 || day > 31) return '日期无效' + return null +} diff --git a/pages/activity.html b/pages/activity.html new file mode 100644 index 0000000..8c87b0c --- /dev/null +++ b/pages/activity.html @@ -0,0 +1,33 @@ + + + + + + + + +私域活动 · 愈心谷 + + + + + + + +
+ 活动中心即将上线
+ 节气推送、社群共创、会员专属活动
+ 敬请期待 +
+ + + + diff --git a/pages/decode.html b/pages/decode.html new file mode 100644 index 0000000..e2f2ebc --- /dev/null +++ b/pages/decode.html @@ -0,0 +1,61 @@ + + + + + + + + + +愈心解码 · 愈心谷 + + + + + + +
+
+ 愈心谷 +
愈心解码
+
一个生日,读懂你的身与心
+
+
+ +
+ + + + + +
+
+
🔮
+
解锁完整深度报告
+
一次解锁,永久查看所有解读
+
¥39.9
+
✨ 天赋优势深度解读
✨ 人生课题方向指引
✨ 12组联合码全解析
✨ 五行体质完整方案
+ + +
+
+ + + + + + diff --git a/pages/health.html b/pages/health.html new file mode 100644 index 0000000..7409ec6 --- /dev/null +++ b/pages/health.html @@ -0,0 +1,75 @@ + + + + + + + + + +健康管理 · 愈心谷 + + + + + + + +
+ +
+ 愈心谷 +
易经 × 黄帝内经 × 五运六气
+
+ +
+
📋 先天档案
+
📊 本周报告
+
☀️ 今日养生
+
+ + +
+
+

📝 建立先天健康档案

+

仅需填写一次,系统自动推算八字、五行、体质,生成终身健康画像

+
+
+
+
+
+
+
+ +

数据仅存本地,不上传服务器

+
+ +
+ + + + + + + +
+ + + + + + diff --git a/pages/know.html b/pages/know.html new file mode 100644 index 0000000..590b6ec --- /dev/null +++ b/pages/know.html @@ -0,0 +1,60 @@ + + + + + + + + +认识自己 · 愈心谷 + + + + + + + + + + + + diff --git a/pages/mindmap.html b/pages/mindmap.html new file mode 100644 index 0000000..3e2d91f --- /dev/null +++ b/pages/mindmap.html @@ -0,0 +1,62 @@ + + + + + + + + + +愈心脑图 · 愈心谷 + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + 💾 已自动保存 +
+ +
+ +
+
+
+
+
+ + + + + diff --git a/pages/mine.html b/pages/mine.html new file mode 100644 index 0000000..f94981a --- /dev/null +++ b/pages/mine.html @@ -0,0 +1,45 @@ + + + + + + + + +我的 · 愈心谷 + + + + + + + + + + + + diff --git a/pages/scale-test.html b/pages/scale-test.html new file mode 100644 index 0000000..082f246 --- /dev/null +++ b/pages/scale-test.html @@ -0,0 +1,27 @@ + + + + + + + + + +量表测试 · 愈心谷 + + + + + + +
加载中…
+ + + + + diff --git a/pages/scales.html b/pages/scales.html new file mode 100644 index 0000000..8d891e7 --- /dev/null +++ b/pages/scales.html @@ -0,0 +1,34 @@ + + + + + + + + + +专业测评 · 愈心谷 + + + + + + +
+ +
愈心谷数字解构 · 专业心理量表库
+
+ +
+ +
+ + + + + diff --git a/pages/shuzi.html b/pages/shuzi.html new file mode 100644 index 0000000..fb11420 --- /dev/null +++ b/pages/shuzi.html @@ -0,0 +1,103 @@ + + + + + + + + + +数字心理学 · 愈心谷 + + + + + + +
+ 愈心谷 +
+ +
+
+

愈心谷数字解构

+
✦ 输入公历出生年月日 ✦
+
+
+ + + + +
+
+
3
+
48
+
+
+ + + + + + +
7
+
1
+
6
+
8
+
2
+
1
+
5
+
+
+
3
+
93
+
+
+
9
+
72
+
+
+
+ +
+
+ + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + + + diff --git a/pages/xingjing.html b/pages/xingjing.html new file mode 100644 index 0000000..379e790 --- /dev/null +++ b/pages/xingjing.html @@ -0,0 +1,237 @@ + + + + + + + + + +星镜 · 愈心谷 + + + + + + +
+ + 愈心谷星镜 +
+
+ + +
+
+
+
今日星象
+
🔭
+
探索你的星盘
+
发现内在的宇宙
+
✦ 开始星盘测试
+
+ +
+
★★★★☆
+
💛 幸运色: · 幸运数字:
+
+ +
+
🔮
星盘测试
生辰排盘 · 性格解读
+
动态星盘
行星运转 · 实时动画
+
视频社区
达人解读 · 热门内容
+
🎬
我的报告
历史记录 · 视频回放
+
🧊
舒尔特训练
3D魔方 · 前额叶激活
+
💫
合盘配对
双人星盘 · 缘分契合度
+
+ +
热门解读
+
+
+ + +
+
← 返回首页
+
+
🔭
+
输入出生信息
+
解锁专属星盘与性格解读
+
+
+
+ + + +
+
+ + +
+
+ + + +
+ +
+
+ + +
+
← 返回修改
+
+ +
1 / 10
+
+
+
+
+
+
+ + +
+
← 返回首页
+
+
+ + +
+
点击行星查看详情
+ +
+ ◎ 实时天象 + 拖拽旋转 · 双指缩放 · 点击行星 +
+
+ + +
+
测试历史
+
+
+ + +
+
+
+
+ + +
+
+
+
探索者
完成测试解锁完整档案
+
+
成就徽章
+
+
会员订阅
+
免费版
¥0 /月
✓ 基础排盘   ✓ 30秒带水印报告   ✓ 每日运势   ✓ 社区浏览
+
星镜会员
¥24 /月 首月¥9.9
✓ 无水印完整视频报告   ✓ 高清3D星盘   ✓ AI数字人多风格   ✓ 专属课程库   ✓ 不限次数生成
+
年度会员
¥168 /年 省 ¥120
✓ 包含全部月会员权益 + 年度专属深度报告
+
预约达人咨询
+
+ + + +
+
+ + +
+
← 返回首页
+
+
🧊
+
舒尔特前额叶训练
+
按顺序点击 1→25,训练专注力与反应速度
+
+
+
+

愈心谷
+

专注
前额叶
+

反应
速度
+

注意力
+

视觉
搜索
+

记忆
+
+
+
+
00:00.0
+
+ +
拖拽旋转 · 点击数字按序消除
+
+ + +
+
← 返回首页
+
+
💫
+
双人合盘 · 缘分配对
+
输入两人的出生信息,解锁星座契合度分析
+
+
+ + + +
+
+ + + + +
+
+
+
👤 你
+
+
+
+
+ 👤 TA + +
+
+
+
+ +
+
+ + + + + + + + + + +
+ + +
+
星镜 · 预览版
+
+ +
+ + + + + + diff --git a/proto/openapi.yaml b/proto/openapi.yaml new file mode 100644 index 0000000..d506713 --- /dev/null +++ b/proto/openapi.yaml @@ -0,0 +1,37 @@ +openapi: 3.0.3 +info: + title: YuXinGu API + version: 0.1.0 + description: 愈心谷 HTTP API(脚手架) +servers: + - url: http://127.0.0.1:8080 +paths: + /api/v1/healthz: + get: + summary: Liveness + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope' + /api/v1/ping: + get: + summary: Ping + responses: + '200': + description: OK +components: + schemas: + Envelope: + type: object + properties: + code: + type: integer + example: 0 + message: + type: string + example: success + data: + type: object diff --git a/scales.html b/scales.html deleted file mode 100644 index 09c519d..0000000 --- a/scales.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - -愈心量表 — 愈心谷 - - - - -
- -
愈心谷数字解构 · 专业心理量表库
-
- -
- -
- - - - diff --git a/yuxingu.html b/yuxingu.html index 57109d8..520db8d 100644 --- a/yuxingu.html +++ b/yuxingu.html @@ -1,5 +1,5 @@ - + @@ -9,390 +9,178 @@ gtag('js', new Date()); gtag('config', 'G-LVVXH3TL04'); - 愈心谷 - + + + + + - + -
愈心脊
+ + - -
-
-
愈心脊
-

愈心谷数字解构

-
-
+
+ 愈心谷 +
+

愈见自己 · 遇见更好

+
- -
- 输入生日,更加了解自己 → -
- - -
- + - - + - -
-
-
🔮 愈心解码
-
一个生日,读懂你的身与心
-
- - - - - - - +
+ +
+
+ 认识自己 + 全部
-
-
+ + - +
+
+ 热门推荐 + 更多 +
+ +
- -
- - 更多服务 - -
+
+
精选工具
+ +
- - - - -
-
-
I want to care more
about you
-
- - - + + + diff --git a/立项文档.md b/立项文档.md new file mode 100644 index 0000000..7f61f05 --- /dev/null +++ b/立项文档.md @@ -0,0 +1 @@ +# 「数观养生」项目立项框架文档## Part 1:需求分析 / 可行性分析 / 商业逻辑 / 收费点设计---# 一、需求分析## 1.1 市场背景与痛点洞察**宏观趋势:**- 大健康产业规模超13万亿元,"治未病"、中医养生赛道政策持续利好- 泛心理测评类产品(MBTI、人格测试)在社交媒体持续高热度,验证了"自我认知"付费意愿- 玄学经济/文化疗愈消费兴起(年轻人"上香""测运势"),但**缺乏合规、可持续、能落地到健康行为的产品**——这是市场空档**用户痛点分层:**| 痛点层级 | 具体表现 | 现有解决方案的不足 ||----------|----------|---------------------|| 认知焦虑 | "我是什么样的人?适合什么生活方式?" | MBTI等偏西方职场语境,无健康落地 || 关系困扰 | 夫妻沟通不畅、亲子冲突、婆媳矛盾 | 心理咨询贵且门槛高;测评类无关系分析 || 健康迷茫 | 知道要养生但不知道"适合自己"的方案 | 养生内容泛化,千人一面,无个性化 || 情感寄托 | 希望被理解、被"算准"、获得确定感 | 算命类产品不合规、无科学成分、无后续服务 |**核心洞察:**> 用户要的不是"算得准",而是**"被理解 + 有方案 + 可持续"**。数字心理学提供"入口的惊喜感",《内经》体质学提供"落地的方案感",这个组合的独特价值在于:**从娱乐化测试自然过渡到长期健康服务**。## 1.2 目标用户画像| 画像 | 描述 | 核心需求 | 付费能力 ||------|------|----------|----------|| **P1 养生新中产女性**(主力) | 30-50岁,家庭健康决策者,信中医、爱转发养生内容 | 全家健康方案、家庭关系调和 | 高,年健康支出5000+ || **P2 泛测评年轻人** | 22-32岁,MBTI重度用户,社交分享驱动 | 自我认知、恋爱契合度、趣味传播 | 中,单次小额付费 || **P3 关系困扰者** | 有具体亲子/夫妻矛盾,寻求解释框架和方法 | 关系解读+沟通建议+咨询 | 高,愿为咨询付费 || **P4 B端机构** | 养生馆、月子中心、健管中心、保险经纪 | 获客工具、增值服务、提升客单 | 年费制付费 |**冷启动优先级:P2引流(传播)→ P1转化(付费主力)→ P3深化(高客单)→ P4规模化(B端)**## 1.3 需求优先级排序(KANO模型简化版)| 需求 | 类型 | 版本规划 ||------|------|----------|| 个人数字性格+体质报告 | 基本型 | MVP || 家庭/伴侣契合度分析 | 期望型(差异化核心)| MVP || 分享卡片/邀请裂变 | 期望型 | MVP || 节气个性化养生推送 | 魅力型(留存核心)| V1.1 || 一对一顾问咨询 | 魅力型 | V1.2 || 实物商品(茶包/膳食)| 期望型 | V1.2+ || 全家健康日历、B端SaaS | 期望型 | V2.0 |## 1.4 需求验证方式(开发前必做)1. **落地页测试**:投放1-2万元广告到假门页面,测点击率和预约留资率(目标:留资率>8%)2. **人工MVP**:先用问卷+人工出报告的方式服务50-100个种子用户,验证付费意愿和复购意向(目标:付费转化>15%,愿意推荐>40%)3. **社群共创**:建种子用户群,测试内容话术哪种最有共鸣("准不准"的反馈直接决定内容库打磨方向)---# 二、可行性分析## 2.1 市场可行性 ✅(中高)- **可比对象**:测测星座(估值数亿,DAU百万级)、口袋心理测试类小程序、各类付费报告类产品——验证了"生日输入→付费报告"模式跑得通- **差异化壁垒**:市面上"数字心理学"和"中医体质"各有产品,但**二者结合+家庭关系+持续养生服务**的完整闭环基本空白- **风险点**:赛道进入门槛低,易被模仿 → 壁垒要靠**内容库深度、专家背书、私域运营和供应链**,而非算法本身## 2.2 技术可行性 ✅(高)- 核心是**规则引擎+内容库**,无高技术门槛:数字计算、五行映射、81种关系矩阵、体质量表评分,均为确定性逻辑- 报告生成、小程序开发均为成熟方案,3人小团队2-3个月可出MVP- AI可用于:内容库初稿生成、后期智能问答顾问(降低咨询人力成本)- **真正的难点在内容而非技术**:需要中医顾问+文案的深度打磨,让报告"读起来准、用起来有效"## 2.3 政策与合规可行性 ⚠️(中,需严格设计)| 风险 | 等级 | 应对 ||------|------|------|| 被认定为封建迷信经营 | 高 | 定位"文化+心理测评+健康生活方式",全程无吉凶祸福表述;使用国标《中医体质分类与判定》做支撑 || 医疗宣称违规(广告法) | 高 | 只说"调理参考""生活建议",全程免责声明;不碰疾病、疗效字眼 || 个人信息保护(生日属敏感信息)| 中 | 明示授权、加密存储、允许注销删除 || 应用商店/微信审核 | 中 | 类目选择"健康/生活",避开"占卜算命"类目;内容预审 || 实物商品资质 | 中 | 食品类走代工厂SC资质,普通食品不宣称功效 |**结论:合规可行,但合规设计必须前置到产品文案层面,不是上线后补救。**## 2.4 财务可行性(MVP阶段测算)**启动投入(前6个月)估算:**| 项目 | 金额(万元) ||------|--------------|| 产品开发(小程序+报告引擎)| 15-25 || 内容库建设(顾问费+文案)| 8-15 || 冷启动投放与运营 | 10-20 || 团队人力(精简5人)| 30-40 || **合计** | **约60-100** |**盈亏平衡粗算:**- 假设客单价均值80元(个人报告+部分家庭报告),毛利率85%- 月成本约12万 → 需月付费单量约1800单- 引流→付费转化率按8%计 → 月需2.2万有效用户 → 依赖裂变系数>1.3或获客成本<50元/付费用户- **关键指标红线:首购CAC ≤ 60元,会员续费率 ≥ 40%,否则模型不成立**## 2.5 团队可行性(所需核心角色)产品负责人 / 中医内容顾问(可外聘签约)/ 全栈开发×1-2 / 内容运营(懂玄学话术+新媒体)/ 私域运营。初期5人内可跑。---# 三、商业逻辑设计## 3.1 商业模式画布(核心摘要)```价值主张:用一个生日,读懂一个人的性格与体质; 用一套五行,调和一个家庭的关系与健康。客户细分:C端家庭健康决策者 + 泛测评年轻人 + B端健康机构渠道通路:小红书/抖音内容种草 → 小程序 → 私域社群 → 会员/电商客户关系:测评(弱关系)→ 订阅推送(中关系)→ 顾问服务(强关系)收入来源:报告付费 + 会员订阅 + 咨询服务 + 实物电商 + B端授权核心资源:内容库、专家IP、私域用户资产、供应链关键活动:内容生产、裂变运营、顾问服务交付成本结构:人力(内容+运营)为主,流量投放为辅```## 3.2 商业飞轮(增长逻辑)```趣味测试引流 → 报告付费转化 → 家庭测试裂变(1人拉全家) ↑ ↓ 分享卡片传播 ← 节气推送留存 ← 会员订阅 → 咨询/商品复购```**三个关键杠杆:**1. **裂变杠杆**:家庭契合度天然要求输入家人生日——"帮妈妈测一测"是自带传播的产品机制,1个用户平均带入2-4个生日数据(也是潜在用户)2. **留存杠杆**:24节气推送 = 每年24次自然触达理由,把一次性测试变成全年陪伴3. **升单杠杆**:报告中埋"钩子"——报告指出问题(如"你的木旺体质今年需重点养肝")→ 会员给方案 → 商品/咨询做交付## 3.3 用户生命周期价值路径(LTV设计)| 阶段 | 动作 | 客单贡献 ||------|------|----------|| 第1周 | 免费简版 → 完整报告 | 39-99元 || 第1月 | 拉家人测契合度 | +199元 || 第2-3月 | 节气推送激活 → 年度会员 | +999元 || 持续 | 养生茶包/膳食包复购 | +300-800元/年 || 深度用户 | 一对一咨询/线下调理 | +500-3000元 |**目标LTV:普通用户150元,会员用户2000+元;用户结构目标——会员渗透率10-15%**---# 四、收费点设计(详细)## 4.1 收费点全景图```免费层(获客) ├─ 简版个人报告(数字+一句话性格+1条养生建议) ├─ 契合度"预览"(显示契合分数,解读锁定) └─ 节气通用养生贴士付费墙设计原则:免费给"结论",付费给"原因+方案"```## 4.2 收费产品矩阵| # | 产品 | 定价 | 定价逻辑 | 转化钩子 ||---|------|------|----------|----------|| 1 | 个人深度报告 | ¥39.9 | 冲动消费门槛内 | 简版报告结尾"你还有3项隐藏特质待解锁" || 2 | 个人尊享报告(含体质测评+年度养生建议)| ¥99 | 与39.9形成价格锚点 | 报告差异对比表 || 3 | 双人契合度报告(伴侣/亲子)| ¥129 | 关系类付费意愿更高 | 免费显示分数不显示解读 || 4 | 家庭图谱报告(3-6人)| ¥299-399 | 家庭决策者高客单 | "输入全家生日,看谁在消耗谁的能量" || 5 | 年度养生会员 | ¥999/年(首年促销599)| 对标健康管理服务 | 报告尾部+节气推送持续转化 || 6 | 一对一顾问咨询 | ¥299-599/次 | 对标心理咨询低位价 | 会员含1次,超出付费 || 7 | 实物商品 | ¥68-298/件 | 五行茶包/节气膳食包 | 报告中"你的专属调理方案"直链 || 8 | B端SaaS授权 | ¥9800-29800/年 | 机构获客工具+分销佣金 | 提供门店定制版报告 |## 4.3 会员权益结构(¥999/年)- ✅ 全家5人不限次报告生成- ✅ 24节气个性化养生方案推送- ✅ 每月家庭养生食谱日历- ✅ 1次一对一顾问咨询/年- ✅ 商城商品9折 + 会员专属礼盒- ✅ 会员社群(专家直播答疑)## 4.4 定价验证计划- MVP期用A/B测试跑三档报告价格(29.9/39.9/49.9),看转化率×客单的最优解- 会员先做"内测价"小批量(100人)验证续费意愿,再定正式价- 重点监控:**报告→会员转化率(目标≥8%)**,这是整个模型的命脉---# 五、风险清单与关键假设| 关键假设 | 验证方式 | 若不成立的Plan B ||----------|----------|-------------------|| 用户愿为"数字+体质"报告付费39.9元 | 落地页+人工MVP | 降价引流,靠会员和商品盈利 || 家庭测试能带来>1.3裂变系数 | MVP期埋点监测 | 转向投放驱动,主打P1人群 || 节气推送能拉动40%+续费 | 首年会员群体观察 | 加重咨询服务和实物权益 || 合规定位能通过平台审核 | 提前与平台类目沟通、法务预审 | 调整为纯"中医体质+健康管理"外壳 |---# 六、下一步行动(建议2周内完成)1. ✅ 确认本框架 → 明确MVP功能边界2. 🔲 落地页投放测试(验证需求真伪)3. 🔲 签约中医内容顾问,启动9数字×体质内容库大纲4. 🔲 法务/合规文案规范初稿(付费墙话术、免责声明模板)5. 🔲 完成后再进入 **Part 2:产品需求文档(PRD)+ 开发文档**---**接下来你想先深入哪一块?** 我建议的顺序是:1. **MVP功能边界清单**(决定开发范围和预算)2. **内容库结构设计**(这是产品灵魂,工作量最大,需尽早启动)3. **PRD开发文档**(前两项确定后再写,避免返工) \ No newline at end of file