All checks were successful
Build and Deploy Front / deploy (push) Successful in 5m49s
理顺模板配置流程与文案,并统一左侧菜单对比度、会员/券/用户编辑抽屉与因子标签展示。 Co-authored-by: Cursor <cursoragent@cursor.com>
121 lines
4.2 KiB
TypeScript
121 lines
4.2 KiB
TypeScript
/**
|
||
* 卖价/买价重量因素:左开右闭区间 (start, end]。
|
||
* 界面与快照统一存为 (0-0.5] 形式,便于 0.312 等重量命中。
|
||
*/
|
||
|
||
/** 重量因素值下拉:0.5 步进至 30,其后固定 999(卖价/买价共用) */
|
||
export function buildWeightFactorSelectOptions(): Array<{ label: string; value: string }> {
|
||
return [
|
||
...Array.from({ length: 60 }, (_, index) => {
|
||
const v = ((index + 1) * 0.5).toFixed(1).replace(/\.0$/, '');
|
||
return { label: v, value: v };
|
||
}),
|
||
{ label: '999', value: '999' },
|
||
];
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
function factorValueSortNumber(v: string): number | null {
|
||
const parsed = parseWeightIntervalBounds(v);
|
||
if (parsed) return parsed.end;
|
||
const n = Number.parseFloat(String(v).trim());
|
||
return Number.isFinite(n) ? n : null;
|
||
}
|
||
|
||
/** 按数值大小升序排列因素值(如 2, 0.5, 1 → 0.5, 1, 2)。 */
|
||
export function sortFactorValuesBySize(values: string[] | undefined): string[] {
|
||
return [...(values ?? [])].sort((a, b) => {
|
||
const na = factorValueSortNumber(String(a));
|
||
const nb = factorValueSortNumber(String(b));
|
||
if (na != null && nb != null && na !== nb) return na - nb;
|
||
if (na != null && nb == null) return -1;
|
||
if (na == null && nb != null) return 1;
|
||
return String(a).localeCompare(String(b), undefined, { numeric: true, sensitivity: 'base' });
|
||
});
|
||
}
|
||
|
||
/** 提取因素值列表中的重量上限并升序排列(用于区间左界)。 */
|
||
export function weightFactorValuesToSortedUpperBounds(values: string[]): number[] {
|
||
const nums = (values ?? [])
|
||
.map((v) => factorValueSortNumber(v))
|
||
.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);
|
||
}
|