From 641f5c40ab98140d989c69e5299ce2055df12fcb Mon Sep 17 00:00:00 2001 From: Aisland Date: Mon, 17 Aug 2026 00:50:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8D=A1=E7=89=87=E9=80=89=E4=B8=AD?= =?UTF-8?q?=E6=95=88=E6=9E=9C(activeSplitId)=20+=20=E5=9F=8B=E7=82=B9?= =?UTF-8?q?=E5=8F=AA=E5=9C=A8=E6=89=93=E5=BC=80=E5=B7=A5=E4=BD=9C=E5=8C=BA?= =?UTF-8?q?=E6=97=B6=E8=AE=A1=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 01_content/src/client/index.tsx | 44 +++++++++++++++++-- .../worklogs/2026-08-16_worktable-v2.md | 14 ++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/01_content/src/client/index.tsx b/01_content/src/client/index.tsx index 0eeb94f..37eaae7 100644 --- a/01_content/src/client/index.tsx +++ b/01_content/src/client/index.tsx @@ -53,6 +53,10 @@ const SNAP_PX = 32 const MARKET_URL = 'https://github.com/hikariming/dshfind' /** 已报到卡片的 CSS order 偏移:未上报(order=0)的 v1 卡片永远排在已上报卡片之前。 */ const ORDER_OFFSET = 1000 +/** 自带分栏实现的遗留项目(如 travelatlas,未接入 openSplit 引擎):埋点用冷却去重,避免每次点击置顶 */ +const LEGACY_SPLIT_IDS = new Set(['travelatlas']) +/** 遗留项目埋点冷却(毫秒):同 id 两次计使用的最小间隔 */ +const LEGACY_BUMP_COOLDOWN = 15000 /** 落点判定余量:松手时指针越出有效落点区(超出底部/顶部/侧边)即视为「无有效落点」。 */ const OVER_BOTTOM_PX = 24 const OVER_TOP_PX = 24 @@ -168,6 +172,9 @@ function WorktableSection(props: any) { const [bottomInset, setBottomInset] = useState(0) const bottomInsetRef = useRef(0) const [floatGeo, setFloatGeo] = useState<{ left: number; width: number | null } | null>(null) + const [activeSplitId, setActiveSplitId] = useState(() => + splitStore.active && splitStore.spec ? splitStore.spec.id : null, + ) const floatRef = useRef(null) const persistView = (patch: Partial) => { @@ -193,6 +200,11 @@ function WorktableSection(props: any) { return () => { registryStore.listeners.delete(sync) } }, []) + // 分栏引擎激活态 → activeSplitId(卡片据此显示选中效果) + useEffect(() => splitStore.subscribe(() => { + setActiveSplitId(splitStore.active && splitStore.spec ? splitStore.spec.id : null) + }), []) + // 侧边栏折叠/展开:保持原停靠位置(不再回弹 footer);折叠态由项目图标框承载。 // 折叠且浮动时:等折叠动画结束(320/750ms 双次重测,取收敛后的几何)再以 fixed 定位 // 图标框到拖前高度——避免在过渡帧上测得展开态宽度导致图标框偏离窄栏中心。 @@ -264,17 +276,40 @@ function WorktableSection(props: any) { }) }, []) + const engineIdsRef = useRef>(new Set()) + const lastLegacyBumpRef = useRef>({}) + + /** 使用埋点:仅「工作区真正打开」计一次使用;点击关闭/重复点击不计(避免每次点击置顶) */ const reportUsed = useCallback((id: string) => { if (typeof id !== 'string' || !id) return const now = Date.now() - persistProjects((prev) => { - if (prev.lastUsed[id] === now) return prev - return { ...prev, lastUsed: { ...prev.lastUsed, [id]: now } } - }) + const bump = () => { + persistProjects((prev) => { + if (prev.lastUsed[id] === now) return prev + return { ...prev, lastUsed: { ...prev.lastUsed, [id]: now } } + }) + } + const engineOpen = splitStore.active && splitStore.spec?.id === id + const knownEngine = engineIdsRef.current.has(id) + if (engineOpen) { + bump() // 引擎项目:本次点击打开了工作区 → 计一次使用 + } else if (knownEngine) { + // 引擎项目:本次是关闭/重复点击 → 不计 + return + } else if (LEGACY_SPLIT_IDS.has(id)) { + // 遗留自带分栏的项目:冷却去重(打开计一次;快速开关/关闭点击不重复计) + if (now - (lastLegacyBumpRef.current[id] ?? 0) > LEGACY_BUMP_COOLDOWN) { + lastLegacyBumpRef.current[id] = now + bump() + } + } else { + bump() // 无判定依据的普通项目:保持原行为 + } }, []) /** 分栏工作区入口(M1 引擎):项目卡片调用 openSplit(spec) 打开声明式布局 */ const openSplit = useCallback((spec: LayoutSpec) => { + engineIdsRef.current.add(spec.id) splitStore.open(spec) }, []) @@ -305,6 +340,7 @@ function WorktableSection(props: any) { reportMeta, reportUsed, openSplit, + activeSplitId, } // ── 拖动(≡ 手柄,与 v1 相同)── diff --git a/02_process/worklogs/2026-08-16_worktable-v2.md b/02_process/worklogs/2026-08-16_worktable-v2.md index b53a44c..08b649b 100644 --- a/02_process/worklogs/2026-08-16_worktable-v2.md +++ b/02_process/worklogs/2026-08-16_worktable-v2.md @@ -246,3 +246,17 @@ (覆盖「先开审图再点旅行」的反向场景)。 - 验证:构建 + node --check 通过;服务端实时下发(含 split-claim/yieldObserver/ta_splitClose 标记);待用户刷新后实测两个方向与反选。 + +## 补记(卡片选中效果 + 埋点收敛) + +- 用户反馈:① 建筑审图打开后卡片无「选中」效果(旅行 Atlas 有);② 「最近」排序下每次点击 + 都置顶,体验差——应只在工作区真正打开(有改动)时计一次使用。 +- 实现: + ① owner props 新增 activeSplitId(工作台订阅 splitStore 激活态下发);planreview 卡片 + data-on=active 高亮(蓝边 + 名称高亮),样式与旅行 Atlas 同类; + ② 埋点收敛(worktable reportUsed 过滤):引擎项目(调用过 openSplit 的 id)仅在本次点击 + 导致工作区打开时计使用,关闭/重复点击不计;遗留自带分栏的项目(travelatlas,不改其代码) + 用 15s 冷却去重;无判定依据的普通项目保持原行为; + ③ planreview 无需改埋点逻辑(引擎内统一过滤)。 +- 验证:两插件构建 + node --check 通过;服务端实时下发(含 activeSplitId/ + LEGACY_BUMP_COOLDOWN 标记);待用户刷新实测。