EOT Studio 桌面布局独立滚动、通话列表自动定位及标注修正
This commit is contained in:
@@ -293,6 +293,18 @@ kbd { min-width: 17px; padding: 0 4px; border: 1px solid #dce5ce; border-bottom-
|
||||
.player-secondary { gap: 7px; }
|
||||
}
|
||||
|
||||
/* Desktop panes scroll independently: the left call list, the middle
|
||||
workspace and the right editor each keep their own scrollbar. */
|
||||
@media (min-width: 901px) {
|
||||
.app-shell { height: 100vh; height: 100dvh; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.workspace-grid { flex: 1; min-height: 0; }
|
||||
.sidebar { min-height: 0; overflow: hidden; }
|
||||
.call-list { flex: 1; min-height: 0; overflow-y: auto; margin-bottom: 14px; scrollbar-width: thin; scrollbar-color: #d3ddc6 transparent; }
|
||||
.main-workspace { min-height: 0; overflow-y: auto; }
|
||||
.annotation-sidebar { min-height: 0; overflow: hidden; }
|
||||
.editor-body, .empty-editor { flex: 1; min-height: 0; overflow-y: auto; }
|
||||
}
|
||||
|
||||
@media (max-width: 1020px) {
|
||||
.workspace-grid { grid-template-columns: 210px minmax(0, 1fr); }
|
||||
.sidebar { grid-row: span 2; }
|
||||
|
||||
@@ -69,6 +69,7 @@ export default function Workbench() {
|
||||
const objectUrls = useRef(new Set<string>());
|
||||
const versionRef = useRef(0);
|
||||
const originalGroupsRef = useRef(new Map<string, string>());
|
||||
const callListRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const notify = useCallback((message: string, kind: Notice["kind"] = "success") => setNotice({ message, kind }), []);
|
||||
const commit = useCallback((next: Dataset | ((current: Dataset) => Dataset)) => {
|
||||
@@ -109,6 +110,7 @@ export default function Workbench() {
|
||||
finally { setLoading(false); }
|
||||
}, [notify]);
|
||||
useEffect(() => { void loadWorkspace(); }, [loadWorkspace]);
|
||||
useEffect(() => { callListRef.current?.querySelector(".call-item.selected")?.scrollIntoView({ block: "nearest" }); }, [callId]);
|
||||
useEffect(() => {
|
||||
if (!initialized || !dirty) return;
|
||||
const timer = setTimeout(() => { try { localStorage.setItem(DRAFT_KEY, JSON.stringify({ transcripts, annotations, updatedAt: Date.now() })); } catch { notify("浏览器草稿空间不足,请保存到项目或导出 CSV", "error"); } }, 350);
|
||||
@@ -350,7 +352,7 @@ export default function Workbench() {
|
||||
</div></header>
|
||||
|
||||
<div className="workspace-grid"><aside className="sidebar"><div className="sidebar-heading"><span className="eyebrow">对话库</span><span className="count-badge">{scopedCalls.length}</span></div><label className="dataset-scope"><span>数据集</span><select aria-label="选择数据集" value={datasetScope} disabled={loading} onChange={event => changeDatasetScope(event.target.value)}>{datasetGroups.map(group => <option key={group} value={group}>{group}</option>)}<option value={ALL_DATASETS}>全部数据</option></select><small>导出可选当前数据集,项目保存保留全部</small></label><label className="search-box"><Icon name="search" size={16} /><input aria-label="搜索通话" value={search} onChange={event => setSearch(event.target.value)} placeholder="搜索名称或通话 ID" /></label><div className="sidebar-tabs"><button className={filter === "all" ? "active" : ""} onClick={() => setFilter("all")}>全部对话</button><button className={filter === "pending" ? "active" : ""} onClick={() => setFilter("pending")}>待处理</button></div>
|
||||
<div className="call-list">{loading ? <p className="muted loading-label">正在读取工作空间…</p> : visibleCalls.length ? visibleCalls.map(row => <button key={row.id} className={`call-item ${callId === row.id ? "selected" : ""}`} onClick={() => chooseCall(row.id)} title={row.id}><span className="call-icon"><Icon name={row.recording ? "headphones" : "file"} size={17} /></span><span className="call-item-copy"><strong>{row.name}</strong><small>{row.id === "demo" ? "标准示例 · 可自由编辑" : row.id.slice(0, 18) + (row.id.length > 18 ? "…" : "")}</small><span>{row.count} 个判断点{row.pending > 0 && <em> · {row.pending} 待处理</em>}</span></span>{callId === row.id && <span className="selected-dot" />}</button>) : <p className="muted loading-label">没有找到对话</p>}</div>
|
||||
<div className="call-list" ref={callListRef}>{loading ? <p className="muted loading-label">正在读取工作空间…</p> : visibleCalls.length ? visibleCalls.map(row => <button key={row.id} className={`call-item ${callId === row.id ? "selected" : ""}`} onClick={() => chooseCall(row.id)} title={row.id}><span className="call-icon"><Icon name={row.recording ? "headphones" : "file"} size={17} /></span><span className="call-item-copy"><strong>{row.name}</strong><small>{row.id === "demo" ? "标准示例 · 可自由编辑" : row.id.slice(0, 18) + (row.id.length > 18 ? "…" : "")}</small><span>{row.count} 个判断点{row.pending > 0 && <em> · {row.pending} 待处理</em>}</span></span>{callId === row.id && <span className="selected-dot" />}</button>) : <p className="muted loading-label">没有找到对话</p>}</div>
|
||||
<div className="sidebar-bottom"><div><span>标注进度</span><strong>{completed}<span> / {scopedAnnotations.length}</span></strong></div><div className="progress-track"><span style={{ width: `${scopedAnnotations.length ? completed / scopedAnnotations.length * 100 : 0}%` }} /></div><p>每次判断,留出合适的接话空间。</p><button className="help-link" onClick={() => setHelpOpen(true)}><Icon name="help" size={16} />标注说明与快捷键</button></div>
|
||||
</aside>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user