feat: 卡片选中效果(activeSplitId) + 埋点只在打开工作区时计数

This commit is contained in:
Aisland
2026-08-17 00:50:36 +08:00
parent 0cbb00ee5f
commit 641f5c40ab
2 changed files with 54 additions and 4 deletions
+40 -4
View File
@@ -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<string | null>(() =>
splitStore.active && splitStore.spec ? splitStore.spec.id : null,
)
const floatRef = useRef<FloatRect | null>(null)
const persistView = (patch: Partial<ViewState>) => {
@@ -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<Set<string>>(new Set())
const lastLegacyBumpRef = useRef<Record<string, number>>({})
/** 使用埋点:仅「工作区真正打开」计一次使用;点击关闭/重复点击不计(避免每次点击置顶) */
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 相同)──
@@ -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 标记);待用户刷新实测。