feat: add team functions department navigation
This commit is contained in:
parent
fe8c17e316
commit
b33cef8c44
@ -1,4 +1,4 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import TeamFunctionsDashboard from './TeamFunctionsDashboard'
|
||||
|
||||
jest.mock('next-auth/react', () => ({
|
||||
@ -7,24 +7,39 @@ jest.mock('next-auth/react', () => ({
|
||||
}))
|
||||
|
||||
describe('TeamFunctionsDashboard', () => {
|
||||
it('shows the four teams and their function branches from the approved map', () => {
|
||||
it('shows department navigation and the selected team introduction', () => {
|
||||
render(<TeamFunctionsDashboard />)
|
||||
|
||||
expect(screen.getByRole('heading', { level: 1, name: '团队职能' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('heading', { level: 2, name: '品牌中心职责地图' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('complementary', { name: '团队职能二级部门导航' })).toBeInTheDocument()
|
||||
|
||||
for (const team of ['品牌创意A部', '品牌创意B部', '空间设计部', '综合支持部']) {
|
||||
expect(screen.getByRole('heading', { level: 2, name: team })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: new RegExp(team) })).toBeInTheDocument()
|
||||
}
|
||||
|
||||
for (const label of ['服务品牌', '前置能力', '定位', '岗位', '职责']) {
|
||||
expect(screen.getAllByText(label).length).toBeGreaterThan(0)
|
||||
}
|
||||
expect(screen.getByRole('region', { name: '品牌创意A部部门介绍' })).toBeInTheDocument()
|
||||
expect(screen.getByText('负责朴美、索菲亚、顾家品牌的视觉创意与内容前置策划,统筹品牌调性、商品卖点及页面与短视频创意。')).toBeInTheDocument()
|
||||
|
||||
for (const item of ['朴美', '索菲亚', '顾家', '欧意朗', '卓禾', '亿家达', '空间设计师', 'AIGC设计师', '批量生产']) {
|
||||
expect(screen.getByText(item)).toBeInTheDocument()
|
||||
for (const item of ['朴美', '索菲亚', '顾家', '品牌视觉策划设计', 'AI+平面+策划', '编导/短视频策划', '品牌调性', '商品卖点', '页面/短视频创意', '经验与复盘']) {
|
||||
expect(screen.getByRole('region', { name: '品牌创意A部部门介绍' }).querySelector(`[data-team-item="${item}"]`)).not.toBeNull()
|
||||
}
|
||||
|
||||
expect(screen.getByRole('link', { name: '返回部门看板' })).toHaveAttribute('href', '/')
|
||||
})
|
||||
|
||||
it('switches the department introduction from the secondary navigation', () => {
|
||||
render(<TeamFunctionsDashboard />)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /空间设计部/ }))
|
||||
|
||||
expect(screen.getByRole('region', { name: '空间设计部部门介绍' })).toBeInTheDocument()
|
||||
expect(screen.getByText('负责空间、置景、灯光、机位与拍摄一体化,为内容生产提供场景和拍摄执行能力。')).toBeInTheDocument()
|
||||
expect(screen.queryByRole('region', { name: '品牌创意A部部门介绍' })).not.toBeInTheDocument()
|
||||
expect(screen.getByText('美术指导')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /品牌创意B部/ }))
|
||||
expect(screen.getByRole('region', { name: '品牌创意B部部门介绍' })).toBeInTheDocument()
|
||||
expect(screen.getByText('欧意朗')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { ArrowLeft, Blocks, BriefcaseBusiness, Check, Layers3, UsersRound } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import DepartmentShell from './DepartmentShell'
|
||||
|
||||
type FunctionBranch = {
|
||||
@ -10,6 +13,7 @@ type FunctionBranch = {
|
||||
type TeamFunction = {
|
||||
name: string
|
||||
lead: string
|
||||
description: string
|
||||
tone: 'coral' | 'navy' | 'blue' | 'teal'
|
||||
branches: FunctionBranch[]
|
||||
}
|
||||
@ -18,6 +22,7 @@ const TEAM_FUNCTIONS: TeamFunction[] = [
|
||||
{
|
||||
name: '品牌创意A部',
|
||||
lead: '伯言',
|
||||
description: '负责朴美、索菲亚、顾家品牌的视觉创意与内容前置策划,统筹品牌调性、商品卖点及页面与短视频创意。',
|
||||
tone: 'coral',
|
||||
branches: [
|
||||
{ label: '服务品牌', items: ['朴美', '索菲亚', '顾家'] },
|
||||
@ -28,6 +33,7 @@ const TEAM_FUNCTIONS: TeamFunction[] = [
|
||||
{
|
||||
name: '品牌创意B部',
|
||||
lead: '清云',
|
||||
description: '负责欧意朗、卓禾、亿家达品牌的视觉创意与内容前置策划,推动品牌辅导、页面及短视频创意落地。',
|
||||
tone: 'navy',
|
||||
branches: [
|
||||
{ label: '服务品牌', items: ['欧意朗', '卓禾', '亿家达'] },
|
||||
@ -38,6 +44,7 @@ const TEAM_FUNCTIONS: TeamFunction[] = [
|
||||
{
|
||||
name: '空间设计部',
|
||||
lead: '希文',
|
||||
description: '负责空间、置景、灯光、机位与拍摄一体化,为内容生产提供场景和拍摄执行能力。',
|
||||
tone: 'blue',
|
||||
branches: [
|
||||
{ label: '定位', items: ['空间、置景、灯光、机位、拍摄一体化'] },
|
||||
@ -48,6 +55,7 @@ const TEAM_FUNCTIONS: TeamFunction[] = [
|
||||
{
|
||||
name: '综合支持部',
|
||||
lead: '无邪',
|
||||
description: '承接3D、AI、平面、修图、视频后期等生产支持,沉淀资产标准并推进批量生产。',
|
||||
tone: 'teal',
|
||||
branches: [
|
||||
{ label: '岗位', items: ['3D设计师', '平面设计师', 'AIGC设计师', '视频后期', '修图'] },
|
||||
@ -104,6 +112,7 @@ function Branch({ branch, tone }: { branch: FunctionBranch; tone: TeamFunction['
|
||||
{branch.items.map((item) => (
|
||||
<div
|
||||
key={item}
|
||||
data-team-item={item}
|
||||
className="flex min-h-10 items-center gap-2 rounded-lg border px-3 py-2 text-sm font-semibold leading-5"
|
||||
style={{ borderColor: colors.line, backgroundColor: colors.node, color: '#26364f' }}
|
||||
>
|
||||
@ -116,41 +125,11 @@ function Branch({ branch, tone }: { branch: FunctionBranch; tone: TeamFunction['
|
||||
)
|
||||
}
|
||||
|
||||
function TeamFunctionPanel({ team }: { team: TeamFunction }) {
|
||||
const colors = toneStyles[team.tone]
|
||||
|
||||
return (
|
||||
<article
|
||||
aria-labelledby={`team-function-${team.tone}`}
|
||||
className="overflow-hidden rounded-xl border border-[#dfe5ed] bg-white"
|
||||
>
|
||||
<header className="flex flex-col gap-4 px-5 py-5 sm:flex-row sm:items-center sm:justify-between sm:px-6">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl" style={{ backgroundColor: colors.accentSoft, color: colors.accent }}>
|
||||
<UsersRound className="h-5 w-5" aria-hidden="true" />
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<h2 id={`team-function-${team.tone}`} className="truncate text-xl font-bold tracking-[-0.02em] text-[#101828] sm:text-2xl">
|
||||
{team.name}
|
||||
</h2>
|
||||
<p className="mt-1 text-sm font-semibold" style={{ color: colors.accentMuted }}>负责人:{team.lead}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="inline-flex min-h-8 items-center self-start rounded-lg px-3 text-xs font-bold sm:self-auto" style={{ backgroundColor: colors.accentSoft, color: colors.accentMuted }}>
|
||||
职能地图
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<div className="border-t border-[#e4e9f0] px-5 py-5 sm:px-6 sm:py-6">
|
||||
<div className="relative space-y-4 md:space-y-5 md:before:absolute md:before:bottom-5 md:before:left-[116px] md:before:top-5 md:before:w-px md:before:bg-[#e9edf3]">
|
||||
{team.branches.map((branch) => <Branch key={branch.label} branch={branch} tone={team.tone} />)}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
export default function TeamFunctionsDashboard() {
|
||||
const [selectedTeamName, setSelectedTeamName] = useState(TEAM_FUNCTIONS[0].name)
|
||||
const selectedTeam = TEAM_FUNCTIONS.find((team) => team.name === selectedTeamName) || TEAM_FUNCTIONS[0]
|
||||
const colors = toneStyles[selectedTeam.tone]
|
||||
|
||||
return (
|
||||
<DepartmentShell>
|
||||
<main className="mx-auto max-w-[1600px] px-4 pb-16 pt-7 sm:px-6 lg:px-8">
|
||||
@ -164,21 +143,62 @@ export default function TeamFunctionsDashboard() {
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section aria-labelledby="team-functions-map-title" className="mt-6 overflow-hidden rounded-xl border border-[#dfe5ed] bg-white shadow-[0_6px_18px_rgba(15,23,42,0.05)]">
|
||||
<header className="flex flex-col gap-3 border-b border-[#e4e9f0] bg-[#f8fafc] px-5 py-5 sm:flex-row sm:items-end sm:justify-between sm:px-7">
|
||||
<div>
|
||||
<h2 id="team-functions-map-title" className="text-xl font-bold tracking-[-0.02em] text-[#101828] sm:text-2xl">品牌中心职责地图</h2>
|
||||
<p className="mt-1 text-sm leading-6 text-[#66758b]">四个团队、三类职责层级;节点内容按现行团队分工整理。</p>
|
||||
<section aria-labelledby="team-functions-map-title" className="mt-6 overflow-hidden rounded-xl border border-[#dfe5ed] bg-white shadow-[0_6px_18px_rgba(15,23,42,0.05)] lg:grid lg:grid-cols-[232px_minmax(0,1fr)]">
|
||||
<aside aria-label="团队职能二级部门导航" className="border-b border-[#e4e9f0] bg-[#f8fafc] lg:border-b-0 lg:border-r">
|
||||
<div className="hidden border-b border-[#e4e9f0] px-4 py-4 lg:block">
|
||||
<p className="text-xs font-bold text-[#66758b]">部门</p>
|
||||
<h2 id="team-functions-map-title" className="mt-1 text-sm font-bold text-[#101828]">品牌中心职责地图</h2>
|
||||
</div>
|
||||
<nav className="flex gap-1 overflow-x-auto p-2 lg:block lg:space-y-1 lg:overflow-visible" aria-label="选择部门">
|
||||
{TEAM_FUNCTIONS.map((team) => {
|
||||
const teamColors = toneStyles[team.tone]
|
||||
const selected = selectedTeam.name === team.name
|
||||
return (
|
||||
<button
|
||||
key={team.name}
|
||||
type="button"
|
||||
aria-current={selected ? 'page' : undefined}
|
||||
onClick={() => setSelectedTeamName(team.name)}
|
||||
className={`flex min-h-[58px] shrink-0 items-center gap-3 rounded-lg px-3 py-2.5 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#1677ff] lg:w-full ${selected ? 'bg-[#1677ff] text-white' : 'text-[#4f6077] hover:bg-[#eef2f6] hover:text-[#101828]'}`}
|
||||
>
|
||||
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg" style={{ backgroundColor: selected ? 'rgba(255,255,255,0.16)' : teamColors.accentSoft, color: selected ? '#fff' : teamColors.accent }}>
|
||||
<UsersRound className="h-4 w-4" aria-hidden="true" />
|
||||
</span>
|
||||
<span className="min-w-0">
|
||||
<span className="block truncate text-sm font-bold">{team.name}</span>
|
||||
<span className={`mt-0.5 block truncate text-xs ${selected ? 'text-white/75' : 'text-[#667085]'}`}>负责人:{team.lead}</span>
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<section aria-label={`${selectedTeam.name}部门介绍`} className="min-w-0">
|
||||
<header className="flex flex-col gap-4 border-b border-[#e4e9f0] px-5 py-5 sm:px-7 sm:py-6 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div className="flex min-w-0 items-start gap-3">
|
||||
<span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl" style={{ backgroundColor: colors.accentSoft, color: colors.accent }}>
|
||||
<UsersRound className="h-5 w-5" aria-hidden="true" />
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs font-bold" style={{ color: colors.accentMuted }}>当前部门</p>
|
||||
<h2 className="mt-1 text-2xl font-bold tracking-[-0.02em] text-[#101828]">{selectedTeam.name}</h2>
|
||||
<p className="mt-1 text-sm font-semibold" style={{ color: colors.accentMuted }}>负责人:{selectedTeam.lead}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 text-xs font-bold text-[#5f7189]" aria-label="职责地图说明">
|
||||
<span className="inline-flex items-center gap-1.5 rounded-lg bg-white px-3 py-2"><BriefcaseBusiness className="h-3.5 w-3.5" aria-hidden="true" />服务与定位</span>
|
||||
<span className="inline-flex items-center gap-1.5 rounded-lg bg-white px-3 py-2"><Layers3 className="h-3.5 w-3.5" aria-hidden="true" />能力与岗位</span>
|
||||
<span className="inline-flex items-center gap-1.5 rounded-lg bg-[#f8fafc] px-3 py-2"><BriefcaseBusiness className="h-3.5 w-3.5" aria-hidden="true" />服务与定位</span>
|
||||
<span className="inline-flex items-center gap-1.5 rounded-lg bg-[#f8fafc] px-3 py-2"><Layers3 className="h-3.5 w-3.5" aria-hidden="true" />能力与岗位</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="grid gap-4 p-4 sm:p-6 xl:grid-cols-2">
|
||||
{TEAM_FUNCTIONS.map((team) => <TeamFunctionPanel key={team.name} team={team} />)}
|
||||
<div className="px-5 py-5 sm:px-7 sm:py-6">
|
||||
<p className="max-w-3xl text-base leading-7 text-[#4f6077]">{selectedTeam.description}</p>
|
||||
<div className="relative mt-6 space-y-4 md:space-y-5 md:before:absolute md:before:bottom-5 md:before:left-[116px] md:before:top-5 md:before:w-px md:before:bg-[#e9edf3]">
|
||||
{selectedTeam.branches.map((branch) => <Branch key={branch.label} branch={branch} tone={selectedTeam.tone} />)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<Link
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user