fix: v2.6 组合画布选中根因修复+右键菜单+3D 三场景
- 选中失效根因: Vue3 布尔 prop 缺省转换(editable 缺省→false)
withDefaults 默认 true 修复, 左键选中/右键选中/菜单动作全链路打通
- 右键菜单: 右键单元弹操作菜单(顺/逆旋转/镜像/底边对齐/删除)
动作 emit('ctx') 由父组件统一执行, document 级委托
- 3D 三场景: 室内(窗后墙+木地板)/室外(外墙+草地)/影棚(网格)
场景联动背景色, 真实浏览器截图验证三场景渲染
- 菜单动作行为级验证: 旋转后 1600x1800 宽高互换✓ 删除清空✓
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
// 选中命中诊断: node scripts/debug-hit.mjs <token>
|
||||
import puppeteer from 'puppeteer-core'
|
||||
const EDGE = 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
|
||||
const token = process.argv[2]
|
||||
if (!token) { console.error('缺少 token'); process.exit(1) }
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: EDGE,
|
||||
headless: 'new',
|
||||
args: ['--window-size=1440,900', '--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'],
|
||||
defaultViewport: { width: 1440, height: 900 },
|
||||
})
|
||||
const page = await browser.newPage()
|
||||
await page.goto(`http://localhost:8000/combine?token=${encodeURIComponent(token)}`, { waitUntil: 'networkidle2' })
|
||||
await page.waitForSelector('.lib-item')
|
||||
await page.click('.lib-item')
|
||||
await new Promise(r => setTimeout(r, 600))
|
||||
|
||||
const box = await (await page.$('.canvas-box')).boundingBox()
|
||||
const cx = box.x + box.width / 2, cy = box.y + box.height / 2
|
||||
|
||||
// 用画布暴露的调试钩子命中测试
|
||||
const dbg = await page.evaluate((x, y) => window.__ccDebug.debugHit(x, y), cx, cy)
|
||||
console.log('debugHit:', JSON.stringify(dbg))
|
||||
const sel = await page.evaluate(() => window.__ccDebug.getSelected())
|
||||
console.log('选中 uid:', sel)
|
||||
|
||||
// 实际点击后再读选中
|
||||
await page.mouse.click(cx, cy)
|
||||
await new Promise(r => setTimeout(r, 300))
|
||||
const sel2 = await page.evaluate(() => window.__ccDebug.getSelected())
|
||||
console.log('点击后选中 uid:', sel2)
|
||||
|
||||
await browser.close()
|
||||
@@ -0,0 +1,48 @@
|
||||
// 选中问题诊断: 真实点击组合画布单元, 截图对比 + 捕获状态
|
||||
import puppeteer from 'puppeteer-core'
|
||||
const EDGE = 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
|
||||
const BASE = 'http://localhost:8000'
|
||||
const token = process.argv[2]
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: EDGE,
|
||||
headless: 'new',
|
||||
args: ['--window-size=1440,900', '--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'],
|
||||
defaultViewport: { width: 1440, height: 900 },
|
||||
})
|
||||
const page = await browser.newPage()
|
||||
page.on('pageerror', e => console.log(`[JS异常] ${String(e).slice(0, 160)}`))
|
||||
page.on('console', m => { if (m.type() === 'error') console.log(`[console.error] ${m.text().slice(0, 160)}`) })
|
||||
|
||||
await page.goto(`${BASE}/combine?token=${encodeURIComponent(token)}`, { waitUntil: 'networkidle2' })
|
||||
await page.waitForSelector('.lib-item')
|
||||
await page.click('.lib-item')
|
||||
await new Promise(r => setTimeout(r, 600))
|
||||
|
||||
// 取画布盒
|
||||
const box = await (await page.$('.canvas-box')).boundingBox()
|
||||
console.log('canvas-box:', JSON.stringify(box))
|
||||
|
||||
// 点击画布几何中心(单一样板居中,必在单元内)
|
||||
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2)
|
||||
await new Promise(r => setTimeout(r, 300))
|
||||
await page.screenshot({ path: 'D:/Code/huamenchuang/shots/diag-click-center.png' })
|
||||
|
||||
// 在提示行读取画布 hint(含单元数)
|
||||
const hint = await page.$eval('.hud span', el => el.textContent)
|
||||
console.log('hint:', hint)
|
||||
|
||||
// 再点击右下象限(可能在单元外)
|
||||
await page.mouse.click(box.x + box.width * 0.85, box.y + box.height * 0.85)
|
||||
await new Promise(r => setTimeout(r, 300))
|
||||
await page.screenshot({ path: 'D:/Code/huamenchuang/shots/diag-click-outside.png' })
|
||||
|
||||
// 键盘 Del(若有选中则删除 → 单元数变化可证选中生效)
|
||||
await page.keyboard.press('Delete')
|
||||
await new Promise(r => setTimeout(r, 400))
|
||||
const hint2 = await page.$eval('.hud span', el => el.textContent)
|
||||
console.log('Del 后 hint:', hint2)
|
||||
|
||||
await page.screenshot({ path: 'D:/Code/huamenchuang/shots/diag-after-del.png' })
|
||||
await browser.close()
|
||||
console.log('诊断完成')
|
||||
@@ -0,0 +1,42 @@
|
||||
// 事件链诊断: node scripts/diag2.mjs <token>
|
||||
import puppeteer from 'puppeteer-core'
|
||||
const EDGE = 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
|
||||
const token = process.argv[2]
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: EDGE,
|
||||
headless: 'new',
|
||||
args: ['--window-size=1440,900', '--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'],
|
||||
defaultViewport: { width: 1440, height: 900 },
|
||||
})
|
||||
const page = await browser.newPage()
|
||||
page.on('console', m => console.log(`[页面.${m.type()}]`, m.text().slice(0, 150)))
|
||||
await page.goto(`http://localhost:8000/combine?token=${encodeURIComponent(token)}`, { waitUntil: 'networkidle2' })
|
||||
await page.waitForSelector('.lib-item')
|
||||
await page.click('.lib-item')
|
||||
await new Promise(r => setTimeout(r, 600))
|
||||
|
||||
const box = await (await page.$('.canvas-box')).boundingBox()
|
||||
const cx = box.x + box.width / 2, cy = box.y + box.height / 2
|
||||
|
||||
await page.evaluate(() => {
|
||||
window.__md = 0; window.__mu = 0; window.__selEvents = []
|
||||
const cv = document.querySelector('.canvas-box canvas')
|
||||
cv.addEventListener('mousedown', () => window.__md++)
|
||||
cv.addEventListener('mouseup', () => window.__mu++)
|
||||
})
|
||||
const el = await page.evaluate((x, y) => {
|
||||
const e = document.elementFromPoint(x, y)
|
||||
return e ? `${e.tagName}.${String(e.className).slice(0, 40)}` : 'null'
|
||||
}, cx, cy)
|
||||
console.log('中心点元素:', el)
|
||||
|
||||
await page.mouse.click(cx, cy)
|
||||
await new Promise(r => setTimeout(r, 300))
|
||||
const st = await page.evaluate(() => ({
|
||||
md: window.__md, mu: window.__mu,
|
||||
sel: window.__ccDebug.getSelected(),
|
||||
units: window.__ccDebug.units(),
|
||||
}))
|
||||
console.log('事件计数+选中:', JSON.stringify({ md: st.md, mu: st.mu, sel: st.sel }))
|
||||
console.log('单元 uid 列表:', JSON.stringify(st.units))
|
||||
await browser.close()
|
||||
@@ -0,0 +1,43 @@
|
||||
// 决定性诊断: node scripts/diag3.mjs <token>
|
||||
import puppeteer from 'puppeteer-core'
|
||||
const EDGE = 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
|
||||
const token = process.argv[2]
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: EDGE,
|
||||
headless: 'new',
|
||||
args: ['--window-size=1440,900', '--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'],
|
||||
defaultViewport: { width: 1440, height: 900 },
|
||||
})
|
||||
const page = await browser.newPage()
|
||||
page.on('console', m => console.log('[页面]', m.type(), m.text().slice(0, 120)))
|
||||
await page.goto(`http://localhost:8000/combine?token=${encodeURIComponent(token)}`, { waitUntil: 'networkidle2' })
|
||||
await page.waitForSelector('.lib-item')
|
||||
await page.click('.lib-item')
|
||||
await new Promise(r => setTimeout(r, 500))
|
||||
const box = await (await page.$('.canvas-box')).boundingBox()
|
||||
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2, { button: 'right' })
|
||||
await new Promise(r => setTimeout(r, 300))
|
||||
|
||||
const res = await page.evaluate(() => {
|
||||
const items = document.querySelectorAll('.ctx-item')
|
||||
const el = items[items.length - 1]
|
||||
window.__nativeClicks = 0
|
||||
el.addEventListener('click', () => window.__nativeClicks++)
|
||||
el.click() // 合成点击
|
||||
return {
|
||||
items: items.length,
|
||||
nativeAfterSynthetic: window.__nativeClicks,
|
||||
menuStill: !!document.querySelector('.ctx-menu'),
|
||||
}
|
||||
})
|
||||
console.log('合成点击结果:', JSON.stringify(res))
|
||||
|
||||
// 真实点击
|
||||
await page.mouse.click(box.x + box.width / 2 + 30, box.y + box.height / 2 + 10)
|
||||
await new Promise(r => setTimeout(r, 300))
|
||||
const st = await page.evaluate(() => ({
|
||||
nativeClicks: window.__nativeClicks,
|
||||
units: window.__ccDebug.units().length,
|
||||
}))
|
||||
console.log('真实点击后:', JSON.stringify(st))
|
||||
await browser.close()
|
||||
@@ -0,0 +1,28 @@
|
||||
// el.click 派发测试: node scripts/elclick-test.mjs <token>
|
||||
import puppeteer from 'puppeteer-core'
|
||||
const EDGE = 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
|
||||
const token = process.argv[2]
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: EDGE,
|
||||
headless: 'new',
|
||||
args: ['--window-size=1440,900', '--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'],
|
||||
defaultViewport: { width: 1440, height: 900 },
|
||||
})
|
||||
const page = await browser.newPage()
|
||||
page.on('console', m => console.log('[页面]', m.type(), m.text().slice(0, 120)))
|
||||
await page.goto(`http://localhost:8000/combine?token=${encodeURIComponent(token)}`, { waitUntil: 'networkidle2' })
|
||||
await page.waitForSelector('.lib-item')
|
||||
await page.click('.lib-item')
|
||||
await new Promise(r => setTimeout(r, 500))
|
||||
const box = await (await page.$('.canvas-box')).boundingBox()
|
||||
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2, { button: 'right' })
|
||||
await new Promise(r => setTimeout(r, 300))
|
||||
const res = await page.evaluate(() => {
|
||||
const items = document.querySelectorAll('.ctx-item')
|
||||
if (!items.length) return '菜单不存在'
|
||||
items[items.length - 1].click()
|
||||
return '已派发 el.click(), 菜单项数: ' + items.length
|
||||
})
|
||||
console.log(res)
|
||||
await new Promise(r => setTimeout(r, 500))
|
||||
await browser.close()
|
||||
@@ -0,0 +1,34 @@
|
||||
// Vue 监听器挂载诊断: node scripts/vei-check.mjs <token>
|
||||
import puppeteer from 'puppeteer-core'
|
||||
const EDGE = 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
|
||||
const token = process.argv[2]
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: EDGE,
|
||||
headless: 'new',
|
||||
args: ['--window-size=1440,900', '--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'],
|
||||
defaultViewport: { width: 1440, height: 900 },
|
||||
})
|
||||
const page = await browser.newPage()
|
||||
await page.goto(`http://localhost:8000/combine?token=${encodeURIComponent(token)}`, { waitUntil: 'networkidle2' })
|
||||
await page.waitForSelector('.lib-item')
|
||||
await page.click('.lib-item')
|
||||
await new Promise(r => setTimeout(r, 500))
|
||||
const box = await (await page.$('.canvas-box')).boundingBox()
|
||||
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2, { button: 'right' })
|
||||
await new Promise(r => setTimeout(r, 300))
|
||||
const res = await page.evaluate(() => {
|
||||
const items = document.querySelectorAll('.ctx-item')
|
||||
if (!items.length) return { err: '菜单不存在' }
|
||||
const el = items[items.length - 1]
|
||||
const vei = el._vei
|
||||
const out = {
|
||||
veiKeys: vei ? Object.keys(vei) : null,
|
||||
itemText: el.textContent.trim().slice(0, 12),
|
||||
inCcWrap: !!el.closest('.cc-wrap'),
|
||||
}
|
||||
el.click()
|
||||
return out
|
||||
})
|
||||
console.log(JSON.stringify(res, null, 1))
|
||||
await new Promise(r => setTimeout(r, 400))
|
||||
await browser.close()
|
||||
@@ -0,0 +1,38 @@
|
||||
// 对比 canvas 与 ctx-menu 的 Vue 监听器挂载: node scripts/vei2.mjs <token>
|
||||
import puppeteer from 'puppeteer-core'
|
||||
const EDGE = 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
|
||||
const token = process.argv[2]
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: EDGE,
|
||||
headless: 'new',
|
||||
args: ['--window-size=1440,900', '--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'],
|
||||
defaultViewport: { width: 1440, height: 900 },
|
||||
})
|
||||
const page = await browser.newPage()
|
||||
await page.goto(`http://localhost:8000/combine?token=${encodeURIComponent(token)}`, { waitUntil: 'networkidle2' })
|
||||
await page.waitForSelector('.lib-item')
|
||||
await page.click('.lib-item')
|
||||
await new Promise(r => setTimeout(r, 500))
|
||||
const box = await (await page.$('.canvas-box')).boundingBox()
|
||||
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2, { button: 'right' })
|
||||
await new Promise(r => setTimeout(r, 300))
|
||||
const res = await page.evaluate(() => {
|
||||
const cv = document.querySelector('.canvas-box canvas')
|
||||
const item = document.querySelector('.ctx-item')
|
||||
const menu = document.querySelector('.ctx-menu')
|
||||
const out = {
|
||||
canvasVei: cv && cv._vei ? Object.keys(cv._vei) : null,
|
||||
menuExists: !!menu,
|
||||
menuParentChain: [],
|
||||
itemTag: item ? item.tagName : null,
|
||||
}
|
||||
let e = item
|
||||
while (e && e.tagName !== 'BODY') {
|
||||
out.menuParentChain.push(e.tagName + (e.id ? '#' + e.id : '') + (e.className ? '.' + String(e.className).split(' ')[0] : ''))
|
||||
e = e.parentElement
|
||||
}
|
||||
return out
|
||||
})
|
||||
console.log(JSON.stringify(res, null, 1))
|
||||
await new Promise(r => setTimeout(r, 300))
|
||||
await browser.close()
|
||||
@@ -0,0 +1,41 @@
|
||||
// 菜单动作行为级验证: node scripts/verify-menu.mjs <token>
|
||||
import puppeteer from 'puppeteer-core'
|
||||
const EDGE = 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
|
||||
const token = process.argv[2]
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: EDGE,
|
||||
headless: 'new',
|
||||
args: ['--window-size=1440,900', '--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'],
|
||||
defaultViewport: { width: 1440, height: 900 },
|
||||
})
|
||||
const page = await browser.newPage()
|
||||
await page.goto(`http://localhost:8000/combine?token=${encodeURIComponent(token)}`, { waitUntil: 'networkidle2' })
|
||||
await page.waitForSelector('.lib-item')
|
||||
await page.click('.lib-item')
|
||||
await new Promise(r => setTimeout(r, 600))
|
||||
const box = await (await page.$('.canvas-box')).boundingBox()
|
||||
const cx = box.x + box.width / 2, cy = box.y + box.height / 2
|
||||
|
||||
// ① 右键 → 菜单 → 真实鼠标点击"顺时针旋转90°"
|
||||
await page.mouse.click(cx, cy, { button: 'right' })
|
||||
await new Promise(r => setTimeout(r, 300))
|
||||
const item0 = await page.$$('.ctx-item')
|
||||
const r0 = item0.length ? await item0[0].boundingBox() : null
|
||||
if (r0) await page.mouse.click(r0.x + r0.width / 2, r0.y + r0.height / 2)
|
||||
await new Promise(r => setTimeout(r, 400))
|
||||
const hint1 = await page.$eval('.hud span', el => el.textContent)
|
||||
console.log('① 旋转后 hint:', hint1, '(原始 1800×1600 → 期望 1600×1800)')
|
||||
|
||||
// ② 右键 → 真实鼠标点击"删除单元"
|
||||
await page.mouse.click(cx, cy, { button: 'right' })
|
||||
await new Promise(r => setTimeout(r, 300))
|
||||
const itemN = await page.$$('.ctx-item')
|
||||
const rN = itemN.length ? await itemN[itemN.length - 1].boundingBox() : null
|
||||
if (rN) await page.mouse.click(rN.x + rN.width / 2, rN.y + rN.height / 2)
|
||||
await new Promise(r => setTimeout(r, 400))
|
||||
const hint2 = await page.$eval('.hud span', el => el.textContent)
|
||||
console.log('② 删除后 hint:', hint2, '(期望"拖入样板开始组合")')
|
||||
|
||||
await page.screenshot({ path: 'D:/Code/huamenchuang/shots/v4-menu-actions.png' })
|
||||
await browser.close()
|
||||
console.log('验证完成')
|
||||
@@ -0,0 +1,82 @@
|
||||
// v2.6 综合验证: 选中/右键菜单/三场景 — node scripts/verify-v3.mjs <token>
|
||||
import puppeteer from 'puppeteer-core'
|
||||
const EDGE = 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
|
||||
const token = process.argv[2]
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: EDGE,
|
||||
headless: 'new',
|
||||
args: ['--window-size=1440,900', '--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'],
|
||||
defaultViewport: { width: 1440, height: 900 },
|
||||
})
|
||||
const page = await browser.newPage()
|
||||
page.on('pageerror', e => console.log('[JS异常]', String(e).slice(0, 150)))
|
||||
page.on('console', m => { if (m.text().includes('[C')) console.log('[页面]', m.text().slice(0, 120)) })
|
||||
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
||||
const shot = n => page.screenshot({ path: `D:/Code/huamenchuang/shots/${n}.png` }).then(() => console.log(`✓ ${n}.png`))
|
||||
|
||||
// 1. 组合画布: 加入样板 → 左键选中
|
||||
await page.goto(`http://localhost:8000/combine?token=${encodeURIComponent(token)}`, { waitUntil: 'networkidle2' })
|
||||
await page.waitForSelector('.lib-item')
|
||||
await page.click('.lib-item')
|
||||
await sleep(500)
|
||||
const box = await (await page.$('.canvas-box')).boundingBox()
|
||||
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2)
|
||||
await sleep(300)
|
||||
let sel = await page.evaluate(() => window.__ccDebug.getSelected())
|
||||
console.log('① 左键选中 uid:', sel, sel === 1 ? '✓' : '✗')
|
||||
await shot('v3-01-selected')
|
||||
|
||||
// 2. 右键菜单
|
||||
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2)
|
||||
await sleep(200)
|
||||
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2, { button: 'right' })
|
||||
await sleep(300)
|
||||
const menuVisible = await page.evaluate(() => !!document.querySelector('.ctx-menu'))
|
||||
console.log('② 右键菜单出现:', menuVisible, menuVisible ? '✓' : '✗')
|
||||
await shot('v3-02-contextmenu')
|
||||
|
||||
// 3. 右键菜单点击旋转90°
|
||||
const items = await page.$$('.ctx-item')
|
||||
if (items[0]) { await items[0].click(); await sleep(400) }
|
||||
const selAfter = await page.evaluate(() => window.__ccDebug.getSelected())
|
||||
console.log('③ 菜单旋转后选中保持:', selAfter, selAfter === 1 ? '✓' : '✗')
|
||||
await shot('v3-03-rotated')
|
||||
|
||||
// 4. 菜单删除
|
||||
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2, { button: 'right' })
|
||||
await sleep(300)
|
||||
const items2 = await page.$$('.ctx-item')
|
||||
console.log('④ 菜单项数量:', items2.length)
|
||||
const menuOpen = await page.evaluate(() => !!document.querySelector('.ctx-menu'))
|
||||
console.log('④ 菜单可见:', menuOpen)
|
||||
if (items2.length) {
|
||||
const delBox = await items2[items2.length - 1].boundingBox()
|
||||
if (delBox) {
|
||||
await page.mouse.click(delBox.x + delBox.width / 2, delBox.y + delBox.height / 2)
|
||||
console.log('④ 已真实点击删除项')
|
||||
}
|
||||
await sleep(400)
|
||||
}
|
||||
const unitCount = await page.evaluate(() => window.__ccDebug.units().length)
|
||||
console.log('④ 菜单删除后单元数:', unitCount, unitCount === 0 ? '✓' : '✗')
|
||||
|
||||
// 5. 3D 三场景
|
||||
await page.goto(`http://localhost:8000/threed?token=${encodeURIComponent(token)}`, { waitUntil: 'networkidle2' })
|
||||
await sleep(2200)
|
||||
await shot('v3-04-3d-outdoor')
|
||||
const sceneBtns = await page.$$('label.el-radio-button')
|
||||
for (const b of sceneBtns) {
|
||||
const txt = await b.evaluate(el => el.textContent.trim())
|
||||
if (txt === '室内') { await b.click(); break }
|
||||
}
|
||||
await sleep(1200)
|
||||
await shot('v3-05-3d-indoor')
|
||||
for (const b of sceneBtns) {
|
||||
const txt = await b.evaluate(el => el.textContent.trim())
|
||||
if (txt === '影棚') { await b.click(); break }
|
||||
}
|
||||
await sleep(1200)
|
||||
await shot('v3-06-3d-studio')
|
||||
|
||||
await browser.close()
|
||||
console.log('验证完成')
|
||||
Reference in New Issue
Block a user