create-pactor-app 1.21.0 → 1.22.1
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/package.json +1 -1
- package/templates/next-app/README.md +5 -4
- package/templates/next-app/app/api/users/route.ts +57 -0
- package/templates/next-app/app/layout.tsx +2 -4
- package/templates/next-app/app/theme.css +111 -0
- package/templates/next-app/next.config.js +32 -0
- package/templates/next-app/package.json +10 -7
- package/templates/next-app/pnpm-workspace.yaml +7 -0
- package/templates/next-app/scripts/build.mjs +35 -0
- package/templates/next-app/src/pactor/config.ts +11 -8
- package/templates/next-app/src/server/accounts.ts +1 -1
- package/templates/next-app/src/server/sitemap.ts +113 -517
- package/templates/next-app/src/server/yaml/pages/home.page.yaml +23 -0
- package/templates/next-app/src/server/yaml/pages/login.page.yaml +69 -0
- package/templates/next-app/src/server/yaml/pages/monthly-detail.page.yaml +23 -0
- package/templates/next-app/src/server/yaml/pages/monthly.page.yaml +41 -0
- package/templates/next-app/src/server/yaml/pages/register.page.yaml +23 -0
- package/templates/next-app/src/server/yaml/pages/users.page.yaml +85 -0
- package/templates/next-app/src/server/yaml/sitemap.yaml +148 -0
- package/templates/spa/README.md +4 -5
- package/templates/spa/package.json +4 -4
- package/templates/spa/pnpm-workspace.yaml +6 -0
- package/templates/spa/src/pactor/config.ts +2 -6
package/package.json
CHANGED
|
@@ -44,6 +44,8 @@ src/
|
|
|
44
44
|
cors.ts # CORS 头(演示放开跨源,含预检响应)
|
|
45
45
|
sitemap.ts # mock sitemap 数据源(layouts / pages / menus 按角色裁剪;
|
|
46
46
|
# 登录 / 登出在 DSL 中用内置 request 动作表达)
|
|
47
|
+
yaml/ # DSL 文件:sitemap.yaml(入口元数据 + 布局 + 页面/菜单全量声明)
|
|
48
|
+
pages/ # 页面 DSL(每页一个 *.page.yaml,按 id 约定加载)
|
|
47
49
|
```
|
|
48
50
|
|
|
49
51
|
`App.tsx` 全部内容即:
|
|
@@ -57,7 +59,6 @@ const httpHooks = createHttpHooks();
|
|
|
57
59
|
pathname={pathname}
|
|
58
60
|
query={query}
|
|
59
61
|
navigate={(to) => router.push(to)}
|
|
60
|
-
pageEndpoint={pageEndpoint}
|
|
61
62
|
httpAdapter={httpHooks}
|
|
62
63
|
/>
|
|
63
64
|
```
|
|
@@ -77,8 +78,8 @@ const httpHooks = createHttpHooks();
|
|
|
77
78
|
- `menus`:导航 / 路由树(`pageId` 引用页面、`layout` 引用布局、
|
|
78
79
|
`public` 免登录;`path` / `title` 缺省从引用页面继承)
|
|
79
80
|
- `auth.loginPath`:未登录重定向目标
|
|
80
|
-
3. 懒加载页:实现 `GET
|
|
81
|
-
|
|
81
|
+
3. 懒加载页:实现 `GET /api/pages/{id}` 返回 PageResource envelope
|
|
82
|
+
(路径为框架缺省;需自定义时给 `SitemapApp` 传 `pageEndpoint` prop)。
|
|
82
83
|
4. 登录 / 登出等接口调用直接在 DSL 中用内置 `request` 动作表达(参考
|
|
83
84
|
`src/server/sitemap.ts` 的登录页 / 布局 DSL,URL 指向你的真实接口);
|
|
84
85
|
有客户端服务层时可用内置 `service.call` 动作桥接,`session.context`
|
|
@@ -87,7 +88,7 @@ const httpHooks = createHttpHooks();
|
|
|
87
88
|
## 跨域与 HTTP 钩子
|
|
88
89
|
|
|
89
90
|
模板的外部接口以 `apiBaseUrl`(`src/pactor/config.ts`,缺省
|
|
90
|
-
`http://localhost:9011`)为基准:`sitemapSources`
|
|
91
|
+
`http://localhost:9011`)为基准:`sitemapSources`
|
|
91
92
|
只写相对路径,由 `SitemapApp` 的 `baseUrl` 补全为外部绝对地址。
|
|
92
93
|
**假定服务端已开通 CORS,浏览器直连,无需项目内代理**——本模板的
|
|
93
94
|
mock API 已在路由层放开跨源(`src/server/cors.ts`:Allow-Origin 回显
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { cookies } from "next/headers";
|
|
2
|
+
|
|
3
|
+
import { findUserBySession, SESSION_COOKIE } from "@/server/accounts";
|
|
4
|
+
import { corsHeaders, corsPreflight } from "@/server/cors";
|
|
5
|
+
|
|
6
|
+
/** 演示用户数据(mock):真实项目由后端用户体系提供 */
|
|
7
|
+
const MOCK_USERS = [
|
|
8
|
+
{ id: "u-1", username: "admin", name: "演示管理员", role: "admin", email: "admin@example.com", createdAt: "2026-01-05" },
|
|
9
|
+
{ id: "u-2", username: "guest", name: "访客", role: "guest", email: "guest@example.com", createdAt: "2026-02-11" },
|
|
10
|
+
{ id: "u-3", username: "zhangwei", name: "张伟", role: "member", email: "zhangwei@example.com", createdAt: "2026-03-02" },
|
|
11
|
+
{ id: "u-4", username: "liuyang", name: "刘洋", role: "member", email: "liuyang@example.com", createdAt: "2026-03-18" },
|
|
12
|
+
{ id: "u-5", username: "chenjing", name: "陈静", role: "member", email: "chenjing@example.com", createdAt: "2026-04-07" },
|
|
13
|
+
{ id: "u-6", username: "wangfang", name: "王芳", role: "member", email: "wangfang@example.com", createdAt: "2026-05-21" },
|
|
14
|
+
{ id: "u-7", username: "zhaolei", name: "赵磊", role: "guest", email: "zhaolei@example.com", createdAt: "2026-06-13" },
|
|
15
|
+
{ id: "u-8", username: "sunna", name: "孙娜", role: "member", email: "sunna@example.com", createdAt: "2026-07-30" },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 用户列表:users.page.yaml 的 http 数据源。
|
|
20
|
+
* 支持 keyword(姓名 / 用户名模糊匹配)与 role(精确匹配)过滤。
|
|
21
|
+
* 接口侧鉴权——前端隐藏菜单不等于安全。
|
|
22
|
+
*/
|
|
23
|
+
export async function GET(request: Request) {
|
|
24
|
+
const store = await cookies();
|
|
25
|
+
const user = findUserBySession(store.get(SESSION_COOKIE)?.value);
|
|
26
|
+
if (!user || !user.permissions.includes("user:view")) {
|
|
27
|
+
return Response.json(
|
|
28
|
+
{ error: "未登录或无权限" },
|
|
29
|
+
{ status: 401, headers: corsHeaders(request) },
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { searchParams } = new URL(request.url);
|
|
34
|
+
const keyword = (searchParams.get("keyword") ?? "").trim().toLowerCase();
|
|
35
|
+
const role = searchParams.get("role") ?? "";
|
|
36
|
+
|
|
37
|
+
const list = MOCK_USERS.filter((row) => {
|
|
38
|
+
if (role && row.role !== role) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (
|
|
42
|
+
keyword &&
|
|
43
|
+
!row.name.toLowerCase().includes(keyword) &&
|
|
44
|
+
!row.username.toLowerCase().includes(keyword)
|
|
45
|
+
) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return Response.json(list, {
|
|
52
|
+
headers: { "cache-control": "no-store", ...corsHeaders(request) },
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 预检(跨源带凭据 / 自定义头时) */
|
|
57
|
+
export const OPTIONS = corsPreflight;
|
|
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
|
|
2
2
|
import { PactorApp } from "@/pactor/App";
|
|
3
3
|
import "@pactor-app/ui/styles.css";
|
|
4
4
|
import "./globals.css";
|
|
5
|
+
import "./theme.css";
|
|
5
6
|
|
|
6
7
|
export const metadata: Metadata = {
|
|
7
8
|
title: "Pactor 动态页面 Starter",
|
|
@@ -10,10 +11,7 @@ export const metadata: Metadata = {
|
|
|
10
11
|
|
|
11
12
|
export default function RootLayout({ children }: LayoutProps<"/">) {
|
|
12
13
|
return (
|
|
13
|
-
<html
|
|
14
|
-
lang="zh-CN"
|
|
15
|
-
className={`h-full antialiased`}
|
|
16
|
-
>
|
|
14
|
+
<html lang="zh-CN" className={`h-full antialiased`}>
|
|
17
15
|
<body className="min-h-full flex flex-col">
|
|
18
16
|
{/*
|
|
19
17
|
* PactorApp 挂在 Next layout 而非 page:App Router 导航时 page 会
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pactor 皮肤文件 —— 由 Theme Studio 生成(基于预置主题「晨雾奶芙」)
|
|
3
|
+
* 用法:在 `@pactor-app/ui/styles.css` 之后加载本文件(后加载者胜出)。
|
|
4
|
+
*/
|
|
5
|
+
:root:not([data-pactor-theme='dark']) {
|
|
6
|
+
color-scheme: light;
|
|
7
|
+
--background: #f8f3ea;
|
|
8
|
+
--foreground: #4a3f33;
|
|
9
|
+
--card: #fffdf8;
|
|
10
|
+
--card-foreground: #4a3f33;
|
|
11
|
+
--popover: oklch(1 0 0);
|
|
12
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
13
|
+
--primary: #c98a5e;
|
|
14
|
+
--primary-foreground: #fff8f0;
|
|
15
|
+
--secondary: oklch(0.97 0 0);
|
|
16
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
17
|
+
--accent: #f3e4d3;
|
|
18
|
+
--accent-foreground: #6b4f35;
|
|
19
|
+
--muted: #f1e9dd;
|
|
20
|
+
--muted-foreground: #8a7a68;
|
|
21
|
+
--destructive: #c0564a;
|
|
22
|
+
--destructive-foreground: oklch(0.985 0 0);
|
|
23
|
+
--success: #7d9b5f;
|
|
24
|
+
--warning: #d9a03f;
|
|
25
|
+
--info: #8a9bb5;
|
|
26
|
+
--border: #e8dccb;
|
|
27
|
+
--input: #e8dccb;
|
|
28
|
+
--ring: #c98a5e;
|
|
29
|
+
--chart-1: #c98a5e;
|
|
30
|
+
--chart-2: #e0b48a;
|
|
31
|
+
--chart-3: #a8c3a0;
|
|
32
|
+
--chart-4: #d98c7a;
|
|
33
|
+
--chart-5: #8a9bb5;
|
|
34
|
+
--sidebar: var(--card);
|
|
35
|
+
--sidebar-foreground: var(--card-foreground);
|
|
36
|
+
--sidebar-primary: var(--primary);
|
|
37
|
+
--sidebar-primary-foreground: var(--primary-foreground);
|
|
38
|
+
--sidebar-accent: var(--accent);
|
|
39
|
+
--sidebar-accent-foreground: var(--accent-foreground);
|
|
40
|
+
--sidebar-border: var(--border);
|
|
41
|
+
--sidebar-ring: var(--ring);
|
|
42
|
+
--switch-track: var(--input);
|
|
43
|
+
--switch-track-checked: var(--primary);
|
|
44
|
+
--switch-thumb: var(--card);
|
|
45
|
+
--slider-track: var(--muted);
|
|
46
|
+
--slider-range: var(--primary);
|
|
47
|
+
--slider-thumb: var(--card);
|
|
48
|
+
--radio-border: var(--input);
|
|
49
|
+
--radio-checked: var(--primary);
|
|
50
|
+
--progress-track: var(--muted);
|
|
51
|
+
--progress-indicator: var(--primary);
|
|
52
|
+
--pagination-active-background: var(--card);
|
|
53
|
+
--pagination-active-border: var(--border);
|
|
54
|
+
--menu-hover-background: var(--accent);
|
|
55
|
+
--menu-hover-foreground: var(--accent-foreground);
|
|
56
|
+
--menu-active-background: var(--accent);
|
|
57
|
+
--menu-active-foreground: var(--accent-foreground);
|
|
58
|
+
--bubble-background: oklch(from var(--primary) 0.93 calc(c * 0.4) h);
|
|
59
|
+
--bubble-hover-background: oklch(from var(--primary) 0.88 calc(c * 0.5) h);
|
|
60
|
+
--calendar-day-hover-background: var(--accent);
|
|
61
|
+
--calendar-day-hover-foreground: var(--accent-foreground);
|
|
62
|
+
--calendar-selected-background: var(--primary);
|
|
63
|
+
--calendar-selected-foreground: var(--primary-foreground);
|
|
64
|
+
--calendar-today-background: var(--accent);
|
|
65
|
+
--calendar-today-foreground: var(--accent-foreground);
|
|
66
|
+
--calendar-today-border: var(--primary);
|
|
67
|
+
--calendar-range-background: var(--accent);
|
|
68
|
+
--calendar-range-foreground: var(--accent-foreground);
|
|
69
|
+
--radius: 12px;
|
|
70
|
+
--flex-gap: 12px;
|
|
71
|
+
--flex-gap-sm: calc(var(--flex-gap) * 0.67);
|
|
72
|
+
--flex-gap-md: calc(var(--flex-gap) * 1.33);
|
|
73
|
+
--flex-gap-lg: calc(var(--flex-gap) * 2);
|
|
74
|
+
--grid-gap: 16px;
|
|
75
|
+
--pagination-active-radius: calc(var(--radius) * 0.8);
|
|
76
|
+
--font-sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
77
|
+
--background-image: none;
|
|
78
|
+
--card-shadow: 0 8px 24px rgb(120 90 60 / 10%);
|
|
79
|
+
--overlay-background: oklch(0 0 0 / 50%);
|
|
80
|
+
--shadow-color: oklch(0 0 0 / 10%);
|
|
81
|
+
--sidebar-width: 16rem;
|
|
82
|
+
--sidebar-width-icon: 3rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
[data-pactor-theme='dark'] {
|
|
86
|
+
color-scheme: dark;
|
|
87
|
+
--background: #221c16;
|
|
88
|
+
--foreground: #f3e9dc;
|
|
89
|
+
--card: #2b241c;
|
|
90
|
+
--card-foreground: #f3e9dc;
|
|
91
|
+
--popover: #2b241c;
|
|
92
|
+
--popover-foreground: #f3e9dc;
|
|
93
|
+
--muted: #322a21;
|
|
94
|
+
--muted-foreground: #a89480;
|
|
95
|
+
--primary: #d9a075;
|
|
96
|
+
--primary-foreground: #2b2016;
|
|
97
|
+
--accent: #453a2e;
|
|
98
|
+
--accent-foreground: #f0dcc8;
|
|
99
|
+
--border: #453b30;
|
|
100
|
+
--input: #453b30;
|
|
101
|
+
--ring: #d9a075;
|
|
102
|
+
--chart-1: #d9a075;
|
|
103
|
+
--chart-2: #e8c8a8;
|
|
104
|
+
--chart-3: #a8c3a0;
|
|
105
|
+
--chart-4: #d98c7a;
|
|
106
|
+
--chart-5: #8a9bb5;
|
|
107
|
+
--destructive: #d98c7a;
|
|
108
|
+
--success: #a8c3a0;
|
|
109
|
+
--warning: #e0b484;
|
|
110
|
+
--info: #a9b8cc;
|
|
111
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js 构建配置。
|
|
3
|
+
*
|
|
4
|
+
* API_BASE_URLS:各环境默认的外部接口基准地址(SitemapApp baseUrl)。
|
|
5
|
+
* - 环境由构建命令注入(scripts/build.mjs 设置 APP_ENV:
|
|
6
|
+
* build / build:dev / build:test → production / development / test);
|
|
7
|
+
* - 解析结果经 env 配置内联进客户端包(src/pactor/config.ts 消费);
|
|
8
|
+
* - test / production 为占位域名,接入真实环境时替换;
|
|
9
|
+
* - 构建环境可设 NEXT_PUBLIC_API_BASE_URL 显式覆盖(域名不符合约定时)。
|
|
10
|
+
*/
|
|
11
|
+
const API_BASE_URLS = {
|
|
12
|
+
/** 本地开发:dev 端口 9011 */
|
|
13
|
+
development: 'http://localhost:9011',
|
|
14
|
+
/** 测试环境:替换为实际测试环境 API 域名 */
|
|
15
|
+
test: 'https://api-test.example.com',
|
|
16
|
+
/** 生产环境:替换为实际生产 API 域名 */
|
|
17
|
+
production: 'https://api.example.com',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const appEnv = ['development', 'test', 'production'].includes(process.env.APP_ENV)
|
|
21
|
+
? process.env.APP_ENV
|
|
22
|
+
: 'development';
|
|
23
|
+
|
|
24
|
+
/** @type {import('next').NextConfig} */
|
|
25
|
+
const nextConfig = {
|
|
26
|
+
env: {
|
|
27
|
+
NEXT_PUBLIC_API_BASE_URL:
|
|
28
|
+
process.env.NEXT_PUBLIC_API_BASE_URL ?? API_BASE_URLS[appEnv],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
module.exports = nextConfig;
|
|
@@ -4,18 +4,21 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "next dev -p 9011",
|
|
7
|
-
"build": "
|
|
7
|
+
"build": "node scripts/build.mjs production",
|
|
8
8
|
"start": "next start -p 9011",
|
|
9
|
-
"lint": "eslint"
|
|
9
|
+
"lint": "eslint",
|
|
10
|
+
"build:dev": "node scripts/build.mjs development",
|
|
11
|
+
"build:test": "node scripts/build.mjs test"
|
|
10
12
|
},
|
|
11
13
|
"dependencies": {
|
|
12
|
-
"@pactor-app/core": "^1.
|
|
13
|
-
"@pactor-app/runtime": "^1.
|
|
14
|
-
"@pactor-app/ui": "^1.
|
|
14
|
+
"@pactor-app/core": "^1.22.0",
|
|
15
|
+
"@pactor-app/runtime": "^1.22.0",
|
|
16
|
+
"@pactor-app/ui": "^1.22.0",
|
|
15
17
|
"next": "16.3.5",
|
|
16
|
-
"pactor-app": "^1.
|
|
18
|
+
"pactor-app": "^1.22.0",
|
|
17
19
|
"react": "19.2.8",
|
|
18
|
-
"react-dom": "19.2.8"
|
|
20
|
+
"react-dom": "19.2.8",
|
|
21
|
+
"yaml": "^2.9.1"
|
|
19
22
|
},
|
|
20
23
|
"devDependencies": {
|
|
21
24
|
"@tailwindcss/postcss": "^4",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 按目标环境打包:
|
|
4
|
+
* node scripts/build.mjs <development|test|production>
|
|
5
|
+
* 对应 package.json 的 build / build:dev / build:test 命令。
|
|
6
|
+
* 环境经 APP_ENV 注入(构建期由 next.config.js 读取并解析
|
|
7
|
+
* API_BASE_URLS,结果内联进客户端包,见 src/pactor/config.ts)。
|
|
8
|
+
* 跨平台:Windows 下经 shell 解析 next.cmd。
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
|
|
12
|
+
const ENVS = ['development', 'test', 'production'];
|
|
13
|
+
const env = process.argv[2];
|
|
14
|
+
|
|
15
|
+
if (!ENVS.includes(env)) {
|
|
16
|
+
console.error(`用法: node scripts/build.mjs <${ENVS.join('|')}>`);
|
|
17
|
+
process.exit(2);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
process.env.APP_ENV = env;
|
|
21
|
+
console.log(`\n构建目标环境: ${env}(APP_ENV=${env})\n`);
|
|
22
|
+
|
|
23
|
+
const child = spawn('next', ['build'], {
|
|
24
|
+
stdio: 'inherit',
|
|
25
|
+
shell: process.platform === 'win32',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
child.on('error', (error) => {
|
|
29
|
+
console.error('启动 next build 失败:', error.message);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
child.on('exit', (code) => {
|
|
34
|
+
process.exit(code ?? 1);
|
|
35
|
+
});
|
|
@@ -4,14 +4,17 @@
|
|
|
4
4
|
* (id / path 冲突后源覆盖前源并告警),app / user / permissions / auth
|
|
5
5
|
* 取第一个源。
|
|
6
6
|
*
|
|
7
|
-
* 外部接口:sources
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* 外部接口:sources 只写相对路径,由 apiBaseUrl 补全为外部绝对地址;
|
|
8
|
+
* 服务端已开通 CORS(cookie 会话见 src/server/cors.ts),浏览器直连,
|
|
9
|
+
* 无需项目内代理。
|
|
10
10
|
*/
|
|
11
11
|
export const sitemapSources: string[] = ["/api/sitemap"];
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
/**
|
|
14
|
+
* 外部接口基准地址(SitemapApp baseUrl)。
|
|
15
|
+
* 由 next.config.js 的 API_BASE_URLS 按构建环境(APP_ENV:
|
|
16
|
+
* build / build:dev / build:test → production / development / test)
|
|
17
|
+
* 解析,经 env 配置内联进客户端包;缺省回退本地开发地址。
|
|
18
|
+
*/
|
|
19
|
+
export const apiBaseUrl =
|
|
20
|
+
process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:9011";
|