- 新增小红书前端模块(启动服务、内容分析、爆款创作、发布记录、技能卡片、拟人设置、AI 助手) - 新增 xhs-agent-desktop 独立桌面 Agent,对接 OMS 任务调度 - 登录页重构,整合微信登录与 OMS JWT 认证 - 订单、渠道、用户中心等页面与 API 增强 Co-authored-by: Cursor <cursoragent@cursor.com>
96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
/**
|
||
* 卖价/买价重量因素:左开右闭区间 (start, end]。
|
||
* 界面与快照统一存为 (0-0.5] 形式,便于 0.312 等重量命中。
|
||
*/
|
||
|
||
export function isWeightPricingFactor(factorKey: string): boolean {
|
||
const k = String(factorKey ?? '').trim();
|
||
if (!k) return false;
|
||
if (k === '重量' || k === 'weight') return true;
|
||
return false;
|
||
}
|
||
|
||
/** 解析重量单元格或因素值为区间上下界;单值 0.5 视为 (0, 0.5]。 */
|
||
export function parseWeightIntervalBounds(text: string): { start: number; end: number } | null {
|
||
const trimmed = String(text ?? '').trim();
|
||
if (!trimmed) return null;
|
||
|
||
const stripped = trimmed.replace(/^\(/, '').replace(/\]$/, '').replace(/^\[/, '');
|
||
const parts = stripped
|
||
.split(/[,,\-~~]/)
|
||
.map((s) => s.trim())
|
||
.filter(Boolean);
|
||
|
||
if (parts.length >= 2) {
|
||
const start = Number.parseFloat(parts[0]);
|
||
const end = Number.parseFloat(parts[1]);
|
||
if (Number.isFinite(start) && Number.isFinite(end) && end > start) {
|
||
return { start, end };
|
||
}
|
||
}
|
||
|
||
const single = Number.parseFloat(parts[0] ?? stripped);
|
||
if (Number.isFinite(single) && single > 0) {
|
||
return { start: 0, end: single };
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
function formatWeightNumber(n: number): string {
|
||
const s = n.toFixed(4).replace(/\.?0+$/, '');
|
||
return s === '' ? '0' : s;
|
||
}
|
||
|
||
/** 格式化为 (start-end] 展示与存储。 */
|
||
export function formatWeightIntervalLeftOpenRightClosed(start: number, end: number): string {
|
||
return `(${formatWeightNumber(start)}-${formatWeightNumber(end)}]`;
|
||
}
|
||
|
||
/** 将单值或旧格式转为区间字符串 (start-end]。 */
|
||
export function weightValueToIntervalCell(
|
||
rawValue: string,
|
||
allWeightValuesInFactor: string[]
|
||
): string {
|
||
const trimmed = String(rawValue ?? '').trim();
|
||
if (!trimmed) return '';
|
||
|
||
const parsed = parseWeightIntervalBounds(trimmed);
|
||
if (
|
||
parsed &&
|
||
(trimmed.includes('-') || trimmed.includes(',') || trimmed.includes(',') || trimmed.includes(']'))
|
||
) {
|
||
return formatWeightIntervalLeftOpenRightClosed(parsed.start, parsed.end);
|
||
}
|
||
|
||
const bounds = weightFactorValuesToSortedUpperBounds(allWeightValuesInFactor);
|
||
const end = Number.parseFloat(trimmed);
|
||
if (!Number.isFinite(end)) return trimmed;
|
||
|
||
const idx = bounds.findIndex((n) => Math.abs(n - end) < 1e-9);
|
||
const start = idx > 0 ? bounds[idx - 1]! : 0;
|
||
const upper = idx >= 0 ? bounds[idx]! : end;
|
||
return formatWeightIntervalLeftOpenRightClosed(start, upper);
|
||
}
|
||
|
||
/** 提取因素值列表中的重量上限并升序排列(用于区间左界)。 */
|
||
export function weightFactorValuesToSortedUpperBounds(values: string[]): number[] {
|
||
const nums = (values ?? [])
|
||
.map((v) => {
|
||
const parsed = parseWeightIntervalBounds(v);
|
||
if (parsed) return parsed.end;
|
||
const n = Number.parseFloat(String(v).trim());
|
||
return Number.isFinite(n) ? n : null;
|
||
})
|
||
.filter((n): n is number => n != null);
|
||
return [...new Set(nums)].sort((a, b) => a - b);
|
||
}
|
||
|
||
/** 提交快照/API 时规范化重量单元格。 */
|
||
export function canonicalWeightFactorCellForApi(
|
||
rawValue: string,
|
||
allWeightValuesInFactor: string[]
|
||
): string {
|
||
return weightValueToIntervalCell(rawValue, allWeightValuesInFactor);
|
||
}
|