fix: 工作台状态持久化(视图覆盖写回views[id],布局条目照旧) + feat: tsx/css等代码文件点击预览

This commit is contained in:
Aisland
2026-08-18 00:27:28 +08:00
parent e28bf28f19
commit fc8c6119c5
4 changed files with 100 additions and 13 deletions
+8 -2
View File
@@ -380,10 +380,16 @@ function WorktableSection(props: any) {
setActiveSplitId(splitStore.active && splitStore.spec ? splitStore.spec.id : null) setActiveSplitId(splitStore.active && splitStore.spec ? splitStore.spec.id : null)
}), []) }), [])
// 引擎内 spec 变更(窗内容/聊天位置/窗位互换)→ 回写布局条目持久化 // 引擎内 spec 变更(窗内容/聊天位置/窗位互换)→ 回写持久化:
// 布局项目写 layouts 条目;入驻项目(视图覆盖工作区)写 views[id]。每个项目都保持上一次的样子。
useEffect(() => { useEffect(() => {
splitStore.onSpecMutated = (spec) => { splitStore.onSpecMutated = (spec) => {
persistProjects((prev) => ({ ...prev, layouts: prev.layouts.map((l) => (l.id === spec.id ? spec : l)) })) persistProjects((prev) => {
if (prev.layouts.some((l) => l.id === spec.id)) {
return { ...prev, layouts: prev.layouts.map((l) => (l.id === spec.id ? spec : l)) }
}
return { ...prev, views: { ...prev.views, [spec.id]: spec } }
})
} }
return () => { splitStore.onSpecMutated = null } return () => { splitStore.onSpecMutated = null }
}, []) }, [])
+1 -1
View File
@@ -937,7 +937,7 @@ function ExplorerPane(props: { row: PaneRow; index: number }) {
url: '/api/worktable/site/' + encodeURIComponent(dir) + '/' + encodeURIComponent(e.name), url: '/api/worktable/site/' + encodeURIComponent(dir) + '/' + encodeURIComponent(e.name),
title: e.name, title: e.name,
}) })
} else if (/\.(md|markdown|mdown|txt|log|pdf|png|jpe?g|gif|webp|svg|bmp|ico)$/i.test(e.name)) { } else if (/\.(md|markdown|mdown|txt|log|tsx|ts|jsx|js|css|json|pdf|png|jpe?g|gif|webp|svg|bmp|ico)$/i.test(e.name)) {
splitStore.openTab(props.row, props.index, { kind: 'file', path: e.path }) splitStore.openTab(props.row, props.index, { kind: 'file', path: e.path })
} else { } else {
setError(T('pane.openLater')) setError(T('pane.openLater'))
+12 -9
View File
@@ -639,14 +639,17 @@
原生视图内部是浏览器扩展页,插件注入不了按钮、拦不到鼠标,也驱动不了内部滚动—— 原生视图内部是浏览器扩展页,插件注入不了按钮、拦不到鼠标,也驱动不了内部滚动——
向用户如实说明后按指示回退);② 视图选项改名「设置」,点开直接内嵌「排序方式 + 管理项目 向用户如实说明后按指示回退);② 视图选项改名「设置」,点开直接内嵌「排序方式 + 管理项目
展开列表」,不再二次点击;③ 管理项目里给现有项目加「变更视图」:换预设拓扑, 展开列表」,不再二次点击;③ 管理项目里给现有项目加「变更视图」:换预设拓扑,
## 补记(卡片选中态统一判定) ## 补记(工作台状态持久化修复 + tsx/css 代码预览)
- 用户反馈:旅行 Atlas 卡片选中后没有高亮边(其他 3 个项目都有);新建项目也要有。 - 用户反馈:① 项目里布置好的窗口内容(如上面浏览器/下面终端)切换项目再切回就变回默认,
- 根因:ta_card 的 data-on 由它自己插件控制(自有引擎状态);用视图覆盖走工作台引擎打开时 每个项目的工作台应保持上一次的样子;② 资源管理器要能点开 tsx/css 文件预览(同 MD)。
两边状态对不上 → 不高亮。 - 根因:onSpecMutated 回写只写 layouts 条目,漏了 views(视图覆盖工作区)→ 常驻项目
- 处理:DOM 桥 sync 里选中态统一由工作台判定——activeSplitId===id → data-on=true; 内容丢失;布局路径本身正常(实测确认)。
引擎开着别的 → false;引擎空闲(activeSplitId=null)→ 不动(保留卡片自带状态, - 处理:① onSpecMutated 按 spec.id 分流:layouts 里有该 id → 更新布局条目;否则写回
兼容自带分栏插件的打开态)。通用规则覆盖所有注册项目与新建项目。 views[id](入驻项目工作区)。所有变更(开标签/关标签/换聊天边/换位)都会触发;
- 验证(functional-diag STEP13):布局打开时 ta/pr 卡 false;旅行设置视图覆盖后点卡片 → ② 资源管理器文件匹配加 tsx/ts/jsx/js/css/json → kind:file 预览(等宽文本展示,同 TXT 模式)。
engineSpecId=travelatlas 且 taOnAfterOpen=true;全 STEP 回归 ERROR_COUNT: 0。 - 验证(functional-diag 重排 STEP14/15 到 STEP10 前 + STEP9 改查 planreview 键 +
恢复误删的 STEP13 打印行):STEP14 布局 layoutSavedTabs=1/layoutRestoredTabs=1、
视图覆盖 taSavedTabs=1/taRestoredTabs=1(切换回来内容保持);STEP15 tsx/css 双标签预览
且内容含 WorktableSection;全 15 STEP ERROR_COUNT: 0。
- 备注:纯客户端改动,F5 即生效。 - 备注:纯客户端改动,F5 即生效。
+79 -1
View File
@@ -186,7 +186,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
try{ try{
var vv=JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')).views||{}; var vv=JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')).views||{};
out.viewsKeys=Object.keys(vv); out.viewsKeys=Object.keys(vv);
var rv=vv[out.viewsKeys[0]]; var rv=vv['planreview'];
if(rv){ out.resTop=(rv.top||[]).length; out.resMain=rv.main.length; out.resChatFull=rv.chatFullHeight===true; out.resId=rv.id; } if(rv){ out.resTop=(rv.top||[]).length; out.resMain=rv.main.length; out.resChatFull=rv.chatFullHeight===true; out.resId=rv.id; }
}catch(e){ out.resErr=String(e) } }catch(e){ out.resErr=String(e) }
var rows2=document.querySelectorAll('.dsh-wt_settings .dsh-wt_manageRow:not(.dsh-wt_manageRowSc)'); var rows2=document.querySelectorAll('.dsh-wt_settings .dsh-wt_manageRow:not(.dsh-wt_manageRowSc)');
@@ -211,6 +211,84 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
})()`); })()`);
console.log('STEP9:', JSON.stringify(step9)); console.log('STEP9:', JSON.stringify(step9));
// 工作台状态持久化:布局项目与视图覆盖项目切换回来都保持上一次内容
const step14 = await evaluate(`(async function(){
var out={};
try{
var st=window.__dshWorktable.splitStore;
// A) 布局项目:卡片打开 → 加终端标签 → 关闭 → 再开 → 标签还在
var layCard=document.querySelector('.dsh-wt_layout');
if(layCard){ layCard.click(); }
await new Promise(function(r){setTimeout(r,400)});
out.layoutOpenId=st.spec?st.spec.id:null;
st.openTab('main',0,{kind:'builtin',type:'terminal'});
await new Promise(function(r){setTimeout(r,300)});
try{ out.layoutSavedTabs=JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')).layouts[0].main[0].tabs.length }catch(e){ out.layoutSavedTabs='ERR' }
st.close();
await new Promise(function(r){setTimeout(r,300)});
layCard=document.querySelector('.dsh-wt_layout');
if(layCard){ layCard.click(); }
await new Promise(function(r){setTimeout(r,400)});
out.layoutRestoredTabs=st.spec?st.spec.main[0].tabs.length:null;
st.close();
await new Promise(function(r){setTimeout(r,300)});
// B) 视图覆盖项目(旅行):先经 UI 设置视图 → 卡片打开 → 加资源管理器标签 → 关闭 → 再开 → 标签还在
var btnS=document.querySelector('.dsh-wt_actions .dsh-wt_iconBtn:nth-child(2)');
if(btnS){ btnS.click(); }
await new Promise(function(r){setTimeout(r,300)});
var rows=document.querySelectorAll('.dsh-wt_settings .dsh-wt_manageRow:not(.dsh-wt_manageRowSc):not(.dsh-wt_manageRowRemoved)');
var taRow=rows[1];
var puzzle=taRow&&taRow.querySelectorAll('.dsh-wt_manageBtn')[taRow.querySelectorAll('.dsh-wt_manageBtn').length-2];
if(puzzle){ puzzle.click(); }
await new Promise(function(r){setTimeout(r,300)});
var presets=document.querySelectorAll('.dsh-wt_presets .dsh-wt_preset');
if(presets[0]){ presets[0].click(); }
await new Promise(function(r){setTimeout(r,400)});
var bd=document.querySelector('.dsh-wt_popBackdrop');
if(bd){ bd.click(); }
await new Promise(function(r){setTimeout(r,300)});
var ta=document.querySelector('.dsh-wt_projects .ta_card');
if(ta){ ta.click(); }
await new Promise(function(r){setTimeout(r,400)});
out.taOpenId=st.spec?st.spec.id:null;
st.openTab('main',0,{kind:'builtin',type:'explorer'});
await new Promise(function(r){setTimeout(r,300)});
try{ out.taSavedTabs=JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')).views['travelatlas'].main[0].tabs.length }catch(e){ out.taSavedTabs='ERR' }
st.close();
await new Promise(function(r){setTimeout(r,300)});
ta=document.querySelector('.dsh-wt_projects .ta_card');
if(ta){ ta.click(); }
await new Promise(function(r){setTimeout(r,400)});
out.taRestoredTabs=(st.spec&&st.spec.id==='travelatlas')?st.spec.main[0].tabs.length:null;
st.close();
await new Promise(function(r){setTimeout(r,300)});
}catch(err){ out.err=String(err) }
return JSON.stringify(out);
})()`);
console.log('STEP14:', JSON.stringify(step14));
// tsx/css 代码文件预览:点击后新标签页显示内容(同 MD 模式)
const step15 = await evaluate(`(async function(){
var out={};
try{
var st=window.__dshWorktable.splitStore;
st.open({id:'t-code',title:'code',top:null,main:[{id:'c1',title:'c',min:200,content:null}],chatWidth:{default:320,min:240,max:600}});
await new Promise(function(r){setTimeout(r,400)});
st.openTab('main',0,{kind:'file',path:'E:\\\\AI_Workspace\\\\DeepseekHarness\\\\Projects\\\\dsh-worktable\\\\01_content\\\\src\\\\client\\\\index.tsx'});
await new Promise(function(r){setTimeout(r,1200)});
out.tsxTab=st.spec.main[0].tabs[0].title;
out.tsxView=!!document.querySelector('.dsh-wt_txt');
out.tsxHasText=document.querySelector('.dsh-wt_txt')?String(document.querySelector('.dsh-wt_txt').textContent).indexOf('WorktableSection')>=0:false;
st.openTab('main',0,{kind:'file',path:'E:\\\\AI_Workspace\\\\DeepseekHarness\\\\Projects\\\\dsh-worktable\\\\01_content\\\\src\\\\client\\\\styles.ts'});
await new Promise(function(r){setTimeout(r,900)});
out.cssTab=st.spec.main[0].tabs[1].title;
out.tabsCount=st.spec.main[0].tabs.length;
st.close();
}catch(err){ out.err=String(err) }
return JSON.stringify(out);
})()`);
console.log('STEP15:', JSON.stringify(step15));
// 删除二次确认:常驻项目/布局 ✕ → 警告弹窗 → 取消不删 / 确认删除;emoji 字号与名称字重统一 // 删除二次确认:常驻项目/布局 ✕ → 警告弹窗 → 取消不删 / 确认删除;emoji 字号与名称字重统一
const step10 = await evaluate(`(async function(){ const step10 = await evaluate(`(async function(){
var out={}; var out={};