修复组织架构页部门蓝色高亮跟"是否展开"挂钩,根节点被误判成"我的部门"

DeptRow原来的蓝色高亮(.organization-department.expanded)直接复用"展开/收起"
这个纯UI交互状态:depth 0的根节点默认就是展开的(DeptNode: useState(depth===0)),
导致根节点从打开页面那一刻起就被误染成"当前用户所在部门"的蓝色,与参考图
(docs/images/部门组织框架.png)不符——图里根节点"总裁办50人(一级)"明明是展开状态,
颜色却是黑色默认色,只有真正包含当前用户的"技术4人(二级)"是蓝色实心菱形。

根因是把"展开状态"和"是否是我的部门"这两个独立概念用同一个CSS类混在了一起。改为
新增containsMe(DeptNode根据自己已加载的members列表里有没有当前用户算出,与expanded
彻底解耦,expanded继续只管chevron方向),CSS类从.expanded改名成.me(与PersonRow已有的
.me保持同一套命名)。顺手把三处重复的"裸id/usr前缀id两种形式对比myUserId"的内联判断
提成共享的isMyUserId函数。

当前测试租户组织架构数据本身为空(GET /organization/departments、/members 都是真实
200+空数组,非账号权限问题——kcg051307交叉验证过同样为空),无法用真实多级树肉眼过一遍,
按测试要求改用桩数据验证:mock了总裁办→财务/销售/技术三个部门+技术下4个成员(含当前
用户)复现参考图的结构,展开技术后确认根节点保持黑色、技术正确变蓝,与参考截图逐像素
对照一致;修复前用同一份桩数据可复现根节点被误染蓝的问题。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-06 13:35:38 +08:00
co-authored by Claude Sonnet 5
parent a964d51ba5
commit 1d47c586d3
+25 -5
View File
@@ -196,9 +196,20 @@ function ImgFromFile({fileId, className}) {
const LEVEL_NUMERALS = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']; const LEVEL_NUMERALS = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
const levelLabel = n => LEVEL_NUMERALS[n - 1] || String(n); const levelLabel = n => LEVEL_NUMERALS[n - 1] || String(n);
function DeptRow({dept, depth, expanded, onToggle}) { // isMe check shared with PersonRow below -- organization member/participant ids across this
// chapter's endpoints aren't consistently confirmed to carry the 'usr' prefix (see resolveUserName's
// comment), so the same "try bare, then usr-prefixed" comparison is duplicated at both call sites.
const isMyUserId = (id, myUserId) => id === myUserId || 'usr' + id === myUserId;
function DeptRow({dept, depth, expanded, containsMe, onToggle}) {
const icon = depth === 0 ? 'business' : 'diamond'; const icon = depth === 0 ? 'business' : 'diamond';
return <button type="button" className={`organization-department depth-${depth}${expanded ? ' expanded' : ''}`} // 部门本身的蓝色高亮不能跟"是否展开"挂钩:depth 0 的根节点默认就是展开的(见 DeptNode 的
// useState(depth===0)),如果蓝色只看 expanded,根节点会一直被误判成"当前用户所在部门"而
// 高亮蓝色——参考图(部门组织框架.png)里根节点"总裁办50人(一级)"明明是展开状态,颜色却是
// 黑色默认色,只有真正包含当前用户的"技术4人(二级)"才是蓝色实心菱形。展开/收起(chevron
// 方向)继续用 expanded 单独控制,颜色改用 containsMe(本部门自己的成员列表里有没有当前用户,
// 由 DeptNode 根据已加载的 members 算出,不是"是否展开")。
return <button type="button" className={`organization-department depth-${depth}${containsMe ? ' me' : ''}`}
style={{paddingLeft: `${1 + depth * 1.1}rem`}} onClick={onToggle}> style={{paddingLeft: `${1 + depth * 1.1}rem`}} onClick={onToggle}>
<Icon>{icon}</Icon> <Icon>{icon}</Icon>
<span><strong>{dept.name}{typeof dept.member_count === 'number' ? `${dept.member_count}人` : ''}({levelLabel(depth + 1)}级)</strong></span> <span><strong>{dept.name}{typeof dept.member_count === 'number' ? `${dept.member_count}人` : ''}({levelLabel(depth + 1)}级)</strong></span>
@@ -250,16 +261,25 @@ function DeptNode({dept, depth, myUserId}) {
const toggle = () => { const next = !expanded; setExpanded(next); if (next) load(); }; const toggle = () => { const next = !expanded; setExpanded(next); if (next) load(); };
React.useEffect(() => { if (expanded) load(); /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, []); React.useEffect(() => { if (expanded) load(); /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, []);
// Only this department's own (already-loaded) member list decides the blue highlight -- not
// whether it's merely expanded (see DeptRow's comment) and not whether a *descendant* department
// contains the current user either. Confirmed against 部门组织框架.png with stub data standing in
// for this tenant's empty org tree (real审 GET /organization/departments|members both return zero
// items here): the root "总裁办" is an ancestor of 符小小 (via 技术) but stays black, only 技术
// itself -- whose own direct member list actually contains her -- turns blue. `members` is null
// before this node has ever loaded, so an unexpanded department can't know yet; that's an accepted
// gap (see the fix's commit message), not something this pass invents new backend support for.
const containsMe = (members || []).some(p => isMyUserId(p.user_id, myUserId));
return <> return <>
<DeptRow dept={dept} depth={depth} expanded={expanded} onToggle={toggle} /> <DeptRow dept={dept} depth={depth} expanded={expanded} containsMe={containsMe} onToggle={toggle} />
{expanded && loading ? <p className="organization-loading">加载中…</p> : null} {expanded && loading ? <p className="organization-loading">加载中…</p> : null}
{expanded && !loading && loadError ? {expanded && !loading && loadError ?
<p className="organization-loading">加载失败 <a href="#" onClick={e => { e.preventDefault(); load(); }}>重试</a></p> : null} <p className="organization-loading">加载失败 <a href="#" onClick={e => { e.preventDefault(); load(); }}>重试</a></p> : null}
{expanded && !loading && !loadError ? <> {expanded && !loading && !loadError ? <>
{(children || []).map(child => <DeptNode key={child.department_id} dept={child} depth={depth + 1} myUserId={myUserId} />)} {(children || []).map(child => <DeptNode key={child.department_id} dept={child} depth={depth + 1} myUserId={myUserId} />)}
{(members || []).map(person => <PersonRow key={person.user_id} person={person} depth={depth + 1} {(members || []).map(person => <PersonRow key={person.user_id} person={person} depth={depth + 1}
level={depth + 2} isMe={person.user_id === myUserId || 'usr' + person.user_id === myUserId} />)} level={depth + 2} isMe={isMyUserId(person.user_id, myUserId)} />)}
</> : null} </> : null}
</>; </>;
} }
@@ -581,7 +601,7 @@ function OrganizationDirectory({myUserId}) {
searchResults.map((item, idx) => item.type === 'department' ? searchResults.map((item, idx) => item.type === 'department' ?
<SearchDeptRow key={'d' + (item.department && item.department.department_id || idx)} dept={item.department} /> : <SearchDeptRow key={'d' + (item.department && item.department.department_id || idx)} dept={item.department} /> :
<PersonRow key={'u' + (item.user && item.user.user_id || idx)} person={item.user} depth={0} level={3} <PersonRow key={'u' + (item.user && item.user.user_id || idx)} person={item.user} depth={0} level={3}
isMe={item.user && (item.user.user_id === myUserId || 'usr' + item.user.user_id === myUserId)} />)} isMe={!!(item.user && isMyUserId(item.user.user_id, myUserId))} />)}
</section> : </section> :
<section className="organization-tree"> <section className="organization-tree">
{roots == null ? <p className="organization-loading">加载中…</p> : {roots == null ? <p className="organization-loading">加载中…</p> :