2d847e154f
中华文明全图鉴——文物全图系统(PC Web 地图 + NestJS API + 管理后台)。 含三大 IP(文物南迁北归 / 国宝海外回归 / 博物馆手艺人)、AI 文物对话、 文物地图与详情、以及 demo-video-kit 演示视频生成工具。
222 lines
8.6 KiB
JavaScript
222 lines
8.6 KiB
JavaScript
// WenwuMap 演示视频配置(使用 tools/demo-video-kit)
|
||
// 运行:node tools/demo-video-kit/src/cli.mjs e2e/demo.config.mjs --out e2e/videos-kit
|
||
import { NARRATION } from "./narration.mjs";
|
||
|
||
const N = (k) => NARRATION[k] || k;
|
||
|
||
export default {
|
||
baseUrl: "http://localhost:3000/map",
|
||
viewport: { width: 1440, height: 900 },
|
||
brand: "中华文明全图鉴 · 功能演示",
|
||
outDir: "e2e/videos-kit",
|
||
voice: { name: "Cherry", model: "qwen-tts", apiKeyEnv: "AI_API_KEY", fallbackVoice: "Tingting" },
|
||
|
||
intro: { narration: N("__intro"), sub: "中华文明全图鉴 · Cultural Heritage Atlas" },
|
||
outro: { narration: N("__outro") },
|
||
|
||
steps: [
|
||
{
|
||
label: "搜索",
|
||
narration: N("搜索“青铜”"),
|
||
run: async ({ page, pause }) => {
|
||
const input = page.getByPlaceholder("搜索文物、机构、城市…");
|
||
await input.click();
|
||
await input.pressSequentially("青铜", { delay: 120 });
|
||
await pause(1500);
|
||
await input.fill("");
|
||
await pause(500);
|
||
},
|
||
},
|
||
{
|
||
label: "城市分布翻页",
|
||
narration: N("城市分布翻页"),
|
||
run: async ({ page, pause }) => {
|
||
const next = page.locator("aside").first().getByRole("button", { name: "下一页" });
|
||
for (let i = 0; i < 2; i++) {
|
||
await next.click({ timeout: 4000 });
|
||
await pause(900);
|
||
}
|
||
},
|
||
},
|
||
{
|
||
label: "门类筛选:青铜器",
|
||
narration: N("门类筛选:青铜器"),
|
||
run: async ({ page, pause }) => {
|
||
const b = page.getByRole("button", { name: "青铜器" }).first();
|
||
await b.click({ timeout: 5000 });
|
||
await pause(1500);
|
||
await b.click({ timeout: 5000 });
|
||
await pause(600);
|
||
},
|
||
},
|
||
{
|
||
label: "年代筛选:唐代",
|
||
narration: N("年代筛选:唐代"),
|
||
run: async ({ page, pause }) => {
|
||
const b = page.getByRole("button", { name: "唐代", exact: true }).first();
|
||
await b.click({ timeout: 5000 });
|
||
await pause(1500);
|
||
await b.click({ timeout: 5000 });
|
||
await pause(600);
|
||
},
|
||
},
|
||
{
|
||
label: "左栏收起/展开",
|
||
narration: N("左栏收起/展开"),
|
||
run: async ({ page, pause }) => {
|
||
const t = page.getByTitle(/收起左栏|展开左栏/);
|
||
await t.click({ timeout: 5000 });
|
||
await pause(1100);
|
||
await t.click({ timeout: 5000 });
|
||
await pause(800);
|
||
},
|
||
},
|
||
{
|
||
label: "右栏收起/展开",
|
||
narration: N("右栏收起/展开"),
|
||
run: async ({ page, pause }) => {
|
||
const t = page.getByTitle(/收起右栏|展开右栏/);
|
||
await t.click({ timeout: 5000 });
|
||
await pause(1100);
|
||
await t.click({ timeout: 5000 });
|
||
await pause(800);
|
||
},
|
||
},
|
||
{
|
||
label: "点击地图标记 · 查看馆藏",
|
||
narration: N("点击地图标记 · 查看馆藏"),
|
||
run: async ({ page, pause }) => {
|
||
const markers = page.locator("div.cursor-pointer.flex-col");
|
||
await markers.first().waitFor({ state: "attached", timeout: 25000 });
|
||
await pause(1200);
|
||
const aside = page.locator("aside").last();
|
||
const total = Math.min(await markers.count(), 8);
|
||
for (let i = 0; i < total; i++) {
|
||
const box = await markers.nth(i).boundingBox();
|
||
if (!box) continue;
|
||
await page.mouse.click(box.x + box.width / 2, box.y + box.height - 3);
|
||
await pause(1300);
|
||
if (
|
||
(await aside.getByText("机构藏品").count()) > 0 ||
|
||
(await page.getByRole("button", { name: /文物信息/ }).count()) > 0
|
||
)
|
||
break;
|
||
}
|
||
},
|
||
},
|
||
{
|
||
label: "进入文物详情",
|
||
narration: N("进入文物详情"),
|
||
run: async ({ page, pause }) => {
|
||
const aside = page.locator("aside").last();
|
||
if ((await aside.getByText("机构藏品").count()) > 0) {
|
||
await aside.locator("button:has(div.truncate)").first().click({ timeout: 4000 });
|
||
await pause(1600);
|
||
}
|
||
await page.getByRole("button", { name: /文物信息/ }).waitFor({ timeout: 8000 });
|
||
},
|
||
},
|
||
{
|
||
label: "展开同一机构列表",
|
||
narration: N("展开同一机构列表"),
|
||
run: async ({ page, pause }) => {
|
||
await page.getByText(/还有 \d+ 件/).first().click({ timeout: 4000 });
|
||
await pause(1500);
|
||
},
|
||
},
|
||
{
|
||
label: "文物信息折叠/展开",
|
||
narration: N("文物信息折叠/展开"),
|
||
run: async ({ page, pause }) => {
|
||
const btn = page.getByRole("button", { name: /文物信息/ });
|
||
await btn.click({ timeout: 4000 });
|
||
await pause(1000);
|
||
await btn.click({ timeout: 4000 });
|
||
await pause(800);
|
||
},
|
||
},
|
||
{
|
||
label: "AI 文物对话 · 流式回答与追问建议",
|
||
narration: N("AI 文物对话 · 流式回答与追问建议"),
|
||
run: async ({ page, pause }) => {
|
||
const aside = page.locator("aside").last();
|
||
await page.getByRole("button", { name: "讲解员" }).first().click({ timeout: 4000 });
|
||
await pause(800);
|
||
await page.getByText("它为什么珍贵?").first().click({ timeout: 4000 });
|
||
await page.locator("div.md-chat").first().waitFor({ timeout: 30000 });
|
||
await pause(3500);
|
||
await aside.getByText("下一步 · 你可以问").waitFor({ timeout: 20000 });
|
||
await aside.locator('button:has-text("?")').first().click({ timeout: 4000 });
|
||
await page.locator("div.md-chat").nth(1).waitFor({ timeout: 30000 });
|
||
await pause(3500);
|
||
},
|
||
},
|
||
{
|
||
label: "图片放大与无极缩放(马踏飞燕)",
|
||
narration: N("图片放大与无极缩放(马踏飞燕)"),
|
||
run: async ({ page, pause }) => {
|
||
const input = page.getByPlaceholder("搜索文物、机构、城市…");
|
||
await input.fill("马踏飞燕");
|
||
await page
|
||
.waitForFunction(() => document.querySelectorAll("div.cursor-pointer.flex-col").length <= 2, null, {
|
||
timeout: 8000,
|
||
})
|
||
.catch(() => {});
|
||
await pause(900);
|
||
const markers = page.locator("div.cursor-pointer.flex-col");
|
||
await markers.first().waitFor({ state: "attached", timeout: 15000 });
|
||
const box = await markers.first().boundingBox();
|
||
await page.mouse.click(box.x + box.width / 2, box.y + box.height - 3);
|
||
await pause(1700);
|
||
const aside = page.locator("aside").last();
|
||
if ((await aside.getByText("机构藏品").count()) > 0) {
|
||
await aside.locator("button:has(div.truncate)").first().click({ timeout: 4000 });
|
||
await pause(1500);
|
||
}
|
||
const cover = page.locator("img.cursor-zoom-in").first();
|
||
await cover.waitFor({ state: "visible", timeout: 6000 });
|
||
const cb = await cover.boundingBox();
|
||
await page.mouse.click(cb.x + cb.width / 2, cb.y + cb.height / 2);
|
||
await pause(1400);
|
||
await page.mouse.move(720, 450);
|
||
for (let i = 0; i < 6; i++) {
|
||
await page.mouse.wheel(0, -320);
|
||
await pause(420);
|
||
}
|
||
await pause(1200);
|
||
await page.keyboard.press("Escape");
|
||
await pause(900);
|
||
},
|
||
},
|
||
{
|
||
label: "文物南迁北归 · 重走万里守护之路",
|
||
narration: N("文物南迁北归 · 重走万里守护之路"),
|
||
run: async ({ page, pause }) => {
|
||
await page.getByPlaceholder("搜索文物、机构、城市…").fill("");
|
||
await pause(700);
|
||
await page.getByRole("button", { name: "文物南迁北归之路" }).click({ timeout: 6000 });
|
||
await pause(2600);
|
||
await page.getByRole("button", { name: "播放" }).click({ timeout: 6000 });
|
||
await pause(12000);
|
||
await page.getByRole("button", { name: "退出路线" }).click({ timeout: 6000 });
|
||
await pause(1000);
|
||
},
|
||
},
|
||
{
|
||
label: "国宝海外回归 · 选择文物,重走回家之路",
|
||
narration: N("国宝海外回归 · 选择文物,重走回家之路"),
|
||
run: async ({ page, pause }) => {
|
||
await page.getByRole("button", { name: "国宝海外回归" }).click({ timeout: 6000 });
|
||
await page.getByText("国宝海外回归 · 选择文物").first().waitFor({ timeout: 6000 });
|
||
await pause(1600);
|
||
await page.locator('button:has-text("现藏")').first().click({ timeout: 6000 });
|
||
await pause(2600);
|
||
await page.getByRole("button", { name: "播放" }).click({ timeout: 6000 });
|
||
await pause(7000);
|
||
await page.getByRole("button", { name: "退出路线" }).click({ timeout: 6000 });
|
||
await pause(1000);
|
||
},
|
||
},
|
||
],
|
||
};
|