@robsun/create-keystone-app 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -10
- package/bin/create-keystone-app.js +199 -60
- package/package.json +1 -1
- package/template/.husky/pre-commit +4 -0
- package/template/.lintstagedrc.json +5 -0
- package/template/.prettierrc +9 -0
- package/template/README.md +45 -23
- package/template/apps/server/.air.toml +44 -0
- package/template/apps/server/README.md +27 -0
- package/template/apps/server/cmd/server/main.go +213 -0
- package/template/apps/server/config.yaml +51 -0
- package/template/apps/server/docs/docs.go +34 -0
- package/template/apps/server/go.mod +13 -0
- package/template/apps/server/go.sum +72 -0
- package/template/apps/server/internal/app/routes/module_routes.go +16 -0
- package/template/apps/server/internal/app/routes/routes.go +226 -0
- package/template/apps/server/internal/app/startup/startup.go +74 -0
- package/template/apps/server/internal/frontend/dist/.gitkeep +1 -0
- package/template/apps/server/internal/frontend/embed.go +28 -0
- package/template/apps/server/internal/frontend/handler.go +122 -0
- package/template/apps/server/internal/{demo/demo.go → modules/demo/handlers.go} +4 -1
- package/template/apps/server/internal/modules/demo/module.go +55 -0
- package/template/apps/server/internal/modules/manifest.go +11 -0
- package/template/apps/server/internal/modules/registry.go +145 -0
- package/template/apps/web/.env.example +3 -0
- package/template/apps/web/README.md +29 -0
- package/template/apps/web/eslint.config.js +35 -0
- package/template/apps/web/package.json +27 -10
- package/template/apps/web/postcss.config.js +6 -0
- package/template/apps/web/src/index.css +3 -0
- package/template/apps/web/src/main.tsx +1 -0
- package/template/apps/web/src/modules/demo/help/overview.md +12 -0
- package/template/apps/web/src/modules/demo/routes.tsx +2 -0
- package/template/apps/web/tailwind.config.js +18 -0
- package/template/apps/web/tests/setup.ts +37 -0
- package/template/apps/web/tsconfig.app.json +3 -3
- package/template/apps/web/vite.config.ts +28 -2
- package/template/docker-compose.yml +45 -0
- package/template/docs/CONVENTIONS.md +61 -88
- package/template/package.json +15 -3
- package/template/scripts/build.bat +133 -0
- package/template/scripts/build.js +25 -0
- package/template/scripts/build.sh +99 -0
- package/template/scripts/clean.bat +35 -0
- package/template/scripts/clean.js +25 -0
- package/template/scripts/clean.sh +34 -0
- package/template/scripts/dev.bat +82 -0
- package/template/scripts/dev.js +25 -0
- package/template/scripts/dev.sh +88 -0
- package/template/scripts/test.bat +86 -0
- package/template/scripts/test.js +25 -0
- package/template/scripts/test.sh +86 -0
- package/template/apps/server/main.go +0 -28
- /package/template/{config.yaml → apps/server/config.example.yaml} +0 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Web Shell
|
|
2
|
+
|
|
3
|
+
## 定位
|
|
4
|
+
- 负责渲染平台 UI,业务模块统一放在 `src/modules/*`。
|
|
5
|
+
- 平台核心能力由 `@robsun/keystone-web-core` 提供。
|
|
6
|
+
|
|
7
|
+
## 入口
|
|
8
|
+
- `src/main.tsx`:应用入口。
|
|
9
|
+
- `src/app.config.ts`:品牌与模块启用配置。
|
|
10
|
+
- `src/modules/<module>/index.ts`:模块注册入口。
|
|
11
|
+
- `apps/web/.env.example`:前端环境变量模板(Vite 读取)。
|
|
12
|
+
|
|
13
|
+
## 新建模块
|
|
14
|
+
1) 创建目录:`src/modules/<module>`。
|
|
15
|
+
2) 在 `index.ts` 调用 `registerModule` 注册路由。
|
|
16
|
+
3) 在 `routes.tsx` 配置菜单、权限、帮助文档。
|
|
17
|
+
4) 在 `app.config.ts` 的 `modules.enabled` 启用模块。
|
|
18
|
+
|
|
19
|
+
## 帮助文档
|
|
20
|
+
- 放在 `src/modules/<module>/help/**`。
|
|
21
|
+
- `helpKey` 与路由 `handle.helpKey` 保持一致。
|
|
22
|
+
|
|
23
|
+
## 常用命令
|
|
24
|
+
- 开发:`pnpm web:dev`
|
|
25
|
+
- Lint:`pnpm -C apps/web lint`
|
|
26
|
+
- 测试:`pnpm -C apps/web test`
|
|
27
|
+
|
|
28
|
+
## 嵌入式构建
|
|
29
|
+
- `pnpm build` 会将产物写入 `apps/server/internal/frontend/dist`。
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from 'typescript-eslint'
|
|
6
|
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
7
|
+
|
|
8
|
+
export default defineConfig([
|
|
9
|
+
globalIgnores([
|
|
10
|
+
'dist',
|
|
11
|
+
'build',
|
|
12
|
+
'*.min.js',
|
|
13
|
+
'coverage',
|
|
14
|
+
'node_modules',
|
|
15
|
+
'.turbo',
|
|
16
|
+
'.pnpm-store',
|
|
17
|
+
'apps/server/tmp',
|
|
18
|
+
'data',
|
|
19
|
+
'*.log',
|
|
20
|
+
'.env*',
|
|
21
|
+
]),
|
|
22
|
+
{
|
|
23
|
+
files: ['**/*.{ts,tsx}'],
|
|
24
|
+
extends: [
|
|
25
|
+
js.configs.recommended,
|
|
26
|
+
tseslint.configs.recommended,
|
|
27
|
+
reactHooks.configs.flat.recommended,
|
|
28
|
+
reactRefresh.configs.vite,
|
|
29
|
+
],
|
|
30
|
+
languageOptions: {
|
|
31
|
+
ecmaVersion: 2020,
|
|
32
|
+
globals: globals.browser,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
])
|
|
@@ -6,28 +6,45 @@
|
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "vite",
|
|
8
8
|
"build": "vite build",
|
|
9
|
-
"
|
|
9
|
+
"build:strict": "tsc -b && vite build",
|
|
10
|
+
"lint": "eslint .",
|
|
11
|
+
"preview": "vite preview",
|
|
12
|
+
"lint:fix": "eslint . --fix",
|
|
13
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
14
|
+
"format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"test": "vitest"
|
|
10
17
|
},
|
|
11
18
|
"dependencies": {
|
|
12
19
|
"@ant-design/icons": "^6.1.0",
|
|
13
20
|
"@robsun/keystone-web-core": "0.1.7",
|
|
14
21
|
"antd": "^6.0.1",
|
|
15
|
-
"cookie": "^1.1.1",
|
|
16
22
|
"dayjs": "^1.11.19",
|
|
17
|
-
"debug": "^4.4.3",
|
|
18
|
-
"extend": "^3.0.2",
|
|
19
23
|
"react": "^19.2.0",
|
|
20
24
|
"react-dom": "^19.2.0",
|
|
21
|
-
"react-router": "^7.10.1"
|
|
22
|
-
"react-router-dom": "^7.10.1",
|
|
23
|
-
"style-to-js": "^1.1.21"
|
|
25
|
+
"react-router-dom": "^7.10.1"
|
|
24
26
|
},
|
|
25
27
|
"devDependencies": {
|
|
28
|
+
"@eslint/js": "^9.39.1",
|
|
29
|
+
"@tailwindcss/postcss": "^4.1.17",
|
|
30
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
31
|
+
"@testing-library/react": "^16.2.0",
|
|
32
|
+
"@testing-library/user-event": "^14.6.1",
|
|
26
33
|
"@types/node": "^24.10.1",
|
|
27
34
|
"@types/react": "^19.2.5",
|
|
28
35
|
"@types/react-dom": "^19.2.3",
|
|
36
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
37
|
+
"autoprefixer": "^10.4.22",
|
|
38
|
+
"eslint": "^9.39.1",
|
|
39
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
40
|
+
"eslint-plugin-react-refresh": "^0.4.24",
|
|
41
|
+
"globals": "^16.5.0",
|
|
42
|
+
"jsdom": "^24.1.0",
|
|
43
|
+
"postcss": "^8.5.6",
|
|
44
|
+
"tailwindcss": "^4.1.17",
|
|
29
45
|
"typescript": "~5.9.3",
|
|
30
|
-
"
|
|
46
|
+
"typescript-eslint": "^8.46.4",
|
|
47
|
+
"vite": "^7.2.4",
|
|
48
|
+
"vitest": "^2.1.4"
|
|
31
49
|
}
|
|
32
|
-
}
|
|
33
|
-
|
|
50
|
+
}
|
|
@@ -4,6 +4,7 @@ import dayjs from 'dayjs'
|
|
|
4
4
|
import { KeystoneApp, getKeystoneConfig, setKeystoneConfig } from '@robsun/keystone-web-core'
|
|
5
5
|
import { appConfig } from './app.config'
|
|
6
6
|
import '@robsun/keystone-web-core/styles/keystone.css'
|
|
7
|
+
import './index.css'
|
|
7
8
|
import './modules/demo'
|
|
8
9
|
|
|
9
10
|
setKeystoneConfig(appConfig)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
helpKey: "demo/tasks"
|
|
3
|
+
title: "Demo Tasks"
|
|
4
|
+
description: "Manage sample tasks in the scaffold."
|
|
5
|
+
category: "demo"
|
|
6
|
+
tags: ["demo", "tasks"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Demo Tasks
|
|
10
|
+
|
|
11
|
+
Use this module as a reference for list + create + update flows. Replace it
|
|
12
|
+
with real business logic when you build new modules.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
export default {
|
|
3
|
+
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
|
4
|
+
corePlugins: {
|
|
5
|
+
preflight: false,
|
|
6
|
+
},
|
|
7
|
+
theme: {
|
|
8
|
+
extend: {
|
|
9
|
+
colors: {
|
|
10
|
+
primary: '#1890ff',
|
|
11
|
+
success: '#52c41a',
|
|
12
|
+
warning: '#faad14',
|
|
13
|
+
error: '#ff4d4f',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
plugins: [],
|
|
18
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
window.matchMedia = vi.fn().mockImplementation((query: string) => ({
|
|
5
|
+
matches: false,
|
|
6
|
+
media: query,
|
|
7
|
+
onchange: null,
|
|
8
|
+
addListener: vi.fn(),
|
|
9
|
+
removeListener: vi.fn(),
|
|
10
|
+
addEventListener: vi.fn(),
|
|
11
|
+
removeEventListener: vi.fn(),
|
|
12
|
+
dispatchEvent: vi.fn(),
|
|
13
|
+
}))
|
|
14
|
+
|
|
15
|
+
if (!('ResizeObserver' in window)) {
|
|
16
|
+
class ResizeObserver {
|
|
17
|
+
observe() {}
|
|
18
|
+
unobserve() {}
|
|
19
|
+
disconnect() {}
|
|
20
|
+
}
|
|
21
|
+
// @ts-expect-error test shim
|
|
22
|
+
window.ResizeObserver = ResizeObserver
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const originalGetComputedStyle = window.getComputedStyle
|
|
26
|
+
window.getComputedStyle = (elt: Element) => {
|
|
27
|
+
const style = originalGetComputedStyle
|
|
28
|
+
? originalGetComputedStyle(elt)
|
|
29
|
+
: ({} as CSSStyleDeclaration)
|
|
30
|
+
return {
|
|
31
|
+
...style,
|
|
32
|
+
getPropertyValue: (prop: string) =>
|
|
33
|
+
style?.getPropertyValue ? style.getPropertyValue(prop) : '',
|
|
34
|
+
} as CSSStyleDeclaration
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export {}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"useDefineForClassFields": true,
|
|
6
6
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
7
|
"module": "ESNext",
|
|
8
|
-
"types": ["vite/client"],
|
|
8
|
+
"types": ["vite/client", "vitest/globals", "@testing-library/jest-dom"],
|
|
9
9
|
"skipLibCheck": true,
|
|
10
10
|
"moduleResolution": "bundler",
|
|
11
11
|
"allowImportingTsExtensions": true,
|
|
@@ -19,5 +19,5 @@
|
|
|
19
19
|
"noFallthroughCasesInSwitch": true,
|
|
20
20
|
"noUncheckedSideEffectImports": true
|
|
21
21
|
},
|
|
22
|
-
"include": ["src"]
|
|
23
|
-
}
|
|
22
|
+
"include": ["src", "tests"]
|
|
23
|
+
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
/// <reference types="vitest" />
|
|
1
2
|
import { createKeystoneViteConfig } from '@robsun/keystone-web-core/vite'
|
|
3
|
+
import path from 'path'
|
|
2
4
|
|
|
3
5
|
export default createKeystoneViteConfig({
|
|
6
|
+
helpDir: path.resolve(__dirname, './src/modules'),
|
|
7
|
+
resolve: {
|
|
8
|
+
alias: {
|
|
9
|
+
'@app': path.resolve(__dirname, './src'),
|
|
10
|
+
},
|
|
11
|
+
},
|
|
4
12
|
server: {
|
|
5
13
|
port: 3000,
|
|
6
14
|
proxy: {
|
|
@@ -10,5 +18,23 @@ export default createKeystoneViteConfig({
|
|
|
10
18
|
},
|
|
11
19
|
},
|
|
12
20
|
},
|
|
13
|
-
|
|
14
|
-
|
|
21
|
+
build: {
|
|
22
|
+
outDir: path.resolve(__dirname, '../server/internal/frontend/dist'),
|
|
23
|
+
emptyOutDir: false,
|
|
24
|
+
sourcemap: true,
|
|
25
|
+
rollupOptions: {
|
|
26
|
+
output: {
|
|
27
|
+
manualChunks: {
|
|
28
|
+
vendor: ['react', 'react-dom'],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
test: {
|
|
34
|
+
globals: true,
|
|
35
|
+
environment: 'jsdom',
|
|
36
|
+
setupFiles: './tests/setup.ts',
|
|
37
|
+
include: ['tests/**/*.{test,spec}.{ts,tsx}'],
|
|
38
|
+
exclude: ['**/node_modules/**', 'tests/e2e/**'],
|
|
39
|
+
},
|
|
40
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
postgres:
|
|
5
|
+
image: postgres:17
|
|
6
|
+
container_name: keystone-postgres
|
|
7
|
+
restart: unless-stopped
|
|
8
|
+
ports:
|
|
9
|
+
- '5432:5432'
|
|
10
|
+
environment:
|
|
11
|
+
POSTGRES_DB: keystone
|
|
12
|
+
POSTGRES_USER: keystone
|
|
13
|
+
POSTGRES_PASSWORD: keystone
|
|
14
|
+
volumes:
|
|
15
|
+
- pgdata:/var/lib/postgresql/data
|
|
16
|
+
healthcheck:
|
|
17
|
+
test: ['CMD-SHELL', 'pg_isready -U keystone -d keystone']
|
|
18
|
+
interval: 10s
|
|
19
|
+
timeout: 5s
|
|
20
|
+
retries: 5
|
|
21
|
+
|
|
22
|
+
# Redis (optional, uncomment when needed)
|
|
23
|
+
# redis:
|
|
24
|
+
# image: redis:7-alpine
|
|
25
|
+
# container_name: keystone-redis
|
|
26
|
+
# restart: unless-stopped
|
|
27
|
+
# ports:
|
|
28
|
+
# - '6379:6379'
|
|
29
|
+
# volumes:
|
|
30
|
+
# - redisdata:/data
|
|
31
|
+
# healthcheck:
|
|
32
|
+
# test: ['CMD', 'redis-cli', 'ping']
|
|
33
|
+
# interval: 10s
|
|
34
|
+
# timeout: 5s
|
|
35
|
+
# retries: 5
|
|
36
|
+
|
|
37
|
+
volumes:
|
|
38
|
+
pgdata:
|
|
39
|
+
driver: local
|
|
40
|
+
# redisdata:
|
|
41
|
+
# driver: local
|
|
42
|
+
|
|
43
|
+
networks:
|
|
44
|
+
default:
|
|
45
|
+
name: keystone-network
|
|
@@ -1,33 +1,42 @@
|
|
|
1
|
-
# Keystone App Conventions
|
|
2
|
-
|
|
3
|
-
##
|
|
4
|
-
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
|
|
8
|
-
##
|
|
9
|
-
- `apps/web
|
|
10
|
-
- `apps/server
|
|
11
|
-
- `apps/
|
|
12
|
-
- `
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- `
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
1
|
+
# Keystone App Conventions
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
- Web/Server 模块结构统一,便于复用与维护。
|
|
5
|
+
- 路由、菜单、权限、帮助文档集中管理。
|
|
6
|
+
- 约定优于配置,降低上手成本。
|
|
7
|
+
|
|
8
|
+
## 项目结构
|
|
9
|
+
- `apps/web/`:React + Vite 壳与业务模块。
|
|
10
|
+
- `apps/server/`:Go 服务端(`cmd/server` 启动,`internal/app` 编排)。
|
|
11
|
+
- `apps/server/internal/modules/`:后端模块注册与实现。
|
|
12
|
+
- `apps/server/internal/frontend/`:嵌入式前端输出目录(`dist/`)。
|
|
13
|
+
- `scripts/`:跨平台 dev/build/test/clean。
|
|
14
|
+
- `apps/server/config.yaml`:服务端配置(支持环境变量覆盖,`docker-compose.yml` 提供依赖)。
|
|
15
|
+
提示:更细节的入口说明见 `apps/web/README.md` 与 `apps/server/README.md`。
|
|
16
|
+
|
|
17
|
+
## 开发流程
|
|
18
|
+
- `pnpm dev`:执行 `scripts/dev.*`,检查环境并启动 Air + Vite。
|
|
19
|
+
- `apps/server/.air.toml`:后端热重载配置。
|
|
20
|
+
- `pnpm server:dev` 仅启动后端;`pnpm web:dev` 仅启动前端。
|
|
21
|
+
|
|
22
|
+
## 配置与环境
|
|
23
|
+
- `apps/server/config.yaml` 为默认配置;`apps/server/config.example.yaml` 用作模板。
|
|
24
|
+
- `apps/server/config.yaml` 供 `go run`/Air 使用。
|
|
25
|
+
- 前端环境变量参考 `apps/web/.env.example`(Vite 会读取)。
|
|
26
|
+
- 后端环境变量需通过 Shell/工具注入(服务端不读取 `.env`)。
|
|
27
|
+
- 本地数据库默认 SQLite:`apps/server/data/keystone-local.db`。
|
|
28
|
+
- 本地存储目录:`apps/server/storage/`(当 `storage.driver=local`)。
|
|
29
|
+
|
|
30
|
+
## 前端模块规范
|
|
31
|
+
- 模块目录:`apps/web/src/modules/<module>`。
|
|
32
|
+
- 在 `index.ts` 注册模块:
|
|
23
33
|
```ts
|
|
24
34
|
import { registerModule } from '@robsun/keystone-web-core'
|
|
25
35
|
import { orderRoutes } from './routes'
|
|
26
36
|
|
|
27
37
|
registerModule({ name: 'orders', routes: orderRoutes })
|
|
28
38
|
```
|
|
29
|
-
|
|
30
|
-
Example route meta:
|
|
39
|
+
- 在 `routes.tsx` 配置菜单/权限/帮助:
|
|
31
40
|
```tsx
|
|
32
41
|
{
|
|
33
42
|
path: 'orders',
|
|
@@ -40,62 +49,15 @@ Example route meta:
|
|
|
40
49
|
},
|
|
41
50
|
}
|
|
42
51
|
```
|
|
52
|
+
- 页面放 `pages/`,组件放 `components/`,API 放 `services/`。
|
|
43
53
|
|
|
44
|
-
##
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
- Use `handle.menu.order` to control menu ordering when needed.
|
|
49
|
-
|
|
50
|
-
## Help Docs
|
|
51
|
-
- Store help markdown under `apps/web/src/modules/<module>/help/**`.
|
|
52
|
-
- Use frontmatter keys: `helpKey`, `title`, `description`, `category`, `tags`, `relatedKeys`.
|
|
53
|
-
- Route `handle.helpKey` must match the markdown `helpKey`.
|
|
54
|
-
- Enable app help by pointing Vite to your help folder (`helpDir` in `vite.config.ts`).
|
|
55
|
-
|
|
56
|
-
Frontmatter example:
|
|
57
|
-
```md
|
|
58
|
-
---
|
|
59
|
-
helpKey: "inventory/warehouses"
|
|
60
|
-
title: "Warehouse Management"
|
|
61
|
-
description: "Manage warehouses and locations."
|
|
62
|
-
category: "inventory"
|
|
63
|
-
tags: ["warehouse", "location"]
|
|
64
|
-
relatedKeys: ["inventory/stock"]
|
|
65
|
-
---
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## API and Services
|
|
69
|
-
- Use `api` from `@robsun/keystone-web-core` for HTTP.
|
|
70
|
-
- If your API base path changes, call `setApiBaseUrl` once at startup.
|
|
71
|
-
- Keep API calls in `services/*.ts`, return typed data, and let pages handle UI state.
|
|
72
|
-
- For paginated endpoints, map `page`/`page_size` (server) to `page`/`pageSize` (web).
|
|
73
|
-
|
|
74
|
-
Example service:
|
|
75
|
-
```ts
|
|
76
|
-
import { api, type ApiResponse, type PaginatedData } from '@robsun/keystone-web-core'
|
|
77
|
-
|
|
78
|
-
export async function listItems(page = 1, pageSize = 20) {
|
|
79
|
-
const { data } = await api.get<ApiResponse<PaginatedData<Item>>>('/items', {
|
|
80
|
-
params: { page, page_size: pageSize },
|
|
81
|
-
})
|
|
82
|
-
return data.data
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## State
|
|
87
|
-
- Use local state for single pages.
|
|
88
|
-
- Use zustand stores for shared state or cross-page flows.
|
|
89
|
-
- Store shape should include `loading`, `error`, `filters`, and a `fetch` method.
|
|
90
|
-
|
|
91
|
-
## Naming
|
|
92
|
-
- Page components: `*Page.tsx` (e.g. `InventoryListPage.tsx`).
|
|
93
|
-
- API modules: `services/api.ts` or `services/<feature>.ts`.
|
|
94
|
-
- Store modules: `stores/*Store.ts` or `stores/use*Store.ts`.
|
|
95
|
-
- Domain types: `types.ts` (export interfaces and enums).
|
|
54
|
+
## Help 文档
|
|
55
|
+
- 文档放在 `apps/web/src/modules/<module>/help/**`。
|
|
56
|
+
- `helpKey` 与路由 `handle.helpKey` 保持一致。
|
|
57
|
+
- Vite 已配置 `helpDir` 扫描 `src/modules`。
|
|
96
58
|
|
|
97
|
-
##
|
|
98
|
-
|
|
59
|
+
## 后端模块规范
|
|
60
|
+
推荐结构:
|
|
99
61
|
```
|
|
100
62
|
apps/server/internal/modules/<module>/
|
|
101
63
|
module.go
|
|
@@ -107,7 +69,7 @@ apps/server/internal/modules/<module>/
|
|
|
107
69
|
bootstrap/seeds/
|
|
108
70
|
```
|
|
109
71
|
|
|
110
|
-
|
|
72
|
+
模块接口:
|
|
111
73
|
```go
|
|
112
74
|
type Module interface {
|
|
113
75
|
Name() string
|
|
@@ -120,15 +82,26 @@ type Module interface {
|
|
|
120
82
|
}
|
|
121
83
|
```
|
|
122
84
|
|
|
123
|
-
|
|
124
|
-
|
|
85
|
+
## 模块注册与启用
|
|
86
|
+
- 在 `apps/server/internal/modules/manifest.go` 注册模块。
|
|
87
|
+
- 在 `apps/server/config.yaml` 的 `modules.enabled` 启用模块。
|
|
88
|
+
- Web 端 `app.config.ts` 保持同名启用列表。
|
|
89
|
+
|
|
90
|
+
## 迁移 / 种子 / 权限
|
|
91
|
+
- `startup.InitializeDatabase` 统一执行:核心迁移 + 模块迁移 + 种子 + 权限注册。
|
|
92
|
+
- 模块权限通过 `RegisterPermissions` 注入。
|
|
93
|
+
|
|
94
|
+
## 任务与队列
|
|
95
|
+
- Worker 在 `cmd/server/main.go` 启动。
|
|
96
|
+
- 模块通过 `RegisterJobs` 注册任务处理器。
|
|
97
|
+
- 队列配置在 `apps/server/config.yaml` 的 `queue` 部分。
|
|
125
98
|
|
|
126
|
-
##
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
-
|
|
99
|
+
## 嵌入式前端
|
|
100
|
+
- Vite `build.outDir` 指向 `apps/server/internal/frontend/dist`。
|
|
101
|
+
- Go 通过 `//go:embed` 嵌入前端产物。
|
|
102
|
+
- 保留 `apps/server/internal/frontend/dist/.gitkeep`,避免 embed 找不到目录。
|
|
130
103
|
|
|
131
104
|
## Testing
|
|
132
|
-
- Web
|
|
133
|
-
- Server
|
|
134
|
-
-
|
|
105
|
+
- Web:`pnpm -C apps/web test`(Vitest)。
|
|
106
|
+
- Server:`go -C apps/server test ./...`。
|
|
107
|
+
- 全量:`pnpm test`。
|
package/template/package.json
CHANGED
|
@@ -3,9 +3,21 @@
|
|
|
3
3
|
"private": true,
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"dev": "
|
|
6
|
+
"dev": "node scripts/dev.js",
|
|
7
|
+
"build": "node scripts/build.js",
|
|
8
|
+
"build:web": "pnpm --filter web build",
|
|
9
|
+
"test": "node scripts/test.js",
|
|
10
|
+
"lint": "pnpm --filter web lint",
|
|
11
|
+
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md,yml,yaml}\"",
|
|
12
|
+
"clean": "node scripts/clean.js",
|
|
7
13
|
"web:dev": "pnpm -C apps/web dev",
|
|
8
|
-
"server:dev": "go -C apps/server run
|
|
14
|
+
"server:dev": "go -C apps/server run ./cmd/server",
|
|
15
|
+
"prepare": "husky"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"husky": "^9.1.7",
|
|
19
|
+
"lint-staged": "^16.2.7",
|
|
20
|
+
"prettier": "^3.7.4"
|
|
9
21
|
},
|
|
10
22
|
"packageManager": "pnpm@9.15.0"
|
|
11
|
-
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
setlocal enabledelayedexpansion
|
|
3
|
+
|
|
4
|
+
echo ========================================
|
|
5
|
+
echo Keystone Production Build
|
|
6
|
+
echo (Single Executable with Embedded Frontend)
|
|
7
|
+
echo ========================================
|
|
8
|
+
echo.
|
|
9
|
+
|
|
10
|
+
set "PROJECT_ROOT=%~dp0.."
|
|
11
|
+
set "FRONTEND_DIR=%PROJECT_ROOT%\apps\web"
|
|
12
|
+
set "BACKEND_DIR=%PROJECT_ROOT%\apps\server"
|
|
13
|
+
set "EMBED_DIR=%BACKEND_DIR%\internal\frontend\dist"
|
|
14
|
+
set "OUTPUT_DIR=%PROJECT_ROOT%\dist"
|
|
15
|
+
set "OUTPUT_NAME=__APP_NAME__"
|
|
16
|
+
|
|
17
|
+
echo Checking prerequisites...
|
|
18
|
+
where node >nul 2>&1
|
|
19
|
+
if errorlevel 1 (
|
|
20
|
+
echo [ERROR] Node.js is not installed
|
|
21
|
+
exit /b 1
|
|
22
|
+
)
|
|
23
|
+
echo [OK] Node.js found
|
|
24
|
+
|
|
25
|
+
where pnpm >nul 2>&1
|
|
26
|
+
if errorlevel 1 (
|
|
27
|
+
echo [ERROR] pnpm is not installed
|
|
28
|
+
exit /b 1
|
|
29
|
+
)
|
|
30
|
+
echo [OK] pnpm found
|
|
31
|
+
|
|
32
|
+
where go >nul 2>&1
|
|
33
|
+
if errorlevel 1 (
|
|
34
|
+
echo [ERROR] Go is not installed
|
|
35
|
+
exit /b 1
|
|
36
|
+
)
|
|
37
|
+
echo [OK] Go found
|
|
38
|
+
|
|
39
|
+
echo.
|
|
40
|
+
echo Building production artifacts...
|
|
41
|
+
echo.
|
|
42
|
+
|
|
43
|
+
if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"
|
|
44
|
+
if not exist "%EMBED_DIR%" mkdir "%EMBED_DIR%"
|
|
45
|
+
|
|
46
|
+
set FAILED=0
|
|
47
|
+
|
|
48
|
+
echo ----------------------------------------
|
|
49
|
+
echo [1/4] Cleaning embed directory
|
|
50
|
+
echo ----------------------------------------
|
|
51
|
+
for /f "delims=" %%i in ('dir /b /a-d "%EMBED_DIR%\*" 2^>nul ^| findstr /v "^\.gitkeep$"') do del /q "%EMBED_DIR%\%%i" 2>nul
|
|
52
|
+
for /d %%i in ("%EMBED_DIR%\*") do rd /s /q "%%i" 2>nul
|
|
53
|
+
echo [OK] Embed directory cleaned
|
|
54
|
+
|
|
55
|
+
echo ----------------------------------------
|
|
56
|
+
echo [2/4] Building Frontend
|
|
57
|
+
echo ----------------------------------------
|
|
58
|
+
cd /d "%FRONTEND_DIR%"
|
|
59
|
+
call pnpm build
|
|
60
|
+
if errorlevel 1 (
|
|
61
|
+
echo [FAIL] Frontend build failed
|
|
62
|
+
set FAILED=1
|
|
63
|
+
cd /d "%PROJECT_ROOT%"
|
|
64
|
+
exit /b 1
|
|
65
|
+
)
|
|
66
|
+
echo [OK] Frontend build completed
|
|
67
|
+
cd /d "%PROJECT_ROOT%"
|
|
68
|
+
|
|
69
|
+
echo ----------------------------------------
|
|
70
|
+
echo [3/4] Building Backend (with embedded frontend)
|
|
71
|
+
echo ----------------------------------------
|
|
72
|
+
cd /d "%BACKEND_DIR%"
|
|
73
|
+
|
|
74
|
+
echo Building for Windows (amd64)...
|
|
75
|
+
set CGO_ENABLED=0
|
|
76
|
+
set GOOS=windows
|
|
77
|
+
set GOARCH=amd64
|
|
78
|
+
go build -ldflags="-s -w" -o "%OUTPUT_DIR%\%OUTPUT_NAME%.exe" ./cmd/server
|
|
79
|
+
if errorlevel 1 (
|
|
80
|
+
echo [FAIL] Windows build failed
|
|
81
|
+
set FAILED=1
|
|
82
|
+
cd /d "%PROJECT_ROOT%"
|
|
83
|
+
exit /b 1
|
|
84
|
+
)
|
|
85
|
+
echo [OK] Windows build completed
|
|
86
|
+
|
|
87
|
+
echo Building for Linux (amd64)...
|
|
88
|
+
set CGO_ENABLED=0
|
|
89
|
+
set GOOS=linux
|
|
90
|
+
set GOARCH=amd64
|
|
91
|
+
go build -ldflags="-s -w" -o "%OUTPUT_DIR%\%OUTPUT_NAME%" ./cmd/server
|
|
92
|
+
if errorlevel 1 (
|
|
93
|
+
echo [FAIL] Linux build failed
|
|
94
|
+
set FAILED=1
|
|
95
|
+
cd /d "%PROJECT_ROOT%"
|
|
96
|
+
exit /b 1
|
|
97
|
+
)
|
|
98
|
+
echo [OK] Linux build completed
|
|
99
|
+
|
|
100
|
+
cd /d "%PROJECT_ROOT%"
|
|
101
|
+
|
|
102
|
+
echo ----------------------------------------
|
|
103
|
+
echo [4/4] Copying configuration template
|
|
104
|
+
echo ----------------------------------------
|
|
105
|
+
if exist "%BACKEND_DIR%\config.example.yaml" (
|
|
106
|
+
copy /Y "%BACKEND_DIR%\config.example.yaml" "%OUTPUT_DIR%\config.example.yaml" >nul
|
|
107
|
+
echo [OK] Config template copied
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
echo.
|
|
111
|
+
echo ========================================
|
|
112
|
+
if "%FAILED%"=="1" goto :build_failed
|
|
113
|
+
|
|
114
|
+
echo Build completed successfully!
|
|
115
|
+
echo.
|
|
116
|
+
echo Executables (frontend embedded):
|
|
117
|
+
echo - dist\%OUTPUT_NAME%.exe (Windows)
|
|
118
|
+
echo - dist\%OUTPUT_NAME% (Linux)
|
|
119
|
+
echo.
|
|
120
|
+
echo Configuration:
|
|
121
|
+
echo - dist\config.example.yaml
|
|
122
|
+
echo.
|
|
123
|
+
echo Usage:
|
|
124
|
+
echo Windows: cd dist ^&^& %OUTPUT_NAME%.exe
|
|
125
|
+
echo Linux: cd dist ^&^& ./%OUTPUT_NAME%
|
|
126
|
+
goto :build_done
|
|
127
|
+
|
|
128
|
+
:build_failed
|
|
129
|
+
echo Build FAILED
|
|
130
|
+
exit /b 1
|
|
131
|
+
|
|
132
|
+
:build_done
|
|
133
|
+
echo ========================================
|