c670b9e454
- 确定性领域引擎(分类/评分/分级/红线/费用/裁决)+LLM(通义千问)语言理解 - 6步评估向导、服务端草稿持久化(跨设备/编辑草稿保护) - 工作流(草稿→风控→管理层)、RBAC、报告导出、校准、客户/费率/红线/最低工资管理 - 专业图标体系替换全部emoji、看板美化 - 生产化:API_BASE可配置(同源反代)、auth密钥惰性读取修复RBAC - 444单测+204前端测试+51 e2e
156 lines
6.1 KiB
JavaScript
156 lines
6.1 KiB
JavaScript
/**
|
|
* 端到端演示录制:登录 → 6 步评估向导 → 评估详情页。
|
|
*
|
|
* 产出:
|
|
* - docs/demo/NN-*.png 关键步骤截图(deviceScaleFactor=2,高清)
|
|
* - docs/demo/walkthrough.webm 全程操作录像(Playwright 原生录制)
|
|
*
|
|
* 运行:node scripts/demo-record.mjs
|
|
*/
|
|
import { chromium } from 'playwright';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
const ROOT = '/Users/freedak/Documents/AIDashboard/RiskAgent';
|
|
const SHOTS = path.join(ROOT, 'docs/demo');
|
|
const VIDEO_DIR = path.join(SHOTS, 'video');
|
|
const BASE = 'http://localhost:5173';
|
|
const W = 1440;
|
|
const H = 900;
|
|
|
|
fs.mkdirSync(SHOTS, { recursive: true });
|
|
fs.mkdirSync(VIDEO_DIR, { recursive: true });
|
|
|
|
let n = 0;
|
|
async function shot(page, label) {
|
|
n += 1;
|
|
const name = `${String(n).padStart(2, '0')}-${label}.png`;
|
|
await page.screenshot({ path: path.join(SHOTS, name) });
|
|
console.log('📸', name);
|
|
}
|
|
|
|
async function expandAll(page) {
|
|
for (let i = 0; i < 15; i += 1) {
|
|
const collapsed = page.locator('button[aria-expanded="false"]');
|
|
const c = await collapsed.count();
|
|
if (c === 0) break;
|
|
try {
|
|
await collapsed.first().click({ timeout: 5000 });
|
|
} catch {
|
|
break;
|
|
}
|
|
await page.waitForTimeout(150);
|
|
}
|
|
}
|
|
|
|
(async () => {
|
|
const browser = await chromium.launch({ headless: true });
|
|
const context = await browser.newContext({
|
|
viewport: { width: W, height: H },
|
|
deviceScaleFactor: 2,
|
|
locale: 'zh-CN',
|
|
recordVideo: { dir: VIDEO_DIR, size: { width: W, height: H } },
|
|
});
|
|
const page = await context.newPage();
|
|
page.setDefaultTimeout(120000);
|
|
const video = page.video();
|
|
|
|
// ---------- 登录 ----------
|
|
await page.goto(BASE, { waitUntil: 'networkidle' });
|
|
await page.waitForTimeout(800);
|
|
const userBox = page.getByPlaceholder('请输入用户名');
|
|
if ((await userBox.count()) > 0) {
|
|
await shot(page, 'login');
|
|
await userBox.fill('销售账号');
|
|
await page.getByPlaceholder('请输入密码').fill('123456');
|
|
await page.getByRole('button', { name: '登录' }).click();
|
|
await page.waitForLoadState('networkidle');
|
|
await page.waitForTimeout(1200);
|
|
}
|
|
|
|
// ---------- 首页:评估历史 ----------
|
|
await shot(page, 'dashboard');
|
|
|
|
// ---------- 进入新建评估 ----------
|
|
await page.getByRole('button', { name: /新建评估/ }).first().click();
|
|
await page.getByText('① 立项信息').waitFor();
|
|
await page.getByPlaceholder('如 某银行信用卡客服 BPO').fill('某全国性股份制银行信用卡中心客服外包项目');
|
|
await page.getByPlaceholder('客户公司全称').fill('某全国性股份制商业银行信用卡中心');
|
|
await page.locator('select').first().selectOption('上海');
|
|
await page.waitForTimeout(300);
|
|
await shot(page, 'step1-立项信息');
|
|
await page.getByRole('button', { name: '下一步', exact: true }).click();
|
|
|
|
// ---------- 步骤②:项目描述 ----------
|
|
await page.getByText('② 项目描述').waitFor();
|
|
const desc =
|
|
'为客户信用卡中心提供电话客服与在线客服外包服务,采用业务/服务外包模式,坐席约 80 人,' +
|
|
'7×12 小时三班排班;服务质量按接通率、平均处理时长与客户满意度考核;结算账期约 3 个月;' +
|
|
'需符合金融数据安全与个人信息保护合规要求,涉及呼叫中心系统对接与驻场管理。';
|
|
await page.locator('textarea').fill(desc);
|
|
await page.waitForTimeout(300);
|
|
await shot(page, 'step2-项目描述');
|
|
await page.getByRole('button', { name: /识别业务类型|识别中/ }).click();
|
|
|
|
// ---------- 步骤③:业务类型确认(LLM 分类) ----------
|
|
await page.getByText('③ 业务类型确认').waitFor({ timeout: 120000 });
|
|
await page.waitForTimeout(600);
|
|
await shot(page, 'step3-业务确认-LLM分类');
|
|
await page.getByRole('button', { name: /补全指标|加载指标中/ }).click();
|
|
|
|
// ---------- 步骤④:指标补全(LLM 预填) ----------
|
|
await page.getByText('④ 指标补全', { exact: false }).waitFor({ timeout: 120000 });
|
|
await page.waitForTimeout(800);
|
|
await shot(page, 'step4-指标补全-LLM预填');
|
|
await page.getByRole('button', { name: /费用输入/ }).click();
|
|
|
|
// ---------- 步骤⑤:报价与成本 ----------
|
|
await page.getByText('⑤ 报价与成本').waitFor();
|
|
await page.getByPlaceholder('如 运维工程师').fill('信用卡客服坐席');
|
|
await page.getByPlaceholder('10', { exact: true }).fill('80');
|
|
await page.getByPlaceholder('10000', { exact: true }).fill('6000');
|
|
await page.getByPlaceholder('默认=工资', { exact: true }).fill('6000');
|
|
await page.getByPlaceholder('0', { exact: true }).fill('500');
|
|
await page.getByPlaceholder('如 16000', { exact: true }).fill('11000');
|
|
await page.waitForTimeout(300);
|
|
await shot(page, 'step5-报价与成本');
|
|
await page.getByRole('button', { name: /运行风险与盈利评估|评估运行中/ }).click();
|
|
|
|
// ---------- 评估详情页 ----------
|
|
await page.waitForURL(/\/assessments\/[^/]+$/, { timeout: 180000 });
|
|
await page.waitForLoadState('networkidle');
|
|
await page.waitForTimeout(2000);
|
|
await shot(page, 'detail-顶部结论');
|
|
|
|
// 等待 AI 综合研判(异步 LLM),最多 ~25s
|
|
for (let i = 0; i < 25; i += 1) {
|
|
if ((await page.getByText(/综合研判|综合意见|AI 综合/).count()) > 0) break;
|
|
await page.waitForTimeout(1000);
|
|
}
|
|
await expandAll(page);
|
|
await page.waitForTimeout(800);
|
|
|
|
// 逐屏滚动截图,覆盖全部分区
|
|
await page.evaluate(() => window.scrollTo(0, 0));
|
|
const totalH = await page.evaluate(() => document.body.scrollHeight);
|
|
const stepY = H - 140;
|
|
for (let y = stepY; y < totalH; y += stepY) {
|
|
await page.evaluate((yy) => window.scrollTo(0, yy), y);
|
|
await page.waitForTimeout(450);
|
|
await shot(page, 'detail-分区');
|
|
if (n > 22) break;
|
|
}
|
|
|
|
await context.close();
|
|
await browser.close();
|
|
|
|
const vp = await video.path();
|
|
const dest = path.join(SHOTS, 'walkthrough.webm');
|
|
fs.renameSync(vp, dest);
|
|
console.log('🎬 video saved:', dest);
|
|
console.log('✅ done, screenshots:', n);
|
|
})().catch((e) => {
|
|
console.error('FAILED:', e);
|
|
process.exit(1);
|
|
});
|