@pactor-app/skills 1.24.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.
- package/LICENSE +21 -0
- package/README.md +40 -0
- package/package.json +26 -0
- package/pactor/SKILL.md +373 -0
- package/pactor/references/dsl-authoring.md +448 -0
- package/prd-to-app/SKILL.md +75 -0
- package/prd-to-app/references/acceptance-checklist.md +47 -0
- package/prd-to-app/references/component-mapping.md +41 -0
- package/prd-to-app/references/mock-data-routes.md +66 -0
- package/prd-to-app/references/page-templates.md +274 -0
- package/prd-to-app/references/project-structure.md +89 -0
- package/prd-to-app/references/theme-and-style.md +41 -0
- package/prd-to-app/template/README.md +26 -0
- package/prd-to-app/template/app/[[...slug]]/page.tsx +7 -0
- package/prd-to-app/template/app/api/auth/login/route.ts +32 -0
- package/prd-to-app/template/app/api/auth/logout/route.ts +22 -0
- package/prd-to-app/template/app/api/data/[...parts]/route.ts +118 -0
- package/prd-to-app/template/app/api/public/pages/[id]/route.ts +40 -0
- package/prd-to-app/template/app/api/sitemap/route.ts +21 -0
- package/prd-to-app/template/app/api/users/route.ts +57 -0
- package/prd-to-app/template/app/favicon.ico +0 -0
- package/prd-to-app/template/app/globals.css +248 -0
- package/prd-to-app/template/app/layout.tsx +49 -0
- package/prd-to-app/template/app/login.css +122 -0
- package/prd-to-app/template/eslint.config.mjs +18 -0
- package/prd-to-app/template/next-env.d.ts +7 -0
- package/prd-to-app/template/next.config.js +32 -0
- package/prd-to-app/template/package.json +34 -0
- package/prd-to-app/template/pnpm-lock.yaml +6854 -0
- package/prd-to-app/template/pnpm-workspace.yaml +7 -0
- package/prd-to-app/template/postcss.config.mjs +7 -0
- package/prd-to-app/template/public/file.svg +1 -0
- package/prd-to-app/template/public/globe.svg +1 -0
- package/prd-to-app/template/public/next.svg +1 -0
- package/prd-to-app/template/public/skins/chenwunaifu.css +111 -0
- package/prd-to-app/template/public/skins/taijingshenlin.css +109 -0
- package/prd-to-app/template/public/skins/xuanzhimoyun.css +110 -0
- package/prd-to-app/template/public/vercel.svg +1 -0
- package/prd-to-app/template/public/window.svg +1 -0
- package/prd-to-app/template/scripts/build.mjs +35 -0
- package/prd-to-app/template/src/pactor/App.tsx +55 -0
- package/prd-to-app/template/src/pactor/actions.ts +438 -0
- package/prd-to-app/template/src/pactor/components/card-links.tsx +86 -0
- package/prd-to-app/template/src/pactor/components/chip-filter.tsx +89 -0
- package/prd-to-app/template/src/pactor/components/chip-node.tsx +46 -0
- package/prd-to-app/template/src/pactor/components/debug-chat.tsx +189 -0
- package/prd-to-app/template/src/pactor/components/index.ts +28 -0
- package/prd-to-app/template/src/pactor/components/log-status.tsx +64 -0
- package/prd-to-app/template/src/pactor/components/status-badge.tsx +46 -0
- package/prd-to-app/template/src/pactor/components/theme-switcher.tsx +136 -0
- package/prd-to-app/template/src/pactor/config.ts +28 -0
- package/prd-to-app/template/src/pactor/mocks/audit-mock.ts +403 -0
- package/prd-to-app/template/src/pactor/mocks/call-logs-mock.ts +875 -0
- package/prd-to-app/template/src/pactor/mocks/client-debug-mock.ts +188 -0
- package/prd-to-app/template/src/pactor/mocks/client-templates-mock.ts +497 -0
- package/prd-to-app/template/src/pactor/mocks/clients-mock.ts +483 -0
- package/prd-to-app/template/src/pactor/mocks/costs-mock.ts +406 -0
- package/prd-to-app/template/src/pactor/mocks/dashboard-mock.ts +192 -0
- package/prd-to-app/template/src/pactor/mocks/model-debug-mock.ts +144 -0
- package/prd-to-app/template/src/pactor/mocks/models-mock.ts +799 -0
- package/prd-to-app/template/src/pactor/mocks/ops-usage-mock.ts +806 -0
- package/prd-to-app/template/src/pactor/mocks/providers-mock.ts +424 -0
- package/prd-to-app/template/src/pactor/mocks/routes-mock.ts +604 -0
- package/prd-to-app/template/src/pactor/services.ts +185 -0
- package/prd-to-app/template/src/pactor/skin.ts +52 -0
- package/prd-to-app/template/src/server/accounts.ts +63 -0
- package/prd-to-app/template/src/server/cors.ts +34 -0
- package/prd-to-app/template/src/server/sitemap.ts +176 -0
- package/prd-to-app/template/src/server/yaml/common/dashboard.yaml +498 -0
- package/prd-to-app/template/src/server/yaml/common/login.yaml +84 -0
- package/prd-to-app/template/src/server/yaml/provider/provider-create.yaml +340 -0
- package/prd-to-app/template/src/server/yaml/provider/provider-detail.yaml +780 -0
- package/prd-to-app/template/src/server/yaml/provider/provider-edit.yaml +366 -0
- package/prd-to-app/template/src/server/yaml/provider/provider-list.yaml +1989 -0
- package/prd-to-app/template/src/server/yaml/route/route-create.yaml +511 -0
- package/prd-to-app/template/src/server/yaml/route/route-detail.yaml +610 -0
- package/prd-to-app/template/src/server/yaml/route/route-edit.yaml +809 -0
- package/prd-to-app/template/src/server/yaml/route/route-list.yaml +1793 -0
- package/prd-to-app/template/src/server/yaml/sitemap.yaml +250 -0
- package/prd-to-app/template/tsconfig.json +34 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { cookies } from "next/headers";
|
|
2
|
+
|
|
3
|
+
import { findUserBySession, SESSION_COOKIE } from "@/server/accounts";
|
|
4
|
+
import { corsHeaders, corsPreflight } from "@/server/cors";
|
|
5
|
+
import { getLazyPage } from "@/server/sitemap";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 懒加载页面端点:sitemap 中未内联 pageDsl 的页面按需获取。
|
|
9
|
+
* 返回 PageResource envelope(id / version / dslVersion / schema),
|
|
10
|
+
* 接口侧鉴权——前端隐藏菜单不等于安全。
|
|
11
|
+
*/
|
|
12
|
+
export async function GET(
|
|
13
|
+
request: Request,
|
|
14
|
+
context: RouteContext<"/api/public/pages/[id]">,
|
|
15
|
+
) {
|
|
16
|
+
const { id } = await context.params;
|
|
17
|
+
const store = await cookies();
|
|
18
|
+
const user = findUserBySession(store.get(SESSION_COOKIE)?.value);
|
|
19
|
+
const entry = getLazyPage(id, user);
|
|
20
|
+
if (!entry) {
|
|
21
|
+
return Response.json(
|
|
22
|
+
{ error: "页面不存在或无权限" },
|
|
23
|
+
{ status: 404, headers: corsHeaders(request) },
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return Response.json(
|
|
27
|
+
{
|
|
28
|
+
id,
|
|
29
|
+
resourceType: "page",
|
|
30
|
+
version: entry.version,
|
|
31
|
+
dslVersion: "1.0",
|
|
32
|
+
schema: entry.pageDsl,
|
|
33
|
+
updatedAt: new Date().toISOString(),
|
|
34
|
+
},
|
|
35
|
+
{ headers: { "cache-control": "no-store", ...corsHeaders(request) } },
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 预检(跨源带凭据 / 自定义头时) */
|
|
40
|
+
export const OPTIONS = corsPreflight;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { cookies } from "next/headers";
|
|
2
|
+
|
|
3
|
+
import { findUserBySession, SESSION_COOKIE } from "@/server/accounts";
|
|
4
|
+
import { corsPreflight, corsHeaders } from "@/server/cors";
|
|
5
|
+
import { buildSitemap } from "@/server/sitemap";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Sitemap 端点(design/dynamic-page.md):
|
|
9
|
+
* 会话感知——同一 URL,匿名与不同角色拿到不同 sitemap。
|
|
10
|
+
* 入口可配置多个此类 URL(src/pactor/config.ts 的 sources)。
|
|
11
|
+
*/
|
|
12
|
+
export async function GET(request: Request) {
|
|
13
|
+
const store = await cookies();
|
|
14
|
+
const user = findUserBySession(store.get(SESSION_COOKIE)?.value);
|
|
15
|
+
return Response.json(buildSitemap(user), {
|
|
16
|
+
headers: { "cache-control": "no-store", ...corsHeaders(request) },
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 预检(跨源带凭据 / 自定义头时) */
|
|
21
|
+
export const OPTIONS = corsPreflight;
|
|
@@ -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
|
+
* 用户列表:用户列表页的 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;
|
|
Binary file
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--background: #ffffff;
|
|
5
|
+
--foreground: #171717;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@theme inline {
|
|
9
|
+
--color-background: var(--background);
|
|
10
|
+
--color-foreground: var(--foreground);
|
|
11
|
+
--font-sans: var(--font-geist-sans);
|
|
12
|
+
--font-mono: var(--font-geist-mono);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@media (prefers-color-scheme: dark) {
|
|
16
|
+
:root {
|
|
17
|
+
--background: #0a0a0a;
|
|
18
|
+
--foreground: #ededed;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
body {
|
|
23
|
+
background: var(--background);
|
|
24
|
+
color: var(--foreground);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* ── 外壳布局:Header 与左侧栏固定,仅中间内容区滚动 ──────────────
|
|
28
|
+
外层 Layout 锁定视口高度并裁切溢出;Sidebar(.shell-main)占满剩余
|
|
29
|
+
高度并允许收缩(需压过其自带 min-h-svh,提升选择器优先级),侧栏
|
|
30
|
+
菜单由 SidebarContent 自带 overflow-auto 滚动,Content 自身滚动。 */
|
|
31
|
+
.shell-root {
|
|
32
|
+
height: 100dvh;
|
|
33
|
+
overflow: hidden;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.shell-root .shell-main {
|
|
37
|
+
flex: 1;
|
|
38
|
+
min-height: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.shell-content {
|
|
42
|
+
overflow-y: auto;
|
|
43
|
+
overscroll-behavior: contain;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* ── 核心概览指标牌:Grid 项默认拉伸,卡片撑满网格 → 同高自适应 ── */
|
|
47
|
+
.stat-card {
|
|
48
|
+
height: 100%;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.stat-card [data-slot="card-content"] {
|
|
52
|
+
display: flex;
|
|
53
|
+
height: 100%;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
gap: 12px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* ── Provider 数据卡:hover 手型 + 阴影过渡;点击主体进详情(ProviderCardLink)── */
|
|
59
|
+
.provider-card {
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
transition:
|
|
62
|
+
box-shadow 0.2s ease,
|
|
63
|
+
border-color 0.2s ease;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.provider-card:hover {
|
|
67
|
+
border-color: color-mix(in srgb, var(--foreground) 20%, transparent);
|
|
68
|
+
box-shadow: 0 8px 24px color-mix(in srgb, var(--foreground) 12%, transparent);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* 操作按钮组(右上角):仅 hover 卡片时显示 */
|
|
72
|
+
.provider-card-actions {
|
|
73
|
+
opacity: 0;
|
|
74
|
+
pointer-events: none;
|
|
75
|
+
transition: opacity 0.2s ease;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.provider-card:hover .provider-card-actions {
|
|
79
|
+
opacity: 1;
|
|
80
|
+
pointer-events: auto;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* ── Provider 数据卡:内容区撑满卡片高度,能力标签行(末位区块)钉在底部 ── */
|
|
84
|
+
.provider-card [data-slot="card-content"] {
|
|
85
|
+
display: flex;
|
|
86
|
+
height: 100%;
|
|
87
|
+
flex-direction: column;
|
|
88
|
+
gap: 12px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.provider-card [data-slot="card-content"] > :last-child {
|
|
92
|
+
margin-top: auto;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ── Model 数据卡:hover 手型 + 阴影过渡;点击主体进详情(ModelCardLink)── */
|
|
96
|
+
.model-card {
|
|
97
|
+
cursor: pointer;
|
|
98
|
+
transition:
|
|
99
|
+
box-shadow 0.2s ease,
|
|
100
|
+
border-color 0.2s ease;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.model-card:hover {
|
|
104
|
+
border-color: color-mix(in srgb, var(--foreground) 20%, transparent);
|
|
105
|
+
box-shadow: 0 8px 24px color-mix(in srgb, var(--foreground) 12%, transparent);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* 操作按钮组(右上角):仅 hover 卡片时显示 */
|
|
109
|
+
.model-card-actions {
|
|
110
|
+
opacity: 0;
|
|
111
|
+
pointer-events: none;
|
|
112
|
+
transition: opacity 0.2s ease;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.model-card:hover .model-card-actions {
|
|
116
|
+
opacity: 1;
|
|
117
|
+
pointer-events: auto;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ── Model 数据卡:内容区撑满卡片高度,路由摘要行(末位区块)钉在底部 ── */
|
|
121
|
+
.model-card [data-slot="card-content"] {
|
|
122
|
+
display: flex;
|
|
123
|
+
height: 100%;
|
|
124
|
+
flex-direction: column;
|
|
125
|
+
gap: 12px;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.model-card [data-slot="card-content"] > :last-child {
|
|
129
|
+
margin-top: auto;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* ── 启用状态徽标:绿色底纹(Badge 的 className 叠加,暗色主题提亮文字)── */
|
|
133
|
+
.badge-enabled {
|
|
134
|
+
background-color: color-mix(in srgb, #22c55e 15%, transparent);
|
|
135
|
+
border-color: color-mix(in srgb, #22c55e 45%, transparent);
|
|
136
|
+
color: #16a34a;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
[data-pactor-theme="dark"] .badge-enabled {
|
|
140
|
+
color: #4ade80;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* Statistic:标题与数值之间统一 6px 节奏 */
|
|
144
|
+
[data-slot="statistic"] {
|
|
145
|
+
display: flex;
|
|
146
|
+
flex-direction: column;
|
|
147
|
+
gap: 6px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* 指标牌右上角图标:弱化处理,避免与主数字争夺视觉层级 */
|
|
151
|
+
.stat-card-icon {
|
|
152
|
+
color: var(--muted-foreground);
|
|
153
|
+
opacity: 0.65;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* ── 调试台(客户端调试 / 模型调试):左右分栏 ────────────────────
|
|
157
|
+
≥64rem 时左对话区 + 右 400px 配置/明细栏,窄屏上下堆叠。
|
|
158
|
+
不用 Tailwind 响应式工具类(lg:flex-row 等):pactor 预编译样式
|
|
159
|
+
加载在本文件之后,同优先级的基础类会覆盖本文件中的响应式规则;
|
|
160
|
+
分栏容器直接挂在 pactor Flex 上(其自带方向/间距类同为单类
|
|
161
|
+
选择器),故 .debug-split 以双类选择器提升优先级压过它们。 */
|
|
162
|
+
.debug-split.debug-split {
|
|
163
|
+
display: flex;
|
|
164
|
+
flex-direction: column;
|
|
165
|
+
gap: 16px;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.debug-split-main {
|
|
169
|
+
min-width: 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.debug-split-side {
|
|
173
|
+
width: 100%;
|
|
174
|
+
flex-shrink: 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@media (min-width: 64rem) {
|
|
178
|
+
.debug-split.debug-split {
|
|
179
|
+
/* 吃掉 page-content 剩余高度;交叉轴默认 stretch → 双列等高 */
|
|
180
|
+
flex: 1;
|
|
181
|
+
min-height: 0;
|
|
182
|
+
flex-direction: row;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.debug-split-main {
|
|
186
|
+
flex: 1;
|
|
187
|
+
min-height: 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.debug-split-side {
|
|
191
|
+
width: 400px;
|
|
192
|
+
min-height: 0;
|
|
193
|
+
/* 右列内容超出时列内滚动(不产生页面级滚动条) */
|
|
194
|
+
overflow-y: auto;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* 右列等高:末位卡片吃掉剩余高度,与左列卡片底边对齐 */
|
|
198
|
+
.debug-split-side > [data-slot="card"]:last-child {
|
|
199
|
+
flex: 1;
|
|
200
|
+
min-height: 0;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* ── 调试台:双列等高、撑满标题以下区域、页面不出滚动条 ──────────
|
|
205
|
+
高度链:Page 根(撑满 Content)→ page-content(flex 列)→
|
|
206
|
+
.debug-split(行模式 flex:1)→ 左列 Card / 对话面板(flex:1 传递高度)→
|
|
207
|
+
消息列表列内滚动。以下为未分层自定义规则,优先于 Tailwind 分层工具类。 */
|
|
208
|
+
.debug-page {
|
|
209
|
+
min-height: 100%;
|
|
210
|
+
display: flex;
|
|
211
|
+
flex-direction: column;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.debug-page [data-slot="page-content"] {
|
|
215
|
+
flex: 1;
|
|
216
|
+
min-height: 0;
|
|
217
|
+
display: flex;
|
|
218
|
+
flex-direction: column;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.debug-chat-card,
|
|
222
|
+
.debug-chat-card [data-slot="card-content"] {
|
|
223
|
+
display: flex;
|
|
224
|
+
flex-direction: column;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@media (min-width: 64rem) {
|
|
228
|
+
/* 行模式下锁死页面高度(而非 min-height),高度链硬约束:
|
|
229
|
+
内容超出时由列内滚动吸收,页面级不出滚动条 */
|
|
230
|
+
.debug-page {
|
|
231
|
+
height: 100%;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.debug-chat-card,
|
|
235
|
+
.debug-chat-card [data-slot="card-content"],
|
|
236
|
+
.debug-chat-root {
|
|
237
|
+
flex: 1;
|
|
238
|
+
min-height: 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* 行模式下列高确定:消息列表吃掉剩余高度并内部滚动(覆盖组件
|
|
242
|
+
内 h-[52vh] 兜底——窄屏上下堆叠时仍按 52vh 展示) */
|
|
243
|
+
.debug-chat-list {
|
|
244
|
+
flex: 1;
|
|
245
|
+
height: auto;
|
|
246
|
+
min-height: 0;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Geist, Geist_Mono } from "next/font/google";
|
|
3
|
+
import { PactorApp } from "@/pactor/App";
|
|
4
|
+
// 顺序约定:globals(本项目 tailwind 工具类 + 皮肤无关的外壳规则)→
|
|
5
|
+
// ui 预建样式(同层冲突时 ui 胜出,保证其内部 base/响应式变体排序生效,
|
|
6
|
+
// 如 Sidebar 的 hidden md:block)→ login.css(登录页视觉,须在 ui 之后,
|
|
7
|
+
// 经 data-slot 钩子压过组件默认样式)。主题令牌由 public/skins/<skin>.css
|
|
8
|
+
// 经下方内联脚本在首屏注入(link 后加载者胜出)。
|
|
9
|
+
import "./globals.css";
|
|
10
|
+
import "@pactor-app/ui/styles.css";
|
|
11
|
+
import "./login.css";
|
|
12
|
+
|
|
13
|
+
export const metadata: Metadata = {
|
|
14
|
+
title: "Pactor Console",
|
|
15
|
+
description: "Pactor Console 管理台",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default function RootLayout({ children }: LayoutProps<"/">) {
|
|
19
|
+
return (
|
|
20
|
+
<html
|
|
21
|
+
lang="zh-CN"
|
|
22
|
+
suppressHydrationWarning
|
|
23
|
+
className={`h-full antialiased`}
|
|
24
|
+
>
|
|
25
|
+
<head>
|
|
26
|
+
<script
|
|
27
|
+
dangerouslySetInnerHTML={{
|
|
28
|
+
// 首屏渲染前加载皮肤 link(data-pactor-skin 标记,对应 public/skins/,
|
|
29
|
+
// 由 Theme Studio 生成;运行时换肤见 src/pactor/skin.ts)并应用明暗
|
|
30
|
+
//(data-pactor-theme),避免闪烁。皮肤与明暗偏好持久化在
|
|
31
|
+
// localStorage(mc-skin / mc-mode),由 Header 的 ThemeSwitcher 写入;
|
|
32
|
+
// mc-mode 缺省时跟随系统并监听变化。
|
|
33
|
+
// suppressHydrationWarning 容忍 data-pactor-theme 造成的 hydration 差异。
|
|
34
|
+
__html: `(function(){var root=document.documentElement;var skin=localStorage.getItem("mc-skin")||"chenwunaifu";var link=document.createElement("link");link.rel="stylesheet";link.href="/skins/"+skin+".css";link.setAttribute("data-pactor-skin",skin);document.head.appendChild(link);var m=window.matchMedia("(prefers-color-scheme: dark)");function apply(){var mode=localStorage.getItem("mc-mode");var dark=mode?mode==="dark":m.matches;if(dark){root.setAttribute("data-pactor-theme","dark")}else{root.removeAttribute("data-pactor-theme")}}apply();m.addEventListener("change",apply)})()`,
|
|
35
|
+
}}
|
|
36
|
+
/>
|
|
37
|
+
</head>
|
|
38
|
+
<body className="h-full flex flex-col">
|
|
39
|
+
{/*
|
|
40
|
+
* PactorApp 挂在 Next layout 而非 page:App Router 导航时 page 会
|
|
41
|
+
* 重挂载、layout 常驻——sitemap 只拉取一次,切换导航只刷新内容区。
|
|
42
|
+
* children(page)恒为 null,路由由 PactorApp 内部按 sitemap 解析。
|
|
43
|
+
*/}
|
|
44
|
+
<PactorApp />
|
|
45
|
+
{children}
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/* 登录页视觉优化:居中渐变背景 + 卡片/表单细节打磨。
|
|
2
|
+
作用域限定 .login-page;DSL 不支持自定义 className,
|
|
3
|
+
故经 Pactor 组件的 data-slot 钩子定位(须在 @pactor-app/ui/styles.css 之后加载)。 */
|
|
4
|
+
|
|
5
|
+
.login-page {
|
|
6
|
+
flex: 1;
|
|
7
|
+
min-height: 100%;
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
padding: 3rem 1rem;
|
|
12
|
+
background: radial-gradient(circle at 20% 15%, rgba(128, 165, 160, .2), transparent 32%), radial-gradient(circle at 85% 88%, rgba(223, 127, 71, .1), transparent 34%), #fff;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.login-card {
|
|
16
|
+
width: min(100%, 420px);
|
|
17
|
+
padding: 32px 28px;
|
|
18
|
+
border-radius: 16px;
|
|
19
|
+
background: rgba(250, 250, 249, .78);
|
|
20
|
+
backdrop-filter: blur(18px);
|
|
21
|
+
-webkit-backdrop-filter: blur(18px);
|
|
22
|
+
box-shadow: 0 24px 70px rgba(35, 42, 39, .14);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@media (prefers-color-scheme: dark) {
|
|
26
|
+
.login-page {
|
|
27
|
+
background:
|
|
28
|
+
radial-gradient(circle at 20% 15%, rgb(128 165 160 / 0.16), transparent 34%),
|
|
29
|
+
radial-gradient(circle at 85% 88%, rgb(223 127 71 / 0.08), transparent 36%),
|
|
30
|
+
#0e1211;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* 登录卡片:磨砂底转为深色半透明,边框按暗场调整 */
|
|
34
|
+
.login-card {
|
|
35
|
+
background: rgb(22 27 25 / 0.72);
|
|
36
|
+
border-color: rgb(255 255 255 / 0.08);
|
|
37
|
+
box-shadow: 0 24px 70px rgba(35, 42, 39, .14);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.login-page [data-slot="button"]:hover {
|
|
41
|
+
box-shadow: 0 6px 16px -6px rgb(0 0 0 / 0.6);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* 登录卡片:更圆、入场动画(全站 Card 无阴影,见 theme.css) */
|
|
46
|
+
.login-page [data-slot="card"] {
|
|
47
|
+
width: 100%;
|
|
48
|
+
max-width: 26rem;
|
|
49
|
+
border-radius: calc(var(--radius) * 2);
|
|
50
|
+
animation: login-card-in 0.45s cubic-bezier(0.21, 1.02, 0.73, 1) both;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@keyframes login-card-in {
|
|
54
|
+
from {
|
|
55
|
+
opacity: 0;
|
|
56
|
+
transform: translateY(12px) scale(0.98);
|
|
57
|
+
}
|
|
58
|
+
to {
|
|
59
|
+
opacity: 1;
|
|
60
|
+
transform: none;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* 标题区三行文字(RESTRICTED ACCESS / 管理后台登录 / 副标题),
|
|
65
|
+
Text 组件经 className 透传定位;卡片内容 gap 较宽,用负 margin 收紧组内间距 */
|
|
66
|
+
.login-page .login-eyebrow {
|
|
67
|
+
display: block;
|
|
68
|
+
font-family: var(--font-geist-mono), ui-monospace, monospace;
|
|
69
|
+
font-size: 0.75rem;
|
|
70
|
+
letter-spacing: 0.18em;
|
|
71
|
+
text-transform: uppercase;
|
|
72
|
+
color: var(--primary);
|
|
73
|
+
margin-bottom: 12px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.login-page .login-heading {
|
|
77
|
+
display: block;
|
|
78
|
+
margin-top: -0.375rem;
|
|
79
|
+
font-size: 1.5rem;
|
|
80
|
+
font-weight: 700;
|
|
81
|
+
letter-spacing: 0.01em;
|
|
82
|
+
margin-bottom: 24px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.login-page .login-subtitle {
|
|
86
|
+
display: block;
|
|
87
|
+
margin-top: -0.375rem;
|
|
88
|
+
font-size: 0.875rem;
|
|
89
|
+
margin-bottom: 28px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.login-page .login-demo-hint {
|
|
93
|
+
display: block;
|
|
94
|
+
font-size: 0.8125rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* 提示条圆角跟随卡片 */
|
|
98
|
+
.login-page [data-slot="alert"] {
|
|
99
|
+
border-radius: calc(var(--radius) * 1.2);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* 输入框加高 */
|
|
103
|
+
.login-page [data-slot="input"] {
|
|
104
|
+
height: 2.75rem;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* 登录按钮:加高、加粗、悬浮/按压反馈 */
|
|
108
|
+
.login-page [data-slot="button"] {
|
|
109
|
+
height: 2.75rem;
|
|
110
|
+
font-weight: 600;
|
|
111
|
+
transition:
|
|
112
|
+
transform 0.15s ease,
|
|
113
|
+
box-shadow 0.15s ease;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.login-page [data-slot="button"]:hover {
|
|
117
|
+
box-shadow: 0 6px 16px -6px rgb(15 23 42 / 0.35);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.login-page [data-slot="button"]:active {
|
|
121
|
+
transform: translateY(1px);
|
|
122
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
+
import nextTs from "eslint-config-next/typescript";
|
|
4
|
+
|
|
5
|
+
const eslintConfig = defineConfig([
|
|
6
|
+
...nextVitals,
|
|
7
|
+
...nextTs,
|
|
8
|
+
// Override default ignores of eslint-config-next.
|
|
9
|
+
globalIgnores([
|
|
10
|
+
// Default ignores of eslint-config-next:
|
|
11
|
+
".next/**",
|
|
12
|
+
"out/**",
|
|
13
|
+
"build/**",
|
|
14
|
+
"next-env.d.ts",
|
|
15
|
+
]),
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export default eslintConfig;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/// <reference types="next" />
|
|
2
|
+
/// <reference types="next/image-types/global" />
|
|
3
|
+
import "./.next/dev/types/routes.d.ts";
|
|
4
|
+
import "./.next/dev/types/root-params.d.ts";
|
|
5
|
+
|
|
6
|
+
// NOTE: This file should not be edited
|
|
7
|
+
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
|
@@ -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;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "prd-app-template",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev -p 9011",
|
|
7
|
+
"build": "node scripts/build.mjs production",
|
|
8
|
+
"start": "next start -p 9011",
|
|
9
|
+
"lint": "eslint",
|
|
10
|
+
"build:dev": "node scripts/build.mjs development",
|
|
11
|
+
"build:test": "node scripts/build.mjs test"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@pactor-app/core": "^1.23.0",
|
|
15
|
+
"@pactor-app/runtime": "^1.23.0",
|
|
16
|
+
"@pactor-app/ui": "^1.23.0",
|
|
17
|
+
"lucide-react": "1.45.0",
|
|
18
|
+
"next": "16.3.5",
|
|
19
|
+
"pactor-app": "^1.23.0",
|
|
20
|
+
"react": "19.2.8",
|
|
21
|
+
"react-dom": "19.2.8",
|
|
22
|
+
"yaml": "^2.9.1"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@tailwindcss/postcss": "^4",
|
|
26
|
+
"@types/node": "^20",
|
|
27
|
+
"@types/react": "^19",
|
|
28
|
+
"@types/react-dom": "^19",
|
|
29
|
+
"eslint": "^9",
|
|
30
|
+
"eslint-config-next": "16.3.5",
|
|
31
|
+
"tailwindcss": "^4",
|
|
32
|
+
"typescript": "^5"
|
|
33
|
+
}
|
|
34
|
+
}
|