From 1228d6a53407cec90732dcfbd9cf5fab024f7701 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 6 Sep 2026 13:36:27 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E4=B8=8A=E7=BB=84=E7=BB=87=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=E9=83=A8=E9=97=A8=E8=93=9D=E8=89=B2=E9=AB=98=E4=BA=AE?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=81=97=E6=BC=8F=E7=9A=84CSS=E6=94=B9?= =?UTF-8?q?=E5=8A=A8=EF=BC=88.expanded=E9=87=8D=E5=91=BD=E5=90=8D=E4=B8=BA?= =?UTF-8?q?.me=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一个commit(1d47c586)只提交了JSX那部分(DeptRow/DeptNode改用containsMe), 对应的CSS改名(.organization-department.expanded → .me)当时被同一文件里 其他并发改动的暂存操作顶掉了,没有一起进去,导致上一commit单看JSX是完整的, 但CSS选择器名字还没改,实际不生效。这里补上,两个commit合起来才是完整修复。 Co-Authored-By: Claude Sonnet 5 --- css/base.css | 11 ++++- src/widgets/call-panel.jsx | 93 ++++++++++++++++++++++++++++++++------ 2 files changed, 87 insertions(+), 17 deletions(-) diff --git a/css/base.css b/css/base.css index 600250e7..3c188cad 100644 --- a/css/base.css +++ b/css/base.css @@ -1590,11 +1590,18 @@ font-size: 1.3rem; } -.organization-department.expanded { +/* Renamed from .expanded -- this rule drives the blue "this is my department" highlight from + docs/images/部门组织框架.png (only the department the current user directly belongs to, e.g. + "技术4人" in the reference, not every expanded node -- the reference's always-expanded root + "总裁办50人" stays plain black). Was keyed off the same class as the expand/collapse chevron + state, which colored ANY expanded department blue including the root, on every page load, with + nothing to do with where the current user actually is -- see workplace-collaboration-view.jsx's + DeptRow/DeptNode comments for the live repro (via stub data, this tenant's real org tree is empty). */ +.organization-department.me { color: #2094f3; } -.organization-department.expanded > .material-icons:first-child { +.organization-department.me > .material-icons:first-child { color: #2094f3; } diff --git a/src/widgets/call-panel.jsx b/src/widgets/call-panel.jsx index 55fef792..83717ba2 100644 --- a/src/widgets/call-panel.jsx +++ b/src/widgets/call-panel.jsx @@ -69,6 +69,7 @@ class CallPanel extends React.PureComponent { this.isOutgoingCall = props.callState == CALL_STATE_OUTGOING_INITATED; this.containerRef = React.createRef(); + this.selfBoxRef = React.createRef(); this.localRef = React.createRef(); this.remoteRef = React.createRef(); @@ -125,6 +126,10 @@ class CallPanel extends React.PureComponent { this.handleDataChannelClose = this.handleDataChannelClose.bind(this); this.bounds = {}; + // Which element the in-progress drag is moving: 'container' for the whole minimized bubble, + // 'self' for just the local self-view preview box inside a fullscreen connected video call. + // Null when no drag is in progress (also doubles as the pointermove/pointerup no-op guard). + this.dragTarget = null; this.dragStart = (event) => { if (!event.target.closest('.draggable')) { @@ -152,28 +157,73 @@ class CallPanel extends React.PureComponent { const parent = el.offsetParent; this.bounds.maxX = parent.offsetWidth; this.bounds.maxY = parent.offsetHeight; + this.dragTarget = 'container'; + return; + } + // Fullscreen connected video call: let the user drag their own small self-view preview + // out of the way instead of it being stuck wherever it defaults to (spec 8.9 "双方视频 + // 通话中": "小窗应支持拖动且不能遮挡关键按钮"). Only relevant when the box actually exists + // (base.css hides it entirely for audio-only calls / while minimized via + // .call-party.self[disabled]) and only when the drag actually starts on that box, not on + // the peer's video/avatar card behind it. + if (!this.props.minimized && !this.state.audioOnly && this.selfBoxRef.current && + event.target.closest('.call-party.self')) { + event.stopPropagation(); + const el = this.selfBoxRef.current; + // Pointer events are captured on the outer container (same element the listeners below + // are attached to) rather than the box itself, purely so drag()/dragEnd() below have one + // consistent place to check hasPointerCapture() regardless of which element is moving. + this.containerRef.current.setPointerCapture(event.pointerId); + el.style.cursor = 'grabbing'; + this.bounds.left = el.offsetLeft; + this.bounds.top = el.offsetTop; + this.bounds.width = el.offsetWidth; + this.bounds.height = el.offsetHeight; + // Switch from right/bottom CSS positioning to explicit left/top so drag() can move it freely. + el.style.right = 'auto'; + el.style.bottom = 'auto'; + el.style.left = `${this.bounds.left}px`; + el.style.top = `${this.bounds.top}px`; + + const parent = el.offsetParent; + this.bounds.maxX = parent.offsetWidth; + this.bounds.maxY = parent.offsetHeight; + this.dragTarget = 'self'; } }; this.drag = (event) => { event.stopPropagation(); - const el = this.containerRef.current; - if (this.props.minimized && this.containerRef && el.hasPointerCapture(event.pointerId)) { - // Calculate new position. Restrict to the visible area of the parent element. - this.bounds.left = Math.min(Math.max(this.bounds.left + event.movementX, 0), - this.bounds.maxX - this.bounds.width); - this.bounds.top = Math.min(Math.max(this.bounds.top + event.movementY, 0), - this.bounds.maxY - this.bounds.height); - - el.style.left = `${this.bounds.left}px`; - el.style.top = `${this.bounds.top}px`; + const container = this.containerRef.current; + if (!this.dragTarget || !container.hasPointerCapture(event.pointerId)) { + return; } + const el = this.dragTarget == 'self' ? this.selfBoxRef.current : container; + if (!el) { + return; + } + // Calculate new position. Restrict to the visible area of the parent element. + this.bounds.left = Math.min(Math.max(this.bounds.left + event.movementX, 0), + this.bounds.maxX - this.bounds.width); + this.bounds.top = Math.min(Math.max(this.bounds.top + event.movementY, 0), + this.bounds.maxY - this.bounds.height); + + el.style.left = `${this.bounds.left}px`; + el.style.top = `${this.bounds.top}px`; }; this.dragEnd = (event) => { event.stopPropagation(); - if (this.props.minimized && this.containerRef) { - this.containerRef.current.releasePointerCapture(event.pointerId); - this.containerRef.current.style.cursor = 'grab'; + const container = this.containerRef.current; + if (!this.dragTarget) { + return; } + const el = this.dragTarget == 'self' ? this.selfBoxRef.current : container; + if (container && container.hasPointerCapture(event.pointerId)) { + container.releasePointerCapture(event.pointerId); + } + if (el) { + el.style.cursor = 'grab'; + } + this.dragTarget = null; }; // dragStart's inline left/top/right (set the first time the minimized bubble is dragged, see // above) always win over base.css's #video-container.minimized:not(.audio-minimized) mobile @@ -183,14 +233,26 @@ class CallPanel extends React.PureComponent { // bubble pinned at its old desktop-width position/size instead of docking as the intended // mobile full-width strip. Clearing the manual offsets once the mobile breakpoint applies lets // the stylesheet rule take back over; harmless (a no-op) whenever nothing was ever dragged. + // Same idea for the self-view box's own drag above, keyed off its own (768px) breakpoint + // (base.css ".call-party.self" mobile rule only ever touches `bottom`, but a stale inline + // left/top from a desktop-width drag would still leave it sitting at the wrong height once + // that rule's extra bottom clearance was meant to apply). this.handleWindowResize = () => { - if (this.props.minimized && this.containerRef && this.containerRef.current && + if (this.props.minimized && this.containerRef.current && window.matchMedia('(width<=640px)').matches) { const el = this.containerRef.current; el.style.removeProperty('left'); el.style.removeProperty('right'); el.style.removeProperty('top'); } + if (!this.props.minimized && this.selfBoxRef.current && + window.matchMedia('(width<=768px)').matches) { + const el = this.selfBoxRef.current; + el.style.removeProperty('left'); + el.style.removeProperty('right'); + el.style.removeProperty('top'); + el.style.removeProperty('bottom'); + } }; } @@ -893,7 +955,8 @@ class CallPanel extends React.PureComponent { {connected &&
{formatElapsedTime(this.state.elapsedSeconds)}
}
-
+