feat: 项目卡片统一加框+小字精简 + 图标点击可选(18emoji) + 新建布局与travelatlas唯一互斥
This commit is contained in:
@@ -74,6 +74,9 @@ const PRESET_DEFS = [
|
||||
{ id: 't3', leftCount: 0, topCount: 1, contentCount: 2, chatFull: true },
|
||||
] as const
|
||||
|
||||
/** 侧栏图标备选集(emoji):布局/快捷方式的图标,点击可换(首项 🧱 为布局默认) */
|
||||
const EMOJI_SET = ['🧱', '🏠', '🎓', '🚗', '✈️', '🌍', '🏥', '📚', '✏️', '⚙️', '🎨', '🎮', '🌏', '📐', '🧪', '🤖', '📦', '💬']
|
||||
|
||||
function buildLayout(presetId: string, name: string): LayoutSpec {
|
||||
const def = PRESET_DEFS.find((d) => d.id === presetId) ?? PRESET_DEFS[0]
|
||||
const mk = (i: number): SplitPane => ({
|
||||
@@ -283,6 +286,8 @@ function WorktableSection(props: any) {
|
||||
const [wsPreset, setWsPreset] = useState<string>('2h')
|
||||
const [wsName, setWsName] = useState('')
|
||||
const [wsError, setWsError] = useState(false)
|
||||
/** 图标选择器:kind + 目标 id + 弹窗锚点坐标(fixed 定位) */
|
||||
const [iconPick, setIconPick] = useState<{ kind: 'layout' | 'shortcut'; id: string; x: number; y: number } | null>(null)
|
||||
const [float, setFloat] = useState<FloatRect | null>(() =>
|
||||
view.dock === 'float' && view.floatTop != null ? { top: view.floatTop } : null,
|
||||
)
|
||||
@@ -622,6 +627,28 @@ function WorktableSection(props: any) {
|
||||
persistProjects((prev) => ({ ...prev, layouts: prev.layouts.filter((l) => l.id !== id) }))
|
||||
}
|
||||
|
||||
// ── 图标选择器(布局 / 快捷方式的侧栏 emoji 点击可换) ──
|
||||
const openIconPick = (kind: 'layout' | 'shortcut', id: string, e: any) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
const r = (e.currentTarget as HTMLElement).getBoundingClientRect()
|
||||
const x = Math.min(r.right + 8, Math.max(16, window.innerWidth - 284))
|
||||
const y = Math.max(MIN_TOP, Math.min(r.top - 4, window.innerHeight - 316))
|
||||
setIconPick({ kind, id, x, y })
|
||||
}
|
||||
const setLayoutIcon = (id: string, icon: string) => {
|
||||
persistProjects((prev) => ({
|
||||
...prev,
|
||||
layouts: prev.layouts.map((l) => (l.id === id ? { ...l, icon } : l)),
|
||||
}))
|
||||
}
|
||||
const setShortcutIcon = (id: string, icon: string) => {
|
||||
persistProjects((prev) => ({
|
||||
...prev,
|
||||
shortcuts: prev.shortcuts.map((s) => (s.id === id ? { ...s, icon } : s)),
|
||||
}))
|
||||
}
|
||||
|
||||
const query = view.query.trim()
|
||||
const queryLower = query.toLowerCase()
|
||||
const visibleShortcuts = projects.shortcuts.filter((s) =>
|
||||
@@ -705,7 +732,7 @@ function WorktableSection(props: any) {
|
||||
if (!wide) {
|
||||
const projectIcons = registeredIds.map((id) => metas[id]?.icon ?? '📦')
|
||||
const shortcutIcons = projects.shortcuts.map((s) => s.icon)
|
||||
const layoutIcons = projects.layouts.map(() => '🧱')
|
||||
const layoutIcons = projects.layouts.map((l) => l.icon ?? '🧱')
|
||||
const icons = [...projectIcons, ...shortcutIcons, ...layoutIcons]
|
||||
const railNames = [
|
||||
...registeredIds.map((id) => projects.nameOverrides[id] ?? metas[id]?.name ?? id),
|
||||
@@ -819,6 +846,35 @@ function WorktableSection(props: any) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{iconPick && (
|
||||
<>
|
||||
<div className="dsh-wt_popBackdrop" onClick={() => setIconPick(null)} />
|
||||
<div className="dsh-wt_iconPop" style={{ left: iconPick.x, top: iconPick.y }}>
|
||||
<div className="dsh-wt_iconPopTitle">{t('icons.title')}</div>
|
||||
<div className="dsh-wt_iconGrid">
|
||||
{EMOJI_SET.map((em) => {
|
||||
const cur = iconPick.kind === 'layout'
|
||||
? (projects.layouts.find((l) => l.id === iconPick.id)?.icon ?? '🧱')
|
||||
: (projects.shortcuts.find((s) => s.id === iconPick.id)?.icon ?? '🔗')
|
||||
return (
|
||||
<button
|
||||
key={em}
|
||||
type="button"
|
||||
className="dsh-wt_iconCell"
|
||||
data-on={cur === em ? 'true' : 'false'}
|
||||
onClick={() => {
|
||||
if (iconPick.kind === 'layout') setLayoutIcon(iconPick.id, em)
|
||||
else setShortcutIcon(iconPick.id, em)
|
||||
setIconPick(null)
|
||||
}}
|
||||
>{em}</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{viewOptionsOpen && (
|
||||
<div className="dsh-wt_menu">
|
||||
<span className="dsh-wt_menuLabel">{t('sort.label')}</span>
|
||||
@@ -858,7 +914,15 @@ function WorktableSection(props: any) {
|
||||
onDragEnd={() => { dragIdRef.current = null }}
|
||||
>
|
||||
<span className="dsh-wt_manageGrip" aria-hidden>≡</span>
|
||||
<span className="dsh-wt_manageIcon" aria-hidden>{layout ? '🧱' : (meta?.icon ?? '📦')}</span>
|
||||
{layout
|
||||
? <span
|
||||
className="dsh-wt_manageIcon dsh-wt_iconPick"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title={t('icons.change')}
|
||||
onClick={(e) => openIconPick('layout', id, e)}
|
||||
>{layout.icon ?? '🧱'}</span>
|
||||
: <span className="dsh-wt_manageIcon" aria-hidden>{meta?.icon ?? '📦'}</span>}
|
||||
<input
|
||||
className="dsh-wt_manageInput"
|
||||
value={display}
|
||||
@@ -879,7 +943,13 @@ function WorktableSection(props: any) {
|
||||
{projects.shortcuts.map((s) => (
|
||||
<div key={s.id} className="dsh-wt_manageRow dsh-wt_manageRowSc">
|
||||
<span className="dsh-wt_manageGrip" aria-hidden>🔗</span>
|
||||
<span className="dsh-wt_manageIcon" aria-hidden>{s.icon}</span>
|
||||
<span
|
||||
className="dsh-wt_manageIcon dsh-wt_iconPick"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title={t('icons.change')}
|
||||
onClick={(e) => openIconPick('shortcut', s.id, e)}
|
||||
>{s.icon}</span>
|
||||
<span className="dsh-wt_manageScName">{s.name}</span>
|
||||
<button type="button" className="dsh-wt_manageBtn" title={t('manage.deleteShortcut')} onClick={() => removeShortcut(s.id)}>✕</button>
|
||||
</div>
|
||||
@@ -903,7 +973,13 @@ function WorktableSection(props: any) {
|
||||
style={{ order: effectiveOrder.indexOf(l.id) + 1000 }}
|
||||
onClick={() => { openSplit(l); reportUsed(l.id) }}
|
||||
>
|
||||
<span className="dsh-wt_layoutIcon" aria-hidden>🧱</span>
|
||||
<span
|
||||
className="dsh-wt_layoutIcon dsh-wt_iconPick"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title={t('icons.change')}
|
||||
onClick={(e) => openIconPick('layout', l.id, e)}
|
||||
>{l.icon ?? '🧱'}</span>
|
||||
<span className="dsh-wt_layoutText">
|
||||
<span className="dsh-wt_layoutName">{projects.nameOverrides[l.id] ?? l.title}</span>
|
||||
<span className="dsh-wt_layoutDesc">{t('layout.desc', { n: String(paneCount) })}</span>
|
||||
@@ -926,7 +1002,13 @@ function WorktableSection(props: any) {
|
||||
rel="noreferrer noopener"
|
||||
title={s.href}
|
||||
>
|
||||
<span className="dsh-wt_shortcutIcon" aria-hidden>{s.icon}</span>
|
||||
<span
|
||||
className="dsh-wt_shortcutIcon dsh-wt_iconPick"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title={t('icons.change')}
|
||||
onClick={(e) => openIconPick('shortcut', s.id, e)}
|
||||
>{s.icon}</span>
|
||||
<span className="dsh-wt_shortcutName">{s.name}</span>
|
||||
<span className="dsh-wt_shortcutBadge">{t('shortcut.badge')}</span>
|
||||
</a>
|
||||
|
||||
@@ -67,6 +67,8 @@ export const zh = {
|
||||
'pane.subagentsEmpty': '当前会话暂无子代理',
|
||||
'split.flip': '切换对话窗位置(左⇄右)',
|
||||
'split.dragSwap': '拖动标题栏与其他窗交换位置',
|
||||
'icons.title': '选择图标',
|
||||
'icons.change': '更换图标',
|
||||
'empty': '暂无项目',
|
||||
} satisfies Record<string, string>
|
||||
|
||||
@@ -136,6 +138,8 @@ export const en = {
|
||||
'pane.subagentsEmpty': 'No subagents in the current session',
|
||||
'split.flip': 'Flip chat side (left ⇄ right)',
|
||||
'split.dragSwap': 'Drag the title bar to swap panes',
|
||||
'icons.title': 'Pick an icon',
|
||||
'icons.change': 'Change icon',
|
||||
'empty': 'No projects yet',
|
||||
} satisfies Record<WorktableKey, string>
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ export type LayoutSpec = {
|
||||
topHeight?: { default: number; min: number; max: number }
|
||||
/** 聊天窗贴边位置:'right'(右列/右下,默认)| 'left'(左列/左下) */
|
||||
chatSide?: 'left' | 'right'
|
||||
/** 布局条目图标(emoji;工作台侧栏展示,点击可换) */
|
||||
icon?: string
|
||||
/** 聊天窗通高(整列):为 true 时聊天占整条右/左列,内容区(含 top 行)全部排在其另一侧 */
|
||||
chatFullHeight?: boolean
|
||||
}
|
||||
@@ -729,6 +731,16 @@ export const splitStore: SplitState = {
|
||||
},
|
||||
}
|
||||
|
||||
/** 兼容桥:travelatlas 分栏浮层(.ta_split)出现 = 其引擎打开 → 本引擎让位。
|
||||
* 不改动 travelatlas 代码,仅在 DOM 层观察其浮层挂载。 */
|
||||
if (typeof document !== 'undefined' && document.body) {
|
||||
const taObserver = new MutationObserver(() => {
|
||||
if (!splitStore.active) return
|
||||
if (document.querySelector('.ta_split')) splitStore.close()
|
||||
})
|
||||
taObserver.observe(document.body, { childList: true, subtree: true })
|
||||
}
|
||||
|
||||
/** 分配各窗宽度(最后一个拿余量) */
|
||||
function allocate(panes: SplitPane[], ws: number[], total: number) {
|
||||
const out: { pane: SplitPane; left: number; width: number }[] = []
|
||||
|
||||
@@ -107,6 +107,22 @@ export const css = [
|
||||
'.dsh-wt_layoutDesc{font-size:10px;line-height:14px;color:var(--dsw-alias-label-tertiary,#6e7683);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
||||
'.dsh-wt_layoutBadge{flex:none;font-size:9px;line-height:14px;padding:0 5px;border-radius:8px;background:var(--dsw-alias-fill-l1,rgba(255,255,255,.06));color:var(--dsw-alias-label-tertiary,#6e7683)}',
|
||||
'.dsh-wt_layoutArrow{flex:none;color:var(--dsw-alias-label-tertiary,#6e7683);font-size:14px;line-height:20px}',
|
||||
// 项目卡片统一:入驻插件卡片(travelatlas/planreview)与布局卡片同款常显框(静止态可见边框+底)
|
||||
'.dsh-wt_projects .ta_card,.dsh-wt_projects .pr_card{border-color:var(--dsw-alias-border-l1,#262b36);background:var(--dsw-alias-fill-l1,rgba(255,255,255,.02))}',
|
||||
'.dsh-wt_projects .ta_card:hover,.dsh-wt_projects .pr_card:hover{border-color:var(--dsw-alias-state-accent-primary,#4f8ef7);background:var(--dsw-alias-fill-l1,rgba(255,255,255,.05))}',
|
||||
'.dsh-wt_projects .ta_card[data-on=true],.dsh-wt_projects .pr_card[data-on=true]{border-color:var(--dsw-alias-state-accent-primary,#4f8ef7);background:var(--dsw-alias-fill-l1,rgba(255,255,255,.05))}',
|
||||
// 标签精简:项目卡片只留名称一行,小字描述行去掉
|
||||
'.dsh-wt_projects .ta_cardDesc,.dsh-wt_projects .pr_cardDesc{display:none}',
|
||||
// 图标选择弹窗(固定定位,点击项目/布局/快捷方式的图标唤出)
|
||||
'.dsh-wt_iconPop{position:fixed;z-index:81;width:256px;padding:8px;border:1px solid var(--dsw-alias-border-l2,#3a4150);border-radius:10px;background:var(--dsw-alias-bg-base,#0b0e14);box-shadow:var(--dsw-shadow-lv2,0 8px 24px rgba(0,0,0,.4))}',
|
||||
'.dsh-wt_iconPopTitle{font-size:10px;letter-spacing:.05em;color:var(--dsw-alias-label-tertiary,#6e7683);padding:1px 4px 6px}',
|
||||
'.dsh-wt_iconGrid{display:grid;grid-template-columns:repeat(6,1fr);gap:4px}',
|
||||
'.dsh-wt_iconCell{display:flex;align-items:center;justify-content:center;aspect-ratio:1;border:1px solid transparent;border-radius:6px;background:transparent;font-size:17px;line-height:1;cursor:pointer;padding:0}',
|
||||
'.dsh-wt_iconCell:hover{background:var(--dsw-alias-fill-l1,rgba(255,255,255,.05));border-color:var(--dsw-alias-border-l2,#3a4150)}',
|
||||
'.dsh-wt_iconCell[data-on=true]{background:var(--dsw-alias-fill-l1,rgba(255,255,255,.08));border-color:var(--dsw-alias-state-accent-primary,#4f8ef7)}',
|
||||
// 可点图标(布局卡 / 管理行 / 快捷方式上的图标,点击换 emoji)
|
||||
'.dsh-wt_iconPick{flex:none;display:inline-flex;align-items:center;justify-content:center;padding:1px;border:none;background:transparent;font:inherit;cursor:pointer;border-radius:4px}',
|
||||
'.dsh-wt_iconPick:hover{background:var(--dsw-alias-fill-l1,rgba(255,255,255,.08))}',
|
||||
// 窗内容:选择器 / 自定义 / 浏览器 / 占位
|
||||
'.dsh-wt_panePicker{flex:1;min-height:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:12px;overflow:auto}',
|
||||
'.dsh-wt_panePicker-row{flex-direction:row;flex-wrap:wrap}',
|
||||
|
||||
@@ -487,3 +487,27 @@
|
||||
{w:92,h:78};STEP3-5 回归不变;ERRORS_COUNT: 0。bundle-eval 补桩
|
||||
(navigator.userAgent/platform、canvas getContext)后 PASS。
|
||||
- 备注:本次为纯客户端改动,F5 即生效,无需重启 dsh web。
|
||||
## 补记(项目卡片统一加框 + 小字精简 + 图标可选 + 新建布局唯一性互斥)
|
||||
|
||||
- 用户反馈(四条命令合并执行):
|
||||
① 加号新建的项目侧栏卡片有框、两个入驻项目(建筑审图/旅行 Atlas)没框 → 统一加框;
|
||||
② 新建布局与旅行 Atlas 可同时打开 → 全部项目共用唯一互斥(开一个关前一个);
|
||||
③ 项目标签只留名字一行,去掉描述小字(建筑审图/旅行 Atlas);
|
||||
④ 左侧 emoji 要能点开一套完整图标列表换选(新项目一直是砖墙 🧱 换不了)。
|
||||
- 处理(不改动 travelatlas/building-agent 代码,全部在本插件侧):
|
||||
① styles.ts:.dsh-wt_projects 内 .ta_card/.pr_card 静止态常显边框+底色(与布局卡同款),
|
||||
静止态带框 + hover 高亮;同时 .ta_cardDesc/.pr_cardDesc display:none(小字去掉);
|
||||
② split.tsx:新增 MutationObserver——.ta_split 浮层(travelatlas 引擎打开)出现即 close() 本引擎;
|
||||
反向(本引擎打开 → 点旅行 Atlas)沿用既有 .ta_splitClose 桥;互斥全项目(含 + 新建布局)成立;
|
||||
③ EMOJI_SET 18 项图标集(🧱🏠🎓🚗✈️🌍🏥📚✏️⚙️🎨🎮🌏📐🧪🤖📦💬);
|
||||
布局卡/管理行/快捷方式的图标都变可点(stopPropagation,不触发卡片本身),
|
||||
弹固定定位图标选择器(6 列网格、当前项高亮);选中持久化到 projects.v1(LayoutSpec 加 icon 字段;
|
||||
快捷方式复用 icon 字段);rail 图标同步使用所选 emoji;locale 加 icons.title/icons.change。
|
||||
- 验证(04_test/functional-diag.cjs 扩展:--window-size=1440,900 + dsf=1 + 新 Chrome profile +
|
||||
Page.addScriptToEvaluateOnNewDocument 种子测试布局 + 点击展开侧栏):
|
||||
STEP6 图标链路:icon0=🧱 → 点图标 popup=true(18 cells) → 点🏠 → icon1=🏠 且 projects.v1
|
||||
saved=🏠;ta/pr 边框色 rgba(255,255,255,.06)(非 transparent)+ desc display:none;
|
||||
STEP7 互斥:本引擎打开时合成挂 .ta_split → engineClosedByFakeTa=true(观察器关闭引擎)。
|
||||
(travelatlas 在无会话 headless 页面不自开分栏,taSplitPresent=false 属预期,改合成验证。)
|
||||
STEP1-5 回归不变(paneWs 均分、picker 自适应、拖标签);ERRORS_COUNT: 0。
|
||||
- 备注:纯客户端改动,F5 即生效,无需重启 dsh web。
|
||||
|
||||
@@ -5,8 +5,9 @@ const WebSocket = require('ws');
|
||||
const PORT = 9335;
|
||||
const proc = spawn('C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', [
|
||||
'--headless=new', '--disable-gpu', '--no-first-run', '--no-default-browser-check',
|
||||
'--window-size=1440,900', '--force-device-scale-factor=1',
|
||||
'--remote-debugging-port=' + PORT,
|
||||
'--user-data-dir=C:\\Users\\SJL\\AppData\\Local\\Temp\\wt-chrome-func2',
|
||||
'--user-data-dir=C:\\Users\\SJL\\AppData\\Local\\Temp\\wt-chrome-func3',
|
||||
'about:blank',
|
||||
], { stdio: 'ignore' });
|
||||
|
||||
@@ -46,6 +47,9 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
await send('Runtime.enable');
|
||||
await send('Log.enable');
|
||||
await send('Page.enable');
|
||||
await send('Page.addScriptToEvaluateOnNewDocument', {
|
||||
source: "try{ if(location.origin==='http://127.0.0.1:3080'){ localStorage.setItem('dsh.worktable.view.v1', JSON.stringify({query:'',searchOpen:false,orderBy:'manual',dock:'footer',floatTop:null,sortMigratedV2:true})); localStorage.setItem('dsh.worktable.projects.v1', JSON.stringify({order:[],lastUsed:{},hidden:[],nameOverrides:{},shortcuts:[],layouts:[{id:'t-layout',title:'\u6d4b\u8bd5\u5e03\u5c40',top:null,left:null,main:[{id:'p1',title:'\u5185\u5bb91',min:200,content:null}],leftWidth:{default:260,min:160,max:480},chatWidth:{default:360,min:240,max:600},topHeight:{default:200,min:120,max:480},chatSide:'right',chatFullHeight:false}]})); } }catch(e){}",
|
||||
});
|
||||
await send('Page.navigate', { url: 'http://127.0.0.1:3080/' });
|
||||
await sleep(11000);
|
||||
|
||||
@@ -55,6 +59,11 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
return r.result && r.result.result ? r.result.result.value : undefined;
|
||||
};
|
||||
|
||||
// 展开侧栏(宿主默认折叠,宽侧栏下工作台才渲染项目卡片)
|
||||
const expanded = await evaluate("(function(){ var b=document.querySelector('button[title=\"Open sidebar\"],button[aria-label=\"Open sidebar\"]'); if(b){b.click(); return true} return false })()");
|
||||
console.log('SIDEBAR_EXPAND:', JSON.stringify(expanded));
|
||||
await sleep(900);
|
||||
|
||||
const step1 = await evaluate("(function(){ var out={}; out.debugExport=!!window.__dshWorktable; var st=window.__dshWorktable&&window.__dshWorktable.splitStore; if(!st) return JSON.stringify(out); out.openResult=st.open({id:'t-diag',title:'diag',top:null,main:[{id:'p1',title:'1',min:200,content:null},{id:'p2',title:'2',min:200,content:null}],chatWidth:{default:320,min:240,max:600}}); out.afterOpen={active:st.active,spec:st.spec&&st.spec.id}; out.paneWs=st.paneWs&&st.paneWs.slice(); out.chatW=st.chatW; out.geom=st.geom; return JSON.stringify(out) })()");
|
||||
console.log('STEP1:', JSON.stringify(step1));
|
||||
await sleep(900);
|
||||
@@ -73,6 +82,59 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const step5 = await evaluate("(function(){ var out={}; out.pane0PickerBack=document.querySelectorAll('.dsh-wt_panePicker').length; var st=window.__dshWorktable.splitStore; st.close(); out.closed=st.active===false; return JSON.stringify(out) })()");
|
||||
console.log('STEP5:', JSON.stringify(step5));
|
||||
|
||||
const step6 = await evaluate(`(async function(){
|
||||
var out={};
|
||||
out.cards=document.querySelectorAll('.dsh-wt_layout').length;
|
||||
var card=document.querySelector('.dsh-wt_layout');
|
||||
if(card){
|
||||
var ic=card.querySelector('.dsh-wt_layoutIcon');
|
||||
out.icon0=ic?ic.textContent:null;
|
||||
if(ic){ ic.dispatchEvent(new MouseEvent('click',{bubbles:true,cancelable:true})); }
|
||||
await new Promise(function(r){setTimeout(r,250)});
|
||||
out.popup=!!document.querySelector('.dsh-wt_iconPop');
|
||||
out.cells=document.querySelectorAll('.dsh-wt_iconCell').length;
|
||||
var cells=document.querySelectorAll('.dsh-wt_iconCell');
|
||||
if(cells[1]){ cells[1].dispatchEvent(new MouseEvent('click',{bubbles:true,cancelable:true})); }
|
||||
await new Promise(function(r){setTimeout(r,250)});
|
||||
out.icon1=card.querySelector('.dsh-wt_layoutIcon').textContent;
|
||||
try{ out.saved=JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')).layouts[0].icon }catch(e){ out.saved='ERR:'+e.message }
|
||||
}
|
||||
var ta=document.querySelector('.dsh-wt_projects .ta_card');
|
||||
if(ta){ var cs=getComputedStyle(ta); out.taBorder=cs.borderTopColor; var desc=ta.querySelector('.ta_cardDesc'); out.taDescDisplay=desc?getComputedStyle(desc).display:null; }
|
||||
var pr=document.querySelector('.dsh-wt_projects .pr_card');
|
||||
if(pr){ var cs2=getComputedStyle(pr); out.prBorder=cs2.borderTopColor; var desc2=pr.querySelector('.pr_cardDesc'); out.prDescDisplay=desc2?getComputedStyle(desc2).display:null; }
|
||||
return JSON.stringify(out);
|
||||
})()`);
|
||||
console.log('STEP6:', JSON.stringify(step6));
|
||||
|
||||
// 唯一性互斥:本引擎布局打开时,点开旅行 Atlas(其浮层 .ta_split 出现)→ 本引擎自动关闭
|
||||
const step7 = await evaluate(`(async function(){
|
||||
var out={};
|
||||
var st=window.__dshWorktable.splitStore;
|
||||
st.open({id:'t-mutex',title:'mutex',top:null,main:[{id:'m1',title:'m',min:200,content:null}],chatWidth:{default:320,min:240,max:600}});
|
||||
out.engineOpenAfterOpen=st.active;
|
||||
var ta=document.querySelector('.ta_card');
|
||||
if(ta){ ta.click(); }
|
||||
await new Promise(function(r){setTimeout(r,700)});
|
||||
out.taSplitPresent=!!document.querySelector('.ta_split');
|
||||
out.engineClosedByTa=st.active===false;
|
||||
// 合成验证:直接向 body 挂 .ta_split(模拟 travelatlas 浮层出现)→ 观察器应关闭本引擎
|
||||
if(st.active){
|
||||
var fake=document.createElement('div');
|
||||
fake.className='ta_split';
|
||||
document.body.appendChild(fake);
|
||||
await new Promise(function(r){setTimeout(r,400)});
|
||||
out.engineClosedByFakeTa=st.active===false;
|
||||
fake.remove();
|
||||
}
|
||||
if(st.active){ st.close(); }
|
||||
var taClose=document.querySelector('.ta_splitClose');
|
||||
if(taClose){ taClose.click(); }
|
||||
await new Promise(function(r){setTimeout(r,400)});
|
||||
return JSON.stringify(out);
|
||||
})()`);
|
||||
console.log('STEP7:', JSON.stringify(step7));
|
||||
|
||||
const errors = events.filter((e) =>
|
||||
e.method === 'Runtime.exceptionThrown' ||
|
||||
(e.method === 'Log.entryAdded' && e.params.entry.level === 'error') ||
|
||||
|
||||
Reference in New Issue
Block a user