create-dwy 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/package.json +1 -1
  2. package/src/create.js +50 -3
  3. package/src/utils.js +4 -0
  4. package/templates/claude-global/CLAUDE.md +81 -6
  5. package/templates/claude-global/rules/dwy-postgres.md +89 -0
  6. package/templates/claude-global/rules/dwy-python-backend.md +492 -0
  7. package/templates/claude-global/rules/dwy-python-core.md +94 -634
  8. package/templates/claude-global/rules/dwy-python-testing.md +115 -13
  9. package/templates/claude-global/rules/dwy-redis.md +88 -0
  10. package/templates/claude-global/rules/dwy-vue-core.md +341 -645
  11. package/templates/claude-global/rules/dwy-vue-pinia.md +47 -34
  12. package/templates/claude-global/rules/dwy-vue-testing.md +120 -55
  13. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-network.md +1 -1
  14. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_nginx.sh +1 -1
  15. package/templates/claude-global/skills/dwy-dolphindb/SKILL.md +22 -15
  16. package/templates/claude-global/skills/dwy-dolphindb/evals/evals.json +23 -0
  17. package/templates/claude-global/skills/dwy-dolphindb/references/dolphindb-rules.md +100 -1
  18. package/templates/claude-global/skills/dwy-ekit/SKILL.md +142 -276
  19. package/templates/claude-global/skills/dwy-eui/SKILL.md +264 -182
  20. package/templates/claude-global/skills/dwy-fullstack-scaffold/SKILL.md +117 -129
  21. package/templates/claude-global/skills/dwy-git-commit/SKILL.md +125 -0
  22. package/templates/claude-global/skills/dwy-publish/SKILL.md +1 -1
  23. package/templates/claude-global/{rules/dwy-tech-stack.md → skills/dwy-tech-stack/SKILL.md} +4 -3
  24. package/templates/project/fullstack-monorepo/CLAUDE.md.hbs +81 -0
  25. package/templates/project/fullstack-monorepo/backend/.python-version +1 -0
  26. package/templates/project/fullstack-monorepo/backend/CLAUDE.md.hbs +68 -0
  27. package/templates/project/fullstack-monorepo/backend/Dockerfile.dev +23 -0
  28. package/templates/project/fullstack-monorepo/backend/alembic/env.py.hbs +68 -0
  29. package/templates/project/fullstack-monorepo/backend/alembic/script.py.mako +26 -0
  30. package/templates/project/fullstack-monorepo/backend/alembic.ini.hbs +43 -0
  31. package/templates/project/fullstack-monorepo/backend/packages/dwy-shared/pyproject.toml +16 -0
  32. package/templates/project/fullstack-monorepo/backend/packages/dwy-shared/src/dwy_shared/__init__.py +12 -0
  33. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/pyproject.toml.hbs +20 -0
  34. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/__init__.py.hbs +40 -0
  35. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/config.py +9 -0
  36. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/models.py.hbs +17 -0
  37. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/protocols.py +14 -0
  38. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/router.py.hbs +22 -0
  39. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/schemas.py +21 -0
  40. package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/service.py +11 -0
  41. package/templates/project/fullstack-monorepo/backend/pyproject.toml.hbs +84 -0
  42. package/templates/project/fullstack-monorepo/backend/src/app/__init__.py +3 -0
  43. package/templates/project/fullstack-monorepo/backend/src/app/config.py.hbs +21 -0
  44. package/templates/project/fullstack-monorepo/backend/src/app/exceptions.py +26 -0
  45. package/templates/project/fullstack-monorepo/backend/src/app/factories.py +16 -0
  46. package/templates/project/fullstack-monorepo/backend/src/app/infra/__init__.py +1 -0
  47. package/templates/project/fullstack-monorepo/backend/src/app/main.py.hbs +65 -0
  48. package/templates/project/fullstack-monorepo/backend/src/app/middleware/__init__.py +1 -0
  49. package/templates/project/fullstack-monorepo/backend/tests/__init__.py +0 -0
  50. package/templates/project/fullstack-monorepo/backend/tests/conftest.py.hbs +42 -0
  51. package/templates/project/fullstack-monorepo/dev.sh.hbs +83 -0
  52. package/templates/project/fullstack-monorepo/docker-compose.dev.yml.hbs +36 -0
  53. package/templates/project/fullstack-monorepo/frontend/CLAUDE.md.hbs +35 -0
  54. package/templates/project/fullstack-monorepo/frontend/Dockerfile +18 -0
  55. package/templates/project/fullstack-monorepo/frontend/apps/web/index.html.hbs +13 -0
  56. package/templates/project/fullstack-monorepo/frontend/apps/web/package.json.hbs +31 -0
  57. package/templates/project/fullstack-monorepo/frontend/apps/web/src/App.vue.hbs +10 -0
  58. package/templates/project/fullstack-monorepo/frontend/apps/web/src/api/client.ts.hbs +11 -0
  59. package/templates/project/fullstack-monorepo/frontend/apps/web/src/features/{{initialDomain}}/.gitkeep +0 -0
  60. package/templates/project/fullstack-monorepo/frontend/apps/web/src/index.css +12 -0
  61. package/templates/project/fullstack-monorepo/frontend/apps/web/src/main.ts +17 -0
  62. package/templates/project/fullstack-monorepo/frontend/apps/web/src/router/index.ts.hbs +24 -0
  63. package/templates/project/fullstack-monorepo/frontend/apps/web/src/stores/.gitkeep +0 -0
  64. package/templates/project/fullstack-monorepo/frontend/apps/web/src/types/.gitkeep +0 -0
  65. package/templates/project/fullstack-monorepo/frontend/apps/web/src/views/HomeView.vue.hbs +36 -0
  66. package/templates/project/fullstack-monorepo/frontend/apps/web/tsconfig.json +7 -0
  67. package/templates/project/fullstack-monorepo/frontend/apps/web/vite.config.ts.hbs +27 -0
  68. package/templates/project/fullstack-monorepo/frontend/nginx.conf +11 -0
  69. package/templates/project/fullstack-monorepo/frontend/package.json.hbs +13 -0
  70. package/templates/project/fullstack-monorepo/frontend/packages/core/package.json.hbs +15 -0
  71. package/templates/project/fullstack-monorepo/frontend/packages/core/src/index.ts +3 -0
  72. package/templates/project/fullstack-monorepo/frontend/packages/features/{{initialDomain}}/package.json.hbs +14 -0
  73. package/templates/project/fullstack-monorepo/frontend/packages/features/{{initialDomain}}/src/index.ts.hbs +16 -0
  74. package/templates/project/fullstack-monorepo/frontend/packages/features/{{initialDomain}}/src/pages/IndexPage.vue.hbs +9 -0
  75. package/templates/project/fullstack-monorepo/frontend/packages/ui/package.json.hbs +15 -0
  76. package/templates/project/fullstack-monorepo/frontend/packages/ui/src/index.ts +3 -0
  77. package/templates/project/fullstack-monorepo/frontend/pnpm-workspace.yaml.hbs +23 -0
  78. package/templates/project/fullstack-monorepo/frontend/tsconfig.base.json.hbs +23 -0
  79. package/templates/claude-global/rules/dwy-api-design.md +0 -110
  80. package/templates/claude-global/rules/dwy-git-commit-convention.md +0 -82
  81. package/templates/claude-global/rules/dwy-git-security.md +0 -53
  82. package/templates/claude-global/rules/dwy-payload-limits.md +0 -186
  83. package/templates/claude-global/rules/dwy-prefer-open-source.md +0 -32
  84. package/templates/claude-global/rules/dwy-project-structure.md +0 -181
  85. package/templates/claude-global/rules/dwy-python-fastapi.md +0 -272
  86. package/templates/claude-global/rules/dwy-python-orm.md +0 -61
  87. package/templates/claude-global/rules/dwy-python-security.md +0 -636
  88. package/templates/claude-global/rules/dwy-server-security.md +0 -518
  89. package/templates/claude-global/rules/dwy-vue-api.md +0 -146
  90. package/templates/claude-global/skills/dwy-ekit/references/data.md +0 -135
  91. package/templates/claude-global/skills/dwy-ekit/references/form.md +0 -68
  92. package/templates/claude-global/skills/dwy-ekit/references/request.md +0 -183
  93. package/templates/claude-global/skills/dwy-ekit/references/ui.md +0 -109
  94. package/templates/claude-global/skills/dwy-vue-core/SKILL.md +0 -890
  95. /package/templates/{claude-global/commands → project/fullstack-monorepo/backend/alembic/versions}/.gitkeep +0 -0
@@ -0,0 +1,42 @@
1
+ """测试通用 fixture。
2
+
3
+ 测试环境变量必须在 import app 之前设置,否则 Settings 实例化会失败。
4
+ """
5
+
6
+ import os
7
+
8
+ # ── 测试环境变量(必须在导入应用前设置)──
9
+ os.environ.setdefault("ENVIRONMENT", "dev")
10
+ os.environ.setdefault("DATABASE_URL", "sqlite+aiosqlite:///./test.db")
11
+ os.environ.setdefault("SECRET_KEY", "test-secret-only-for-tests")
12
+ os.environ.setdefault("REDIS_URL", "redis://localhost:{{portPrefix}}6379/0")
13
+
14
+ from collections.abc import AsyncGenerator # noqa: E402
15
+
16
+ import pytest_asyncio # noqa: E402
17
+ from httpx import ASGITransport, AsyncClient # noqa: E402
18
+
19
+
20
+ @pytest_asyncio.fixture(autouse=True)
21
+ async def setup_db() -> AsyncGenerator[None]:
22
+ """每个测试用例前后创建/销毁表。"""
23
+ from dwyeapi.database import Base
24
+
25
+ from app.main import app # noqa: F401 -- 触发应用装配
26
+ from dwy_shared.database import engine # 若 dwy-shared 没有 engine,则改 import 来源
27
+
28
+ async with engine.begin() as conn:
29
+ await conn.run_sync(Base.metadata.create_all)
30
+ yield
31
+ async with engine.begin() as conn:
32
+ await conn.run_sync(Base.metadata.drop_all)
33
+
34
+
35
+ @pytest_asyncio.fixture
36
+ async def client() -> AsyncGenerator[AsyncClient]:
37
+ """异步 HTTP 测试客户端。"""
38
+ from app.main import app
39
+
40
+ transport = ASGITransport(app=app)
41
+ async with AsyncClient(transport=transport, base_url="http://test") as ac:
42
+ yield ac
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ BACKEND_PORT={{portPrefix}}8001
6
+ FRONTEND_PORT={{portPrefix}}5173
7
+ BACKEND_PID=""
8
+ FRONTEND_PID=""
9
+
10
+ # ── 清理函数 ──
11
+
12
+ cleanup() {
13
+ echo ""
14
+ echo "正在关闭服务..."
15
+ [ -n "$FRONTEND_PID" ] && kill "$FRONTEND_PID" 2>/dev/null
16
+ [ -n "$BACKEND_PID" ] && kill "$BACKEND_PID" 2>/dev/null
17
+ wait 2>/dev/null
18
+ echo "已关闭前后端进程。Docker 基础设施保持运行。"
19
+ }
20
+ trap cleanup EXIT
21
+
22
+ kill_port() {
23
+ local port=$1
24
+ local pids
25
+ pids=$(lsof -ti:"$port" 2>/dev/null || true)
26
+ if [ -n "$pids" ]; then
27
+ echo " 端口 $port 被占用 (PID: $pids),正在释放..."
28
+ echo "$pids" | xargs kill -9 2>/dev/null || true
29
+ sleep 0.5
30
+ fi
31
+ }
32
+
33
+ # ── 1. 释放被占用的端口 ──
34
+
35
+ echo "▶ 检查端口占用..."
36
+ kill_port $BACKEND_PORT
37
+ kill_port $FRONTEND_PORT
38
+
39
+ # ── 2. 启动 Docker 基础设施 ──
40
+
41
+ echo "▶ 启动 Docker 基础设施..."
42
+ docker compose -f "$SCRIPT_DIR/docker-compose.dev.yml" up -d --remove-orphans
43
+
44
+ echo "▶ 等待 PostgreSQL 就绪..."
45
+ until docker compose -f "$SCRIPT_DIR/docker-compose.dev.yml" exec -T db pg_isready > /dev/null 2>&1; do
46
+ sleep 1
47
+ done
48
+
49
+ echo "▶ 等待 Redis 就绪..."
50
+ until docker compose -f "$SCRIPT_DIR/docker-compose.dev.yml" exec -T redis redis-cli ping > /dev/null 2>&1; do
51
+ sleep 1
52
+ done
53
+
54
+ echo ""
55
+ echo "✔ 基础设施就绪"
56
+ echo " PostgreSQL → 127.0.0.1:{{portPrefix}}5432"
57
+ echo " Redis → 127.0.0.1:{{portPrefix}}6379"
58
+ echo ""
59
+
60
+ # ── 3. 启动后端 ──
61
+
62
+ echo "▶ 启动后端 (port $BACKEND_PORT)..."
63
+ cd "$SCRIPT_DIR/backend"
64
+ uv run uvicorn app.main:app --reload --port $BACKEND_PORT &
65
+ BACKEND_PID=$!
66
+
67
+ # ── 4. 启动前端 ──
68
+
69
+ echo "▶ 启动前端 (port $FRONTEND_PORT)..."
70
+ cd "$SCRIPT_DIR/frontend"
71
+ pnpm dev &
72
+ FRONTEND_PID=$!
73
+
74
+ echo ""
75
+ echo "════════════════════════════════════════"
76
+ echo " {{projectName}} 开发环境已启动"
77
+ echo " 前端: http://localhost:$FRONTEND_PORT"
78
+ echo " 后端: http://localhost:$BACKEND_PORT"
79
+ echo " 按 Ctrl+C 关闭前后端"
80
+ echo "════════════════════════════════════════"
81
+ echo ""
82
+
83
+ wait
@@ -0,0 +1,36 @@
1
+ services:
2
+ db:
3
+ image: postgres:16.12-alpine3.23
4
+ ports:
5
+ - "127.0.0.1:{{portPrefix}}5432:5432"
6
+ environment:
7
+ TZ: Asia/Shanghai
8
+ PGTZ: Asia/Shanghai
9
+ POSTGRES_DB: ${POSTGRES_DB}
10
+ POSTGRES_USER: ${POSTGRES_USER}
11
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
12
+ command: ["postgres", "-c", "timezone=Asia/Shanghai"]
13
+ volumes:
14
+ - pgdata:/var/lib/postgresql/data
15
+ healthcheck:
16
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
17
+ interval: 5s
18
+ timeout: 5s
19
+ retries: 5
20
+
21
+ redis:
22
+ image: redis:7.4.9-alpine3.21
23
+ ports:
24
+ - "127.0.0.1:{{portPrefix}}6379:6379"
25
+ command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
26
+ volumes:
27
+ - redisdata:/data
28
+ healthcheck:
29
+ test: ["CMD", "redis-cli", "ping"]
30
+ interval: 5s
31
+ timeout: 3s
32
+ retries: 5
33
+
34
+ volumes:
35
+ pgdata:
36
+ redisdata:
@@ -0,0 +1,35 @@
1
+ # Frontend 架构约束
2
+
3
+ ## 技术栈
4
+
5
+ - Vue 3 + TypeScript + Vite + Pinia + Vue Router
6
+ - 组件库:`@dwydev/eui`(**禁止**引入 Element Plus、Ant Design Vue、Naive UI 等其他组件库)
7
+ - 工具库:`@dwydev/ekit`(HTTP、storage、cookie、date、validators、copy、qs、file、hooks、masking)
8
+
9
+ ## 仓库布局
10
+
11
+ pnpm workspace monorepo:
12
+
13
+ - `apps/web/` — 主 SPA 应用,唯一入口
14
+ - `packages/core/` — 横切核心(HTTP / config / format / 加密等通用工具)
15
+ - `packages/ui/` — 项目级共享 UI 组件(基于 `@dwydev/eui` 二次封装)
16
+ - `packages/features/<domain>/` — 业务域分包,每个 feature 自管路由 + 页面
17
+
18
+ ## 依赖管理
19
+
20
+ 所有第三方包版本统一在 `pnpm-workspace.yaml` 的 `catalog:` 字段管理,子包用 `catalog:` 引用。
21
+
22
+ ## 加新业务域 feature
23
+
24
+ 复制 `packages/features/{{initialDomain}}/` 改名 → 在 `apps/web/src/router/index.ts` 用 `createFeatureRoutes()` 注册。
25
+
26
+ ## 禁止事项
27
+
28
+ - 禁止重复造轮子:HTTP 用 `@dwydev/ekit` 的 `createRequest`,日期用其 date 模块,剪贴板/防抖等用其 hooks
29
+ - 禁止在 `apps/web/` 中堆积业务页面,新业务必须放到 `packages/features/<domain>/`
30
+ - 禁止跨 feature 直接 import 对方内部模块,必须经由 `packages/core` 或事件总线
31
+
32
+ ## 测试
33
+
34
+ - 类型检查:`pnpm typecheck`
35
+ - 构建:`pnpm build`
@@ -0,0 +1,18 @@
1
+ # 构建阶段
2
+ FROM node:20-alpine AS builder
3
+
4
+ WORKDIR /app
5
+
6
+ COPY . .
7
+
8
+ RUN corepack enable && pnpm install --frozen-lockfile && pnpm build
9
+
10
+ # 生产阶段
11
+ FROM nginx:alpine
12
+
13
+ COPY --from=builder /app/apps/web/dist /usr/share/nginx/html
14
+ COPY nginx.conf /etc/nginx/conf.d/default.conf
15
+
16
+ EXPOSE 80
17
+
18
+ CMD ["nginx", "-g", "daemon off;"]
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="description" content="{{projectDescription}}" />
7
+ <title>{{projectName}}</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@{{projectName}}/web",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "vue-tsc -b && vite build",
8
+ "preview": "vite preview",
9
+ "typecheck": "vue-tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "vue": "catalog:",
13
+ "vue-router": "catalog:",
14
+ "pinia": "catalog:",
15
+ "axios": "catalog:",
16
+ "@dwydev/eui": "catalog:",
17
+ "@dwydev/ekit": "catalog:",
18
+ "@{{projectName}}/core": "workspace:*",
19
+ "@{{projectName}}/ui": "workspace:*",
20
+ "@{{projectName}}/features-{{initialDomain}}": "workspace:*"
21
+ },
22
+ "devDependencies": {
23
+ "vite": "catalog:",
24
+ "typescript": "catalog:",
25
+ "vue-tsc": "catalog:",
26
+ "@vitejs/plugin-vue": "catalog:",
27
+ "@tailwindcss/vite": "catalog:",
28
+ "tailwindcss": "catalog:",
29
+ "@vue/tsconfig": "catalog:"
30
+ }
31
+ }
@@ -0,0 +1,10 @@
1
+ <script setup lang="ts">
2
+ import { EConfigProvider, EToast } from '@dwydev/eui'
3
+ </script>
4
+
5
+ <template>
6
+ <EConfigProvider>
7
+ <RouterView />
8
+ <EToast />
9
+ </EConfigProvider>
10
+ </template>
@@ -0,0 +1,11 @@
1
+ import { createRequest } from '@dwydev/ekit'
2
+
3
+ /**
4
+ * 全局 HTTP 客户端。
5
+ * - baseURL `/api` 走 vite dev proxy 转发到后端
6
+ * - token 注入 / 401 refresh / 响应 unwrap 由 ekit 插件链处理,按需开启
7
+ */
8
+ export const http = createRequest({
9
+ baseURL: '/api',
10
+ timeout: 15000,
11
+ })
@@ -0,0 +1,12 @@
1
+ @import "tailwindcss";
2
+
3
+ html,
4
+ body,
5
+ #app {
6
+ height: 100%;
7
+ margin: 0;
8
+ }
9
+
10
+ body {
11
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
12
+ }
@@ -0,0 +1,17 @@
1
+ import { createApp } from 'vue'
2
+ import { createPinia } from 'pinia'
3
+ import { EUI } from '@dwydev/eui'
4
+
5
+ import App from './App.vue'
6
+ import router from './router'
7
+
8
+ import '@dwydev/eui/theme/tokens.css'
9
+ import './index.css'
10
+
11
+ const app = createApp(App)
12
+
13
+ app.use(createPinia())
14
+ app.use(router)
15
+ app.use(EUI)
16
+
17
+ app.mount('#app')
@@ -0,0 +1,24 @@
1
+ import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
2
+ import { createFeatureRoutes } from '@{{projectName}}/features-{{initialDomain}}'
3
+
4
+ const routes: RouteRecordRaw[] = [
5
+ {
6
+ path: '/',
7
+ name: 'home',
8
+ component: () => import('../views/HomeView.vue'),
9
+ meta: { title: '{{projectName}}' },
10
+ },
11
+ ...createFeatureRoutes(),
12
+ ]
13
+
14
+ const router = createRouter({
15
+ history: createWebHistory(),
16
+ routes,
17
+ })
18
+
19
+ router.afterEach((to) => {
20
+ const title = (to.meta?.title as string | undefined) || '{{projectName}}'
21
+ document.title = title
22
+ })
23
+
24
+ export default router
@@ -0,0 +1,36 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { http } from '../api/client'
4
+
5
+ const status = ref<string>('')
6
+ const loading = ref(false)
7
+
8
+ async function checkBackend() {
9
+ loading.value = true
10
+ try {
11
+ const res = await http.get('/health')
12
+ status.value = JSON.stringify(res, null, 2)
13
+ } catch (err) {
14
+ status.value = `error: ${(err as Error).message}`
15
+ } finally {
16
+ loading.value = false
17
+ }
18
+ }
19
+ </script>
20
+
21
+ <template>
22
+ <main class="min-h-screen flex flex-col items-center justify-center gap-6 p-8">
23
+ <h1 class="text-3xl font-semibold">Welcome to {{projectName}}</h1>
24
+ <p class="text-sm text-gray-500">{{projectDescription}}</p>
25
+
26
+ <button
27
+ class="px-4 py-2 rounded-md bg-black text-white text-sm disabled:opacity-50"
28
+ :disabled="loading"
29
+ @click="checkBackend"
30
+ >
31
+ \{{ loading ? 'Checking...' : 'Check backend /api/health' }}
32
+ </button>
33
+
34
+ <pre v-if="status" class="text-xs bg-gray-100 p-3 rounded max-w-xl overflow-auto">\{{ status }}</pre>
35
+ </main>
36
+ </template>
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "types": ["vite/client"]
5
+ },
6
+ "include": ["src/**/*", "src/**/*.vue"]
7
+ }
@@ -0,0 +1,27 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+ import tailwindcss from '@tailwindcss/vite'
4
+ import { fileURLToPath, URL } from 'node:url'
5
+
6
+ export default defineConfig({
7
+ plugins: [vue(), tailwindcss()],
8
+ resolve: {
9
+ alias: {
10
+ '@': fileURLToPath(new URL('./src', import.meta.url)),
11
+ '@{{projectName}}/core': fileURLToPath(new URL('../../packages/core/src/index.ts', import.meta.url)),
12
+ '@{{projectName}}/ui': fileURLToPath(new URL('../../packages/ui/src/index.ts', import.meta.url)),
13
+ '@{{projectName}}/features-{{initialDomain}}': fileURLToPath(
14
+ new URL('../../packages/features/{{initialDomain}}/src/index.ts', import.meta.url),
15
+ ),
16
+ },
17
+ },
18
+ server: {
19
+ port: {{portPrefix}}5173,
20
+ proxy: {
21
+ '/api': {
22
+ target: '{{apiProxy}}',
23
+ changeOrigin: true,
24
+ },
25
+ },
26
+ },
27
+ })
@@ -0,0 +1,11 @@
1
+ server {
2
+ listen 80;
3
+ server_name _;
4
+
5
+ root /usr/share/nginx/html;
6
+ index index.html;
7
+
8
+ location / {
9
+ try_files $uri $uri/ /index.html;
10
+ }
11
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "{{projectName}}-frontend-workspace",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "pnpm --filter web dev",
7
+ "build": "pnpm -r build",
8
+ "preview": "pnpm --filter web preview",
9
+ "typecheck": "pnpm -r typecheck"
10
+ },
11
+ "devDependencies": {},
12
+ "packageManager": "pnpm@10.19.0"
13
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@{{projectName}}/core",
3
+ "private": true,
4
+ "type": "module",
5
+ "main": "./src/index.ts",
6
+ "types": "./src/index.ts",
7
+ "dependencies": {
8
+ "axios": "catalog:",
9
+ "zod": "catalog:",
10
+ "@dwydev/ekit": "catalog:"
11
+ },
12
+ "peerDependencies": {
13
+ "vue": "catalog:"
14
+ }
15
+ }
@@ -0,0 +1,3 @@
1
+ // 横切核心:HTTP / config / format / 加密等通用工具
2
+ // 业务无关,可被任意 feature 包消费
3
+ export {}
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "@{{projectName}}/features-{{initialDomain}}",
3
+ "private": true,
4
+ "type": "module",
5
+ "main": "./src/index.ts",
6
+ "types": "./src/index.ts",
7
+ "dependencies": {
8
+ "@{{projectName}}/core": "workspace:*",
9
+ "@{{projectName}}/ui": "workspace:*",
10
+ "vue": "catalog:",
11
+ "vue-router": "catalog:",
12
+ "@dwydev/eui": "catalog:"
13
+ }
14
+ }
@@ -0,0 +1,16 @@
1
+ import type { RouteRecordRaw } from 'vue-router'
2
+
3
+ /**
4
+ * 业务域 `{{initialDomain}}` 的路由工厂。
5
+ * 主 app 通过 `...createFeatureRoutes()` 装载本域路由。
6
+ */
7
+ export function createFeatureRoutes(): RouteRecordRaw[] {
8
+ return [
9
+ {
10
+ path: '/{{initialDomain}}',
11
+ name: '{{initialDomain}}-index',
12
+ component: () => import('./pages/IndexPage.vue'),
13
+ meta: { title: '{{initialDomain}}' },
14
+ },
15
+ ]
16
+ }
@@ -0,0 +1,9 @@
1
+ <script setup lang="ts">
2
+ </script>
3
+
4
+ <template>
5
+ <div class="p-8">
6
+ <h2 class="text-xl font-semibold">{{initialDomain}} feature index</h2>
7
+ <p class="text-sm text-gray-500 mt-2">Edit packages/features/{{initialDomain}}/src/pages/IndexPage.vue</p>
8
+ </div>
9
+ </template>
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@{{projectName}}/ui",
3
+ "private": true,
4
+ "type": "module",
5
+ "main": "./src/index.ts",
6
+ "types": "./src/index.ts",
7
+ "dependencies": {
8
+ "@dwydev/eui": "catalog:",
9
+ "@dwydev/ekit": "catalog:"
10
+ },
11
+ "peerDependencies": {
12
+ "vue": "catalog:",
13
+ "vue-router": "catalog:"
14
+ }
15
+ }
@@ -0,0 +1,3 @@
1
+ // 项目级共享 UI 组件(基于 @dwydev/eui 二次封装)
2
+ // 仅放跨 feature 复用的展示型组件,不放业务逻辑
3
+ export {}
@@ -0,0 +1,23 @@
1
+ packages:
2
+ - 'apps/*'
3
+ - 'packages/*'
4
+ - 'packages/features/*'
5
+
6
+ catalog:
7
+ vue: ^3.5.32
8
+ vue-router: ^4.6.4
9
+ pinia: ^3.0.4
10
+ axios: ^1.7.0
11
+ dayjs: ^1.11.13
12
+ zod: ^3.23.0
13
+ '@vueuse/core': ^11.0.0
14
+ lucide-vue-next: ^0.460.0
15
+ tailwindcss: ^4.0.0
16
+ '@tailwindcss/vite': ^4.0.0
17
+ '@dwydev/eui': ^2.1.0
18
+ '@dwydev/ekit': ^0.8.0
19
+ vite: ^8.0.0
20
+ typescript: ^5.6.0
21
+ vue-tsc: ^2.1.0
22
+ '@vitejs/plugin-vue': ^5.1.0
23
+ '@vue/tsconfig': ^0.7.0
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "jsx": "preserve",
14
+ "useDefineForClassFields": true,
15
+ "baseUrl": ".",
16
+ "paths": {
17
+ "@/*": ["apps/web/src/*"],
18
+ "@{{projectName}}/core": ["packages/core/src/index.ts"],
19
+ "@{{projectName}}/ui": ["packages/ui/src/index.ts"],
20
+ "@{{projectName}}/features-{{initialDomain}}": ["packages/features/{{initialDomain}}/src/index.ts"]
21
+ }
22
+ }
23
+ }