¥
立即购买

异步函数生成器

10 浏览
1 试用
0 购买
Dec 8, 2025更新

本提示词专为JavaScript开发场景设计,能够根据具体异步任务需求生成符合最佳实践的async/await函数代码。通过结构化参数输入,自动构建包含错误处理、性能优化和技术说明的高质量异步解决方案,适用于数据获取、文件操作、API调用等多种异步场景,帮助开发者快速实现可靠且易于维护的异步编程逻辑。

函数定义 async function fetchProducts(endpoint, { pageSize = 50, maxPages = 5, headers = {}, signal, onProgress } = {})

  • 参数说明
    • endpoint: 字符串,产品分页接口的起始地址(如 /products 或完整URL)
    • pageSize: 每页大小,默认 50。若起始URL未包含该参数则自动添加
    • maxPages: 最大抓取页数,默认 5
    • headers: 追加到每次请求的请求头对象
    • signal: 可选的 AbortSignal,用于整体任务取消
    • onProgress: 可选回调函数,每页完成时报告进度。形如 onProgress({ page, url, received, total, fromCache, retries, status })

代码实现

// 简单的全局(进程内)ETag缓存,按URL键控
const ETagCache = new Map(); // url -> { etag: string, body: any }

/**
 * 解析 Retry-After,返回等待毫秒数。如果不存在则按指数退避生成。
 * attempt 从 1 开始计数(第一次重试的序号)
 */
function computeRetryDelayMs(headers, attempt, baseMs = 400) {
  // 优先使用服务端提供的 Retry-After
  const retryAfter = headers?.get?.('Retry-After');
  if (retryAfter) {
    // 可能是秒,或HTTP日期
    const seconds = Number(retryAfter);
    if (Number.isFinite(seconds) && seconds >= 0) {
      return Math.max(0, seconds * 1000);
    }
    const dateMs = Date.parse(retryAfter);
    if (!Number.isNaN(dateMs)) {
      return Math.max(0, dateMs - Date.now());
    }
  }
  // 指数退避 + 抖动:base * 2^(attempt-1) + [0, 250)
  const exp = baseMs * Math.pow(2, attempt - 1);
  const jitter = Math.floor(Math.random() * 250);
  return exp + jitter;
}

/** 支持可取消的 sleep */
function sleep(ms, signal) {
  return new Promise((resolve, reject) => {
    if (signal?.aborted) return reject(signal.reason || new DOMException('Aborted', 'AbortError'));
    const t = setTimeout(() => {
      cleanup();
      resolve();
    }, ms);
    const onAbort = () => {
      cleanup();
      reject(signal.reason || new DOMException('Aborted', 'AbortError'));
    };
    const cleanup = () => {
      clearTimeout(t);
      if (signal) signal.removeEventListener('abort', onAbort);
    };
    if (signal) signal.addEventListener('abort', onAbort, { once: true });
  });
}

/** 为相对/绝对URL安全地添加/覆盖查询参数 */
function ensureQueryParam(url, key, value) {
  const [path, query = ''] = url.split('?');
  const sp = new URLSearchParams(query);
  if (!sp.has(key)) sp.set(key, String(value));
  return `${path}?${sp.toString()}`;
}

/** 从响应中解析 next 链接(优先 body.next,其次 Link: <...>; rel=next) */
function getNextUrl(body, headers) {
  if (body && typeof body.next === 'string' && body.next) return body.next;
  const link = headers?.get?.('Link');
  if (link) {
    const m = link.match(/<([^>]+)>\s*;\s*rel="?next"?/i);
    if (m) return m[1];
  }
  return null;
}

/** 将原始产品数据规范化为 {id, name, price, updatedAt} */
function normalizeItem(raw) {
  const id = raw?.id ?? raw?.productId ?? raw?._id ?? null;
  const name = raw?.name ?? raw?.title ?? '';
  const priceVal = typeof raw?.price === 'number' ? raw.price : parseFloat(raw?.price);
  const updatedRaw = raw?.updatedAt ?? raw?.updated_at ?? raw?.modifiedAt ?? raw?.updated ?? raw?.modified ?? null;

  let updatedISO;
  if (updatedRaw) {
    const d = new Date(updatedRaw);
    updatedISO = Number.isFinite(d.getTime()) ? d.toISOString() : new Date(0).toISOString();
  } else {
    updatedISO = new Date(0).toISOString(); // 缺失时使用极早时间,排序置后
  }

  return {
    id,
    name,
    price: Number.isFinite(priceVal) ? priceVal : null,
    updatedAt: updatedISO,
  };
}

/**
 * 单请求封装:ETag缓存、8s超时、最多3次重试(网络错误/429/5xx)、支持 Retry-After。
 * 返回 { body, status, headers, fromCache, retries }
 */
async function fetchWithRetry(url, { headers = {}, signal, timeoutMs = 8000, maxRetries = 3 }) {
  let attempt = 0; // 含初次请求
  let lastErr = null;

  while (attempt <= maxRetries) {
    const controller = new AbortController();
    const composedSignal = controller.signal;
    // 绑定外部 signal -> 内部
    const onAbort = () => controller.abort(signal.reason || new DOMException('Aborted', 'AbortError'));
    if (signal) {
      if (signal.aborted) {
        throw signal.reason || new DOMException('Aborted', 'AbortError');
      }
      signal.addEventListener('abort', onAbort, { once: true });
    }

    // 设置超时
    const timeoutId = setTimeout(() => {
      controller.abort(new DOMException('Timeout', 'TimeoutError'));
    }, timeoutMs);

    try {
      attempt++;
      const reqHeaders = new Headers(headers);
      const cached = ETagCache.get(url);
      if (cached?.etag) {
        reqHeaders.set('If-None-Match', cached.etag);
      }

      const res = await fetch(url, { method: 'GET', headers: reqHeaders, signal: composedSignal });
      const { status, headers: resHeaders } = res;

      // 处理 304:使用缓存体
      if (status === 304) {
        clearTimeout(timeoutId);
        if (signal) signal.removeEventListener('abort', onAbort);
        if (cached?.body != null) {
          return { body: cached.body, status, headers: resHeaders, fromCache: true, retries: attempt - 1 };
        }
        // 接收到304但无缓存体,视为异常(不重试 or 作为一次错误后按指数退避重试)
        lastErr = new Error('304 received but cache body missing');
        // 尝试下一次重试(极少见的状态不同步)
      } else if (status >= 200 && status < 300) {
        // 正常 2xx
        const body = await res.json().catch(() => {
          throw new Error('Failed to parse JSON');
        });
        const etag = resHeaders.get('ETag') || resHeaders.get('Etag') || resHeaders.get('etag');
        if (etag) {
          ETagCache.set(url, { etag, body });
        }
        clearTimeout(timeoutId);
        if (signal) signal.removeEventListener('abort', onAbort);
        return { body, status, headers: resHeaders, fromCache: false, retries: attempt - 1 };
      } else if (status === 429 || (status >= 500 && status < 600)) {
        // 需要重试的状态码
        const delay = computeRetryDelayMs(resHeaders, attempt);
        clearTimeout(timeoutId);
        if (signal) signal.removeEventListener('abort', onAbort);
        if (attempt > maxRetries) {
          const text = await res.text().catch(() => '');
          throw new Error(`Request failed after retries: ${status} ${text?.slice(0, 160)}`);
        }
        await sleep(delay, signal);
        continue; // 下一次尝试
      } else {
        // 其他 4xx:不重试
        const text = await res.text().catch(() => '');
        clearTimeout(timeoutId);
        if (signal) signal.removeEventListener('abort', onAbort);
        throw new Error(`Request failed: ${status} ${text?.slice(0, 160)}`);
      }
    } catch (err) {
      clearTimeout(timeoutId);
      if (signal) signal.removeEventListener('abort', onAbort);

      // 取消直接抛出
      if (err?.name === 'AbortError') throw err;

      lastErr = err;
      if (attempt > maxRetries) {
        throw lastErr;
      }
      const delay = computeRetryDelayMs(null, attempt);
      await sleep(delay, signal);
      continue;
    }
  }

  // 理论上不会到达
  throw lastErr || new Error('Unknown fetch error');
}

/**
 * 主函数:分页抓取产品,重试/超时/并发/ETag缓存/标准化/去重/排序。
 * 返回 { items, total, fromCache, durationMs, errors }
 */
async function fetchProducts(endpoint, { pageSize = 50, maxPages = 5, headers = {}, signal, onProgress } = {}) {
  const start = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();

  // 并发上限
  const MAX_CONCURRENCY = 3;

  // 初始URL:注入 pageSize(若已存在则不改变)
  let firstUrl = ensureQueryParam(endpoint, 'pageSize', pageSize);

  // 结果汇总
  const byId = new Map(); // id -> normalizedItem(保留最新updatedAt)
  let anyFromCache = false;
  let serverTotal = null;
  const errors = [];

  // 队列 + 并发 worker
  const queue = [];
  const visited = new Set(); // 防止意外循环
  let pagesFetched = 0;

  // 入队初始页
  queue.push(firstUrl);
  visited.add(firstUrl);

  async function processPage(url, pageIndex) {
    const resp = await fetchWithRetry(url, { headers, signal, timeoutMs: 8000, maxRetries: 3 });
    const { body, status, headers: resHeaders, fromCache, retries } = resp;
    anyFromCache = anyFromCache || !!fromCache;

    // 解析产品列表
    const list = body?.items ?? body?.products ?? body?.data ?? [];
    const total = (typeof body?.total === 'number' && body.total >= 0) ? body.total : null;
    if (serverTotal == null && total != null) serverTotal = total;

    // 规范化 + 去重(按最新updatedAt保留)
    for (const raw of list) {
      const item = normalizeItem(raw);
      if (item.id == null) continue; // 跳过无ID项
      const prev = byId.get(item.id);
      if (!prev) {
        byId.set(item.id, item);
      } else {
        const prevTs = new Date(prev.updatedAt).getTime();
        const currTs = new Date(item.updatedAt).getTime();
        if (currTs >= prevTs) {
          byId.set(item.id, item);
        }
      }
    }

    // 进度回调
    if (typeof onProgress === 'function') {
      try {
        onProgress({
          page: pageIndex,
          url,
          received: Array.isArray(list) ? list.length : 0,
          total,
          fromCache: !!fromCache,
          retries,
          status,
        });
      } catch {
        // 保持稳健,忽略回调内部错误
      }
    }

    // 解析下一页
    const nextUrl = getNextUrl(body, resHeaders);
    return { nextUrl };
  }

  // Worker:从队列取任务直到满足条件或被取消
  async function worker(id) {
    while (!signal?.aborted && queue.length > 0 && pagesFetched < maxPages) {
      const url = queue.shift();
      const pageIndex = pagesFetched + 1;
      try {
        const { nextUrl } = await processPage(url, pageIndex);
        pagesFetched++;
        if (nextUrl && !visited.has(nextUrl) && pagesFetched < maxPages) {
          visited.add(nextUrl);
          queue.push(nextUrl);
        }
      } catch (err) {
        // 记录错误并继续处理队列中其他任务(线性分页通常不会再有后续)
        errors.push({
          url,
          message: String(err?.message || err),
          name: err?.name || 'Error',
        });
        // 失败页不增加 pagesFetched(因为没有成功处理该页)
        // 不再入队下一页,因为无法获取 next 链接
      }
    }
  }

  // 启动并发 worker(在单链分页上并发自然退化为 1,但逻辑仍健壮)
  const workers = Array.from({ length: Math.min(MAX_CONCURRENCY, queue.length) }, (_, i) => worker(i));
  await Promise.all(workers);

  // 汇总与排序(按 updatedAt 倒序)
  const items = Array.from(byId.values()).sort(
    (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
  );

  const end = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
  return {
    items,
    total: serverTotal != null ? serverTotal : items.length,
    fromCache: anyFromCache,
    durationMs: Math.round(end - start),
    errors,
  };
}

错误处理

  • 网络错误、5xx、429:
    • 最多 3 次重试(指数退避:约 400ms、800ms、1600ms,包含随机抖动)
    • 若响应包含 Retry-After,则优先使用该值(支持秒与HTTP日期)
  • 304 Not Modified:
    • 命中缓存时返回缓存体,并标记 fromCache=true
    • 若收到 304 时缓存缺失(极少见状态不同步),将按重试策略处理
  • 超时:
    • 每次请求 8s 超时(AbortController)
    • 若调用方传入 signal,取消会立即中止所有等待与请求
  • 其他 4xx:
    • 不重试,直接记录错误
  • onProgress 回调内抛错被安全忽略,不影响主流程

使用示例

// 取消控制(可选)
const controller = new AbortController();

// 进度回调(可选)
function onProgress(evt) {
  // evt: { page, url, received, total, fromCache, retries, status }
  console.log(`Page #${evt.page} status=${evt.status} items=${evt.received} total=${evt.total ?? 'n/a'} cache=${evt.fromCache} retries=${evt.retries}`);
}

(async () => {
  try {
    const result = await fetchProducts('/products', {
      pageSize: 100,
      maxPages: 5,
      headers: { Accept: 'application/json' },
      signal: controller.signal,
      onProgress,
    });

    console.log('Total:', result.total);
    console.log('Duration(ms):', result.durationMs);
    console.log('From cache:', result.fromCache);
    console.log('Errors:', result.errors);
    console.log('First item:', result.items[0]); // {id,name,price,updatedAt}
  } catch (e) {
    console.error('Fetch failed:', e);
  }
})();

技术要点

  • 异步与重试策略
    • 使用 async/await 保持可读性,单独封装 fetchWithRetry 提升复用性与可测试性
    • 对网络错误、429 与 5xx 进行指数退避重试,并尊重服务端 Retry-After
    • 重试等待通过 sleep 实现,并支持外部 AbortSignal 取消
  • 超时与取消
    • 每次请求创建独立 AbortController 并设置 8s 超时
    • 合并外部 signal:外部取消会中止所有请求与等待
  • ETag 缓存
    • 以 URL 为键缓存 {etag, body},命中时发送 If-None-Match
    • 收到 304 时使用缓存体,减少带宽与延迟
  • 并发分页
    • 任务队列 + worker 设计,最大并发 3
    • 单链式 next 会自然退化为并发 1,但在存在分支或预取场景也能稳定工作
  • 数据规范化与去重
    • 统一为 {id,name,price,updatedAt},缺失字段安全处理
    • 按 id 去重,并保留最新 updatedAt 的记录
    • 按 updatedAt 倒序排序,便于展示最新变更
  • 返回结构
    • {items, total, fromCache, durationMs, errors}
    • total 优先使用服务端 total,否则使用去重后的 items.length
  • 兼容性与最佳实践
    • 使用原生 fetch、Headers、AbortController 与 URLSearchParams
    • 不依赖第三方库,避免安全风险
    • 所有回调与错误都进行边界处理,保证稳健性与可维护性

函数定义

async function scrapeArticles(urls, { concurrency = 5, timeoutMs = 6000, cacheTTL = 600000, signal } = {})

  • 参数说明

    • urls: string[],待抓取的 URL 列表(仅支持 http/https)
    • concurrency: number,并发上限(默认 5)
    • timeoutMs: number,单请求超时毫秒数(默认 6000ms)
    • cacheTTL: number,内存缓存 TTL(默认 10 分钟 = 600000ms)
    • signal: AbortSignal,可选的外部取消信号,用于整体任务取消
  • 返回值

    • Promise<{ list: Array<{ url: string; title: string | null; publishedAt: string | null; length: number }>, failed: Array<{ url: string; reason: string }> }>
    • list:去重后的成功结果集合(依据 canonical 或规范化 URL)
    • failed:失败的 URL 与失败原因

注意:若传入的 AbortSignal 触发,将尽可能中止正在执行的请求;函数不会抛错而是把已完成和失败的请求通过 allSettled 汇总并返回(失败中会标记 Abort)。

代码实现

// 模块级内存缓存(基于 TTL)。键为规范化 URL 或 canonical 规范化 URL。
const __articleCache = new Map(); // key -> { value, expiresAt }

/**
 * 规范化 URL:
 * - 只接受 http/https
 * - 小写协议与主机名,移除 hash
 * - 清理默认端口(:80 / :443)
 * - 规范路径(去除多余斜杠与根路径尾部斜杠)
 * - 搜索参数排序且移除常见追踪参数
 */
function normalizeUrl(input) {
  let u;
  try {
    u = new URL(input);
  } catch {
    throw new Error(`Invalid URL: ${input}`);
  }
  if (!/^https?:$/.test(u.protocol)) {
    throw new Error(`Unsupported protocol (only http/https): ${input}`);
  }
  u.protocol = u.protocol.toLowerCase();
  u.hostname = u.hostname.toLowerCase();

  // remove default ports
  if ((u.protocol === 'http:' && u.port === '80') || (u.protocol === 'https:' && u.port === '443')) {
    u.port = '';
  }

  // remove hash
  u.hash = '';

  // normalize path: collapse multiple slashes
  u.pathname = u.pathname.replace(/\/{2,}/g, '/');
  // remove trailing slash except root
  if (u.pathname.length > 1 && u.pathname.endsWith('/')) {
    u.pathname = u.pathname.slice(0, -1);
  }

  // clean & sort search params
  const trackingParams = new Set([
    'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',
    'fbclid', 'gclid', 'yclid', 'mc_cid', 'mc_eid'
  ]);
  const params = Array.from(u.searchParams.entries())
    .filter(([k]) => !trackingParams.has(k.toLowerCase()))
    .sort(([a], [b]) => a.localeCompare(b));
  u.search = '';
  for (const [k, v] of params) u.searchParams.append(k, v);

  return u.toString();
}

/** 简易 HTML 实体解码(覆盖常见实体与数字实体) */
function decodeHtmlEntities(str) {
  if (!str) return str;
  const map = { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'", nbsp: ' ' };
  return str
    .replace(/&(#\d+|#x[0-9a-fA-F]+|[a-zA-Z]+);/g, (m, t) => {
      if (t[0] === '#') {
        const code = t[1].toLowerCase() === 'x' ? parseInt(t.slice(2), 16) : parseInt(t.slice(1), 10);
        if (!Number.isNaN(code)) return String.fromCodePoint(code);
        return m;
      }
      return map[t] ?? m;
    })
    .trim();
}

/** 从标签中提取纯文本(粗略移除内部标签) */
function stripTags(html) {
  return decodeHtmlEntities(
    html
      .replace(/<script[\s\S]*?<\/script>/gi, '')
      .replace(/<style[\s\S]*?<\/style>/gi, '')
      .replace(/<[^>]+>/g, ' ')
      .replace(/\s+/g, ' ')
      .trim()
  );
}

/** 获取首个匹配的开始/结束标签内容 */
function getFirstTagInnerHTML(html, tag) {
  const re = new RegExp(`<${tag}\\b[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'i');
  const m = html.match(re);
  return m ? m[1] : null;
}

/** 解析 <title> 与 <h1> */
function extractTitle(html) {
  const titleRaw = getFirstTagInnerHTML(html, 'title');
  if (titleRaw) {
    const t = stripTags(titleRaw);
    if (t) return t;
  }
  const h1Raw = getFirstTagInnerHTML(html, 'h1');
  if (h1Raw) {
    const t = stripTags(h1Raw);
    if (t) return t;
  }
  return null;
}

/** 解析 <link rel="canonical" href="...">(返回相对或绝对 URL 字符串) */
function extractCanonical(html) {
  // 捕获所有 link 标签,解析 rel 和 href
  const links = html.match(/<link\b[^>]*>/gi) || [];
  for (const tag of links) {
    // 解析属性
    const attrs = {};
    tag.replace(/(\w[\w:-]*)\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>]+))/g, (_, name, _v, v1, v2, v3) => {
      attrs[name.toLowerCase()] = (v1 ?? v2 ?? v3 ?? '').trim();
      return _;
    });
    const rel = (attrs.rel || '').toLowerCase();
    if (rel.split(/\s+/).includes('canonical') && attrs.href) {
      return attrs.href.trim();
    }
  }
  return null;
}

/** 解析 meta[property="article:published_time"] 或 meta[name="article:published_time"] */
function extractPublishedTimeFromMeta(html) {
  const metas = html.match(/<meta\b[^>]*>/gi) || [];
  for (const tag of metas) {
    const attrs = {};
    tag.replace(/(\w[\w:-]*)\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>]+))/g, (_, name, _v, v1, v2, v3) => {
      attrs[name.toLowerCase()] = (v1 ?? v2 ?? v3 ?? '').trim();
      return _;
    });
    const prop = (attrs.property || attrs.name || '').toLowerCase();
    if (prop === 'article:published_time' && attrs.content) {
      return attrs.content.trim();
    }
  }
  return null;
}

/** 从首个 <p> 文本中尽力提取日期时间(支持 ISO 与常见中文格式) */
function extractPublishedTimeFromFirstParagraph(html) {
  const p = getFirstTagInnerHTML(html, 'p');
  if (!p) return null;
  const text = stripTags(p);

  // 1) 直接尝试 Date.parse 可识别格式(包含 ISO/RFC)
  const direct = Date.parse(text);
  if (!Number.isNaN(direct)) return new Date(direct).toISOString();

  // 2) 匹配 YYYY-MM-DD( HH:MM(:SS)?(Z|±HH:MM)?)
  const isoLike = text.match(/(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}))?(?:Z|([+-]\d{2}:?\d{2}))?)?/);
  if (isoLike) {
    const [ , Y, M, D, h='0', m='0', s='0', tz] = isoLike;
    const base = new Date(Date.UTC(+Y, +M - 1, +D, +h, +m, +s));
    if (tz && tz !== 'Z') {
      // 处理时区偏移
      const m2 = tz.match(/([+-]\d{2}):?(\d{2})/);
      if (m2) {
        const offsetMin = (parseInt(m2[1], 10) * 60) + parseInt(m2[2], 10);
        // UTC = local - offset => 已按 UTC 构建了 base,无需额外处理
        // 但 isoLike 在无 tz 时按 UTC 处理;有 tz 时我们已用 UTC 构造,再按 offset 调整
        base.setUTCMinutes(base.getUTCMinutes() - offsetMin);
      }
    }
    return base.toISOString();
  }

  // 3) 中文日期:YYYY年MM月DD日 HH:MM(:SS)?
  const zh = text.match(/(\d{4})年(\d{1,2})月(\d{1,2})日(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?/);
  if (zh) {
    const [ , Y, M, D, h='0', m='0', s='0'] = zh;
    const d = new Date(+Y, +M - 1, +D, +h, +m, +s);
    return new Date(d.getTime()).toISOString();
  }

  return null;
}

/** 提取 HTML 信息:title、canonical、publishedAt(ISO string 或 null) */
function parseHtml(html, baseUrl) {
  const title = extractTitle(html);

  let publishedAt = extractPublishedTimeFromMeta(html);
  if (publishedAt) {
    const ts = Date.parse(publishedAt);
    publishedAt = Number.isNaN(ts) ? null : new Date(ts).toISOString();
  } else {
    publishedAt = extractPublishedTimeFromFirstParagraph(html);
  }

  const canonicalHref = extractCanonical(html);
  let canonical = null;
  if (canonicalHref) {
    try {
      const abs = new URL(canonicalHref, baseUrl).toString();
      canonical = abs;
    } catch {
      // ignore bad canonical
    }
  }

  return { title, canonical, publishedAt };
}

/** TTL 缓存:读取 */
function cacheGet(key) {
  const hit = __articleCache.get(key);
  if (hit && hit.expiresAt > Date.now()) return hit.value;
  if (hit) __articleCache.delete(key);
  return null;
}

/** TTL 缓存:写入 */
function cacheSet(key, value, ttl) {
  __articleCache.set(key, { value, expiresAt: Date.now() + ttl });
}

/** 组合多个 AbortSignal(支持 AbortSignal.any 的环境;无则降级) */
function combineSignals(signals) {
  const valid = signals.filter(Boolean);
  if (valid.length === 0) return undefined;
  if (typeof AbortSignal !== 'undefined' && typeof AbortSignal.any === 'function') {
    return AbortSignal.any(valid);
  }
  const controller = new AbortController();
  const onAbort = (e) => {
    try { controller.abort(e?.target?.reason); } catch { controller.abort(); }
    cleanup();
  };
  const cleanup = () => valid.forEach(s => s.removeEventListener('abort', onAbort));
  let alreadyAborted = false;
  for (const s of valid) {
    if (s.aborted) {
      alreadyAborted = true;
      try { controller.abort(s.reason); } catch { controller.abort(); }
      break;
    }
  }
  if (!alreadyAborted) valid.forEach(s => s.addEventListener('abort', onAbort, { once: true }));
  return controller.signal;
}

/** 基于并发的 limiter */
function createLimiter(limit, outerSignal) {
  let active = 0;
  const queue = [];
  const runNext = () => {
    if (outerSignal?.aborted) return;
    if (active >= limit || queue.length === 0) return;
    const task = queue.shift();
    active++;
    task().finally(() => {
      active--;
      runNext();
    });
  };
  return (fn) => new Promise((resolve) => {
    const wrapped = () => fn().then(resolve, resolve); // 将错误留待 allSettled 处理
    queue.push(wrapped);
    runNext();
  });
}

/** 单 URL 抓取与解析 */
async function fetchOne(originalUrl, { timeoutMs, cacheTTL, outerSignal }) {
  const normInput = normalizeUrl(originalUrl);

  // 命中缓存
  const cached = cacheGet(normInput);
  if (cached) return cached;

  // 每请求单独超时控制
  const timeoutCtrl = new AbortController();
  const tId = setTimeout(() => {
    try { timeoutCtrl.abort(new Error('Timeout')); } catch { timeoutCtrl.abort(); }
  }, timeoutMs);

  const combinedSignal = combineSignals([timeoutCtrl.signal, outerSignal]);

  try {
    const res = await fetch(originalUrl, {
      method: 'GET',
      headers: {
        'Accept': 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.8',
        // 可选 UA,部分站点对默认 UA 严格
        'User-Agent': 'scrapeArticles/1.0 (+https://example.com)'
      },
      redirect: 'follow',
      // gzip/br/deflate 由 fetch 实现自动协商与解压
      signal: combinedSignal
    });

    if (!res.ok) {
      throw new Error(`HTTP ${res.status} ${res.statusText}`);
    }

    const finalUrl = res.url || originalUrl;
    const html = await res.text();

    const { title, canonical, publishedAt } = parseHtml(html, finalUrl);

    // 使用 canonical 或最终跳转后的 URL 进行规范化
    const finalNorm = normalizeUrl(canonical ? new URL(canonical, finalUrl).toString() : finalUrl);

    const result = {
      url: finalNorm,
      title: title || null,
      publishedAt: publishedAt || null,
      // 以字符长度衡量,如需字节可用 Buffer.byteLength(html, 'utf8')(Node 环境)
      length: html.length
    };

    // 写入缓存(输入规范化 URL 与 canonical 规范化 URL 都写入,提升命中率)
    cacheSet(normInput, result, cacheTTL);
    cacheSet(finalNorm, result, cacheTTL);

    return result;
  } finally {
    clearTimeout(tId);
  }
}

/**
 * 主函数:批量抓取文章标题与时间(限流、超时、缓存、去重、allSettled 汇总)
 */
async function scrapeArticles(urls, { concurrency = 5, timeoutMs = 6000, cacheTTL = 600000, signal } = {}) {
  if (!Array.isArray(urls)) throw new Error('urls must be an array of strings');
  if (concurrency <= 0) throw new Error('concurrency must be > 0');

  // 预过滤非法/重复输入(按规范化 URL 去重)
  const uniqueInputs = [];
  const seen = new Set();
  for (const u of urls) {
    if (typeof u !== 'string' || !u.trim()) continue;
    try {
      const n = normalizeUrl(u);
      if (!seen.has(n)) {
        seen.add(n);
        uniqueInputs.push({ original: u, normalized: n });
      }
    } catch {
      // 跳过非法 URL(最终会在 failed 中体现)
      uniqueInputs.push({ original: u, normalized: null });
    }
  }

  const limiter = createLimiter(concurrency, signal);
  const tasks = uniqueInputs.map(({ original, normalized }) =>
    limiter(async () => {
      if (!normalized) {
        // 预规范化失败
        return { status: 'rejected', reason: new Error('Invalid URL'), original };
      }
      try {
        const value = await fetchOne(original, { timeoutMs, cacheTTL, outerSignal: signal });
        return { status: 'fulfilled', value, original };
      } catch (err) {
        return { status: 'rejected', reason: err, original };
      }
    })
  );

  // 等待任务全部 settle(内部每项已包装为 fulfilled/rejected 结构)
  const settled = await Promise.all(tasks);

  // 汇总:去重(基于 canonical 或规范化 URL)
  const finalList = [];
  const finalFailed = [];

  const dedupe = new Set();
  for (const s of settled) {
    if (s.status === 'fulfilled') {
      const item = s.value;
      const key = normalizeUrl(item.url);
      if (!dedupe.has(key)) {
        dedupe.add(key);
        finalList.push(item);
      }
    } else {
      const reason = s.reason;
      const msg = (reason && (reason.message || String(reason))) || 'Unknown error';
      finalFailed.push({
        url: s.original,
        reason: msg
      });
    }
  }

  return { list: finalList, failed: finalFailed };
}

错误处理

  • 输入校验:urls 必须为字符串数组;concurrency > 0;不支持非 http/https 协议
  • 超时控制:每个请求通过 AbortController 实现 timeoutMs 超时,中止 fetch
  • 取消控制:可接收外部 AbortSignal;与超时信号组合,任一触发即中止请求
  • 网络/HTTP 错误:非 2xx/3xx 响应抛出 HTTP 错误并记入 failed
  • HTML 解析容错:采用健壮的正则提取,若 title 或时间缺失,返回 null 而非抛错
  • 缓存过期:TTL 到期自动清理;命中后直接返回缓存结果,减少重复请求

注意:函数最终使用 allSettled 风格汇总,无论个别请求失败或被中止,均返回 { list, failed },不会整体抛错(除非参数校验失败)。

使用示例

(async () => {
  const urls = [
    'https://example.com/article/123',
    'https://example.com/article/123?utm_source=twitter',
    'https://blog.example.org/post/abc',
    'https://news.example.com/path#section',
    'invalid-url'
  ];

  const controller = new AbortController();
  // 可选:在 10 秒后取消整个批量任务
  // setTimeout(() => controller.abort(new Error('User aborted')), 10000);

  const { list, failed } = await scrapeArticles(urls, {
    concurrency: 4,
    timeoutMs: 6000,
    cacheTTL: 10 * 60 * 1000,
    signal: controller.signal
  });

  console.log('Success:', list);
  console.log('Failed:', failed);
})();
  • 预期输出说明
    • list 中每项包含:
      • url:canonical 规范化 URL(若页面提供),否则为最终跳转后的规范化 URL
      • title:页面标题或首个 h1(若 title 缺失)
      • publishedAt:优先取 meta[property="article:published_time"],否则尝试从首个段落解析时间(ISO 字符串)
      • length:HTML 文本的字符长度
    • failed 包含无法请求/解析的 URL 与失败原因(如超时、HTTP 错误、无效 URL、被取消等)

技术要点

  • 并发控制:自实现轻量 limiter,避免额外依赖;保证同一时间最多 concurrency 个请求
  • 取消与超时:使用 AbortController;通过 combineSignals 合并外部 signal 与超时 signal,任一触发即中断 fetch
  • allSettled 汇总:每个任务内部已包裹为 fulfilled/rejected 结构,最终统一汇总为 { list, failed }
  • 去重策略:优先使用页面 canonical 链接规范化;无 canonical 时以最终跳转 URL 的规范化结果为去重键
  • 规范化 URL:去除 hash、处理默认端口、排序查询参数、清理常见追踪参数,尽量将等价 URL 归一
  • 内存缓存(TTL):输入规范化 URL 与 canonical 规范化 URL 同步写入缓存,提升命中率;减少重复抓取与压力
  • 解析策略:
    • 标题:先取 ,缺失时取首个 <h1></li> <li>时间:优先 meta[property="article:published_time"];缺失时尝试从首段文本解析常见时间格式(ISO/中文日期)</li> <li>HTML 解析采用正则提取常见标签,避免引入重型 DOM 解析库,满足中等复杂度抓取任务</li> </ul> </li> <li>兼容性: <ul> <li>需运行在支持 fetch 与 AbortController 的环境(Node.js 18+ 或现代浏览器)</li> <li>gzip/br 自动协商由 fetch 实现;无需显式设置 Accept-Encoding</li> </ul> </li> <li>安全与健壮性: <ul> <li>限制协议为 http/https,拒绝其他协议</li> <li>对错误进行分门别类的捕获与记录,避免影响整体任务</li> <li>避免使用已废弃 API 与不必要的第三方依赖</li> </ul> </li> </ul> <p>如果你需要扩展:支持重试策略、更多日期格式、内容字节长度统计、或将缓存替换为 LRU 等,我可以进一步完善。</p> </div> </div> </div> <div class="prompt-examples-v2-tab-content " id="example-2"> <div class="prompt-examples-v2-output"> <button class="prompt-examples-v2-popup-btn" onclick="showExamplePopup(2, 'CSV批量上报', this)" title="在弹窗中查看完整内容"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/> </svg> </button> <div class="html-content-display prompt-examples-v2-output-text"> <p>函数定义 async function processCsvAndUpload(csvPath, endpoint, { batchSize = 100, concurrency = 4, signal, onProgress } = {})</p> <ul> <li>参数说明 <ul> <li>csvPath: string。本地 UTF-8 CSV 文件路径,首行为表头。</li> <li>endpoint: string。批量上报的 HTTP(S) JSON 接口地址(POST)。</li> <li>options: <ul> <li>batchSize: number,分批大小,默认 100。</li> <li>concurrency: number,同时并发发送的批量请求数上限,默认 4。</li> <li>signal: AbortSignal,可选。用于全程取消操作。</li> <li>onProgress: function,可选。形如 onProgress({ linesRead, batchesDispatched, batchesCompleted, batchesSucceeded, batchesFailed, recordsProcessed })。</li> </ul> </li> </ul> </li> <li>返回值 <ul> <li>Promise<{ total, batchesSucceeded, batchesFailed, skipped, elapsedMs, files }></li> <li>total: 成功进入上报流程的记录数(不含表头,不含被跳过的空行/损坏行)。</li> <li>batchesSucceeded: 成功上报(HTTP 2xx)的批次数。</li> <li>batchesFailed: 达到重试上限仍失败的批次数。</li> <li>skipped: 本地解析阶段跳过的行数(空行/无效行/字段数不匹配)。</li> <li>elapsedMs: 总耗时(毫秒)。</li> <li>files: { success: string, errors: string },分别为 success.ndjson 与 errors.ndjson 的绝对路径。</li> </ul> </li> </ul> <p>代码实现 注意:该实现仅依赖 Node.js 内置模块(Node.js >= 18,内置 fetch),无第三方依赖。</p> <pre><code class="language-js">// Node.js >= 18 import fs from 'node:fs'; import path from 'node:path'; import { once } from 'node:events'; export async function processCsvAndUpload( csvPath, endpoint, { batchSize = 100, concurrency = 4, signal, onProgress } = {} ) { assertPositiveInteger(batchSize, 'batchSize'); assertPositiveInteger(concurrency, 'concurrency'); if (typeof endpoint !== 'string' || !/^https?:\/\//i.test(endpoint)) { throw new TypeError('endpoint must be a valid HTTP/HTTPS URL string'); } if (signal?.aborted) throw abortError(); const start = Date.now(); const dir = path.resolve(path.dirname(csvPath)); const successPath = path.join(dir, 'success.ndjson'); const errorsPath = path.join(dir, 'errors.ndjson'); // 输出文件写入流(NDJSON) const successStream = fs.createWriteStream(successPath, { flags: 'w', encoding: 'utf8', highWaterMark: 1 << 20 }); const errorsStream = fs.createWriteStream(errorsPath, { flags: 'w', encoding: 'utf8', highWaterMark: 1 << 20 }); // 捕获写入流错误 const streamErrorHandler = (err) => { // 将任何写入错误升级为操作失败 throw err; }; successStream.on('error', streamErrorHandler); errorsStream.on('error', streamErrorHandler); const stats = { linesRead: 0, // 读到的有效数据行(不含表头) recordsProcessed: 0, // 进入批处理的记录数 batchesDispatched: 0, batchesCompleted: 0, batchesSucceeded: 0, batchesFailed: 0, skipped: 0, }; const notify = () => { if (typeof onProgress === 'function') { try { onProgress({ linesRead: stats.linesRead, batchesDispatched: stats.batchesDispatched, batchesCompleted: stats.batchesCompleted, batchesSucceeded: stats.batchesSucceeded, batchesFailed: stats.batchesFailed, recordsProcessed: stats.recordsProcessed, }); } catch { // 忽略用户回调中的异常,避免影响主流程 } } }; // 简单的并发控制器(不创建队列:当活跃任务达到 concurrency 时,读入会等待最早完成的任务) const active = new Set(); async function runLimited(fn) { while (active.size >= concurrency) { if (signal?.aborted) throw abortError(); await Promise.race(active); } let p; p = (async () => { try { return await fn(); } finally { active.delete(p); } })(); active.add(p); return p; } // 写入 NDJSON 的工具函数(处理背压) async function writeNdjson(stream, obj) { const line = JSON.stringify(obj) + '\n'; if (!stream.write(line)) { await once(stream, 'drain'); } } // 解析 Retry-After 头(秒或日期) function parseRetryAfter(headerValue) { if (!headerValue) return null; const secs = Number(headerValue); if (Number.isFinite(secs)) return secs * 1000; const date = new Date(headerValue); const delta = date.getTime() - Date.now(); return Number.isFinite(delta) && delta > 0 ? delta : null; } // 带指数退避重试的 POST async function postWithRetry(url, jsonBody, { signal, maxRetries = 3, baseDelayMs = 500 }) { let attempt = 0; // 最多 attempt = 0..maxRetries,成功或遇到不可重试错误则提前返回 while (attempt <= maxRetries) { if (signal?.aborted) throw abortError(); try { const res = await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json', 'accept': 'application/json, text/plain, */*', }, body: jsonBody, // 将外部 signal 直接传入:可在请求中途取消 signal, keepalive: true, }); const status = res.status; if (res.ok) { // 尝试读取少量响应信息(非强制) let responseSnippet = ''; try { responseSnippet = await res.text(); if (responseSnippet.length > 2048) responseSnippet = responseSnippet.slice(0, 2048); } catch { /* ignore body read errors */ } return { ok: true, status, responseSnippet }; } // 可重试的状态码:5xx/429 if ((status >= 500 && status <= 599) || status === 429) { if (attempt === maxRetries) { const bodyText = await safeReadText(res); return { ok: false, retryable: true, status, bodyText, }; } // 计算等待时间:Retry-After 优先,其次指数退避 + 抖动 const retryAfterMs = parseRetryAfter(res.headers.get('retry-after')); const backoff = retryAfterMs ?? Math.min(30000, baseDelayMs * 2 ** attempt) + Math.floor(Math.random() * 250); await sleep(backoff, signal); attempt += 1; continue; } // 不可重试的 4xx/其它错误 const bodyText = await safeReadText(res); return { ok: false, retryable: false, status, bodyText }; } catch (err) { // fetch 抛出(网络错误/超时/取消) if (isAbortError(err)) throw err; if (attempt === maxRetries) { return { ok: false, retryable: true, status: 0, bodyText: String(err?.message ?? err) }; } const backoff = Math.min(30000, baseDelayMs * 2 ** attempt) + Math.floor(Math.random() * 250); await sleep(backoff, signal); attempt += 1; } } // 正常不会走到此处 return { ok: false, retryable: true, status: 0, bodyText: 'unknown error' }; } function isAbortError(err) { return (err && (err.name === 'AbortError' || err.code === 'ABORT_ERR')); } function abortError() { // 在 Node18+ 中 DOMException 可能不可用,退回 Error try { // eslint-disable-next-line no-undef return new DOMException('The operation was aborted', 'AbortError'); } catch { const e = new Error('The operation was aborted'); e.name = 'AbortError'; return e; } } async function sleep(ms, signal) { if (ms <= 0) return; await new Promise((resolve, reject) => { const t = setTimeout(resolve, ms); const onAbort = () => { clearTimeout(t); reject(abortError()); }; if (signal) { if (signal.aborted) { onAbort(); } else { signal.addEventListener('abort', onAbort, { once: true }); } } }); } async function safeReadText(res) { try { const t = await res.text(); return t.length > 4096 ? t.slice(0, 4096) : t; } catch { return ''; } } function assertPositiveInteger(n, name) { if (!Number.isInteger(n) || n <= 0) { throw new TypeError(`${name} must be a positive integer`); } } // CSV 流式解析(RFC4180 近似实现) async function* readCsvRecords(filePath, { signal }) { const stream = fs.createReadStream(filePath, { encoding: 'utf8', highWaterMark: 1 << 20 }); if (signal) { const onAbort = () => stream.destroy(abortError()); if (signal.aborted) onAbort(); else signal.addEventListener('abort', onAbort, { once: true }); } let buf = ''; let i = 0; let inQuotes = false; let field = ''; let record = []; let sawAny = false; for await (const chunk of stream) { if (!sawAny && chunk.charCodeAt(0) === 0xFEFF) { // 去除 UTF-8 BOM buf += chunk.slice(1); } else { buf += chunk; } sawAny = true; while (i < buf.length) { const ch = buf[i]; if (inQuotes) { if (ch === '"') { const next = buf[i + 1]; if (next === '"') { field += '"'; // 转义双引号 i += 2; continue; } inQuotes = false; i += 1; continue; } field += ch; i += 1; continue; } else { if (ch === '"') { inQuotes = true; i += 1; continue; } if (ch === ',') { record.push(field); field = ''; i += 1; continue; } if (ch === '\n' || ch === '\r') { // 结束一条记录 record.push(field); field = ''; // 跳过 CRLF if (ch === '\r' && buf[i + 1] === '\n') i += 2; else i += 1; yield record; record = []; continue; } field += ch; i += 1; } } // 移除已消费段,保留尾部未完成字段/记录 buf = ''; i = 0; } // 文件结束,输出最后一条(若存在) if (inQuotes) { // 未闭合引号,作为损坏行交给上层处理(会被当作字段数不匹配) } if (field.length > 0 || record.length > 0) { record.push(field); yield record; } } // 表头唯一化(重复列名添加后缀 _2, _3,...) function uniquifyHeaders(headers) { const map = new Map(); return headers.map((h0) => { let h = String(h0 ?? '').trim(); if (h.startsWith('\uFEFF')) h = h.slice(1); // 再次保险去BOM const count = (map.get(h) ?? 0) + 1; map.set(h, count); return count === 1 ? h : `${h}_${count}`; }); } // 主流程:读取 -> 组批 -> 并发上报 const batch = []; let batchId = 0; let rowNumber = 0; // 数据行号(不含表头,首条数据行为 1) let headers = null; let headerLen = 0; const reader = readCsvRecords(csvPath, { signal }); try { // 读取表头 const first = await reader.next(); if (first.done) { await closeStreams(); return finalizeResult(); } headers = uniquifyHeaders(first.value); headerLen = headers.length; // 持续读取数据行 for await (const rec of reader) { if (signal?.aborted) throw abortError(); // 跳过完全空行 if (rec.length === 1 && rec[0].trim() === '') { stats.skipped += 1; await writeNdjson(errorsStream, { status: 'error', reason: 'empty_line', rowNumber: rowNumber + 1, // 预增前的下一行 }); continue; } rowNumber += 1; stats.linesRead += 1; // 字段对齐:严格要求字段数一致,不一致则跳过并记录 if (rec.length !== headerLen) { stats.skipped += 1; await writeNdjson(errorsStream, { status: 'error', reason: 'field_count_mismatch', rowNumber, expected: headerLen, actual: rec.length, raw: rec, }); // 每 1000 行或错误发生时可通知 if (rowNumber % 1000 === 0) notify(); continue; } const obj = {}; for (let i = 0; i < headerLen; i += 1) { obj[headers[i]] = rec[i]; } batch.push({ rowNumber, data: obj }); stats.recordsProcessed += 1; if (batch.length >= batchSize) { const toSend = batch.splice(0, batch.length); stats.batchesDispatched += 1; const myBatchId = ++batchId; // 并发受限执行 runLimited(() => handleBatch(myBatchId, toSend)).catch(async (err) => { // 将内部错误冒泡:取消时会是 AbortError,其他错误也终止流程 // 尽快关闭流并重抛 await closeStreams().catch(() => {}); throw err; }); // 每发送一个批次就通知一次 notify(); } if (rowNumber % 1000 === 0) { notify(); } } // 发送最后不足一批的数据 if (batch.length > 0) { const toSend = batch.splice(0, batch.length); stats.batchesDispatched += 1; const myBatchId = ++batchId; runLimited(() => handleBatch(myBatchId, toSend)).catch(async (err) => { await closeStreams().catch(() => {}); throw err; }); notify(); } // 等待所有进行中的任务完成 while (active.size > 0) { if (signal?.aborted) throw abortError(); await Promise.race(active); } await closeStreams(); return finalizeResult(); } catch (err) { // 主流程异常(包含取消) await closeStreams().catch(() => {}); throw err; } async function handleBatch(myBatchId, items) { if (signal?.aborted) throw abortError(); // 准备 JSON body(仅发送 data 对象数组) const payload = items.map((it) => it.data); const jsonBody = JSON.stringify(payload); const res = await postWithRetry(endpoint, jsonBody, { signal, maxRetries: 3, baseDelayMs: 500 }); stats.batchesCompleted += 1; if (res.ok) { stats.batchesSucceeded += 1; // 对成功的每条记录写入 success.ndjson(保留可观测性) // 如需降采样,可考虑仅写入批级记录 for (const it of items) { await writeNdjson(successStream, { status: 'success', batchId: myBatchId, rowNumber: it.rowNumber, endpoint, responseStatus: 200, }); } } else { stats.batchesFailed += 1; // 将整个批次写入死信文件(逐条记录,包含失败原因) const reason = res.retryable ? 'network_or_server' : 'client_error'; const message = res.bodyText ? truncate(res.bodyText, 2048) : undefined; for (const it of items) { await writeNdjson(errorsStream, { status: 'error', reason, batchId: myBatchId, rowNumber: it.rowNumber, endpoint, httpStatus: res.status, message, data: it.data, }); } } notify(); } function truncate(s, n) { return s && s.length > n ? s.slice(0, n) : s; } async function closeStreams() { await Promise.all([ new Promise((resolve) => successStream.end(resolve)), new Promise((resolve) => errorsStream.end(resolve)), ]); } function finalizeResult() { const elapsedMs = Date.now() - start; return { total: stats.recordsProcessed, batchesSucceeded: stats.batchesSucceeded, batchesFailed: stats.batchesFailed, skipped: stats.skipped, elapsedMs, files: { success: successPath, errors: errorsPath, }, }; } } </code></pre> <p>错误处理</p> <ul> <li>全链路取消支持:通过 options.signal 将 AbortSignal 传入 CSV 读取、重试等待与 fetch 请求中。一旦外部取消,立即抛出 AbortError 并中止流程。</li> <li>CSV 解析异常: <ul> <li>引号未闭合、字段数与表头不一致、空行等,均计入 skipped,并写入 errors.ndjson,便于后续排查。</li> </ul> </li> <li>网络/服务端错误: <ul> <li>对 5xx 与 429 进行最多 3 次指数退避重试,并支持 Retry-After 头;网络异常(如 ECONNRESET)同样重试。</li> <li>超过重试上限仍失败的批次,将整批写入 errors.ndjson(逐记录记录),并计入 batchesFailed。</li> </ul> </li> <li>客户端错误(如 4xx 非 429):视为不可重试错误,直接写入 errors.ndjson。</li> <li>写入流错误:监听 success.ndjson 与 errors.ndjson 的 error 事件,任何写入异常都会终止流程并抛出。</li> </ul> <p>使用示例</p> <pre><code class="language-js">import { processCsvAndUpload } from './processCsvAndUpload.js'; const ac = new AbortController(); (async () => { try { const result = await processCsvAndUpload( './data/input.csv', 'https://api.example.com/ingest/batch', { batchSize: 200, concurrency: 4, signal: ac.signal, onProgress: ({ linesRead, batchesDispatched, batchesCompleted, batchesSucceeded, batchesFailed, recordsProcessed }) => { console.log( `lines=${linesRead}, dispatched=${batchesDispatched}, completed=${batchesCompleted}, ok=${batchesSucceeded}, fail=${batchesFailed}, records=${recordsProcessed}` ); }, } ); console.log('Done:', result); // 预期输出: // { // total: 12345, // batchesSucceeded: 60, // batchesFailed: 2, // skipped: 10, // elapsedMs: 9876, // files: { success: '/abs/path/success.ndjson', errors: '/abs/path/errors.ndjson' } // } } catch (e) { if (e.name === 'AbortError') { console.error('Canceled by user'); } else { console.error('Failed:', e); } } })(); // 在需要时取消 // ac.abort(); </code></pre> <p>技术要点</p> <ul> <li>流式 CSV 解析: <ul> <li>自实现近 RFC4180 的解析器,逐字符处理引号、转义双引号、CRLF/LF 行尾,避免一次性读入整个文件。</li> <li>去除 UTF-8 BOM;对重复列名自动唯一化,保证对象键无冲突。</li> </ul> </li> <li>分批与并发: <ul> <li>使用轻量并发控制器(Set + Promise.race)限制并发数,实现读入与上传的流水线式处理,减少内存占用。</li> <li>读入端仅在达成批次时才等待并发空位,有效平衡 I/O 与网络的吞吐。</li> </ul> </li> <li>可靠性: <ul> <li>对 5xx/429/网络错误进行指数退避重试,并优先尊重 Retry-After;加入抖动防止同步重试。</li> <li>将不可重试错误与超过重试上限的请求统一写入 errors.ndjson(逐记录),提升可观测性和后续补偿操作的便利性。</li> <li>成功写入 success.ndjson(逐记录),若数据量极大可调整为批级记录以降低 I/O。</li> </ul> </li> <li>取消与背压: <ul> <li>将 AbortSignal 贯穿 CSV 读取、sleep、fetch;任何阶段取消都会快速失败。</li> <li>NDJSON 写入考虑背压,使用 drain 事件进行 await,避免内存暴涨。</li> </ul> </li> <li>统计与进度: <ul> <li>提供 linesRead、recordsProcessed、批次派发/完成/成功/失败等指标;默认在派发批次、完成批次与每 1000 行时进行回调。</li> </ul> </li> <li>兼容性与安全: <ul> <li>使用 Node.js 内置 fetch(Node >= 18),无第三方依赖。</li> <li>避免使用弃用 API;严格校验输入参数,防止错误使用。</li> <li>HTTP 请求限定为 POST JSON,设置合理的 Accept/Content-Type,避免注入风险。</li> </ul> </li> <li>可扩展建议: <ul> <li>如果服务端支持部分成功反馈,可在成功回包中解析并区分记录级成功/失败,细化 success/errors 记录。</li> <li>如需严格内存控制,可在并发达到上限时暂停文件读取(当前设计通过等待最早完成任务达到类似效果;必要时可接入 stream.pause/resume)。</li> </ul> </li> </ul> </div> </div> </div> </div> </div> </section> <div id="examplePopupModal" class="prompt-examples-v2-modal"> <div class="prompt-examples-v2-modal-content"> <div class="prompt-examples-v2-modal-header"> <h3 id="popupTitle">示例详情</h3> <button class="prompt-examples-v2-modal-close" onclick="closeExamplePopup()"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line> </svg> </button> </div> <div class="prompt-examples-v2-modal-body"> <div id="popupContent" class="prompt-examples-v2-popup-content"></div> </div> </div> </div> <script> // 弹窗功能JavaScript function showExamplePopup(exampleIndex, title, buttonElement) { const modal = document.getElementById('examplePopupModal'); const popupTitle = document.getElementById('popupTitle'); const popupContent = document.getElementById('popupContent'); // 设置弹窗标题 popupTitle.textContent = title; // 获取对应示例的内容 const tabContent = document.getElementById('example-' + exampleIndex); const outputText = tabContent.querySelector('.prompt-examples-v2-output-text'); if (outputText) { // 克隆内容到弹窗中 popupContent.innerHTML = outputText.innerHTML; } else { popupContent.innerHTML = '<p>暂无内容</p>'; } // 显示弹窗 modal.classList.add('show'); document.body.style.overflow = 'hidden'; // 防止背景滚动 } function closeExamplePopup() { const modal = document.getElementById('examplePopupModal'); modal.classList.remove('show'); document.body.style.overflow = ''; // 恢复背景滚动 } // 点击背景关闭弹窗 document.addEventListener('DOMContentLoaded', function() { const modal = document.getElementById('examplePopupModal'); if (modal) { modal.addEventListener('click', function(e) { if (e.target === modal) { closeExamplePopup(); } }); } // ESC键关闭弹窗 document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { closeExamplePopup(); } }); }); </script> <script> document.addEventListener('DOMContentLoaded', function() { // Tab切换功能 const tabs = document.querySelectorAll('.prompt-examples-v2-tab'); const contents = document.querySelectorAll('.prompt-examples-v2-tab-content'); tabs.forEach(tab => { tab.addEventListener('click', function() { const targetId = this.getAttribute('data-tab'); // 移除所有active状态 tabs.forEach(t => t.classList.remove('active')); contents.forEach(c => c.classList.remove('active')); // 添加当前active状态 this.classList.add('active'); const targetContent = document.getElementById(targetId); if (targetContent) { targetContent.classList.add('active'); } }); }); }); </script> <section class="solved-problems-v2"> <div class="solved-problems-v2-container"> <h2 class="solved-problems-v2-title">解决的问题</h2> <div class="solved-problems-v2-content"> <div class="solved-problems-v2-item"> <div class="html-content-display solved-problems-v2-item-text"> <p>为前端/全栈/Node 开发者与技术团队提供一键式的异步函数生成体验:基于简单的任务描述,自动产出符合最佳实践的 async/await 代码、完善的错误处理、清晰的使用示例与关键说明;显著缩短开发与评审时间、降低线上故障风险、统一团队编码风格;覆盖数据获取、文件读写与服务请求等高频场景,帮助个人快速交付、团队高质量复用,并推动从试用到持续付费的转化。</p> </div> </div> </div> </div> </section> <style> .solved-problems-v2-item > .solved-problems-v2-item-text { background-color: transparent !important; border: none !important; padding: 0 !important; } </style> <div class="suitable-users-v2"> <div class="suitable-users-v2-container"> <h2 class="suitable-users-v2-title">适用用户</h2> <div class="suitable-users-v2-list"> <div class="suitable-users-v2-card" style="background-color: #E3F2FD;"> <div class="suitable-users-v2-card-content"> <h3 class="suitable-users-v2-card-title">前端工程师</h3> <p class="suitable-users-v2-card-description">在新页面快速搭建数据请求与渲染逻辑;为列表、图片等多源数据并发加载;为上传下载加入超时与重试;统一错误提示与空态处理。</p> </div> </div> <div class="suitable-users-v2-card" style="background-color: #F1F8E9;"> <div class="suitable-users-v2-card-content"> <h3 class="suitable-users-v2-card-title">Node.js后端开发</h3> <p class="suitable-users-v2-card-description">封装与外部服务的异步调用;为批量任务设置并发限制与队列;完善日志与异常分级;减少手写样板,缩短交付周期。</p> </div> </div> <div class="suitable-users-v2-card" style="background-color: #FFFDE7;"> <div class="suitable-users-v2-card-content"> <h3 class="suitable-users-v2-card-title">技术负责人/架构师</h3> <p class="suitable-users-v2-card-description">沉淀团队通用模板与约定;统一超时、重试、超限策略;把控代码可维护性与规范;显著降低线上故障率。</p> </div> </div> </div> </div> </div> <section class="feature-summary-v2"> <div class="feature-summary-v2-container"> <h2 class="feature-summary-v2-title">特征总结</h2> <div class="feature-summary-v2-content"> <div class="feature-summary-v2-item"> <span class="feature-summary-v2-bullet">•</span> <span class="feature-summary-v2-text">一键生成符合最佳实践的异步函数,内置注释与说明,直接拷贝可用。</span> </div> <div class="feature-summary-v2-item"> <span class="feature-summary-v2-bullet">•</span> <span class="feature-summary-v2-text">自动加上超时与重试策略,网络不稳也能稳妥返回,显著减少线上告警。</span> </div> <div class="feature-summary-v2-item"> <span class="feature-summary-v2-bullet">•</span> <span class="feature-summary-v2-text">内建错误分类与兜底提示,异常可追踪可定位,排查成本更可控更透明。</span> </div> <div class="feature-summary-v2-item"> <span class="feature-summary-v2-bullet">•</span> <span class="feature-summary-v2-text">支持并发与队列控制,批量请求不阻塞,稳定提升整体吞吐与响应速度。</span> </div> <div class="feature-summary-v2-item"> <span class="feature-summary-v2-bullet">•</span> <span class="feature-summary-v2-text">可按业务场景定制参数与模板,团队统一规范,跨项目快速复用落地。</span> </div> <div class="feature-summary-v2-item"> <span class="feature-summary-v2-bullet">•</span> <span class="feature-summary-v2-text">自动生成使用示例与调用说明,新人也能即刻上手,减少口头传授成本。</span> </div> <div class="feature-summary-v2-item"> <span class="feature-summary-v2-bullet">•</span> <span class="feature-summary-v2-text">针对数据获取、文件处理等常见任务,给出稳健方案,避免重复造轮子。</span> </div> <div class="feature-summary-v2-item"> <span class="feature-summary-v2-bullet">•</span> <span class="feature-summary-v2-text">结合边界条件检查与输入校验,预防隐形缺陷,降低不可预期风险。</span> </div> <div class="feature-summary-v2-item"> <span class="feature-summary-v2-bullet">•</span> <span class="feature-summary-v2-text">输出结构清晰、注释标准,代码易读易测,方便后续扩展与维护工作。</span> </div> </div> </div> </section> <section class="usage-guide-v2"> <div class="usage-guide-v2-container"> <h2 class="usage-guide-v2-title">如何使用购买的提示词模板</h2> <div class="usage-guide-v2-content"> <div class="usage-guide-v2-item"> <div class="usage-guide-v2-text"> <h4 class="usage-guide-v2-subtitle">1. 直接在外部 Chat 应用中使用</h4> <p class="usage-guide-v2-description">将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。</p> <div class="usage-guide-v2-icons"> <a href="https://chat.deepseek.com/" target="_blank" class="usage-guide-v2-icon-item"> <img src="https://explinks-prod-apis.oss-cn-beijing.aliyuncs.com/apiDefaultIconADI20240909773605e9f9b1.png" alt="DeepSeek" class="usage-guide-v2-icon"> </a> <a href="https://www.doubao.com/chat/" target="_blank" class="usage-guide-v2-icon-item"> <img src="https://explinks-prod-apis.oss-cn-beijing.aliyuncs.com/apiDefaultIconADI20240909336605e9c295.png" alt="豆包" class="usage-guide-v2-icon"> </a> <a href="https://www.tongyi.com/" target="_blank" class="usage-guide-v2-icon-item"> <img src="https://explinks-prod-apis.oss-cn-beijing.aliyuncs.com/apiDefaultIcon9f8a29a9-4e4f-49ab-ac0d-7985823a6f0c.png" alt="通义" class="usage-guide-v2-icon"> </a> <a href="https://www.kimi.com/" target="_blank" class="usage-guide-v2-icon-item"> <img src=" https://explinks-prod-apis.oss-cn-beijing.aliyuncs.com/LOGO_IMG/579c8d7f234543b2832c95c5ddb38515" alt="Kimi" class="usage-guide-v2-icon"> </a> <a href="https://chatgpt.com/" target="_blank" class="usage-guide-v2-icon-item"> <img src=" https://explinks-prod-apis.oss-cn-beijing.aliyuncs.com/LOGO_IMG/cc5204a95abd46b2b603c9c0101b4ef0" alt="ChatGPT" class="usage-guide-v2-icon"> </a> <a href="https://grok.com/" target="_blank" class="usage-guide-v2-icon-item"> <img src="https://explinks-prod-apis.oss-cn-beijing.aliyuncs.com/LOGO_IMG/b84a84ef3daf43649d3fdbee8f170992" alt="Grok" class="usage-guide-v2-icon"> </a> <a href="https://gemini.google.com/" target="_blank" class="usage-guide-v2-icon-item"> <img src="https://explinks-prod-apis.oss-cn-beijing.aliyuncs.com/LOGO_IMG/57840102146847898b70184c9ee87aff" alt="Gemini" class="usage-guide-v2-icon"> </a> </div> </div> </div> <div class="usage-guide-v2-item"> <div class="usage-guide-v2-text"> <h4 class="usage-guide-v2-subtitle">2. 发布为 API 接口调用</h4> <p class="usage-guide-v2-description">把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。 </div> </div> <div class="usage-guide-v2-item"> <div class="usage-guide-v2-text"> <h4 class="usage-guide-v2-subtitle">3. 在 MCP Client 中配置使用</h4> <p class="usage-guide-v2-description">在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。 </p> </div> </div> </div> </div> </section> </div> <div class="prompt-detail-v2-right"> <div class="prompt-price-card"> <div class="price-info-section"> <div class="price-title">AI 提示词价格</div> <div class="price-row"> <span class="current-price" style="color: #2563eb;">¥20.00元</span> </div> <div class="price-description">先用后买,用好了再付款,超安全!</div> <div class="button-row"> <a class="buy-btn try-first-btn" href="https://tools.explinks.com/prompt-trial?slug=js_async_function_generator&try">在线免费用提示词</a> </div> </div> <div class="benefits-section"> <h4 class="benefits-title">您购买后可以获得什么</h4> <div class="benefits-list"> <div class="benefit-item"> <span class="benefit-icon">✓</span> <div class="benefit-content"> <div class="benefit-main">获得完整提示词模板</div> <div class="benefit-detail">- 共 660 tokens</div> <div class="benefit-detail">- 3 个可调节参数</div> <div class="benefit-tags"> <span class="tag">{ 异步任务描述 }</span> <span class="tag">{ 函数用途 }</span> <span class="tag">{ 复杂度级别 }</span> </div> </div> </div> <div class="benefit-item"> <span class="benefit-icon">✓</span> <div class="benefit-content"> <div class="benefit-main">获得社区贡献内容的使用权</div> <div class="benefit-detail">- 精选社区优质案例,助您快速上手提示词</div> </div> </div> </div> </div> <div class="online-trial-container"> <a class="online-trial-btn" href="https://tools.explinks.com/prompt-trial?slug=js_async_function_generator&buy">购买</a> </div> <div class="coupon-promo-section"> <div class="coupon-promo-text">使用提示词兑换券,低至 ¥ 9.9</div> <a href="/packs/integrate-user" target="_blank" class="coupon-promo-link">了解兑换券 →</a> </div> </div> <script> document.addEventListener('DOMContentLoaded', function () { // 检查用户是否已购买该提示词 checkUserPurchaseStatus(); }); async function checkUserPurchaseStatus() { try { const response = await fetch('/api/user/prompt/manage/get-user-prompt-purchase-relation', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ promptNo: 'f7ef7c11940f4660a2295324a52afe2d' }) }); const result = await response.json(); if (result.success && result.data === true) { // 用户已购买,修改所有购买按钮 updateBuyButtonsForPurchased(); } } catch (error) { console.error('检查购买状态失败:', error); } } function updateBuyButtonsForPurchased() { // 获取所有购买按钮 const buyButtons = document.querySelectorAll('.buy-btn'); buyButtons.forEach(button => { // 修改按钮文案为"去查看" button.textContent = '去查看'; // 修改跳转链接 button.href = 'https://console.explinks.com/myHome/prompts'; }); } </script> </div> </div> </div> <!-- 相关推荐模块 --> <div class="zone-item limited-free-zone"> <div class="limited-free-tag-container"> <div class="limited-free-tag"> 限时半价 </div> </div> <div class="limited-free-header"> <div class="limited-free-title-section"> <h2 class="limited-free-main-title"> <svg xmlns="http://www.w3.org/2000/svg" t="1756971155228" class="icon" viewBox="0 0 1024 1024" version="1.1" p-id="13558" width="64" height="64"> <script xmlns=""/> <path d="M836.345598 257.721713l-28.427446 0c14.415314-24.441669 20.741393-47.75463 18.720364-69.379137-1.677199-18.098193-10.254551-43.982724-41.686425-64.611553-26.059516-17.143449-59.57177-21.348214-96.751554-12.059664-60.292178 15.137769-131.649366 67.180052-178.566932 130.073474C462.687386 178.852434 391.387503 126.809127 331.096349 111.671359c-37.210483-9.348925-70.751391-5.114484-96.807836 12.059664-31.404244 20.62883-39.983643 46.51336-41.659819 64.611553-2.02103 21.624507 4.246721 44.937468 18.720364 69.379137l-28.427446 0c-43.043329 0-78.073146 35.056424-78.073146 78.132498l0 100.636025c0 34.075073 22.474874 64.190975 54.991452 74.30431-3.090385 8.550747-4.708232 17.390066-4.708232 26.347065l0 301.923424c0 43.046399 35.069727 78.117149 78.117149 78.117149l552.85857 0c43.044352 0 78.05882-35.069727 78.05882-78.117149L864.166223 537.140587c0-9.014304-1.558495-17.882276-4.649903-26.376741 32.468482-10.169617 54.890144-40.285518 54.890144-74.274634l0-100.609419C914.405441 292.806789 879.390973 257.721713 836.345598 257.721713M786.10638 861.540931 537.426601 861.540931 537.426601 514.636037l248.680802 0c12.394285 0 22.445198 10.110265 22.445198 22.50455l0 301.923424C808.551578 851.459319 798.500665 861.540931 786.10638 861.540931M568.049039 257.721713c39.549761-45.61899 91.462085-81.468476 133.614114-92.043323 21.896706-5.487991 39.807634-3.985778 52.780087 4.565992 13.781888 9.055237 16.23373 17.129122 16.785292 23.227004 2.192945 22.35924-21.784142 52.504818-33.108049 64.250326L568.049039 257.721713zM858.763167 335.880817l0 100.609419c0 12.394285-10.024308 22.473851-22.417569 22.473851L537.426601 458.964086 537.426601 313.390593l298.918997 0C848.738859 313.390593 858.763167 323.488579 858.763167 335.880817M481.812979 514.636037l0 346.904894L233.24781 861.540931c-12.423961 0-22.477944-10.082636-22.477944-22.476921L210.769866 537.140587c0-12.394285 10.053983-22.50455 22.477944-22.50455L481.812979 514.636037zM248.039701 193.471386c0.547469-6.097882 3.03308-14.172791 16.756639-23.227004 12.882402-8.55177 30.794353-10.127661 52.809763-4.565992 41.917692 10.515494 93.773733 46.36805 133.410476 92.043323L281.088398 257.721713C270.948457 247.451812 245.81401 216.366839 248.039701 193.471386M160.445714 335.880817c0-12.391215 10.053983-22.490224 22.474874-22.490224l298.891368 0 0 145.573493L182.920588 458.964086c-12.420891 0-22.474874-10.079566-22.474874-22.473851L160.445714 335.880817z" fill="#ffffff" p-id="13559"/> <script xmlns=""/> </svg> 不要错过! </h2> <p class="limited-free-subtitle">半价获取高级提示词-优惠即将到期</p> </div> <div class="limited-free-countdown" data-end-time="2025-12-13 23:59:59"> <div class="countdown-item"> <div class="countdown-number" data-type="days">17</div> <div class="countdown-label">天</div> </div> <div class="countdown-separator">:</div> <div class="countdown-item"> <div class="countdown-number" data-type="hours">23</div> <div class="countdown-label">小时</div> </div> <div class="countdown-separator">:</div> <div class="countdown-item"> <div class="countdown-number" data-type="minutes">59</div> <div class="countdown-label">分钟</div> </div> <div class="countdown-separator">:</div> <div class="countdown-item"> <div class="countdown-number" data-type="seconds">59</div> <div class="countdown-label">秒</div> </div> </div> </div> <div class="zone-content"> <div class="prompt-slider-container"> <div class="prompt-slider"> <div class="prompt-card-mini" data-prompt-slug="mystic_black_rose"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/098c8dc9-9bdc-4bff-a9ca-9014d3b3396e.png');" href="https://prompts.explinks.com/mystic_black_rose?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=creativity"> 创意 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://tools.explinks.com/prompt-trial?slug=mystic_black_rose&title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 黑玫瑰神秘背景 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://tools.explinks.com/prompt-trial?slug=mystic_black_rose&price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="female_motorcycle_stunts"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-video" onmouseenter="this.querySelector('video').play(); this.querySelector('.prompt-card-mini-play-icon').classList.add('hidden');" onmouseleave="this.querySelector('video').pause(); this.querySelector('.prompt-card-mini-play-icon').classList.remove('hidden');" href="https://prompts.explinks.com/female_motorcycle_stunts?desc" target="_blank"> <video class="prompt-card-mini-background-video" muted loop> <source src="https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/a480c150-b3a0-4eb1-b08d-5d6466727056.mp4" type="video/mp4"> </video> <div class="prompt-card-mini-play-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="10" stroke="#333333" stroke-width="1.5" fill="none"/> <polygon points="10,8 16,12 10,16" stroke="#333333" stroke-width="1.5" fill="none" stroke-linejoin="round"/> </svg> </div> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=prompt"> 提示词 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://tools.explinks.com/prompt-trial?slug=female_motorcycle_stunts&title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 极限机车特技表演 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://tools.explinks.com/prompt-trial?slug=female_motorcycle_stunts&price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="mystical_spacewoman_cosmos"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/b08c8557-d8b6-4c78-9227-2596b9612e6f.png');" href="https://prompts.explinks.com/mystical_spacewoman_cosmos?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=content_creation"> 内容创作 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://tools.explinks.com/prompt-trial?slug=mystical_spacewoman_cosmos&title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 太空中的神秘美女 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://tools.explinks.com/prompt-trial?slug=mystical_spacewoman_cosmos&price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="epic_warrior_vs_dragon_battle"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/8dea4b90-df72-40e2-8848-7424578cdfcb.png');" href="https://prompts.explinks.com/epic_warrior_vs_dragon_battle?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=art"> 艺术 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://tools.explinks.com/prompt-trial?slug=epic_warrior_vs_dragon_battle&title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 奇幻勇士与巨龙对决 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://tools.explinks.com/prompt-trial?slug=epic_warrior_vs_dragon_battle&price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="ultra_detail_closeup_portraits"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/bf6b3637-63cd-4ffa-afd7-b28102c983f6.png');" href="https://prompts.explinks.com/ultra_detail_closeup_portraits?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=prompt"> 提示词 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/ultra_detail_closeup_portraits?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 极致特写肖像艺术 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/ultra_detail_closeup_portraits?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥12.50元</span> <span class="prompt-card-mini-original-price">¥25</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="pixar_aurora_steam_train"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/28979275-ffb9-4093-a4c2-4480aa12d877.png');" href="https://prompts.explinks.com/pixar_aurora_steam_train?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=prompt"> 提示词 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://tools.explinks.com/prompt-trial?slug=pixar_aurora_steam_train&title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 雪域极光列车 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://tools.explinks.com/prompt-trial?slug=pixar_aurora_steam_train&price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="misty_lake_monster_footage"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-video" onmouseenter="this.querySelector('video').play(); this.querySelector('.prompt-card-mini-play-icon').classList.add('hidden');" onmouseleave="this.querySelector('video').pause(); this.querySelector('.prompt-card-mini-play-icon').classList.remove('hidden');" href="https://prompts.explinks.com/misty_lake_monster_footage?desc" target="_blank"> <video class="prompt-card-mini-background-video" muted loop> <source src="https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/dff2d0bf-ec43-4a9f-bb56-d03e185329fe.mp4" type="video/mp4"> </video> <div class="prompt-card-mini-play-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="10" stroke="#333333" stroke-width="1.5" fill="none"/> <polygon points="10,8 16,12 10,16" stroke="#333333" stroke-width="1.5" fill="none" stroke-linejoin="round"/> </svg> </div> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=prompt"> 提示词 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://tools.explinks.com/prompt-trial?slug=misty_lake_monster_footage&title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 迷雾湖怪纪实 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://tools.explinks.com/prompt-trial?slug=misty_lake_monster_footage&price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥12.50元</span> <span class="prompt-card-mini-original-price">¥25</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="master_fantasy_art_portrait"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/9f0c4a6e-abd7-41da-8b10-9ddffedc92a5.png');" href="https://prompts.explinks.com/master_fantasy_art_portrait?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=prompt"> 提示词 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/master_fantasy_art_portrait?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 幻想艺术人物肖像 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/master_fantasy_art_portrait?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥12.50元</span> <span class="prompt-card-mini-original-price">¥25</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="desolate_rite_metamorphosis"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-video" onmouseenter="this.querySelector('video').play(); this.querySelector('.prompt-card-mini-play-icon').classList.add('hidden');" onmouseleave="this.querySelector('video').pause(); this.querySelector('.prompt-card-mini-play-icon').classList.remove('hidden');" href="https://prompts.explinks.com/desolate_rite_metamorphosis?desc" target="_blank"> <video class="prompt-card-mini-background-video" muted loop> <source src="https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/d853f3ad-8192-44a1-afb8-3bd079945969.mp4" type="video/mp4"> </video> <div class="prompt-card-mini-play-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="10" stroke="#333333" stroke-width="1.5" fill="none"/> <polygon points="10,8 16,12 10,16" stroke="#333333" stroke-width="1.5" fill="none" stroke-linejoin="round"/> </svg> </div> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=prompt"> 提示词 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://tools.explinks.com/prompt-trial?slug=desolate_rite_metamorphosis&title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 荒土仪式暗黑蜕变 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://tools.explinks.com/prompt-trial?slug=desolate_rite_metamorphosis&price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥12.50元</span> <span class="prompt-card-mini-original-price">¥25</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="blue_cosplay_girl_boxing_gym"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-video" onmouseenter="this.querySelector('video').play(); this.querySelector('.prompt-card-mini-play-icon').classList.add('hidden');" onmouseleave="this.querySelector('video').pause(); this.querySelector('.prompt-card-mini-play-icon').classList.remove('hidden');" href="https://prompts.explinks.com/blue_cosplay_girl_boxing_gym?desc" target="_blank"> <video class="prompt-card-mini-background-video" muted loop> <source src="https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/e2dae429-3a21-4c49-befb-7610e39f97ce.mp4" type="video/mp4"> </video> <div class="prompt-card-mini-play-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="10" stroke="#333333" stroke-width="1.5" fill="none"/> <polygon points="10,8 16,12 10,16" stroke="#333333" stroke-width="1.5" fill="none" stroke-linejoin="round"/> </svg> </div> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=prompt"> 提示词 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/blue_cosplay_girl_boxing_gym?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 拳击馆蓝色角色扮演女孩 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/blue_cosplay_girl_boxing_gym?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="aloof_languid_idol_half_length"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/823841e0-f910-4ed8-9f71-0ea96e82e6b7.png');" href="https://prompts.explinks.com/aloof_languid_idol_half_length?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=prompt"> 提示词 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/aloof_languid_idol_half_length?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 高冷慵懒爱豆半身肖像 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/aloof_languid_idol_half_length?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥12.50元</span> <span class="prompt-card-mini-original-price">¥25</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="dreamy_liquid_color_dogs"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/8c58b4ce-3452-43fc-a8bd-27286c07d3ae.png');" href="https://prompts.explinks.com/dreamy_liquid_color_dogs?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=art"> 艺术 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/dreamy_liquid_color_dogs?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 梦幻彩色液体小狗 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/dreamy_liquid_color_dogs?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="golden_cyberpunk_portrait"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/0051e3e0-83e8-442a-a5f5-0eb1bd593b6f.png');" href="https://prompts.explinks.com/golden_cyberpunk_portrait?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=art"> 艺术 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/golden_cyberpunk_portrait?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 黄金质感侧脸肖像 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/golden_cyberpunk_portrait?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="beachside_friendship_toast"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-video" onmouseenter="this.querySelector('video').play(); this.querySelector('.prompt-card-mini-play-icon').classList.add('hidden');" onmouseleave="this.querySelector('video').pause(); this.querySelector('.prompt-card-mini-play-icon').classList.remove('hidden');" href="https://prompts.explinks.com/beachside_friendship_toast?desc" target="_blank"> <video class="prompt-card-mini-background-video" muted loop> <source src="https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/9e333ad7-e54b-4366-af34-753e034f4919.mp4" type="video/mp4"> </video> <div class="prompt-card-mini-play-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="10" stroke="#333333" stroke-width="1.5" fill="none"/> <polygon points="10,8 16,12 10,16" stroke="#333333" stroke-width="1.5" fill="none" stroke-linejoin="round"/> </svg> </div> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=content_creation"> 内容创作 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/beachside_friendship_toast?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 海滩午后友谊时光 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/beachside_friendship_toast?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥12.50元</span> <span class="prompt-card-mini-original-price">¥25</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="cinematic_motivational_video"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-video" onmouseenter="this.querySelector('video').play(); this.querySelector('.prompt-card-mini-play-icon').classList.add('hidden');" onmouseleave="this.querySelector('video').pause(); this.querySelector('.prompt-card-mini-play-icon').classList.remove('hidden');" href="https://prompts.explinks.com/cinematic_motivational_video?desc" target="_blank"> <video class="prompt-card-mini-background-video" muted loop> <source src="https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/a00eab52-5ed1-4334-9a32-570e24ea8680.mp4" type="video/mp4"> </video> <div class="prompt-card-mini-play-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="10" stroke="#333333" stroke-width="1.5" fill="none"/> <polygon points="10,8 16,12 10,16" stroke="#333333" stroke-width="1.5" fill="none" stroke-linejoin="round"/> </svg> </div> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=video"> 视频 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/cinematic_motivational_video?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 励志短片创作 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/cinematic_motivational_video?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥12.50元</span> <span class="prompt-card-mini-original-price">¥25</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="ghibli_kings_speech_cinematic"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-video" onmouseenter="this.querySelector('video').play(); this.querySelector('.prompt-card-mini-play-icon').classList.add('hidden');" onmouseleave="this.querySelector('video').pause(); this.querySelector('.prompt-card-mini-play-icon').classList.remove('hidden');" href="https://prompts.explinks.com/ghibli_kings_speech_cinematic?desc" target="_blank"> <video class="prompt-card-mini-background-video" muted loop> <source src="https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/6849cd34-6eb9-4198-9fee-175db1315171.mp4" type="video/mp4"> </video> <div class="prompt-card-mini-play-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="10" stroke="#333333" stroke-width="1.5" fill="none"/> <polygon points="10,8 16,12 10,16" stroke="#333333" stroke-width="1.5" fill="none" stroke-linejoin="round"/> </svg> </div> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=prompt"> 提示词 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/ghibli_kings_speech_cinematic?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 吉卜力风格国王演讲动态镜头 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/ghibli_kings_speech_cinematic?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="playful_catgirl_dynamic_video"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-video" onmouseenter="this.querySelector('video').play(); this.querySelector('.prompt-card-mini-play-icon').classList.add('hidden');" onmouseleave="this.querySelector('video').pause(); this.querySelector('.prompt-card-mini-play-icon').classList.remove('hidden');" href="https://prompts.explinks.com/playful_catgirl_dynamic_video?desc" target="_blank"> <video class="prompt-card-mini-background-video" muted loop> <source src="https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/0038b07d-4468-4ca4-8173-47ca76340480.mp4" type="video/mp4"> </video> <div class="prompt-card-mini-play-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="10" stroke="#333333" stroke-width="1.5" fill="none"/> <polygon points="10,8 16,12 10,16" stroke="#333333" stroke-width="1.5" fill="none" stroke-linejoin="round"/> </svg> </div> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=api"> API </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/playful_catgirl_dynamic_video?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 俏皮猫娘动态演绎 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/playful_catgirl_dynamic_video?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥12.50元</span> <span class="prompt-card-mini-original-price">¥25</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="modern_fashion_vector_art"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/b9ebea6e-69cb-4a8d-a8e8-cb2906c65cb1.png');" href="https://prompts.explinks.com/modern_fashion_vector_art?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=design"> 设计 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/modern_fashion_vector_art?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 现代时尚矢量艺术创作 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/modern_fashion_vector_art?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="cat_ear_hand_motion_closeup"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-video" onmouseenter="this.querySelector('video').play(); this.querySelector('.prompt-card-mini-play-icon').classList.add('hidden');" onmouseleave="this.querySelector('video').pause(); this.querySelector('.prompt-card-mini-play-icon').classList.remove('hidden');" href="https://prompts.explinks.com/cat_ear_hand_motion_closeup?desc" target="_blank"> <video class="prompt-card-mini-background-video" muted loop> <source src="https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/32a0796a-be65-4a2d-b48e-9b01745dc706.mp4" type="video/mp4"> </video> <div class="prompt-card-mini-play-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="10" stroke="#333333" stroke-width="1.5" fill="none"/> <polygon points="10,8 16,12 10,16" stroke="#333333" stroke-width="1.5" fill="none" stroke-linejoin="round"/> </svg> </div> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=other"> 其它 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/cat_ear_hand_motion_closeup?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 猫耳动作动态特写 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/cat_ear_hand_motion_closeup?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="alien_travel_posters"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/33b173e7-4043-4a5f-8d90-b56e7025a940.png');" href="https://prompts.explinks.com/alien_travel_posters?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=design"> 设计 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/alien_travel_posters?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 外星行星旅行海报 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/alien_travel_posters?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="elegant_military_girl_portrait"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/10f3960e-3d0f-41d1-b624-3f4fd7e77567.png');" href="https://prompts.explinks.com/elegant_military_girl_portrait?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=prompt"> 提示词 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/elegant_military_girl_portrait?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 军事少女唯美肖像 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/elegant_military_girl_portrait?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="cute_3d_fluffy_penguin"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/2873f9a8-dc98-4129-922c-db01f4ca97a3.png');" href="https://prompts.explinks.com/cute_3d_fluffy_penguin?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=tool"> 工具 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/cute_3d_fluffy_penguin?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 3D蓬松小企鹅 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/cute_3d_fluffy_penguin?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="whimsical_animal_dance_video"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-video" onmouseenter="this.querySelector('video').play(); this.querySelector('.prompt-card-mini-play-icon').classList.add('hidden');" onmouseleave="this.querySelector('video').pause(); this.querySelector('.prompt-card-mini-play-icon').classList.remove('hidden');" href="https://prompts.explinks.com/whimsical_animal_dance_video?desc" target="_blank"> <video class="prompt-card-mini-background-video" muted loop> <source src="https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/ee93fd3e-69db-431a-a5f2-607ad948876d.mp4" type="video/mp4"> </video> <div class="prompt-card-mini-play-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="10" stroke="#333333" stroke-width="1.5" fill="none"/> <polygon points="10,8 16,12 10,16" stroke="#333333" stroke-width="1.5" fill="none" stroke-linejoin="round"/> </svg> </div> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=other"> 其它 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/whimsical_animal_dance_video?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 欢乐动物舞蹈动画 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/whimsical_animal_dance_video?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥12.50元</span> <span class="prompt-card-mini-original-price">¥25</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="moonlit_catwoman_portrait"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/147ae463-fb00-41f4-afcb-ea90a5ffce8b.png');" href="https://prompts.explinks.com/moonlit_catwoman_portrait?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=content_generation"> 内容生成 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/moonlit_catwoman_portrait?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 月光下的猫女肖像 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/moonlit_catwoman_portrait?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="slow_retreat_gesture_scene"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-video" onmouseenter="this.querySelector('video').play(); this.querySelector('.prompt-card-mini-play-icon').classList.add('hidden');" onmouseleave="this.querySelector('video').pause(); this.querySelector('.prompt-card-mini-play-icon').classList.remove('hidden');" href="https://prompts.explinks.com/slow_retreat_gesture_scene?desc" target="_blank"> <video class="prompt-card-mini-background-video" muted loop> <source src="https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/3594f067-1a0f-4d34-94a7-e50e3678045f.mp4" type="video/mp4"> </video> <div class="prompt-card-mini-play-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="10" stroke="#333333" stroke-width="1.5" fill="none"/> <polygon points="10,8 16,12 10,16" stroke="#333333" stroke-width="1.5" fill="none" stroke-linejoin="round"/> </svg> </div> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=animation"> 动画 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/slow_retreat_gesture_scene?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 缓步退后动作场景 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/slow_retreat_gesture_scene?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="3d_cartoon_character_design"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/d59c0946-aedc-445b-87b6-94249ba99cff.png');" href="https://prompts.explinks.com/3d_cartoon_character_design?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=3d_design"> 3D设计 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://tools.explinks.com/prompt-trial?slug=3d_cartoon_character_design&title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 活力卡通角色模型设计 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://tools.explinks.com/prompt-trial?slug=3d_cartoon_character_design&price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="gentle_breeze_serene_path"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-video" onmouseenter="this.querySelector('video').play(); this.querySelector('.prompt-card-mini-play-icon').classList.add('hidden');" onmouseleave="this.querySelector('video').pause(); this.querySelector('.prompt-card-mini-play-icon').classList.remove('hidden');" href="https://prompts.explinks.com/gentle_breeze_serene_path?desc" target="_blank"> <video class="prompt-card-mini-background-video" muted loop> <source src="https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/6f11d519-e7bd-43d1-a0ce-ebdc5d41f778.mp4" type="video/mp4"> </video> <div class="prompt-card-mini-play-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="10" stroke="#333333" stroke-width="1.5" fill="none"/> <polygon points="10,8 16,12 10,16" stroke="#333333" stroke-width="1.5" fill="none" stroke-linejoin="round"/> </svg> </div> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=other"> 其它 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/gentle_breeze_serene_path?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 微风拂动小径画面 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/gentle_breeze_serene_path?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> <div class="prompt-card-mini" data-prompt-slug="photoreal_rainy_urban_girl"> <a class="prompt-card-mini-header-section prompt-card-mini-header-section-with-bg" style="background-image: url('https://apihub-public.oss-cn-beijing.aliyuncs.com/uploads/781d828a-40cb-4930-a2cb-af46ae42ef9d.png');" href="https://prompts.explinks.com/photoreal_rainy_urban_girl?desc" target="_blank"> </a> <a class="prompt-card-mini-category-tag-positioned" href="https://prompts.explinks.com/search?biz_cat_slug=image_generation"> 图像生成 </a> <div class="prompt-card-mini-content-section"> <div class="prompt-card-mini-header"> <a href="https://prompts.explinks.com/photoreal_rainy_urban_girl?title" class="prompt-card-mini-title prompt-card-mini-title-link" target="_blank"> 雨夜都市少女肖像 </a> </div> <div class="prompt-card-mini-actions"> <div class="prompt-card-mini-price-section"> <a href="https://prompts.explinks.com/photoreal_rainy_urban_girl?price" target="_blank" style="text-decoration: none; color: inherit;"> <span class="prompt-card-mini-price-value prompt-card-mini-price-discount">¥10.00元</span> <span class="prompt-card-mini-original-price">¥20</span> <span class="prompt-card-mini-price-limited-tag">限时</span> </a> </div> </div> </div> </div> </div> <button class="slider-btn slider-btn-prev" data-direction="prev"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none"> <path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> </button> <button class="slider-btn slider-btn-next" data-direction="next"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none"> <path d="M9 18L15 12L9 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> </button> </div> </div> </div> <script src="https://static.explinks.com/prompt/static/js/home/countdown.js?v=1765352183901"></script><script src="https://static.explinks.com/prompt/static/js/home/zone-section.js?v=1765352183901"></script> <div id="footer"> <div class="footer-container"> <div class="footer-content"> <div class="footer-section"> <h5 class="footer-section-title"> 热门提示词</h5> <ul class="footer-section-links"> <li> <a href="https://prompts.explinks.com/quick_keyword_generator" target="_blank">快速关键词生成助手</a> </li> <li> <a href="https://prompts.explinks.com/content_strategy_guide" target="_blank">内容营销策略制定</a> </li> <li> <a href="https://prompts.explinks.com/rapid_test_scenario" target="_blank">快速测试场景生成器</a> </li> <li> <a href="https://prompts.explinks.com/novel_writing_guide" target="_blank">小说创作策略指南</a> </li> <li> <a href="https://prompts.explinks.com/seo_keyword_generator" target="_blank">SEO优化关键词生成助手</a> </li> <li> <a href="https://prompts.explinks.com/article_title_generator" target="_blank">文章标题生成器</a> </li> </ul> </div> <div class="footer-section"> <h5 class="footer-section-title"> 热门角色</h5> <ul class="footer-section-links"> <li> <a href="https://prompts.explinks.com/category/content_creator" target="_blank">内容创作者</a> </li> <li> <a href="https://prompts.explinks.com/category/developer" target="_blank">开发者</a> </li> <li> <a href="https://prompts.explinks.com/category/product_manager" target="_blank">产品经理</a> </li> <li> <a href="https://prompts.explinks.com/category/business_consultant" target="_blank">商业顾问</a> </li> <li> <a href="https://prompts.explinks.com/category/marketing_personnel" target="_blank">市场营销</a> </li> <li> <a href="https://prompts.explinks.com/category/business_owner" target="_blank">企业管理者</a> </li> <li> <a href="https://prompts.explinks.com/category/seo_expert" target="_blank">SEO专家</a> </li> <li> <a href="https://prompts.explinks.com/category/data_analyst" target="_blank">数据分析师</a> </li> </ul> </div> <div class="footer-section"> <h5 class="footer-section-title"> 热门业务</h5> <ul class="footer-section-links"> <li> <a href="https://prompts.explinks.com/category/developer?biz_cat_slug=code" target="_blank">代码</a> </li> <li> <a href="https://prompts.explinks.com/category/content_creator?biz_cat_slug=content_creation" target="_blank">内容创作</a> </li> <li> <a href="https://prompts.explinks.com/category/human_resources_personnel?biz_cat_slug=human_resources" target="_blank">人力资源</a> </li> <li> <a href="https://prompts.explinks.com/category/data_analyst?biz_cat_slug=data_analysis" target="_blank">数据分析</a> </li> <li> <a href="https://prompts.explinks.com/category/writer?biz_cat_slug=creative_writing" target="_blank">创意写作</a> </li> <li> <a href="https://prompts.explinks.com/category/illustrator?biz_cat_slug=art" target="_blank">艺术插画</a> </li> </ul> </div> <div class="footer-section"> <h5 class="footer-section-title"> 大模型API</h5> <ul class="footer-section-links"> <li> <a href="https://www.explinks.com/api/ai_deepseek_brand" target="_blank">DeepSeek</a> </li> <li> <a href="https://www.explinks.com/api/ai_openai_brand" target="_blank">OpenAI</a> </li> <li> <a href="https://www.explinks.com/api/ai_anthropic_brand" target="_blank">Claude</a> </li> <li> <a href="https://www.explinks.com/api/ai_gemini_brand" target="_blank">Gemini</a> </li> <li> <a href="https://www.explinks.com/api/ai_grok_brand" target="_blank">Grok</a> </li> <li> <a href="https://www.explinks.com/api/ai_tongyi_brand" target="_blank">Qwen</a> </li> </ul> </div> <div class="footer-section"> <h5 class="footer-section-title"> 使用我们的提示词工具</h5> <ul class="footer-section-links"> <li> <a target="_blank">提示词API化工具(敬请期待)</a> </li> <li> <a href="https://tools.explinks.com/prompt-generator" target="_blank">提示词应用工具</a> </li> <li> <a href="https://console.explinks.com/myHome/prompts" target="_blank">我的提示词库</a> </li> <li> <a href="https://prompts.explinks.com/packs/partners" target="_blank">加入分销计划,零成本获得收益</a> </li> </ul> </div> </div> <div class="footer-bottom"> <div class="footer-brand"> <div class="footer-brand-logo"> <a href="https://www.explinks.com/" target="_blank"> <figure class="footer-logo-wrapper"> <img decoding="async" src="https://cdn.explinks.com/wp-content/uploads/2023/12/image-e1703756327221.png" alt="幂简集成ICON" class="footer-logo-img"/> </figure> </a> </div> </div> <div class="footer-bottom-left"> <div class="footer-copyright"> <p>Copyright © 2024 All Rights Reserved <a href="https://www.explinks.com/company/about" target="_blank">北京蜜堂有信科技有限公司</a></p> </div> <div class="footer-address"> <p>公司地址: 北京市朝阳区光华路和乔大厦C座1508</p> </div> </div> <div class="footer-bottom-right"> <div class="footer-license-info"> <div class="footer-license-item"> <p>增值电信业务经营许可证:京B2-20191889</p> </div> <div class="footer-license-icon"> <img decoding="async" src="https://cdn.explinks.com/wp-content/uploads/2023/12/police.png" alt="icon" class="footer-police-icon"/> </div> <div class="footer-license-item"> <p><a href="https://beian.miit.gov.cn/" target="_blank" rel="nofollow">京ICP备18034931号-7</a></p> </div> </div> <div class="footer-feedback"> <p>意见反馈:010-53324933,mtyy@miitang.com</p> </div> </div> </div> </div> </div> <div class="sidebar-components-container"> <div class="sidebar"> <button class="sidebar-button" title="反馈问题" id="feedbackButton"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"/> <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/> <line x1="12" y1="17" x2="12" y2="17"/> </svg> </button> <button class="sidebar-button up-button" title="返回顶部"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M12 19V5"/> <path d="M5 12l7-7 7 7"/> </svg> </button> </div> <div class="toast" id="comingSoonToast"> <div class="toast-content"> <span>敬请期待...</span> <button class="toast-close">×</button> </div> </div> <div class="modal" id="feedbackModal"> <div class="modal-content"> <div class="modal-header"> <h3 class="modal-title">反馈问题</h3> <button class="modal-close">×</button> </div> <form id="feedbackForm"> <div class="form-group"> <label class="form-label" for="description">描述 <span class="required">*</span></label> <textarea class="form-control" id="description" maxlength="200" required placeholder="请在此描述您要反馈的问题(200字以内)"></textarea> </div> <div class="form-group"> <label class="form-label">截图</label> <div class="image-upload" id="imageUpload"> <input type="file" id="imageInput" multiple accept="image/*" style="display: none;"> <p>点击或拖拽图片到此处上传(最多5张)</p> </div> <div class="image-preview" id="imagePreview"></div> </div> <div class="form-group"> <label class="form-label" for="contact">联系方式</label> <input type="text" class="form-control" id="contact" placeholder="QQ/邮箱/任选其一"> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" id="cancelButton">取消</button> <button type="submit" class="btn btn-primary">确定</button> </div> </form> </div> </div> <script> document.addEventListener('DOMContentLoaded', () => { const upButton = document.querySelector('.up-button'); const toast = document.getElementById('comingSoonToast'); const toastClose = document.querySelector('.toast-close'); const feedbackButton = document.getElementById('feedbackButton'); const feedbackModal = document.getElementById('feedbackModal'); const modalClose = feedbackModal.querySelector('.modal-close'); const cancelButton = document.getElementById('cancelButton'); const feedbackForm = document.getElementById('feedbackForm'); const imageUpload = document.getElementById('imageUpload'); const imageInput = document.getElementById('imageInput'); const imagePreview = document.getElementById('imagePreview'); let toastTimeout; // 返回顶部按钮逻辑 window.addEventListener('scroll', () => { if (window.scrollY > 300) { upButton.classList.add('show'); } else { upButton.classList.remove('show'); } }); upButton.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); // 显示提示框函数 function showToast() { clearTimeout(toastTimeout); toast.classList.add('show'); toastTimeout = setTimeout(() => { toast.classList.remove('show'); }, 3000); } // 关闭提示框 toastClose.addEventListener('click', () => { toast.classList.remove('show'); clearTimeout(toastTimeout); }); // // 为三个按钮添加点击事件 // const comingSoonButtons = [ // document.querySelector('button[title="AI助理"]'), // document.querySelector('button[title="最近浏览"]'), // document.querySelector('button[title="对比列表"]') // ]; // // comingSoonButtons.forEach(button => { // button.addEventListener('click', showToast); // }); // 反馈模态框相关逻辑 function openModal() { feedbackModal.classList.add('show'); document.body.style.overflow = 'hidden'; } function closeModal() { feedbackModal.classList.remove('show'); document.body.style.overflow = ''; feedbackForm.reset(); imagePreview.innerHTML = ''; } feedbackButton.addEventListener('click', openModal); modalClose.addEventListener('click', closeModal); cancelButton.addEventListener('click', closeModal); // 点击模态框外部关闭 feedbackModal.addEventListener('click', (e) => { if (e.target === feedbackModal) { closeModal(); } }); // 图片上传相关逻辑 imageUpload.addEventListener('click', () => { imageInput.click(); }); imageInput.addEventListener('change', handleImageUpload); function handleImageUpload() { const files = Array.from(imageInput.files); const existingImages = imagePreview.querySelectorAll('.image-container'); const totalImages = existingImages.length + files.length; if (totalImages > 5) { alert('最多只能上传5张图片'); return; } files.forEach(file => { if (!file.type.startsWith('image/')) { return; } const reader = new FileReader(); reader.onload = (e) => { const container = document.createElement('div'); container.className = 'image-container'; const img = document.createElement('img'); img.src = e.target.result; container.appendChild(img); const deleteBtn = document.createElement('button'); deleteBtn.className = 'image-delete'; deleteBtn.innerHTML = '×'; deleteBtn.title = '删除图片'; deleteBtn.onclick = function () { if (confirm('确定要删除这张图片吗?')) { container.remove(); } }; container.appendChild(deleteBtn); imagePreview.appendChild(container); }; reader.readAsDataURL(file); }); } // 拖拽上传 imageUpload.addEventListener('dragover', (e) => { e.preventDefault(); imageUpload.style.borderColor = '#4a90e2'; }); imageUpload.addEventListener('dragleave', () => { imageUpload.style.borderColor = '#ddd'; }); imageUpload.addEventListener('drop', (e) => { e.preventDefault(); imageUpload.style.borderColor = '#ddd'; const files = Array.from(e.dataTransfer.files); if (files.length > 5) { alert('最多只能上传5张图片'); return; } imageInput.files = e.dataTransfer.files; handleImageUpload(); }); // 表单提交 feedbackForm.addEventListener('submit', async (e) => { e.preventDefault(); const description = document.getElementById('description').value.trim(); const contact = document.getElementById('contact').value.trim(); // if (!description || !contact) { if (!description) { alert('请填写反馈内容!'); return; } // 收集图片数据 const images = []; const imageElements = imagePreview.querySelectorAll('img'); imageElements.forEach(img => { images.push(img.src); }); // 准备要提交的数据 const formData = { description, contact, images }; try { const response = await fetch('https://api.explinks.com/feedback/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); if (response.ok) { alert('反馈提交成功!'); closeModal(); } else { throw new Error('提交失败'); } } catch (error) { alert('提交失败,请稍后重试!'); console.error('提交表单时出错:', error); } }); }); </script> </div> <script src="https://static.explinks.com/prompt/static/js/statistics/index.js?v=1765352183901"></script> <script src="https://static.explinks.com/hub/static/script/baidu-analytics.js?v=1765352183901"></script> <script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script> <script>LA.init({id: "3NS00J5GfuD7Tvg6", ck: "3NS00J5GfuD7Tvg6"})</script> <script> (function(){ var h = window.location.hostname; var d = ''; try { var p = h.split('.'); if (p.length >= 2) { d = '.' + p.slice(-2).join('.'); } } catch (e) {} var isIp = /^\d{1,3}(?:\.\d{1,3}){3}$/.test(h); var domainAttr = (!isIp && h !== 'localhost' && d) ? ('; domain=' + d) : ''; function getCookie(name){ var cs = document.cookie ? document.cookie.split('; ') : []; for (var i=0;i<cs.length;i++){ var parts = cs[i].split('='); var key = parts.shift(); var value = parts.join('='); if (key === name){ return decodeURIComponent(value || ''); } } return null; } function delCookie(name){ document.cookie = name + '=; path=/; Max-Age=0; SameSite=Lax' + domainAttr; } window.clearPromotionCookie = function(){ delCookie('promotion-code'); delCookie('promotion-code-set'); }; var setTs = getCookie('promotion-code-set'); var setNum = Number(setTs); if (setTs && String(setNum) === setTs) { fetch('https://prompts.explinks.com/api/user/should-clear-promotion-code', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ setTimeMillis: setTs, referer: document.referrer || '' }) }) .then(function(r){ return r ? r.json() : null; }) .then(function(res){ if (res && (res.success === true || res.code === 200) && (res.data === true || res.data === 'true')) { window.clearPromotionCookie(); } }) .catch(function(){}); } var u = new URL(window.location.href); var v = (u.searchParams.get('ref') || u.searchParams.get('REF') || '').split('?')[0].trim(); if (v) { (function(){ var api = 'https://prompts.explinks.com/api/user/validate-promotion-code'; var payload = { promotionCode: v, referer: document.referrer || '' }; fetch(api, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) .then(function(r){ return r ? r.json() : null; }) .then(function(res){ if (res && (res.success === true || res.code === 200) && (res.data === true || res.data === 'true')) { var ttl = 7200; var setTime = Date.now(); document.cookie = 'promotion-code=' + encodeURIComponent(v) + '; path=/; Max-Age=' + ttl + '; SameSite=Lax' + domainAttr; document.cookie = 'promotion-code-set=' + encodeURIComponent(String(setTime)) + '; path=/; Max-Age=' + ttl + '; SameSite=Lax' + domainAttr; } }) .catch(function(){}); })(); } })(); </script> <script src="https://static.explinks.com/prompt/static/js/statistics/view-count.js?v=1765352183901"></script> <script> // 页面加载完成后自动记录浏览量 ViewCountStatistics.autoRecordViewCount('js_async_function_generator'); </script> <script src="https://static.explinks.com/prompt/static/js/prompt-detail-v2/countdown.js?v=1765352183901"></script> <!-- Highlight.js JavaScript --> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> <script> // 初始化代码高亮 document.addEventListener('DOMContentLoaded', function () { hljs.highlightAll(); }); </script> </body> </html>