diff --git a/src/components/md-table.tsx b/src/components/md-table.tsx index dd4cb7d..1ce9ef7 100644 --- a/src/components/md-table.tsx +++ b/src/components/md-table.tsx @@ -3,6 +3,7 @@ import { createSignal, For, Show, createEffect, createMemo, createResource } fro import { marked } from '../markdown'; import { loadCSV, CSV, processVariables, isCSV } from './utils/csv-loader'; import { resolvePath } from './utils/path'; +import { areAllLabelsNumeric, weightedRandomIndex } from './utils/weighted-random'; export interface TableProps { roll?: boolean; @@ -108,8 +109,23 @@ customElement('md-table', { roll: false, remix: false }, (props, { element }) => // 随机切换 tab const handleRoll = () => { - const randomIndex = Math.floor(Math.random() * filteredRows().length); - setActiveTab(randomIndex); + const filtered = filteredRows(); + if (filtered.length === 0) return; + + // 检查所有 label 是否都是整数/整数范围格式 + const labels = filtered.map(row => row.label); + if (areAllLabelsNumeric(labels)) { + // 使用加权随机 + const randomIndex = weightedRandomIndex(labels); + if (randomIndex !== -1) { + setActiveTab(randomIndex); + } + } else { + // 使用简单随机 + const randomIndex = Math.floor(Math.random() * filtered.length); + setActiveTab(randomIndex); + } + // 滚动到可视区域 setTimeout(() => { const activeButton = tabsContainer?.querySelector('.border-blue-600'); diff --git a/src/components/utils/weighted-random.ts b/src/components/utils/weighted-random.ts new file mode 100644 index 0000000..86da653 --- /dev/null +++ b/src/components/utils/weighted-random.ts @@ -0,0 +1,88 @@ +/** + * 解析整数范围字符串 + * @param label 标签字符串,可能是单个整数(如 "1")或整数范围(如 "1-3") + * @returns 如果成功解析返回 [min, max],否则返回 null + */ +export function parseIntegerRange(label: string): [number, number] | null { + const trimmed = label.trim(); + + // 尝试匹配整数范围格式 (如 "1-3") + const rangeMatch = trimmed.match(/^(\d+)\s*-\s*(\d+)$/); + if (rangeMatch) { + const min = parseInt(rangeMatch[1]!, 10); + const max = parseInt(rangeMatch[2]!, 10); + if (min <= max) { + return [min, max]; + } + } + + // 尝试匹配单个整数格式 (如 "5") + const intMatch = trimmed.match(/^(\d+)$/); + if (intMatch) { + const num = parseInt(intMatch[1]!, 10); + return [num, num]; + } + + return null; +} + +/** + * 计算加权随机的总权重 + * @param labels 标签数组 + * @returns 总权重值 + */ +export function calculateTotalWeight(labels: string[]): number { + let total = 0; + for (const label of labels) { + const range = parseIntegerRange(label); + if (range) { + const [min, max] = range; + total += max - min + 1; + } + } + return total; +} + +/** + * 根据权重随机选择一个索引 + * @param labels 标签数组 + * @param totalWeight 总权重(可选,会自行计算) + * @returns 选中的索引,如果没有有效权重则返回 -1 + */ +export function weightedRandomIndex(labels: string[], totalWeight?: number): number { + const weight = totalWeight ?? calculateTotalWeight(labels); + + if (weight === 0) { + return -1; + } + + // 生成一个随机权重值 (1 到 totalWeight) + const roll = Math.floor(Math.random() * weight) + 1; + + let cumulative = 0; + for (let i = 0; i < labels.length; i++) { + const range = parseIntegerRange(labels[i]!); + if (range) { + const [min, max] = range; + const labelWeight = max - min + 1; + cumulative += labelWeight; + + if (roll <= cumulative) { + return i; + } + } + } + + // 理论上不会到这里,但为了安全返回最后一个 + return labels.length - 1; +} + +/** + * 检查所有 label 是否都是整数或整数范围格式 + * @param labels 标签数组 + * @returns 如果所有 label 都是整数/整数范围格式返回 true + */ +export function areAllLabelsNumeric(labels: string[]): boolean { + if (labels.length === 0) return false; + return labels.every(label => parseIntegerRange(label) !== null); +} diff --git a/src/markdown/table.ts b/src/markdown/table.ts index effbd7a..0388061 100644 --- a/src/markdown/table.ts +++ b/src/markdown/table.ts @@ -57,7 +57,6 @@ export default function markedTable(): MarkedExtension { const csvData = tableToCSV(headers, rows); // 渲染为 md-table 组件,内联 CSV 数据 - console.log(csvData) return `${csvData}\n`; } }