const { formatMagnitudeNumber, formatScaledValue, getOption, normalizeIndex, parsePositiveNumber, selectBestUnit } = require('../calculator-helpers.js') const TWO_PI = 2 * Math.PI const DIAGRAM_COMPONENT_SRC = { c: '/assets/filter-diagram/capacitor-h.svg', l: '/assets/filter-diagram/inductor-h.svg', r: '/assets/filter-diagram/resistor-h.svg' } const NETWORK_OPTIONS = [ { key: 'rc', label: 'RC' }, { key: 'rl', label: 'RL' } ] const RESPONSE_OPTIONS = [ { key: 'lowpass', label: '低通' }, { key: 'highpass', label: '高通' } ] const RESISTANCE_UNIT_OPTIONS = [ { label: 'mΩ', factor: 1e-3 }, { label: 'Ω', factor: 1 }, { label: 'kΩ', factor: 1e3 }, { label: 'MΩ', factor: 1e6 }, { label: 'GΩ', factor: 1e9 } ] const CAPACITANCE_UNIT_OPTIONS = [ { label: 'pF', factor: 1e-12 }, { label: 'nF', factor: 1e-9 }, { label: 'μF', factor: 1e-6 }, { label: 'mF', factor: 1e-3 }, { label: 'F', factor: 1 } ] const INDUCTANCE_UNIT_OPTIONS = [ { label: 'pH', factor: 1e-12 }, { label: 'nH', factor: 1e-9 }, { label: 'μH', factor: 1e-6 }, { label: 'mH', factor: 1e-3 }, { label: 'H', factor: 1 }, { label: 'kH', factor: 1e3 } ] const FREQUENCY_UNIT_OPTIONS = [ { label: 'Hz', factor: 1 }, { label: 'kHz', factor: 1e3 }, { label: 'MHz', factor: 1e6 }, { label: 'GHz', factor: 1e9 }, { label: 'THz', factor: 1e12 } ] function getReactiveUnitOptions(networkKey) { return networkKey === 'rl' ? INDUCTANCE_UNIT_OPTIONS : CAPACITANCE_UNIT_OPTIONS } function getDiagramComponentSrc(componentKey) { return DIAGRAM_COMPONENT_SRC[componentKey] || DIAGRAM_COMPONENT_SRC.r } function getDiagramComponentLabel(componentKey) { return String(componentKey || '').toUpperCase() } function getDiagramComponents(networkKey, responseKey) { if (networkKey === 'rc' && responseKey === 'highpass') { return { series: 'c', shunt: 'r' } } if (networkKey === 'rl' && responseKey === 'lowpass') { return { series: 'l', shunt: 'r' } } if (networkKey === 'rl' && responseKey === 'highpass') { return { series: 'r', shunt: 'l' } } return { series: 'r', shunt: 'c' } } function calculateMissingValue(networkKey, values) { const hasResistance = Number.isFinite(values.resistance) const hasReactive = Number.isFinite(values.reactive) const hasFrequency = Number.isFinite(values.frequency) const validCount = [hasResistance, hasReactive, hasFrequency].filter(Boolean).length if (validCount < 2) { return { computedKey: '', errorText: '' } } if (validCount > 2) { return { computedKey: '', errorText: '保留两项,清空一项用于计算' } } if (networkKey === 'rl') { if (!hasResistance) { return { computedKey: 'resistance', value: TWO_PI * values.frequency * values.reactive } } if (!hasReactive) { return { computedKey: 'reactive', value: values.resistance / (TWO_PI * values.frequency) } } return { computedKey: 'frequency', value: values.resistance / (TWO_PI * values.reactive) } } if (!hasResistance) { return { computedKey: 'resistance', value: 1 / (TWO_PI * values.frequency * values.reactive) } } if (!hasReactive) { return { computedKey: 'reactive', value: 1 / (TWO_PI * values.frequency * values.resistance) } } return { computedKey: 'frequency', value: 1 / (TWO_PI * values.resistance * values.reactive) } } function buildState(source = {}) { const networkIndex = normalizeIndex(source.filterNetworkIndex, NETWORK_OPTIONS, 0) const responseIndex = normalizeIndex(source.filterResponseIndex, RESPONSE_OPTIONS, 0) const network = getOption(NETWORK_OPTIONS, networkIndex) const response = getOption(RESPONSE_OPTIONS, responseIndex) const reactiveUnitOptions = getReactiveUnitOptions(network.key) const resistanceUnitIndex = normalizeIndex(source.filterResistanceUnitIndex, RESISTANCE_UNIT_OPTIONS, 1) const capacitanceUnitIndex = normalizeIndex(source.filterCapacitanceUnitIndex, CAPACITANCE_UNIT_OPTIONS, 1) const inductanceUnitIndex = normalizeIndex(source.filterInductanceUnitIndex, INDUCTANCE_UNIT_OPTIONS, 3) const frequencyUnitIndex = normalizeIndex(source.filterFrequencyUnitIndex, FREQUENCY_UNIT_OPTIONS, 0) const reactiveUnitIndex = network.key === 'rl' ? inductanceUnitIndex : capacitanceUnitIndex const resistanceUnit = getOption(RESISTANCE_UNIT_OPTIONS, resistanceUnitIndex) const reactiveUnit = getOption(reactiveUnitOptions, reactiveUnitIndex) const frequencyUnit = getOption(FREQUENCY_UNIT_OPTIONS, frequencyUnitIndex) const resistanceText = String(source.filterResistanceValue || '') const reactiveText = String(source.filterReactiveValue || '') const frequencyText = String(source.filterFrequencyValue || '') const resistanceNumber = parsePositiveNumber(resistanceText) const reactiveNumber = parsePositiveNumber(reactiveText) const frequencyNumber = parsePositiveNumber(frequencyText) const invalidInput = [resistanceNumber, reactiveNumber, frequencyNumber].some((value) => Number.isNaN(value)) const values = { frequency: Number.isFinite(frequencyNumber) ? frequencyNumber * frequencyUnit.factor : null, reactive: Number.isFinite(reactiveNumber) ? reactiveNumber * reactiveUnit.factor : null, resistance: Number.isFinite(resistanceNumber) ? resistanceNumber * resistanceUnit.factor : null } const calculated = invalidInput ? { computedKey: '', errorText: '输入值需大于 0' } : calculateMissingValue(network.key, values) const computedValue = Number.isFinite(calculated.value) ? calculated.value : null const diagram = getDiagramComponents(network.key, response.key) let resistanceDisplayUnit = resistanceUnit let resistanceDisplayUnitIndex = resistanceUnitIndex let reactiveDisplayUnit = reactiveUnit let reactiveDisplayUnitIndex = reactiveUnitIndex let frequencyDisplayUnit = frequencyUnit let frequencyDisplayUnitIndex = frequencyUnitIndex let resistanceDisplayValue = Number.isFinite(resistanceNumber) ? formatMagnitudeNumber(resistanceNumber, { fallbackText: '' }) : resistanceText let reactiveDisplayValue = Number.isFinite(reactiveNumber) ? formatMagnitudeNumber(reactiveNumber, { fallbackText: '' }) : reactiveText let frequencyDisplayValue = Number.isFinite(frequencyNumber) ? formatMagnitudeNumber(frequencyNumber, { fallbackText: '' }) : frequencyText if (computedValue !== null && calculated.computedKey === 'resistance') { const selected = selectBestUnit(RESISTANCE_UNIT_OPTIONS, computedValue, resistanceUnitIndex) resistanceDisplayUnit = selected.unit resistanceDisplayUnitIndex = selected.index resistanceDisplayValue = formatScaledValue(computedValue, resistanceDisplayUnit, { fallbackText: '' }) } else if (computedValue !== null && calculated.computedKey === 'reactive') { const selected = selectBestUnit(reactiveUnitOptions, computedValue, reactiveUnitIndex) reactiveDisplayUnit = selected.unit reactiveDisplayUnitIndex = selected.index reactiveDisplayValue = formatScaledValue(computedValue, reactiveDisplayUnit, { fallbackText: '' }) } else if (computedValue !== null && calculated.computedKey === 'frequency') { const selected = selectBestUnit(FREQUENCY_UNIT_OPTIONS, computedValue, frequencyUnitIndex) frequencyDisplayUnit = selected.unit frequencyDisplayUnitIndex = selected.index frequencyDisplayValue = formatScaledValue(computedValue, frequencyDisplayUnit, { fallbackText: '' }) } return { filterCapacitanceUnitIndex: network.key === 'rc' ? reactiveDisplayUnitIndex : capacitanceUnitIndex, filterComputedKey: calculated.computedKey || '', filterErrorText: calculated.errorText || '', filterFrequencyDisplayValue: frequencyDisplayValue, filterFrequencyUnitIndex: frequencyDisplayUnitIndex, filterFrequencyUnitOptions: FREQUENCY_UNIT_OPTIONS, filterFrequencyUnitText: frequencyDisplayUnit.label, filterFrequencyValue: frequencyText, filterFormulaText: network.key === 'rl' ? 'fc = R / (2πL)' : 'fc = 1 / (2πRC)', filterInductanceUnitIndex: network.key === 'rl' ? reactiveDisplayUnitIndex : inductanceUnitIndex, filterNetworkIndex: networkIndex, filterNetworkKey: network.key, filterNetworkOptions: NETWORK_OPTIONS, filterNetworkText: network.label, filterReactiveDisplayValue: reactiveDisplayValue, filterReactiveName: network.key === 'rl' ? '电感' : '电容', filterReactiveSymbol: network.key === 'rl' ? 'L' : 'C', filterReactiveUnitIndex: reactiveDisplayUnitIndex, filterReactiveUnitOptions: reactiveUnitOptions, filterReactiveUnitText: reactiveDisplayUnit.label, filterReactiveValue: reactiveText, filterResistanceDisplayValue: resistanceDisplayValue, filterResistanceUnitIndex: resistanceDisplayUnitIndex, filterResistanceUnitOptions: RESISTANCE_UNIT_OPTIONS, filterResistanceUnitText: resistanceDisplayUnit.label, filterResistanceValue: resistanceText, filterResponseIndex: responseIndex, filterResponseKey: response.key, filterResponseOptions: RESPONSE_OPTIONS, filterResponseText: response.label, filterSeriesLabel: getDiagramComponentLabel(diagram.series), filterSeriesComponentSrc: getDiagramComponentSrc(diagram.series), filterShuntLabel: getDiagramComponentLabel(diagram.shunt), filterShuntComponentSrc: getDiagramComponentSrc(diagram.shunt) } } function createInitialState() { return buildState({ filterCapacitanceUnitIndex: 1, filterFrequencyUnitIndex: 0, filterInductanceUnitIndex: 3, filterNetworkIndex: 0, filterReactiveValue: '', filterResistanceUnitIndex: 1, filterResistanceValue: '', filterResponseIndex: 0 }) } function updateState(state, changedData = {}) { return buildState({ ...state, ...changedData }) } function normalizeValue(state, fieldKey, fieldValue) { const source = buildState(state) const config = { frequency: { options: FREQUENCY_UNIT_OPTIONS, unitIndex: source.filterFrequencyUnitIndex, unitIndexKey: 'filterFrequencyUnitIndex', valueKey: 'filterFrequencyValue' }, reactive: { options: source.filterReactiveUnitOptions, unitIndex: source.filterReactiveUnitIndex, unitIndexKey: source.filterNetworkKey === 'rl' ? 'filterInductanceUnitIndex' : 'filterCapacitanceUnitIndex', valueKey: 'filterReactiveValue' }, resistance: { options: RESISTANCE_UNIT_OPTIONS, unitIndex: source.filterResistanceUnitIndex, unitIndexKey: 'filterResistanceUnitIndex', valueKey: 'filterResistanceValue' } }[fieldKey] if (!config) return source const text = fieldValue === undefined ? source[config.valueKey] : fieldValue const numberValue = parsePositiveNumber(text) if (!Number.isFinite(numberValue)) { return updateState(source, { [config.valueKey]: text }) } const currentUnit = getOption(config.options, config.unitIndex) const baseValue = numberValue * currentUnit.factor const selected = selectBestUnit(config.options, baseValue, config.unitIndex) return updateState(source, { [config.unitIndexKey]: selected.index, [config.valueKey]: formatScaledValue(baseValue, selected.unit, { fallbackText: '' }) }) } module.exports = { CAPACITANCE_UNIT_OPTIONS, FREQUENCY_UNIT_OPTIONS, INDUCTANCE_UNIT_OPTIONS, NETWORK_OPTIONS, RESISTANCE_UNIT_OPTIONS, RESPONSE_OPTIONS, createInitialState, normalizeValue, updateState }