From a2d9fcb259621829bf4ec9680b5dbeac95ab5805 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 15 十月 2026 16:19:54 +0800
Subject: [PATCH] fix(crm): 销售目标达成分析默认查当前季度
---
scripts/bi-realtime-table-qa.mjs | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 104 insertions(+), 0 deletions(-)
diff --git a/scripts/bi-realtime-table-qa.mjs b/scripts/bi-realtime-table-qa.mjs
new file mode 100644
index 0000000..8a2ade2
--- /dev/null
+++ b/scripts/bi-realtime-table-qa.mjs
@@ -0,0 +1,104 @@
+import assert from 'node:assert/strict';
+import fs from 'node:fs';
+import http from 'node:http';
+import path from 'node:path';
+import { createRequire } from 'node:module';
+import { fileURLToPath } from 'node:url';
+
+const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
+const require = createRequire(path.join(root, 'package.json'));
+const { parse, compileScript } = require('vue/compiler-sfc');
+const ts = require('typescript');
+const { chromium } = require('C:/Users/浜�/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright');
+const source = fs.readFileSync(path.join(root, 'src/views/bi/dashboard/modules/RealtimeTable.vue'), 'utf8');
+const { descriptor } = parse(source);
+const script = compileScript(descriptor, { id: 'qa', inlineTemplate: true });
+const module = ts.transpileModule(script.content, {
+ compilerOptions: { target: ts.ScriptTarget.ESNext, module: ts.ModuleKind.ESNext },
+}).outputText.replace(/from ['"]vue['"]/g, 'from "/vue.js"');
+const html = `<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>BI 婊氬姩楠岃瘉</title>
+<style>body{background:#020817;color:#ebf0fc;font-family:Microsoft YaHei,sans-serif;margin:24px}h1{font-size:18px}main{max-width:1100px;margin:auto}.card{padding:16px;background:#0a1834;border:1px solid #153554;border-radius:8px} ${descriptor.styles.map(s => s.content).join('\n')}</style>
+<main><h1>鏈�杩戝叆搴撹褰�</h1><div class="card"><div id="app"></div></div></main>
+<script type="module">
+import {createApp, h, ref} from '/vue.js';import Table from '/table.js';
+const rows=ref([]), height=ref(180), show=ref(true); const makeRows=n=>Array.from({length:n},(_,i)=>({'鍏ュ簱鍗曞彿':'RK-'+String(i+1).padStart(4,'0'),'浜у搧鍚嶇О':'楠岃瘉浜у搧 '+(i+1),'浠撳簱':'鍗楅�氫粨','鏁伴噺':100+i}));
+rows.value=makeRows(20); window.qa={setRows:n=>rows.value=makeRows(n), refresh:()=>rows.value=[...rows.value], setHeight:n=>height.value=n, hide:()=>show.value=false};
+createApp({render:()=>show.value?h(Table,{rows:rows.value,maxHeight:height.value,speed:30}):null}).mount('#app');
+</script></html>`;
+const vueFile = path.join(path.dirname(require.resolve('vue/package.json')), 'dist/vue.esm-browser.js');
+const server = http.createServer((request, response) => {
+ response.setHeader('Content-Type', request.url === '/' ? 'text/html; charset=utf-8' : 'text/javascript; charset=utf-8');
+ response.end(request.url === '/table.js' ? module : request.url === '/vue.js' ? fs.readFileSync(vueFile) : html);
+});
+await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
+let browser;
+const errors = [];
+const output = path.join(root, '../scripts/bi-realtime-scroll-20261014');
+fs.mkdirSync(output, { recursive: true });
+try {
+ browser = await chromium.launch({ headless: true, executablePath: 'C:/Program Files/Google/Chrome/Application/chrome.exe' });
+ const page = await browser.newPage({ viewport: { width: 1440, height: 800 } });
+ page.on('pageerror', error => errors.push(error.message));
+ await page.goto(`http://127.0.0.1:${server.address().port}`);
+ await page.waitForFunction(() => document.querySelector('.table-track')?.getAnimations().length === 1);
+ const offset = () => page.locator('.table-track').evaluate(el => new DOMMatrix(getComputedStyle(el).transform).m42);
+ const top = await page.locator('.header-table').evaluate(el => el.getBoundingClientRect().top);
+ const first = await offset();
+ await page.waitForTimeout(500);
+ assert.ok((await offset()) < first - 8, 'Rows must move upwards');
+ assert.equal(await page.locator('.header-table').evaluate(el => el.getBoundingClientRect().top), top, 'Header must stay fixed');
+ await page.locator('.realtime-table').hover();
+ const paused = await offset();
+ await page.waitForTimeout(250);
+ assert.ok(Math.abs((await offset()) - paused) < 1, 'Hover must pause');
+ await page.mouse.move(0, 0);
+ await page.waitForTimeout(200);
+ assert.ok((await offset()) < paused - 3, 'Mouse leave must resume');
+ const refreshOffset = await offset();
+ await page.evaluate(() => window.qa.refresh());
+ await page.waitForTimeout(100);
+ assert.ok((await offset()) < refreshOffset, 'Refresh must preserve scroll position');
+ await page.locator('.table-track').evaluate(el => {
+ const animation = el.getAnimations()[0];
+ animation.currentTime = Number(animation.effect.getTiming().duration) - 200;
+ });
+ await page.waitForTimeout(400);
+ assert.ok((await offset()) > -20, 'Loop must wrap seamlessly');
+ await page.locator('.realtime-table').focus();
+ const focused = await offset();
+ await page.waitForTimeout(100);
+ assert.ok(Math.abs((await offset()) - focused) < 1, 'Focus must pause');
+ await page.keyboard.press('ArrowDown');
+ assert.ok((await offset()) < focused - 35, 'Keyboard must advance rows');
+ await page.evaluate(() => document.activeElement.blur());
+ await page.screenshot({ path: path.join(output, 'desktop.png'), fullPage: true });
+ await page.setViewportSize({ width: 390, height: 844 });
+ await page.waitForTimeout(150);
+ assert.ok(await page.locator('.table-track').evaluate(el => el.getAnimations().length === 1), 'Resize must keep one animation');
+ await page.screenshot({ path: path.join(output, 'mobile.png'), fullPage: true });
+ await page.evaluate(() => window.qa.setRows(2));
+ await page.waitForTimeout(150);
+ assert.equal(await page.locator('.table-track').evaluate(el => el.getAnimations().length), 0, 'Short table must not scroll');
+ assert.equal(await page.locator('table[aria-hidden="true"]').count(), 0, 'Short table must not duplicate rows');
+ await page.evaluate(() => window.qa.setRows(30));
+ await page.waitForFunction(() => document.querySelector('.table-track').getAnimations().length === 1);
+ await page.emulateMedia({ reducedMotion: 'reduce' });
+ await page.waitForTimeout(150);
+ assert.equal(await page.locator('.table-track').evaluate(el => el.getAnimations().length), 0, 'Reduced motion must disable autoplay');
+ assert.equal(await page.locator('.table-viewport').evaluate(el => getComputedStyle(el).overflowY), 'auto', 'Reduced motion must allow manual scroll');
+ await page.emulateMedia({ reducedMotion: 'no-preference' });
+ await page.waitForFunction(() => document.querySelector('.table-track').getAnimations().length === 1);
+ await page.evaluate(() => window.qa.setHeight(2000));
+ await page.waitForTimeout(150);
+ assert.equal(await page.locator('.table-track').evaluate(el => el.getAnimations().length), 0, 'Larger viewport must stop scrolling when rows fit');
+ await page.evaluate(() => window.qa.hide());
+ await page.waitForTimeout(100);
+ assert.equal(await page.evaluate(() => document.getAnimations().length), 0, 'Unmount must clean up animations');
+ assert.deepEqual(errors, []);
+ const result = { passed: true, checks: ['continuous scroll', 'fixed header', 'hover pause/resume', 'refresh continuity', 'seamless loop', 'keyboard navigation', 'responsive resize', 'short data', 'reduced motion', 'unmount cleanup'], screenshots: ['desktop.png', 'mobile.png'] };
+ fs.writeFileSync(path.join(output, 'result.json'), JSON.stringify(result, null, 2));
+ console.log(JSON.stringify(result));
+} finally {
+ await browser?.close();
+ server.close();
+}
--
Gitblit v1.9.3