项目文件夹

文件
wehub-resource-sync 9b395f5cc3
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:48 +08:00

116 行
4.7 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
/**
* dongchedi specs — key configuration overview (配置概览) of a car series.
*
* Reads `pageProps.overviewData` from the SSR series page: body dimensions,
* powertrain (engine / gearbox / 0-100), drivetrain + suspension, and
* airbags. This is the unsigned SSR overview — the full per-trim parameter
* sheet sits behind a ByteDance-signed XHR and is deliberately not faked.
*/
import { cli, Strategy } from '@jackwener/opencli/registry';
import { CommandExecutionError } from '@jackwener/opencli/errors';
import {
SPECS_COLUMNS,
assertPlainObject,
clean,
dcdFetchPageProps,
normalizeSeriesId,
} from './utils.js';
/** Distinct, cleaned, non-empty values for a key across an array of objects. */
function uniqueVals(arr, key) {
const out = [];
for (const o of (Array.isArray(arr) ? arr : [])) {
const v = clean(o?.[key]);
if (v && !out.includes(v)) out.push(v);
}
return out;
}
/** Build a min-max range string from a numeric field across rows ("6.4-7.2s"). */
function rangeOf(arr, key, suffix = '') {
const nums = (Array.isArray(arr) ? arr : [])
.map((o) => parseFloat(String(o?.[key]).replace(/[^\d.]/g, '')))
.filter((n) => Number.isFinite(n));
if (nums.length === 0) return '';
const lo = Math.min(...nums); const hi = Math.max(...nums);
return lo === hi ? `${lo}${suffix}` : `${lo}-${hi}${suffix}`;
}
/** Summarize airbag coverage into a readable list. */
function summarizeAirbags(airbagArr) {
const a = (Array.isArray(airbagArr) ? airbagArr : []);
if (a.length === 0) return '';
const any = (key) => a.some((x) => x?.[key]);
const labels = [
['main_airbag', '主'], ['vice_airbag', '副'], ['front_airbag', '前'],
['rear_airbag', '后'], ['side_air_curtain', '侧气帘'],
['main_knee_airbag', '主膝部'], ['vice_knee_airbag', '副膝部'],
];
const present = labels.filter(([k]) => any(k)).map(([, label]) => label);
return present.length ? present.join('/') + '气囊' : '';
}
/**
* Pure parser: overviewData → field/value rows. Exported for unit tests.
*/
export function parseSpecs(overviewData, seriesId) {
const ov = assertPlainObject(overviewData, 'dongchedi overviewData');
const space0 = (Array.isArray(ov.space) && ov.space[0]) || {};
const power = ov.power || {};
const powerItems = power.power_item || [];
const manip0 = (Array.isArray(ov.manipulation) && ov.manipulation[0]) || {};
const dims = (space0.length && space0.width && space0.height)
? `${space0.length} × ${space0.width} × ${space0.height} mm`
: '';
const drivetrain = [clean(manip0.driver_form), clean(manip0.fourwheel_drive_type)]
.filter(Boolean).join(' · ');
const suspension = (manip0.front_suspension_form || manip0.rear_suspension_form)
? `前 ${clean(manip0.front_suspension_form) || '?'} / 后 ${clean(manip0.rear_suspension_form) || '?'}`
: '';
const fields = [
['series_id', String(seriesId)],
['dimensions', dims],
['wheelbase', space0.wheelbase ? `${space0.wheelbase} mm` : ''],
['power', clean(power.overview)],
['engine', uniqueVals(powerItems, 'engine_description').join(' / ')],
['gearbox', uniqueVals(powerItems, 'gearbox_description').join(' / ')],
['energy', uniqueVals(powerItems, 'fuel_form').join(' / ')],
['acceleration', rangeOf(powerItems, 'acceleration_time', 's')],
['drivetrain', drivetrain],
['suspension', suspension],
['airbags', summarizeAirbags(ov.airbag)],
];
return fields.map(([field, value]) => ({ field, value }));
}
cli({
site: 'dongchedi',
name: 'specs',
access: 'read',
aliases: ['config'],
description: '懂车帝车系配置概览(尺寸 / 动力 / 发动机 / 变速箱 / 四驱 / 悬挂 / 气囊)',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'series_id', required: true, positional: true, help: '车系 ID(来自 search 的 series_id,或 /auto/series/<id> URL' },
],
columns: SPECS_COLUMNS,
func: async (args) => {
const seriesId = normalizeSeriesId(args.series_id);
const pp = await dcdFetchPageProps(`/auto/series/${seriesId}`, `specs ${seriesId}`);
const rows = parseSpecs(pp.overviewData, seriesId);
// Every series has at least dimensions/power; an all-empty sheet means
// the overview block was absent (layout drift) — don't emit a blank sheet.
if (rows.every((r) => r.field === 'series_id' || !r.value)) {
throw new CommandExecutionError(
`dongchedi specs ${seriesId}`,
'No spec overview found for this series (layout may have changed).',
);
}
return rows;
},
});