const { formatMagnitudeNumber, formatScaledValue, getOption, normalizeIndex, parsePositiveNumber, selectBestUnit } = require('./calculator-helpers') const TWO_PI = 2 * Math.PI const MODE_OPTIONS = [ { key: 'capacitive', label: '容抗' }, { key: 'inductive', label: '感抗' } ] const REACTANCE_UNIT_OPTIONS = [ { label: 'mΩ', factor: 1e-3 }, { label: 'Ω', factor: 1 }, { label: 'kΩ', factor: 1e3 }, { label: 'MΩ', factor: 1e6 }, { label: 'GΩ', factor: 1e9 } ] const ADMITTANCE_UNIT_OPTIONS = [ { label: 'pS', factor: 1e-12 }, { label: 'nS', factor: 1e-9 }, { label: 'μS', factor: 1e-6 }, { label: 'mS', factor: 1e-3 }, { label: 'S', factor: 1 }, { label: 'kS', factor: 1e3 }, { label: 'MS', factor: 1e6 } ] 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(modeKey) { return modeKey === 'inductive' ? INDUCTANCE_UNIT_OPTIONS : CAPACITANCE_UNIT_OPTIONS } function formatUnitValue(baseValue, options, fallbackIndex = 0) { if (!Number.isFinite(baseValue)) return '--' const selected = selectBestUnit(options, baseValue, fallbackIndex) return formatScaledValue(baseValue, selected.unit, { fallbackText: '', includeUnit: true }) } function createEmptyResultRows(modeKey) { const isInductive = modeKey === 'inductive' return [ { label: isInductive ? '感抗 XL' : '容抗 XC', meta: isInductive ? 'XL = 2πfL' : 'XC = 1 / (2πfC)', value: '--' }, { label: isInductive ? '感抗导纳 YL' : '容抗导纳 YC', meta: 'Y = 1 / X', value: '--' } ] } function calculateResultRows(modeKey, frequency, reactive) { const rows = createEmptyResultRows(modeKey) if (!Number.isFinite(frequency) || !Number.isFinite(reactive)) return rows const reactance = modeKey === 'inductive' ? TWO_PI * frequency * reactive : 1 / (TWO_PI * frequency * reactive) const admittance = 1 / reactance return [ { ...rows[0], value: formatUnitValue(reactance, REACTANCE_UNIT_OPTIONS, 1) }, { ...rows[1], value: formatUnitValue(admittance, ADMITTANCE_UNIT_OPTIONS, 4) } ] } function buildState(source = {}) { const modeIndex = normalizeIndex(source.reactanceModeIndex, MODE_OPTIONS, 0) const mode = getOption(MODE_OPTIONS, modeIndex) const reactiveUnitOptions = getReactiveUnitOptions(mode.key) const capacitanceUnitIndex = normalizeIndex(source.reactanceCapacitanceUnitIndex, CAPACITANCE_UNIT_OPTIONS, 1) const frequencyUnitIndex = normalizeIndex(source.reactanceFrequencyUnitIndex, FREQUENCY_UNIT_OPTIONS, 0) const inductanceUnitIndex = normalizeIndex(source.reactanceInductanceUnitIndex, INDUCTANCE_UNIT_OPTIONS, 3) const reactiveUnitIndex = mode.key === 'inductive' ? inductanceUnitIndex : capacitanceUnitIndex const frequencyUnit = getOption(FREQUENCY_UNIT_OPTIONS, frequencyUnitIndex) const reactiveUnit = getOption(reactiveUnitOptions, reactiveUnitIndex) const frequencyText = String(source.reactanceFrequencyValue || '') const reactiveText = String(source.reactanceReactiveValue || '') const frequencyNumber = parsePositiveNumber(frequencyText) const reactiveNumber = parsePositiveNumber(reactiveText) const invalidInput = [frequencyNumber, reactiveNumber].some((value) => Number.isNaN(value)) const values = { frequency: Number.isFinite(frequencyNumber) ? frequencyNumber * frequencyUnit.factor : null, reactive: Number.isFinite(reactiveNumber) ? reactiveNumber * reactiveUnit.factor : null } let frequencyDisplayUnit = frequencyUnit let frequencyDisplayUnitIndex = frequencyUnitIndex let reactiveDisplayUnit = reactiveUnit let reactiveDisplayUnitIndex = reactiveUnitIndex let frequencyDisplayValue = Number.isFinite(frequencyNumber) ? formatMagnitudeNumber(frequencyNumber, { fallbackText: '' }) : frequencyText let reactiveDisplayValue = Number.isFinite(reactiveNumber) ? formatMagnitudeNumber(reactiveNumber, { fallbackText: '' }) : reactiveText const resultRows = invalidInput ? createEmptyResultRows(mode.key) : calculateResultRows(mode.key, values.frequency, values.reactive) return { reactanceCapacitanceUnitIndex: mode.key === 'capacitive' ? reactiveDisplayUnitIndex : capacitanceUnitIndex, reactanceErrorText: invalidInput ? '输入值需大于 0' : '', reactanceFrequencyDisplayValue: frequencyDisplayValue, reactanceFrequencyUnitIndex: frequencyDisplayUnitIndex, reactanceFrequencyUnitOptions: FREQUENCY_UNIT_OPTIONS, reactanceFrequencyUnitText: frequencyDisplayUnit.label, reactanceFrequencyValue: frequencyText, reactanceInductanceUnitIndex: mode.key === 'inductive' ? reactiveDisplayUnitIndex : inductanceUnitIndex, reactanceModeIndex: modeIndex, reactanceModeKey: mode.key, reactanceModeOptions: MODE_OPTIONS, reactanceModeText: mode.label, reactanceReactiveDisplayValue: reactiveDisplayValue, reactanceReactiveName: mode.key === 'inductive' ? '电感' : '电容', reactanceReactiveSymbol: mode.key === 'inductive' ? 'L' : 'C', reactanceReactiveUnitIndex: reactiveDisplayUnitIndex, reactanceReactiveUnitOptions: reactiveUnitOptions, reactanceReactiveUnitText: reactiveDisplayUnit.label, reactanceReactiveValue: reactiveText, reactanceResultRows: resultRows } } function createInitialState() { return buildState({ reactanceCapacitanceUnitIndex: 1, reactanceFrequencyUnitIndex: 0, reactanceInductanceUnitIndex: 3, reactanceModeIndex: 0, reactanceReactiveValue: '' }) } function updateState(state, changedData = {}) { return buildState({ ...state, ...changedData }) } function clearInputs(state = {}) { return updateState(state, { reactanceFrequencyValue: '', reactanceReactiveValue: '' }) } function normalizeValue(state, fieldKey, fieldValue) { const source = buildState(state) const config = { frequency: { options: FREQUENCY_UNIT_OPTIONS, unitIndex: source.reactanceFrequencyUnitIndex, unitIndexKey: 'reactanceFrequencyUnitIndex', valueKey: 'reactanceFrequencyValue' }, reactive: { options: source.reactanceReactiveUnitOptions, unitIndex: source.reactanceReactiveUnitIndex, unitIndexKey: source.reactanceModeKey === 'inductive' ? 'reactanceInductanceUnitIndex' : 'reactanceCapacitanceUnitIndex', valueKey: 'reactanceReactiveValue' } }[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 = { ADMITTANCE_UNIT_OPTIONS, CAPACITANCE_UNIT_OPTIONS, FREQUENCY_UNIT_OPTIONS, INDUCTANCE_UNIT_OPTIONS, MODE_OPTIONS, REACTANCE_UNIT_OPTIONS, clearInputs, createInitialState, normalizeValue, updateState }