视觉统一(Zen Minimal × Future Organic 白皮书语言,全站浅色单主题): - 登录页:品牌→由壹点·品牌中心,白纸底+发丝线+柔光光晕,黑按钮 - 作品过稿:深灰头→白皮书,返回链接→返回创意资产库 - 绩效考核:桌面+移动双头部换白皮书,标题改绩效考核,返回改链接 - 组织架构:BoardShell+页面 76 处调色板全量替换(#eef1f4→#fafafa 等) - admin 后台:MaterialsHeader(面包屑治理与设置),Tab 栏墨色高亮 - 深色主题废弃:globals.css 移除媒体查询,tailwind darkMode:'class' 不挂 .dark - 设计文档同步更新:AGENTS.md UI 基线/DESIGN.md/PRODUCT.md/README.md/design-qa.md 个人档案(原个人中心): - API 合并权威人员档案(getEmployeeByUserId → 花名/工号/部门/职位/M-P等级/主管/状态) - 页面白皮书化:权威档案卡(只读 HR 字段)+ 账号区(头像上传)+ 素材/收藏 顶栏平台化: - MaterialsHeader 双模式:首页(五板块导航+全套工具+菜单)/板块(面包屑) - 菜单重排:我的档案/绩效考核/作品过稿看板‖更新记录/提交反馈‖治理与设置‖退出 - 菜单头身份行:花名+职位·部门(权威档案) - 首页排序:01 创意资产库(全员)→ 02 组织管理(管理员)→ 03-05 规划中 - 素材库改名创意资产库,全站同步(索引/导航/卡片/按钮/页脚) - 排行榜换白皮书(白底发丝线+琥珀前三名) 构建修复: - tsconfig 排除 output/(本地审计临时文件不再卡类型检查) - 存储边界门禁豁免 output/(与 .dockerignore 对齐)
395 lines
18 KiB
TypeScript
395 lines
18 KiB
TypeScript
/*
|
||
* 方向契约(用户裁定方向:Zen Minimal × Future Organic 融合 · 2026-08-15)
|
||
* THESIS: 看板首页是一张"编辑页"——白纸、黑字、发丝线、超大留白;五个板块排成一列
|
||
* 编号索引(作品集列表语言),悬停整行浮现;唯一的柔光来自一枚有机渐变光晕。
|
||
* 拒绝卡片阵列、拒绝重色块、拒绝装饰;安静得像一本品牌中心的白皮书。
|
||
* OWN-WORLD: 白 #fafafa/#fff、墨 #1a1c1d、正文灰 #6b7075、注脚灰 #86898e、发丝线 #e7e8ea、
|
||
* 柔光紫 #6e79c8 系(唯一彩色氛围)+ 浅晕 #eef0fa、微型大写字距标注、
|
||
* 黑实心按钮 8px 圆角、无阴影平面化;全部文字对比过 WCAG(≥4.5:1 / 大字 ≥3:1)。
|
||
* STORY: 打开即读:巨字「品牌中心」+ 一句矿脉定位;视线沿编号索引下移——
|
||
* 01 组织管理、02 资产是活的(可点,行变黑);03 仪表盘在长;04/05 是待启程的灰行。
|
||
* 两块已上线板块各有一张白卡给入口;页脚是跟踪字距的微型注脚。
|
||
* FIRST VIEWPORT: 极简顶栏(logo+由壹点·品牌中心)→ 超大 600 字重「品牌中心」+ 定位语 →
|
||
* 右侧一枚柔光渐变光晕压角 → 下方五条发丝线分隔的编号索引行。
|
||
* FORM: 用户在 Stitch 自生成的候选 3(Zen Minimal)与 4(Future Organic)融合,
|
||
* 参考作品集文件夹两份 DESIGN.md(Monochrome/Studio Minimalist)裁定。
|
||
* FINISH: unreviewed and undocumented is unfinished; this build ends with the finish review, the verdict, and DESIGN.md
|
||
*/
|
||
|
||
'use client'
|
||
|
||
import { useEffect, useRef } from 'react'
|
||
import Link from 'next/link'
|
||
import { useRouter } from 'next/navigation'
|
||
import { useSession } from 'next-auth/react'
|
||
import gsap from 'gsap'
|
||
import { useGSAP } from '@gsap/react'
|
||
import MaterialsHeader from '@/components/board/MaterialsHeader'
|
||
|
||
gsap.registerPlugin(useGSAP)
|
||
|
||
const ADMIN_ROLES = ['ADMIN', 'SYSTEM_ADMIN', 'DEPT_ADMIN']
|
||
|
||
function StatusDot({ tone }: { tone: 'live' | 'building' | 'planned' }) {
|
||
const tones = { live: 'bg-[#157a4b]', building: 'bg-[#6e79c8]', planned: 'bg-[#9aa0a8]' } as const
|
||
return <span aria-hidden="true" className={`inline-block h-[7px] w-[7px] shrink-0 rounded-full ${tones[tone]}`} />
|
||
}
|
||
|
||
const labelCls = 'text-[11px] uppercase tracking-[0.14em] text-[#86898e]'
|
||
const focusRing = 'focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1a1c1d]/70 focus-visible:ring-offset-2 focus-visible:ring-offset-[#fafafa]'
|
||
|
||
/** 会话加载中的骨架行(防止管理员首帧闪灰) */
|
||
function SkeletonIndex() {
|
||
return (
|
||
<div className="border-t border-[#e7e8ea]">
|
||
{Array.from({ length: 5 }).map((_, i) => (
|
||
<div key={i} className="flex items-center gap-4 border-b border-[#e7e8ea] py-9 md:gap-8">
|
||
<div className="h-3 w-8 shrink-0 animate-pulse bg-[#eceef0]" />
|
||
<div className="min-w-0 flex-1 space-y-2.5">
|
||
<div className="h-5 w-44 animate-pulse bg-[#eceef0]" />
|
||
<div className="h-3 w-72 max-w-full animate-pulse bg-[#f2f3f5]" />
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default function BoardHomePage() {
|
||
const { data: session, status } = useSession()
|
||
const router = useRouter()
|
||
const pageRef = useRef<HTMLDivElement>(null)
|
||
const sessionLoading = status === 'loading'
|
||
|
||
const sessionUser = session?.user as { role?: string } | undefined
|
||
const isAdmin = ADMIN_ROLES.includes(sessionUser?.role || '')
|
||
|
||
useEffect(() => {
|
||
if (status === 'unauthenticated') {
|
||
router.replace('/login')
|
||
}
|
||
}, [status, router])
|
||
|
||
const indexRows = [
|
||
{
|
||
no: '01',
|
||
title: '创意资产库',
|
||
en: 'Creative Assets',
|
||
tone: 'live' as const,
|
||
status: '运行中',
|
||
live: true,
|
||
href: '/materials',
|
||
desc: '素材浏览、上传、分享与下载——品牌中心的创意资产都在这里,全部功能照常使用。',
|
||
},
|
||
{
|
||
no: '02',
|
||
title: '组织管理',
|
||
en: 'People & Org',
|
||
tone: 'live' as const,
|
||
status: '已上线',
|
||
live: isAdmin,
|
||
href: '/organization',
|
||
desc: '人员主档以钉钉唯一值绑定 HR 花名册,组织树与在职状态每日 02:00 自动同步。',
|
||
},
|
||
{
|
||
no: '03',
|
||
title: '中心仪表盘',
|
||
en: 'Dashboard',
|
||
tone: 'building' as const,
|
||
status: '建设中',
|
||
live: false,
|
||
desc: '将接入:核心指标 · 业务趋势 · 六品牌健康度 · 团队产能 · 风险预警',
|
||
},
|
||
{
|
||
no: '04',
|
||
title: '业务经营',
|
||
en: 'Operations',
|
||
tone: 'planned' as const,
|
||
status: '规划中',
|
||
live: false,
|
||
desc: '将包含:新品上新 · 品牌塑造 · 创意生产 · 业绩转化 · 项目协同',
|
||
},
|
||
{
|
||
no: '05',
|
||
title: '治理与设置',
|
||
en: 'Governance',
|
||
tone: 'planned' as const,
|
||
status: '规划中',
|
||
live: false,
|
||
desc: '将包含:权限角色 · 数据口径 · 编辑能力 · 通知机制',
|
||
},
|
||
]
|
||
|
||
useGSAP(() => {
|
||
if (typeof window.matchMedia !== 'function') return
|
||
|
||
const motion = gsap.matchMedia()
|
||
motion.add('(prefers-reduced-motion: no-preference)', () => {
|
||
const tl = gsap.timeline({ defaults: { ease: 'expo.out' } })
|
||
tl
|
||
// 主标题:对焦揭幕——虚焦+宽字距 → 聚焦收拢(重、慢,配 quint 级缓动)
|
||
.fromTo(
|
||
'[data-hero-title]',
|
||
{ filter: 'blur(16px)', opacity: 0, letterSpacing: '0.16em' },
|
||
{
|
||
filter: 'blur(0px)',
|
||
opacity: 1,
|
||
letterSpacing: '-0.03em',
|
||
duration: 1.15,
|
||
ease: 'power4.out',
|
||
clearProps: 'filter,opacity,letterSpacing',
|
||
},
|
||
)
|
||
// 柔光:同语言聚焦绽放
|
||
.fromTo(
|
||
'[data-orb]',
|
||
{ autoAlpha: 0, scale: 0.9, filter: 'blur(12px)' },
|
||
{ autoAlpha: 1, scale: 1, filter: 'blur(0px)', duration: 1.3, clearProps: 'filter' },
|
||
'-=0.8',
|
||
)
|
||
// 定位语
|
||
.fromTo(
|
||
'[data-hero]',
|
||
{ y: 12, autoAlpha: 0 },
|
||
{ y: 0, autoAlpha: 1, duration: 0.55, clearProps: 'opacity,visibility,transform' },
|
||
'-=0.85',
|
||
)
|
||
// 图纸先行:每行的发丝线依次画出,内容再落位
|
||
.fromTo(
|
||
'[data-row-line]',
|
||
{ scaleX: 0 },
|
||
{ scaleX: 1, duration: 0.6, stagger: 0.07 },
|
||
'-=0.55',
|
||
)
|
||
.fromTo(
|
||
'[data-row]',
|
||
{ y: 14, autoAlpha: 0 },
|
||
{ y: 0, autoAlpha: 1, duration: 0.55, stagger: 0.07, clearProps: 'opacity,visibility,transform' },
|
||
'-=0.5',
|
||
)
|
||
// 白卡:虚焦落位(与标题同语言)
|
||
.fromTo(
|
||
'[data-card]',
|
||
{ y: 20, autoAlpha: 0, filter: 'blur(8px)' },
|
||
{
|
||
y: 0,
|
||
autoAlpha: 1,
|
||
filter: 'blur(0px)',
|
||
duration: 0.75,
|
||
stagger: 0.12,
|
||
clearProps: 'opacity,visibility,transform,filter',
|
||
},
|
||
'-=0.4',
|
||
)
|
||
.fromTo(
|
||
'[data-foot]',
|
||
{ autoAlpha: 0 },
|
||
{ autoAlpha: 1, duration: 0.6, clearProps: 'opacity,visibility' },
|
||
'-=0.35',
|
||
)
|
||
// 柔光光晕的呼吸(极慢、纯 opacity)
|
||
gsap.to('[data-orb]', { opacity: 0.75, duration: 5, yoyo: true, repeat: -1, ease: 'sine.inOut' })
|
||
})
|
||
|
||
return () => motion.revert()
|
||
}, { scope: pageRef })
|
||
|
||
return (
|
||
// direction: zen-minimal × future-organic fusion (user-curated) — THESIS: 白皮书式的编号索引,悬停浮现
|
||
<div ref={pageRef} className="board-home min-h-screen bg-[#fafafa] text-[#1a1c1d]">
|
||
<style>{`.board-home ::selection { background: #eef0fa; color: #1a1c1d; }`}</style>
|
||
|
||
{/* ===== 平台顶栏(首页模式:品牌 + 五大板块导航 + 工具 + 用户菜单) ===== */}
|
||
<MaterialsHeader session={session} />
|
||
|
||
<main className="mx-auto max-w-[1280px] px-6">
|
||
{/* ===== 编辑页首:巨字 + 柔光 ===== */}
|
||
<section className="relative overflow-hidden pb-16 pt-20 md:pb-24 md:pt-28">
|
||
{/* 柔光渐变光晕:唯一的有色氛围(来自 Future Organic) */}
|
||
<div
|
||
data-orb
|
||
aria-hidden="true"
|
||
className="pointer-events-none absolute -top-10 right-[-60px] h-[340px] w-[340px] rounded-full opacity-90 md:h-[420px] md:w-[420px]"
|
||
style={{
|
||
background:
|
||
'radial-gradient(circle at 38% 40%, #eef0fa 0%, #dfe3f6 32%, rgba(224,228,248,0.55) 55%, rgba(250,250,250,0) 72%)',
|
||
}}
|
||
/>
|
||
<div className="relative">
|
||
{/* 巨型标题:入场对焦揭幕(GSAP blur+tracking),悬停字距呼吸(CSS) */}
|
||
<h1
|
||
data-hero-title
|
||
className="group cursor-default text-[clamp(60px,9vw,132px)] font-semibold leading-[1.04] tracking-[-0.03em] transition-[letter-spacing] duration-700 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:tracking-[0.035em]"
|
||
>
|
||
品牌中心
|
||
</h1>
|
||
<p data-hero className="mt-6 max-w-xl text-[15px] leading-relaxed text-[#6b7075] md:text-[16px]">
|
||
品牌的矿脉,从人到资产到经营,统一开采。
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
{/* ===== 编号索引(作品集列表语言;加载中骨架化防闪灰) ===== */}
|
||
{sessionLoading ? (
|
||
<SkeletonIndex />
|
||
) : (
|
||
<section aria-label="板块索引" className="border-t border-[#e7e8ea]">
|
||
{indexRows.map((row) =>
|
||
row.live ? (
|
||
<Link
|
||
key={row.no}
|
||
data-row
|
||
href={row.href ?? '#'}
|
||
className={`group relative grid grid-cols-[44px_1fr_auto] items-baseline gap-4 rounded-sm py-7 transition-colors duration-[350ms] ease-[cubic-bezier(0.22,1,0.36,1)] hover:bg-white md:grid-cols-[64px_1fr_160px_120px_32px] md:gap-8 md:py-9 ${focusRing}`}
|
||
>
|
||
{/* 基线发丝线:入场画出,悬停加深 */}
|
||
<span
|
||
data-row-line
|
||
aria-hidden="true"
|
||
className="absolute inset-x-0 bottom-0 h-px origin-left bg-[#e7e8ea] transition-colors duration-[350ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:bg-[#d9dcdf]"
|
||
/>
|
||
{/* 悬停定位线:从左划出 */}
|
||
<span
|
||
aria-hidden="true"
|
||
className="absolute inset-x-0 bottom-[-1px] h-[2px] origin-left scale-x-0 bg-[#1a1c1d] transition-transform duration-[350ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:scale-x-100"
|
||
/>
|
||
<span className="font-en text-[13px] tabular-nums text-[#86898e] transition-all duration-[350ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:translate-x-1 group-hover:text-[#5a66b8]">
|
||
{row.no}
|
||
</span>
|
||
<div className="min-w-0">
|
||
<h2 className="text-[24px] font-semibold tracking-tight transition-transform duration-[350ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:translate-x-1.5 md:text-[30px]">
|
||
{row.title}
|
||
</h2>
|
||
<p className="mt-1.5 text-[13px] leading-relaxed text-[#6b7075] transition-all duration-[350ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:translate-x-1 group-hover:text-[#4a4f55]">
|
||
{row.desc}
|
||
</p>
|
||
</div>
|
||
<span className={`font-en hidden text-[11px] uppercase tracking-[0.14em] text-[#86898e] transition-colors duration-[350ms] group-hover:text-[#6b7075] md:inline`}>
|
||
{row.en}
|
||
</span>
|
||
<span className="flex items-center gap-2">
|
||
<StatusDot tone={row.tone} />
|
||
<span className="text-[12px] text-[#6b7075]">{row.status}</span>
|
||
</span>
|
||
<span
|
||
aria-hidden="true"
|
||
className="hidden -translate-x-2 text-[18px] text-[#1a1c1d] opacity-0 transition-all duration-[350ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:translate-x-0 group-hover:opacity-100 group-focus-visible:translate-x-0 group-focus-visible:opacity-100 md:inline"
|
||
>
|
||
→
|
||
</span>
|
||
</Link>
|
||
) : (
|
||
<div
|
||
key={row.no}
|
||
data-row
|
||
aria-disabled="true"
|
||
className="relative grid grid-cols-[44px_1fr_auto] items-baseline gap-4 py-7 md:grid-cols-[64px_1fr_160px_120px_32px] md:gap-8 md:py-9"
|
||
>
|
||
<span
|
||
data-row-line
|
||
aria-hidden="true"
|
||
className="absolute inset-x-0 bottom-0 h-px origin-left bg-[#e7e8ea]"
|
||
/>
|
||
<span className="font-en text-[13px] tabular-nums text-[#86898e]">{row.no}</span>
|
||
<div className="min-w-0">
|
||
<h2 className="text-[24px] font-medium tracking-tight text-[#8f9398] md:text-[30px]">
|
||
{row.title}
|
||
</h2>
|
||
<p className="mt-1.5 text-[13px] leading-relaxed text-[#86898e]">{row.desc}</p>
|
||
</div>
|
||
<span className="font-en hidden text-[11px] uppercase tracking-[0.14em] text-[#86898e] md:inline">
|
||
{row.en}
|
||
</span>
|
||
<span className="flex items-center gap-2">
|
||
<StatusDot tone={row.tone} />
|
||
<span className="text-[12px] text-[#86898e]">{row.status}</span>
|
||
</span>
|
||
<span aria-hidden="true" className="hidden text-[18px] text-transparent md:inline">
|
||
·
|
||
</span>
|
||
</div>
|
||
),
|
||
)}
|
||
</section>
|
||
)}
|
||
|
||
{/* ===== 两块已上线板块的入口白卡 ===== */}
|
||
{sessionLoading ? (
|
||
<div className="grid gap-6 py-16 md:grid-cols-2 md:gap-8 md:py-24">
|
||
{Array.from({ length: 2 }).map((_, i) => (
|
||
<div key={i} className="h-[260px] animate-pulse rounded-xl border border-[#e7e8ea] bg-white/60" />
|
||
))}
|
||
</div>
|
||
) : (
|
||
<section className="grid gap-6 py-16 md:grid-cols-2 md:gap-8 md:py-24">
|
||
<div data-card className="rounded-xl border border-[#e7e8ea] bg-white p-8 transition-all duration-[400ms] ease-[cubic-bezier(0.22,1,0.36,1)] hover:-translate-y-1.5 hover:border-[#c9ccd1] md:p-10">
|
||
<div className="flex items-center gap-2.5">
|
||
<StatusDot tone="live" />
|
||
<span className="text-[12px] text-[#6b7075]">运行中</span>
|
||
<span className={`font-en ml-auto text-[11px] uppercase tracking-[0.14em] ${labelCls}`}>01 / Assets</span>
|
||
</div>
|
||
<h3 className="mt-5 text-[22px] font-semibold tracking-tight">创意资产库</h3>
|
||
<p className="mt-3 text-[14px] leading-relaxed text-[#6b7075]">
|
||
素材浏览、上传、分享与下载——创意资产的全部日常都在这里。
|
||
</p>
|
||
<div className="mt-7">
|
||
<Link
|
||
href="/materials"
|
||
className={`inline-flex min-h-11 items-center rounded-lg bg-[#1a1c1d] px-6 text-[13px] font-medium text-white transition-all duration-200 hover:opacity-80 active:scale-[0.98] ${focusRing}`}
|
||
>
|
||
进入创意资产库
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
<div data-card className="rounded-xl border border-[#e7e8ea] bg-white p-8 transition-all duration-[400ms] ease-[cubic-bezier(0.22,1,0.36,1)] hover:-translate-y-1.5 hover:border-[#c9ccd1] md:p-10">
|
||
<div className="flex items-center gap-2.5">
|
||
<StatusDot tone="live" />
|
||
<span className="text-[12px] text-[#6b7075]">已上线</span>
|
||
<span className={`font-en ml-auto text-[11px] uppercase tracking-[0.14em] ${labelCls}`}>02 / People</span>
|
||
</div>
|
||
<h3 className="mt-5 text-[22px] font-semibold tracking-tight">组织管理</h3>
|
||
<p className="mt-3 text-[14px] leading-relaxed text-[#6b7075]">
|
||
真实组织树、M/P 等级、汇报线与在职状态,数据源 HR 花名册。
|
||
</p>
|
||
<div className="mt-7 flex flex-wrap gap-3">
|
||
{isAdmin ? (
|
||
<Link
|
||
href="/organization"
|
||
className={`inline-flex min-h-11 items-center rounded-lg bg-[#1a1c1d] px-6 text-[13px] font-medium text-white transition-all duration-200 hover:opacity-80 active:scale-[0.98] ${focusRing}`}
|
||
>
|
||
进入人员架构
|
||
</Link>
|
||
) : (
|
||
<span className="inline-flex min-h-11 items-center rounded-lg border border-[#e7e8ea] px-6 text-[13px] text-[#6b7075]">
|
||
需管理员权限
|
||
</span>
|
||
)}
|
||
<Link
|
||
href="/performance-survey"
|
||
className={`inline-flex min-h-11 items-center rounded-lg border border-[#1a1c1d] bg-white px-6 text-[13px] font-medium transition-all duration-200 hover:bg-[#1a1c1d] hover:text-white active:scale-[0.98] ${focusRing}`}
|
||
>
|
||
绩效考核
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
{/* ===== 微型注脚 ===== */}
|
||
<footer data-foot className="flex flex-wrap items-center gap-x-4 gap-y-2 border-t border-[#e7e8ea] py-10">
|
||
<span className="font-en text-[10px] uppercase tracking-[0.2em] text-[#86898e]">Ground</span>
|
||
<span className="text-[12px] text-[#6b7075]">
|
||
本看板由「由壹点3D素材库」改造而来,素材库现有功能不受影响。
|
||
</span>
|
||
<Link
|
||
href="/materials"
|
||
className={`rounded-sm text-[12px] font-medium text-[#5a66b8] underline-offset-4 hover:underline ${focusRing}`}
|
||
>
|
||
直达创意资产库 →
|
||
</Link>
|
||
</footer>
|
||
</main>
|
||
</div>
|
||
)
|
||
}
|