diff --git a/AGENTS.md b/AGENTS.md index 4ade33d..069be90 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -94,12 +94,14 @@ - 如果任务准备上线、范围较大、或存在不确定性,再追加:`release_gate` - 只要任务落入高风险边界,`orchestrator` 默认采用更保守的判断和验证策略。 -UI 基线规则: +UI 基线规则(2026-08-15 更新): -- 当前项目的 UI 统一标准默认以首页为基线。 -- 首页对应文件:`app/page.tsx` -- `ui_auditor` 在审查其它页面时,优先对齐首页的视觉语言、主按钮风格、信息层级、间距节奏和状态反馈。 -- 如果某个后台页面或特殊业务页面需要偏离首页风格,必须说明原因和影响范围。 +- 平台主体已切换为「由壹点·品牌中心」看板,UI 统一标准以**看板首页**为基线(`app/page.tsx`)。 +- 基线语言:Zen Minimal × Future Organic 白皮书风(白纸 #fafafa / 墨字 #1a1c1d / 发丝线 / 编号索引 / 柔紫光晕点缀 / 无阴影平面化),完整令牌见 `DESIGN.md`。 +- **全站浅色单主题**:深色主题已废弃(`tailwind darkMode: 'class'` 且不挂 `.dark`,`dark:` 变体全部失效);新代码不得再写 `dark:` 类。 +- logo 双版本:浅底表面用 `loginLogoUrl`(深色图案),黑底遗留表面用 `headerLogoUrl`。 +- `ui_auditor` 在审查其它页面时,优先对齐上述语言;遗留页面(admin/profile/share/edit 与组织架构页旧壳)按「逐页统一」节奏迁移。 +- 如果某个后台页面或特殊业务页面需要偏离基线风格,必须说明原因和影响范围。 ## 5. 前端体验与 Impeccable 规则 @@ -124,3 +126,4 @@ UI 基线规则: - `README.md` - `docs/CODEX_MULTI_AGENTS.md` +- `.private\gongxiangp2-完整敏感接管清单-2026-08-13.md` diff --git a/CLAUDE.md b/CLAUDE.md index 214a485..2215026 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,6 +63,35 @@ npm run lint # ESLint - `gongxiangpan-redis`(生产 Redis) - `youyidian-3d-shadow`(影子环境应用) +### ⚠️ 生产实际部署形态(2026-08-14 现场核实,与旧文档不一致,以此为准) + +CLAUDE.md 旧版记的容器名/端口映射与线上对不上,实际是**蓝绿部署 + 独立 nginx 反代**: + +- **应用容器采用蓝绿双实例**:`youyidian-3d-material-blue` / `youyidian-3d-material-green`,同一时刻一个接流量、另一个待命回退。容器**不直接映射宿主机端口**(`3000/tcp` 不外露)。 +- **流量入口是一个独立的 local-proxy nginx**(不是 TrueNAS 系统自带的那个),监听宿主机 `4320`: + - 配置路径:`/mnt/PoolOne/apps/youyidian-3d/local-proxy/youyidian-3d-material/nginx.conf` + - 通过 `upstream` 里的容器内网 IP(如 `172.19.0.3:3000`)指向当前生效实例 + - **切换蓝绿 = 改这个 `upstream` IP + `nginx -s reload`**,`current-container` 文件记录当前指向 +- 应用挂载:`/mnt/PoolOne/apps/youyidian-3d/uploads:/app/uploads`(uploads 实际路径,注意**不是**部署脚本默认的 `/mnt/PoolOne/youyidian-uploads`) +- 应用连的数据库是 `192.168.58.20:5432/lobechat`(宿主机 5432 由 `gongxiangpan-postgres-1` 映射) + +### ⚠️ 大文件分片上传 500 的一个基础设施坑(2026-08-14 已修) + +**现象**:视频等大文件分片上传,chunk 0 就返回 500,前端报"上传块 0 失败,已重试多次"或"分片合并文件必须同时提供 uploadId",但**应用容器日志里查不到该 chunk 请求**——因为请求在 nginx 层就被拦下了。 + +**根因**:local-proxy nginx(以 `nobody` 运行)和 TrueNAS 系统 nginx(以 `www-data` 运行)**共用同一个临时缓冲目录 `/var/lib/nginx/body`**。当系统 nginx 重建该目录并锁成 `www-data drwx------` 后,`nobody` 无法写入,10MB 的分片缓冲失败,nginx 直接回 500。`nginx -s reload` 或系统 nginx 重启都可能触发。 + +**修复**:给 local-proxy 配独立临时目录,不再和系统 nginx 抢。在 `nginx.conf` 的 `http {}` 块内加: +``` +client_body_temp_path /tmp/client_body; +proxy_temp_path /tmp/proxy; +fastcgi_temp_path /tmp/fastcgi; +uwsgi_temp_path /tmp/uwsgi; +scgi_temp_path /tmp/scgi; +``` +并 `mkdir -p` 这些目录、`chown -R nobody:nogroup`、`chmod 700`,再 reload。诊断时先看 `local-proxy/.../error.log` 有没有 `open() "..." failed (13: Permission denied)`。 + + ### 上游报表库(人事与组织架构数据源) ⚠️ **重要认知**:花名册**不是**从钉钉通讯录来的。钉钉在本项目中只负责登录鉴权与消息推送,从未调用过通讯录接口。人事权威源是公司报表库。 @@ -392,10 +421,12 @@ app/ │ ├── external/ # 外部 API 集成 │ └── user/ # 用户资料操作 ├── [id]/share # 公开分享页面 -├── admin/ # 管理员后台页面 +├── admin/ # 管理员后台页面(素材库管理,旧壳待统一) ├── edit/[id]/ # 素材编辑页面 ├── login/ # 登录页面 -└── page.tsx # 首页(素材网格) +├── materials/ # 创意资产库(原素材库首页,2026-08-15 迁入) +├── organization/ # 组织管理(人员架构顶层页,2026-08-15 上移) +└── page.tsx # 品牌中心看板首页(登录落点,Zen×Organic 白皮书风) components/ ├── upload/ # 上传组件 @@ -408,7 +439,8 @@ components/ │ ├── RealPhotoUploader.tsx # 实拍图 │ └── SpecialBatchUploadUI.tsx # 批量上传界面(音乐、视频、3D、品牌VI等) ├── admin/ # 管理后台组件 -├── home/ # 首页组件 +├── board/ # 看板壳组件(BoardShell、MaterialsHeader——新视觉语言) +├── home/ # 创意资产库(原首页)组件 └── detail/ # 详情页组件 lib/ diff --git a/DESIGN.md b/DESIGN.md index fc8c08c..d5ee8d0 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -3,6 +3,7 @@ > 记录自建成代码,非先行规范。方向:用户裁定的 Zen Minimal × Future Organic 融合(2026-08-15)。 +> **全站浅色单主题**:深色主题已废弃(2026-08-15 用户裁定),`darkMode: 'class'` 且不挂 `.dark`,所有 `dark:` 变体失效;新代码禁止再写 `dark:` 类。遗留 `dark:` 类 inert 保留,随逐页统一清理。 ## World diff --git a/PRODUCT.md b/PRODUCT.md index fe695d3..0c212e2 100644 --- a/PRODUCT.md +++ b/PRODUCT.md @@ -32,7 +32,9 @@ Next.js 14 (App Router) + React 18 + TypeScript + TailwindCSS + PostgreSQL(Prism ## Capabilities and Constraints -- 组织架构页:组织树 + 人员列表,展示 M/P 等级、人员归属(部门路径)、直属主管、在职状态。 +- 看板首页 `/`:五大板块编号索引(01 创意资产库全员可进 / 02 组织管理仅管理员 / 03-05 规划中如实标灰),入场"对焦"动效,悬停物理重量语言。 +- 创意资产库 `/materials`:原素材库首页,业务功能不动;顶栏已统一为看板语言(MaterialsHeader,功能与旧顶栏等价)。 +- 组织架构页 `/organization`:组织树 + 人员列表,展示 M/P 等级、人员归属(部门路径)、直属主管、在职状态。 - 纯展示(只读),不改数据。 - 权限:系统管理员看全部;部门负责人看本部门。 - 离职人员默认隐藏,可切换「含离职」。 @@ -44,6 +46,7 @@ Next.js 14 (App Router) + React 18 + TypeScript + TailwindCSS + PostgreSQL(Prism - 平台名:由壹点·品牌中心(隶属由壹点软件集团;素材库模块沿用「由壹点3D素材」之名)。 - **首页视觉方向(2026-08-15 用户裁定):Zen Minimal × Future Organic 融合**——白纸(#fafafa)黑字(#1a1c1d)的瑞士编辑排印、发丝线、超大留白、编号索引列表(作品集语言)、右上角唯一柔紫光晕(#6e79c8 系)缓慢呼吸;平面化无阴影,黑实心按钮 8px 圆角。方向源自用户在 Stitch 自生成的候选 3/4 + 作品集文件夹两份 DESIGN.md(Monochrome/Studio Minimalist)。此前的「云采石场」语言仅保留在组织架构页(BoardShell),后续按新方向对齐。 - **logo 双版本规则**:黑底表面(素材库旧顶栏)用 `headerLogoUrl`(亮色图案);浅底表面(看板首页、BoardShell)用 `loginLogoUrl`(深蓝+橙图案)。两者内容不同,不可混用。 +- **全站浅色单主题(2026-08-15 裁定)**:深色主题废弃,`tailwind darkMode: 'class'` 且不挂 `.dark`;新代码禁止 `dark:` 类。 - 动效标准:略丰富但克制(用户既定),GSAP + matchMedia(prefers-reduced-motion) + fromTo + clearProps,200-500ms,信息效率优先。 ## Evidence on Hand diff --git a/README.md b/README.md index cb624e2..be90666 100644 --- a/README.md +++ b/README.md @@ -75,13 +75,15 @@ ```text gongxiangp2/ ├── app/ # 页面、App Router、API 路由 -│ ├── admin/ # 管理后台页面 +│ ├── admin/ # 管理后台页面(素材库管理,旧壳待统一) │ ├── api/ # 业务 API │ ├── login/ # 登录页 +│ ├── materials/ # 创意资产库(原素材库首页) +│ ├── organization/ # 组织管理(人员架构顶层页) │ ├── register/ # 注册页 │ ├── share/ # 分享页 -│ └── page.tsx # 首页 -├── components/ # 通用组件、后台组件、上传组件 +│ └── page.tsx # 品牌中心看板首页(登录落点) +├── components/ # 通用组件、后台组件、上传组件、看板壳(board/) ├── lib/ # 业务工具、上传/下载/转码/权限逻辑 ├── prisma/ # Prisma schema ├── scripts/ # 运维/修复/清理脚本 diff --git a/app/admin/page.tsx b/app/admin/page.tsx index faa13cc..26f3396 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useState } from 'react' import { useSession } from 'next-auth/react' import { useRouter } from 'next/navigation' -import Header from '@/components/Header' +import MaterialsHeader from '@/components/board/MaterialsHeader' import DepartmentPermissionsTab from '@/components/DepartmentPermissionsTab' import SuperAdminsTab from '@/components/SuperAdminsTab' import MaterialManagement from '@/components/admin/MaterialManagement' @@ -269,7 +269,7 @@ export default function AdminPage() { if (status === 'loading' || loading) { return (
-
加载中...
+
加载中...
) } @@ -279,8 +279,8 @@ export default function AdminPage() { } return ( -
-
+
+ {/* Toast通知 */} {toast && ( @@ -295,7 +295,7 @@ export default function AdminPage() {
-

系统管理

+

治理与设置

{canManageSystemTabs && ( @@ -334,7 +334,7 @@ export default function OrgPage() { setSelectedPath(null) }} className={`w-full flex items-center justify-between px-3 py-2 text-left ${ - selectedGroup === 'unclassified' ? 'bg-[#e3ecf5]' : 'hover:bg-[#f7f8fa]' + selectedGroup === 'unclassified' ? 'bg-[#eef0fa]' : 'hover:bg-[#f4f5f7]' }`} > 未确定 @@ -350,7 +350,7 @@ export default function OrgPage() { setSelectedPath(null) }} className={`w-full flex items-center justify-between px-3 py-2 text-left ${ - selectedGroup === 'unbound' ? 'bg-[#e3ecf5]' : 'hover:bg-[#f7f8fa]' + selectedGroup === 'unbound' ? 'bg-[#eef0fa]' : 'hover:bg-[#f4f5f7]' }`} > 未绑定账号 @@ -364,21 +364,21 @@ export default function OrgPage() { {/* 右侧:人员明细 */} -
+
{selectedGroup === 'org' && selectedPath ? ( <> - + L{selectedPath.split('/').length} - + {selectedPath.split('/').pop()} ) : ( - + {selectedGroup === 'unclassified' ? '未确定部门' : '未绑定账号'} )} @@ -388,41 +388,41 @@ export default function OrgPage() { value={keyword} onChange={(e) => setKeyword(e.target.value)} placeholder="搜索姓名 / 工号 / 职位" - className="w-40 md:w-52 text-[13px] px-3 py-1.5 rounded border border-[#d5dae0] bg-[#fbfcfd] focus:outline-none focus:border-[#4a90d9] focus:ring-2 focus:ring-[#e3ecf5]" + className="w-40 md:w-52 text-[13px] px-3 py-1.5 rounded border border-[#d5dae0] bg-[#f4f5f7] focus:outline-none focus:border-[#5a66b8] focus:ring-2 focus:ring-[#eef0fa]" />
{selectedGroup === 'org' && selectedPath && ( -

{selectedPath}

+

{selectedPath}

)}
{/* 统计行 */} {selectedGroup === 'org' && ( -
+
-
+
{selectedEmployees.filter((e) => e.employmentState !== 'RESIGNED').length}
-
在职
+
在职
-
+
{leaderCount}
-
负责人
+
负责人
-
+
{levelCounts[0]?.[0] ?? '—'}
-
最常见等级
+
最常见等级
-
+
{selectedEmployees.filter((e) => e.managerName).length}
-
有主管记录
+
有主管记录
)} @@ -430,68 +430,68 @@ export default function OrgPage() { {/* 人员表 */} {selectedGroup === 'unbound' ? (
-

+

以下账号没有钉钉 ID,无法自动绑定权威人员档案,需人工核对后手动绑定。

{data.unbound.map((u) => (
- + {u.realName ?? u.username} - @{u.username} + @{u.username}
- {u.oldDepartment || '无部门'} + {u.oldDepartment || '无部门'}
))}
) : filteredEmployees.length === 0 ? ( -
该部门暂无人员
+
该部门暂无人员
) : (
- - - - - - - + + + + + + + {filteredEmployees.map((e) => ( - + - - + + - +
人员工号职位等级直属主管状态
人员工号职位等级直属主管状态
- + {(e.displayName ?? e.realName ?? '?').slice(0, 1)} - + {e.displayName ?? e.realName ?? e.username} {e.isLeadership && ( - + 负责人 )}
{e.jobNumber ?? '—'}{e.positionTitle ?? '—'}{e.jobNumber ?? '—'}{e.positionTitle ?? '—'} {e.positionLevel ? ( {e.positionLevel} @@ -501,15 +501,15 @@ export default function OrgPage() { 未记录 )} {e.managerName ?? '—'}{e.managerName ?? '—'} {STATE_LABEL[e.employmentState ?? 'UNKNOWN'] ?? '未知'} diff --git a/app/page.tsx b/app/page.tsx index f206a2e..1113153 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -18,27 +18,18 @@ 'use client' -import { useEffect, useRef, useState } from 'react' +import { useEffect, useRef } from 'react' import Link from 'next/link' import { useRouter } from 'next/navigation' -import { useSession, signOut } from 'next-auth/react' +import { useSession } from 'next-auth/react' import gsap from 'gsap' import { useGSAP } from '@gsap/react' -import BrandLogo from '@/components/BrandLogo' -import { DEFAULT_LOGIN_LOGO_URL } from '@/lib/brand-assets' +import MaterialsHeader from '@/components/board/MaterialsHeader' gsap.registerPlugin(useGSAP) const ADMIN_ROLES = ['ADMIN', 'SYSTEM_ADMIN', 'DEPT_ADMIN'] -const NAV_ITEMS: Array<{ key: string; label: string; href?: string; planned?: boolean; adminOnly?: boolean }> = [ - { key: 'dashboard', label: '中心仪表盘', href: '/' }, - { key: 'organization', label: '组织管理', href: '/organization', adminOnly: true }, - { key: 'assets', label: '资产与AI自动化', href: '/materials' }, - { key: 'business', label: '业务经营', planned: true }, - { key: 'governance', label: '治理与设置', planned: true }, -] - function StatusDot({ tone }: { tone: 'live' | 'building' | 'planned' }) { const tones = { live: 'bg-[#157a4b]', building: 'bg-[#6e79c8]', planned: 'bg-[#9aa0a8]' } as const return