const { formatMagnitudeNumber, getOption, normalizeIndex, parseLooseNumber } = require('../calculator-helpers.js') const SQRT3 = Math.sqrt(3) const DEG_PER_RAD = 180 / Math.PI const RAD_PER_DEG = Math.PI / 180 const CONNECTION_OPTIONS = [ { key: 'star', label: '星形' }, { key: 'delta', label: '三角形' } ] const ROW_OPTIONS = [ { key: 'lineVoltage', label: '线电压 UL', unit: 'V', placeholder: '380', editable: true, field: 'threePhaseLineVoltage' }, { key: 'lineCurrent', label: '线电流 IL', unit: 'A', placeholder: '10', editable: true, field: 'threePhaseLineCurrent' }, { key: 'phaseVoltage', label: '相电压 UP', unit: 'V', placeholder: '220', editable: true, field: 'threePhasePhaseVoltage' }, { key: 'phaseCurrent', label: '相电流 IP', unit: 'A', placeholder: '10', editable: true, field: 'threePhasePhaseCurrent' }, { key: 'apparentPower', label: '视在功率 S', unit: 'VA', placeholder: '6580', editable: true, field: 'threePhaseApparentPower' }, { key: 'activePower', label: '实际功率 P', unit: 'W', placeholder: '5000', editable: true, field: 'threePhaseActivePower' }, { key: 'reactivePower', label: '无功功率 Q', unit: 'var', placeholder: '3000', editable: true, field: 'threePhaseReactivePower' }, { key: 'powerFactor', label: '功率因素 PF', unit: '', placeholder: '0.85', editable: true, field: 'threePhasePowerFactor' }, { key: 'phaseAngle', label: '相位角 φ', unit: '°', placeholder: '31.8', editable: true, field: 'threePhasePhaseAngle' } ] const ELECTRICAL_INPUT_KEYS = [ 'threePhaseLineVoltage', 'threePhaseLineCurrent', 'threePhasePhaseVoltage', 'threePhasePhaseCurrent', 'threePhaseApparentPower' ] const POWER_INPUT_KEYS = [ 'threePhaseActivePower', 'threePhaseReactivePower', 'threePhasePowerFactor', 'threePhasePhaseAngle' ] const INPUT_KEYS = ELECTRICAL_INPUT_KEYS.concat(POWER_INPUT_KEYS) const POWER_DRIVER_KEYS = [ 'threePhaseActivePower', 'threePhaseReactivePower', 'threePhasePowerFactor', 'threePhasePhaseAngle' ] function formatNumber(value) { return formatMagnitudeNumber(value, { fallbackText: '--' }) } function getSignFrom(values) { if (Number.isFinite(values.reactivePower) && values.reactivePower !== 0) { return values.reactivePower < 0 ? -1 : 1 } if (Number.isFinite(values.phaseAngle) && values.phaseAngle !== 0) { return values.phaseAngle < 0 ? -1 : 1 } return 1 } function getPreferredPowerKey(source, values) { const preferred = source.threePhasePowerDriver if (POWER_DRIVER_KEYS.includes(preferred)) { const valueName = { threePhaseActivePower: 'activePower', threePhaseReactivePower: 'reactivePower', threePhasePowerFactor: 'powerFactor', threePhasePhaseAngle: 'phaseAngle' }[preferred] if (Number.isFinite(values[valueName])) return preferred } return POWER_DRIVER_KEYS.find((key) => { const valueName = { threePhaseActivePower: 'activePower', threePhaseReactivePower: 'reactivePower', threePhasePowerFactor: 'powerFactor', threePhasePhaseAngle: 'phaseAngle' }[key] return Number.isFinite(values[valueName]) }) || '' } function deriveFromKnownS(values, preferredKey) { const result = { activePower: values.activePower, phaseAngle: values.phaseAngle, powerFactor: values.powerFactor, reactivePower: values.reactivePower } const apparentPower = values.apparentPower const qSign = getSignFrom(values) const driver = preferredKey || getPreferredPowerKey({}, values) if (!Number.isFinite(apparentPower)) return result if (driver === 'threePhaseActivePower' && Number.isFinite(values.activePower)) { if (values.activePower > apparentPower) return { ...result, errorText: '实际功率不能大于视在功率' } result.powerFactor = apparentPower === 0 ? 0 : values.activePower / apparentPower result.reactivePower = qSign * Math.sqrt(Math.max(0, apparentPower * apparentPower - values.activePower * values.activePower)) result.phaseAngle = Math.atan2(result.reactivePower, values.activePower) * DEG_PER_RAD return result } if (driver === 'threePhaseReactivePower' && Number.isFinite(values.reactivePower)) { if (Math.abs(values.reactivePower) > apparentPower) return { ...result, errorText: '无功功率绝对值不能大于视在功率' } result.activePower = Math.sqrt(Math.max(0, apparentPower * apparentPower - values.reactivePower * values.reactivePower)) result.powerFactor = apparentPower === 0 ? 0 : result.activePower / apparentPower result.phaseAngle = Math.atan2(values.reactivePower, result.activePower) * DEG_PER_RAD return result } if (driver === 'threePhasePowerFactor' && Number.isFinite(values.powerFactor)) { result.activePower = apparentPower * values.powerFactor result.reactivePower = qSign * apparentPower * Math.sqrt(Math.max(0, 1 - values.powerFactor * values.powerFactor)) result.phaseAngle = Math.atan2(result.reactivePower, result.activePower) * DEG_PER_RAD return result } if (driver === 'threePhasePhaseAngle' && Number.isFinite(values.phaseAngle)) { const angleRad = values.phaseAngle * RAD_PER_DEG result.powerFactor = Math.cos(angleRad) result.activePower = apparentPower * result.powerFactor result.reactivePower = apparentPower * Math.sin(angleRad) return result } return result } function derivePowerTriangle(values, preferredKey) { if (Number.isFinite(values.apparentPower)) { return deriveFromKnownS(values, preferredKey) } const result = { activePower: values.activePower, apparentPower: null, phaseAngle: values.phaseAngle, powerFactor: values.powerFactor, reactivePower: values.reactivePower } const p = values.activePower const q = values.reactivePower const pf = values.powerFactor const angle = values.phaseAngle if (Number.isFinite(p) && Number.isFinite(q)) { result.apparentPower = Math.sqrt(p * p + q * q) result.powerFactor = result.apparentPower === 0 ? 0 : p / result.apparentPower result.phaseAngle = Math.atan2(q, p) * DEG_PER_RAD return result } if (Number.isFinite(p) && Number.isFinite(pf)) { if (pf <= 0 && p > 0) return { ...result, errorText: '功率因素为 0 时实际功率只能为 0' } result.apparentPower = pf === 0 ? 0 : p / pf result.reactivePower = getSignFrom(values) * Math.sqrt(Math.max(0, result.apparentPower * result.apparentPower - p * p)) result.phaseAngle = Math.atan2(result.reactivePower, p) * DEG_PER_RAD return result } if (Number.isFinite(p) && Number.isFinite(angle)) { const angleRad = angle * RAD_PER_DEG const cosValue = Math.cos(angleRad) if (cosValue <= 0 && p > 0) return { ...result, errorText: '相位角需小于 90° 才能由实际功率反推' } result.powerFactor = cosValue result.apparentPower = cosValue === 0 ? 0 : p / cosValue result.reactivePower = p * Math.tan(angleRad) return result } if (Number.isFinite(q) && Number.isFinite(pf)) { const reactiveRatio = Math.sqrt(Math.max(0, 1 - pf * pf)) if (reactiveRatio === 0 && q !== 0) return { ...result, errorText: '功率因素为 1 时无功功率应为 0' } result.apparentPower = reactiveRatio === 0 ? 0 : Math.abs(q) / reactiveRatio result.activePower = result.apparentPower * pf result.phaseAngle = Math.atan2(q, result.activePower) * DEG_PER_RAD return result } if (Number.isFinite(q) && Number.isFinite(angle)) { const tanValue = Math.tan(angle * RAD_PER_DEG) if (Math.abs(tanValue) < 1e-12 && q !== 0) return { ...result, errorText: '相位角为 0° 时无功功率应为 0' } result.activePower = tanValue === 0 ? 0 : q / tanValue if (result.activePower < 0) return { ...result, errorText: '无功功率与相位角方向不一致' } result.apparentPower = Math.sqrt(result.activePower * result.activePower + q * q) result.powerFactor = result.apparentPower === 0 ? 0 : result.activePower / result.apparentPower return result } return result } function validate(values) { if ([values.lineVoltage, values.lineCurrent, values.phaseVoltage, values.phaseCurrent, values.apparentPower, values.activePower, values.reactivePower, values.powerFactor, values.phaseAngle] .some((value) => Number.isNaN(value))) { return '输入值格式无效' } if (Number.isFinite(values.lineVoltage) && values.lineVoltage <= 0) return '线电压需大于 0' if (Number.isFinite(values.lineCurrent) && values.lineCurrent <= 0) return '线电流需大于 0' if (Number.isFinite(values.phaseVoltage) && values.phaseVoltage <= 0) return '相电压需大于 0' if (Number.isFinite(values.phaseCurrent) && values.phaseCurrent <= 0) return '相电流需大于 0' if (Number.isFinite(values.apparentPower) && values.apparentPower < 0) return '视在功率不能为负数' if (Number.isFinite(values.activePower) && values.activePower < 0) return '实际功率不能为负数' if (Number.isFinite(values.powerFactor) && (values.powerFactor < 0 || values.powerFactor > 1)) return '功率因素范围为 0-1' if (Number.isFinite(values.phaseAngle) && Math.abs(values.phaseAngle) > 90) return '相位角范围为 -90° 到 90°' return '' } function assignVoltageFromLine(result, connectionKey, lineVoltage) { result.lineVoltage = lineVoltage result.phaseVoltage = connectionKey === 'star' ? lineVoltage / SQRT3 : lineVoltage } function assignVoltageFromPhase(result, connectionKey, phaseVoltage) { result.phaseVoltage = phaseVoltage result.lineVoltage = connectionKey === 'star' ? phaseVoltage * SQRT3 : phaseVoltage } function assignCurrentFromLine(result, connectionKey, lineCurrent) { result.lineCurrent = lineCurrent result.phaseCurrent = connectionKey === 'star' ? lineCurrent : lineCurrent / SQRT3 } function assignCurrentFromPhase(result, connectionKey, phaseCurrent) { result.phaseCurrent = phaseCurrent result.lineCurrent = connectionKey === 'star' ? phaseCurrent : phaseCurrent * SQRT3 } function resolveElectricalValues(connectionKey, values, preferredKey = '') { const result = { apparentPower: Number.isFinite(values.apparentPower) ? values.apparentPower : null, lineCurrent: null, lineVoltage: null, phaseCurrent: null, phaseVoltage: null } if (preferredKey === 'threePhasePhaseVoltage' && Number.isFinite(values.phaseVoltage)) { assignVoltageFromPhase(result, connectionKey, values.phaseVoltage) } else if (preferredKey === 'threePhaseLineVoltage' && Number.isFinite(values.lineVoltage)) { assignVoltageFromLine(result, connectionKey, values.lineVoltage) } else if (Number.isFinite(values.lineVoltage)) { assignVoltageFromLine(result, connectionKey, values.lineVoltage) } else if (Number.isFinite(values.phaseVoltage)) { assignVoltageFromPhase(result, connectionKey, values.phaseVoltage) } if (preferredKey === 'threePhasePhaseCurrent' && Number.isFinite(values.phaseCurrent)) { assignCurrentFromPhase(result, connectionKey, values.phaseCurrent) } else if (preferredKey === 'threePhaseLineCurrent' && Number.isFinite(values.lineCurrent)) { assignCurrentFromLine(result, connectionKey, values.lineCurrent) } else if (Number.isFinite(values.lineCurrent)) { assignCurrentFromLine(result, connectionKey, values.lineCurrent) } else if (Number.isFinite(values.phaseCurrent)) { assignCurrentFromPhase(result, connectionKey, values.phaseCurrent) } if (!Number.isFinite(result.apparentPower) && Number.isFinite(result.lineVoltage) && Number.isFinite(result.lineCurrent)) { result.apparentPower = SQRT3 * result.lineVoltage * result.lineCurrent } if (!Number.isFinite(result.lineCurrent) && Number.isFinite(result.apparentPower) && Number.isFinite(result.lineVoltage) && result.lineVoltage > 0) { assignCurrentFromLine(result, connectionKey, result.apparentPower / (SQRT3 * result.lineVoltage)) } if (!Number.isFinite(result.lineVoltage) && Number.isFinite(result.apparentPower) && Number.isFinite(result.lineCurrent) && result.lineCurrent > 0) { assignVoltageFromLine(result, connectionKey, result.apparentPower / (SQRT3 * result.lineCurrent)) } return result } function formatEditableValue(value) { return Number.isFinite(value) ? formatNumber(value) : '' } function buildRows(connectionKey, values, powerResult, preferredElectricalKey = '') { const electricalValues = resolveElectricalValues(connectionKey, { ...values, apparentPower: Number.isFinite(values.apparentPower) ? values.apparentPower : powerResult.apparentPower }, preferredElectricalKey) const displayValues = { activePower: Number.isFinite(powerResult.activePower) ? powerResult.activePower : values.activePower, apparentPower: electricalValues.apparentPower, lineCurrent: electricalValues.lineCurrent, lineVoltage: electricalValues.lineVoltage, phaseAngle: Number.isFinite(powerResult.phaseAngle) ? powerResult.phaseAngle : values.phaseAngle, phaseCurrent: electricalValues.phaseCurrent, phaseVoltage: electricalValues.phaseVoltage, powerFactor: Number.isFinite(powerResult.powerFactor) ? powerResult.powerFactor : values.powerFactor, reactivePower: Number.isFinite(powerResult.reactivePower) ? powerResult.reactivePower : values.reactivePower } const displayText = { activePower: formatEditableValue(displayValues.activePower), apparentPower: formatEditableValue(displayValues.apparentPower), lineCurrent: formatEditableValue(displayValues.lineCurrent), lineVoltage: formatEditableValue(displayValues.lineVoltage), phaseAngle: formatEditableValue(displayValues.phaseAngle), phaseCurrent: formatEditableValue(displayValues.phaseCurrent), phaseVoltage: formatEditableValue(displayValues.phaseVoltage), powerFactor: formatEditableValue(displayValues.powerFactor), reactivePower: formatEditableValue(displayValues.reactivePower) } return ROW_OPTIONS.map((row) => ({ ...row, value: displayText[row.key] || (row.editable ? '' : '--') })) } function buildState(source = {}) { const connectionIndex = normalizeIndex(source.threePhaseConnectionIndex, CONNECTION_OPTIONS, 0) const connection = getOption(CONNECTION_OPTIONS, connectionIndex) const rawValues = INPUT_KEYS.reduce((result, key) => { result[key] = String(source[key] === undefined || source[key] === null ? '' : source[key]) return result }, {}) const values = { activePower: parseLooseNumber(rawValues.threePhaseActivePower), apparentPower: parseLooseNumber(rawValues.threePhaseApparentPower), lineCurrent: parseLooseNumber(rawValues.threePhaseLineCurrent), lineVoltage: parseLooseNumber(rawValues.threePhaseLineVoltage), phaseAngle: parseLooseNumber(rawValues.threePhasePhaseAngle), phaseCurrent: parseLooseNumber(rawValues.threePhasePhaseCurrent), phaseVoltage: parseLooseNumber(rawValues.threePhasePhaseVoltage), powerFactor: parseLooseNumber(rawValues.threePhasePowerFactor), reactivePower: parseLooseNumber(rawValues.threePhaseReactivePower) } const validationError = validate(values) const preferredElectricalKey = ELECTRICAL_INPUT_KEYS.includes(source.threePhaseElectricalDriver) ? source.threePhaseElectricalDriver : '' const electricalValues = validationError ? {} : resolveElectricalValues(connection.key, values, preferredElectricalKey) const preferredPowerKey = getPreferredPowerKey(source, values) const powerResult = validationError ? {} : derivePowerTriangle({ ...values, ...electricalValues }, preferredPowerKey) const errorText = validationError || powerResult.errorText || '' return { ...rawValues, threePhaseConnectionIndex: connectionIndex, threePhaseConnectionKey: connection.key, threePhaseConnectionOptions: CONNECTION_OPTIONS, threePhaseElectricalDriver: preferredElectricalKey, threePhaseErrorText: errorText, threePhasePowerDriver: preferredPowerKey, threePhaseRows: buildRows(connection.key, values, powerResult || {}, preferredElectricalKey) } } function createInitialState() { return buildState({}) } function updateState(state, changedData = {}) { return buildState({ ...state, ...changedData }) } function clearInputs(state = {}) { return updateState(state, INPUT_KEYS.reduce((result, key) => { result[key] = '' return result }, { threePhaseElectricalDriver: '', threePhasePowerDriver: '' })) } module.exports = { CONNECTION_OPTIONS, ELECTRICAL_INPUT_KEYS, POWER_DRIVER_KEYS, clearInputs, createInitialState, updateState }