# OpenCLI JavaScript API 参考

在 `eval` 操作中使用 JavaScript 与页面交互。

## DOM 查询和选择

### 基础查询

```javascript
// 单个元素
document.querySelector(".class")                    // 第一个匹配元素
document.querySelector("#id")                       // ID 选择
document.querySelector("[name=field]")              // 属性选择

// 多个元素
document.querySelectorAll(".class")                 // 所有匹配元素
document.getElementsByTagName("div")                // 按标签名
document.getElementsByClassName("class")           // 按类名

// XPath 查询
document.evaluate(
    "//button[contains(text(), 'Click')]",
    document,
    null,
    XPathResult.FIRST_ORDERED_NODE_TYPE,
    null
).singleNodeValue
```

### CSS 选择器

```javascript
// 类和 ID
".my-class"
"#my-id"

// 属性
"[data-id='123']"
"input[type=text]"
"a[href^='https']"          // 前缀匹配
"a[href$='.pdf']"           // 后缀匹配
"a[href*='/search']"        // 包含匹配

// 位置
".class:first-child"
".class:last-child"
".class:nth-child(3)"

// 组合
"div.class1.class2"         // 同时有两个类
"div.class1 span"           // 后代
"div.class1 > span"         // 直接子元素
"h1 + p"                    // 相邻兄弟
"h1 ~ p"                    // 通用兄弟
```

## 文本提取

### 获取文本内容

```javascript
// 完整文本
document.querySelector(".title")?.textContent

// 清洁的文本（去除空白）
document.querySelector(".title")?.textContent.trim()

// HTML 内容
document.querySelector(".content")?.innerHTML

// 特定属性
document.querySelector("img")?.src
document.querySelector("a")?.href
document.querySelector("input")?.value
```

### 批量提取

```javascript
// 提取所有文本内容
Array.from(document.querySelectorAll(".item")).map(el => 
    el.textContent.trim()
)

// 提取多个属性
Array.from(document.querySelectorAll("a")).map(el => ({
    text: el.textContent.trim(),
    href: el.href,
    title: el.title
}))
```

## 表单操作

### 获取表单数据

```javascript
// 读取单个输入
document.querySelector("input[name=email]")?.value

// 读取所有表单字段
const form = document.querySelector("form");
new FormData(form)  // FormData 对象

// 转换为对象
const formData = {};
new FormData(form).forEach((value, key) => {
    if (formData[key]) {
        // 处理多个同名字段
        if (!Array.isArray(formData[key])) {
            formData[key] = [formData[key]];
        }
        formData[key].push(value);
    } else {
        formData[key] = value;
    }
});
JSON.stringify(formData)

// 获取表单字段列表
Array.from(document.querySelectorAll("form input, form textarea, form select"))
    .map(field => ({
        name: field.name,
        type: field.type,
        value: field.value,
        required: field.required,
        placeholder: field.placeholder
    }))
```

### 检查表单状态

```javascript
// 检查必填字段
document.querySelector("input[required]")?.value === ""

// 检查表单有效性
document.querySelector("form")?.checkValidity()

// 获取验证错误
document.querySelector("input").validationMessage

// 检查选中状态（复选框和单选按钮）
document.querySelector("input[type=checkbox]")?.checked
document.querySelector("input[type=radio]:checked")?.value
```

## 页面信息

### 获取页面元数据

```javascript
// 标题和 URL
document.title
window.location.href
window.location.pathname
window.location.search                  // 查询参数
window.location.hash                    // 锚点

// 页面大小
document.documentElement.scrollWidth
document.documentElement.scrollHeight
window.innerWidth
window.innerHeight

// Cookie
document.cookie                         // 所有 cookie（字符串）

// 历史
window.history.length
```

### 获取 Meta 信息

```javascript
// 获取 meta 标签
document.querySelector("meta[name=description]")?.content
document.querySelector("meta[property='og:title']")?.content

// 获取所有 meta 标签
Array.from(document.querySelectorAll("meta")).map(meta => ({
    name: meta.getAttribute("name") || meta.getAttribute("property"),
    content: meta.getAttribute("content")
}))
```

## 列表和表格提取

### 提取列表项

```javascript
// 简单列表
Array.from(document.querySelectorAll(".item")).map(el => 
    el.textContent.trim()
)

// 复杂列表项（带多个字段）
Array.from(document.querySelectorAll(".product")).map(product => ({
    name: product.querySelector(".name")?.textContent.trim(),
    price: product.querySelector(".price")?.textContent.trim(),
    url: product.querySelector("a")?.href,
    image: product.querySelector("img")?.src
}))
```

### 提取表格

```javascript
// 提取表格为 JSON
const table = document.querySelector("table");
const headers = Array.from(table.querySelectorAll("th"))
    .map(h => h.textContent.trim());
const rows = Array.from(table.querySelectorAll("tbody tr"))
    .map(row => {
        const cells = Array.from(row.querySelectorAll("td"))
            .map(cell => cell.textContent.trim());
        return Object.fromEntries(headers.map((h, i) => [h, cells[i]]));
    });
JSON.stringify(rows)

// 提取表格为数组
const rows = Array.from(document.querySelectorAll("table tbody tr"))
    .map(row => 
        Array.from(row.querySelectorAll("td"))
            .map(cell => cell.textContent.trim())
    );
JSON.stringify(rows)
```

## 元素交互模拟

### 点击事件

```javascript
// 模拟点击
document.querySelector(".button")?.click()

// 创建和分发自定义点击事件
const element = document.querySelector(".button");
const event = new MouseEvent("click", {
    bubbles: true,
    cancelable: true,
    view: window
});
element?.dispatchEvent(event)
```

### 输入事件

```javascript
// 设置输入值并触发事件
const input = document.querySelector("input[name=search]");
input.value = "search term";
input.dispatchEvent(new Event("input", { bubbles: true }));
input.dispatchEvent(new Event("change", { bubbles: true }));

// 获取并验证
input.value
```

### 键盘事件

```javascript
// 模拟按键
const element = document.querySelector("input");
element.focus();
element.dispatchEvent(new KeyboardEvent("keydown", {
    key: "Enter",
    code: "Enter",
    keyCode: 13,
    bubbles: true
}));
element.dispatchEvent(new KeyboardEvent("keyup", {
    key: "Enter",
    bubbles: true
}));
```

### 选择和排序

```javascript
// 设置选择框的值
const select = document.querySelector("select");
select.value = "option2";
select.dispatchEvent(new Event("change", { bubbles: true }));

// 获取所有选项
Array.from(document.querySelector("select").options)
    .map(opt => ({ value: opt.value, text: opt.text }))
```

## 导航和页面控制

### 页面导航

```javascript
// 导航到新 URL
window.location.href = "https://example.com"
window.location.assign("https://example.com")

// 刷新页面
window.location.reload()
window.location.reload(true)                // 强制刷新（忽略缓存）

// 返回历史
window.history.back()
window.history.forward()
window.history.go(-1)
```

### 滚动

```javascript
// 滚动到顶部
window.scrollTo(0, 0)

// 滚动到底部
window.scrollTo(0, document.documentElement.scrollHeight)

// 滚动指定元素到视图
document.querySelector(".section")?.scrollIntoView()
document.querySelector(".section")?.scrollIntoView({ behavior: "smooth" })

// 获取滚动位置
window.scrollX
window.scrollY

// 按像素滚动
window.scrollBy(0, 100)                    // 向下滚动 100px
window.scrollBy(0, -100)                   // 向上滚动 100px
```

## 延迟和等待

### 延迟执行

```javascript
// 延迟后返回值
new Promise(resolve => {
    setTimeout(() => resolve("done"), 2000)
})

// 等待元素出现（最多 10 秒）
new Promise(resolve => {
    const interval = setInterval(() => {
        const element = document.querySelector(".loaded");
        if (element) {
            clearInterval(interval);
            resolve(element.textContent);
        }
    }, 100);
    setTimeout(() => clearInterval(interval), 10000);
})

// 等待加载完成
window.fetch("...").then(response => response.json())
```

### 条件等待

```javascript
// 等待文本出现
new Promise(resolve => {
    const checkText = () => {
        const text = document.body.textContent;
        if (text.includes("Success")) {
            resolve(true);
        } else {
            setTimeout(checkText, 100);
        }
    };
    checkText();
})

// 等待特定条件
new Promise(resolve => {
    const check = () => {
        if (document.querySelectorAll(".item").length > 10) {
            resolve("Enough items loaded");
        } else {
            setTimeout(check, 100);
        }
    };
    check();
})
```

## 网络和数据

### 获取链接和资源

```javascript
// 提取所有链接
Array.from(document.querySelectorAll("a")).map(a => ({
    text: a.textContent.trim(),
    href: a.href,
    target: a.target
}))

// 提取所有图片
Array.from(document.querySelectorAll("img")).map(img => ({
    src: img.src,
    alt: img.alt,
    width: img.width,
    height: img.height
}))

// 提取所有视频
Array.from(document.querySelectorAll("video")).map(video => ({
    src: video.src,
    controls: video.controls,
    duration: video.duration
}))
```

### 监听网络请求

```javascript
// 保存原始 fetch
window.__originalFetch = window.fetch;
window.__requests = [];

// 拦截 fetch 请求
window.fetch = function(...args) {
    window.__requests.push({
        url: args[0],
        method: args[1]?.method || "GET",
        timestamp: new Date().toISOString()
    });
    return window.__originalFetch.apply(this, args);
};

// 获取记录的请求
JSON.stringify(window.__requests)
```

## JSON 序列化

### 安全的序列化

```javascript
// 基础序列化
JSON.stringify({ key: "value" })

// 处理循环引用
JSON.stringify(
    document.querySelector(".item"),
    (key, value) => {
        if (typeof value === "object") {
            return "[Object]";
        }
        return value;
    }
)

// 提取特定属性
JSON.stringify({
    title: document.title,
    url: window.location.href,
    items: Array.from(document.querySelectorAll(".item"))
        .map(el => el.textContent.trim())
})
```

## 常用代码片段

### 检测页面是否加载完成

```javascript
// 检查 document.readyState
document.readyState === "complete"

// 等待 DOMContentLoaded
new Promise(resolve => {
    if (document.readyState !== "loading") {
        resolve();
    } else {
        document.addEventListener("DOMContentLoaded", resolve);
    }
})
```

### 检测是否有错误

```javascript
// 检查错误类消息
document.body.textContent.includes("error")
document.body.textContent.includes("Error")
document.body.textContent.includes("failed")

// 检查 404 或其他错误页面
document.querySelector(".error-page") !== null
document.querySelector(".not-found") !== null
```

### 获取页面统计

```javascript
{
    title: document.title,
    url: window.location.href,
    wordCount: document.body.innerText.split(/\s+/).length,
    imageCount: document.querySelectorAll("img").length,
    linkCount: document.querySelectorAll("a").length,
    headings: {
        h1: document.querySelectorAll("h1").length,
        h2: document.querySelectorAll("h2").length,
        h3: document.querySelectorAll("h3").length
    },
    loadTime: performance.timing.loadEventEnd - performance.timing.navigationStart
}
```

## 调试和日志

### 控制台日志

```javascript
// 打印到控制台（可通过浏览器 DevTools 查看）
console.log("Debug info")
console.warn("Warning")
console.error("Error")

// 保存到全局变量
window.__debugInfo = {
    timestamp: new Date().toISOString(),
    selector: ".target",
    found: document.querySelector(".target") !== null
};
"Debug info saved to window.__debugInfo"
```

### 性能测量

```javascript
// 测量执行时间
const start = performance.now();
// ... do something ...
const end = performance.now();
`Execution time: ${end - start}ms`

// 获取性能指标
{
    navigationStart: performance.timing.navigationStart,
    loadEventEnd: performance.timing.loadEventEnd,
    totalTime: performance.timing.loadEventEnd - performance.timing.navigationStart
}
```
