// 探针:重复 :key 会不会让翻页画出错内容(合成载荷:key 全同、值各不同)。 import { readFileSync } from 'node:fs' import { registerHooks } from 'node:module' import { parse, compileScript, compileTemplate } from '@vue/compiler-sfc' import { createRenderer, h, ref, nextTick } from '@vue/runtime-core' const root = new URL('../../../../../src/modules/cockpit/', import.meta.url) registerHooks({ load(url, context, nextLoad) { if (url.endsWith('.json')) return { format: 'module', shortCircuit: true, source: `export default ${readFileSync(new URL(url), 'utf8')}` } if (url.endsWith('.vue')) { const { descriptor } = parse(readFileSync(new URL(url), 'utf8'), { filename: new URL(url).pathname }) const script = compileScript(descriptor, { id: 'probe', genDefaultAs: '__sfc__' }) const template = compileTemplate({ source: descriptor.template.content, filename: new URL(url).pathname, id: 'probe', compilerOptions: { bindingMetadata: script.bindings }, }) return { format: 'module', shortCircuit: true, source: `${script.content}\n${template.code}\n__sfc__.render = render; export default __sfc__;` } } return nextLoad(url, context) }, }) const nodeOps = { createElement: (tag) => ({ tag, props: {}, children: [], parent: null, text: '', style: {}, addEventListener() {}, removeEventListener() {} }), createText: (text) => ({ tag: '#text', text, children: [], parent: null, props: {}, style: {}, addEventListener() {}, removeEventListener() {} }), createComment: (text) => ({ tag: '#comment', text, children: [], parent: null, props: {}, style: {}, addEventListener() {}, removeEventListener() {} }), setText: (node, text) => { node.text = text }, setElementText: (node, text) => { node.text = text; node.children = [] }, insert: (child, parent, anchor) => { if (child.parent) child.parent.children = child.parent.children.filter((c) => c !== child) child.parent = parent if (!parent) return if (anchor) { const i = parent.children.indexOf(anchor); parent.children.splice(i < 0 ? parent.children.length : i, 0, child) } else parent.children.push(child) }, remove: (child) => { if (child.parent) child.parent.children = child.parent.children.filter((c) => c !== child); child.parent = null }, parentNode: (node) => node.parent, nextSibling: (node) => (node.parent ? node.parent.children[node.parent.children.indexOf(node) + 1] || null : null), querySelector: () => null, setScopeId: () => {}, patchProp: (el, key, prev, next) => { el.props[key] = next }, } const { createApp } = createRenderer(nodeOps) const { default: PanelBlocks } = await import(new URL('components/PanelBlocks.vue', root)) const { operationPanel } = await import(new URL('domain/infrastructurePanels.js', root)) const { section } = await import(new URL('domain/sectionState.js', root)) function findAll(node, predicate, acc = []) { if (!node) return acc if (predicate(node)) acc.push(node) for (const c of node.children || []) findAll(c, predicate, acc) return acc } function textOf(node) { if (!node) return '' const own = node.tag === '#text' ? node.text : typeof node.props?.text === 'string' ? node.props.text : '' return own + (node.children || []).map(textOf).join('') } // 合成载荷:699 条指标,metricCode 全同(真实 GAS 就是这个形状),value 各不相同 V0..V698 const mk = (i) => ({ metricCode: 'same_code', metricName: `M${i}`, category: 'SUPPLY', unit: 'MPa', value: String(i), capacityValue: '1', loadRatio: '0.5', loadState: 'NORMAL', pointCodes: [`FAC${i}`], freshness: { status: 'CURRENT' }, }) const raw = { industries: [{ industry: { code: 'GAS', label: '燃气' }, state: 'READY', metrics: Array.from({ length: 699 }, (_, i) => mk(i)) }], excluded: {} } const data = operationPanel.length > 2 ? null : null const { toOperatingInformation } = await import(new URL('domain/infrastructurePanels.js', root)) const view = operationPanel(toOperatingInformation(raw), section('success', { loaded: true }), { scope: 'all' }) const rows = view.blocks[1].rows console.log('domain row keys distinct:', new Set(rows.map((r) => r.key)).size, '/', rows.length) console.log('row[0] meta:', rows[0].meta.join(' · ')) console.log('row[11] meta:', rows[11].meta.join(' · ')) const viewRef = ref(view) const app = createApp({ render: () => h(PanelBlocks, { view: viewRef.value }) }) app.config.warnHandler = () => {} const container = nodeOps.createElement('#root') app.mount(container) function renderedMetas() { return findAll(container, (n) => n.props && 'data-item' in n.props).map((card) => { const metas = findAll(card, (n) => n.props && n.props['class'] && String(n.props['class']).includes('panel-item__meta')) return metas.length ? textOf(metas[0]) : '(no meta)' }) } console.log('page1 rendered:', renderedMetas().slice(0, 2).join(' || ')) const btn2 = findAll(container, (n) => String(n.props?.['data-page-number']) === '2')[0] btn2.props.onClick() await nextTick() console.log('page2 rendered:', renderedMetas().slice(0, 2).join(' || ')) console.log('page2 expected:', rows[10].meta.join(' · '), '||', rows[11].meta.join(' · ')) const btn70 = findAll(container, (n) => String(n.props?.['data-page-number']) === '70')[0] btn70?.props.onClick() await nextTick() console.log('page70 rendered:', renderedMetas().join(' || '))