/** * 领域接口服务的共用底座(票 05)。 * * 三个领域模块(`mapApi` / `detailApi` / `runtimeApi`)都做同一件事: * 拼查询串 → 走票 04 的 `requestCockpitJson` → 用 `sectionFromResult` 收成一个分区。 * 这段样板收在这里,领域模块只留「路径 + 转换函数 + 空判据」。 * * 分工不变(票 04 的票面):地址拼接、凭据、mock/real 切换全在 `cockpitApi.js`; * 本文件与领域模块**不出现** `Authorization`,也读不到令牌。 */ import { requestCockpitJson } from './cockpitApi.js' import { sectionFromResult } from '../domain/sectionState.js' /** 拼查询串;空值(undefined/null/'')跳过,数组按重复键展开。 */ export function buildQuery(params) { const search = new URLSearchParams() for (const [key, value] of Object.entries(params || {})) { if (value === undefined || value === null || value === '') continue if (Array.isArray(value)) { for (const item of value) { if (item !== undefined && item !== null && item !== '') search.append(key, String(item)) } continue } search.append(key, String(value)) } const text = search.toString() return text ? `?${text}` : '' } /** * 取一个分区。 * @param {string} path 领域路径 * @param {object} [options] * @param {object} [options.params] 查询参数 * @param {(raw: any) => any} [options.toData] 载荷 → 领域对象 * @param {(raw: any) => boolean} [options.isEmpty] 空数据判据 * @param {(raw: any) => string} [options.freshnessOf] 取新鲜度字符串 * @param {object} [options.config] / [options.fetchImpl] 单测注入 */ export async function fetchSection(path, options = {}) { const result = await requestCockpitJson(`${path}${buildQuery(options.params)}`, { config: options.config, fetchImpl: options.fetchImpl, }) return sectionFromResult(result, { toData: options.toData, isEmpty: options.isEmpty, freshnessOf: options.freshnessOf, }) } /** 「列表长度为 0」这类通用空判据。 */ export function isEmptyBy(predicate) { return (payload) => { try { return !!predicate(payload) } catch { // 载荷形状意外时不要当成空——交给转换层记 missing,别静默显示「无数据」。 return false } } }