index.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. const {
  2. formatMagnitudeNumber,
  3. getOption,
  4. normalizeIndex,
  5. parseLooseNumber
  6. } = require('../calculator-helpers.js')
  7. const SQRT3 = Math.sqrt(3)
  8. const DEG_PER_RAD = 180 / Math.PI
  9. const RAD_PER_DEG = Math.PI / 180
  10. const CONNECTION_OPTIONS = [
  11. { key: 'star', label: '星形' },
  12. { key: 'delta', label: '三角形' }
  13. ]
  14. const ROW_OPTIONS = [
  15. { key: 'lineVoltage', label: '线电压 UL', unit: 'V', placeholder: '380', editable: true, field: 'threePhaseLineVoltage' },
  16. { key: 'lineCurrent', label: '线电流 IL', unit: 'A', placeholder: '10', editable: true, field: 'threePhaseLineCurrent' },
  17. { key: 'phaseVoltage', label: '相电压 UP', unit: 'V', placeholder: '220', editable: true, field: 'threePhasePhaseVoltage' },
  18. { key: 'phaseCurrent', label: '相电流 IP', unit: 'A', placeholder: '10', editable: true, field: 'threePhasePhaseCurrent' },
  19. { key: 'apparentPower', label: '视在功率 S', unit: 'VA', placeholder: '6580', editable: true, field: 'threePhaseApparentPower' },
  20. { key: 'activePower', label: '实际功率 P', unit: 'W', placeholder: '5000', editable: true, field: 'threePhaseActivePower' },
  21. { key: 'reactivePower', label: '无功功率 Q', unit: 'var', placeholder: '3000', editable: true, field: 'threePhaseReactivePower' },
  22. { key: 'powerFactor', label: '功率因素 PF', unit: '', placeholder: '0.85', editable: true, field: 'threePhasePowerFactor' },
  23. { key: 'phaseAngle', label: '相位角 φ', unit: '°', placeholder: '31.8', editable: true, field: 'threePhasePhaseAngle' }
  24. ]
  25. const ELECTRICAL_INPUT_KEYS = [
  26. 'threePhaseLineVoltage',
  27. 'threePhaseLineCurrent',
  28. 'threePhasePhaseVoltage',
  29. 'threePhasePhaseCurrent',
  30. 'threePhaseApparentPower'
  31. ]
  32. const POWER_INPUT_KEYS = [
  33. 'threePhaseActivePower',
  34. 'threePhaseReactivePower',
  35. 'threePhasePowerFactor',
  36. 'threePhasePhaseAngle'
  37. ]
  38. const INPUT_KEYS = ELECTRICAL_INPUT_KEYS.concat(POWER_INPUT_KEYS)
  39. const POWER_DRIVER_KEYS = [
  40. 'threePhaseActivePower',
  41. 'threePhaseReactivePower',
  42. 'threePhasePowerFactor',
  43. 'threePhasePhaseAngle'
  44. ]
  45. function formatNumber(value) {
  46. return formatMagnitudeNumber(value, { fallbackText: '--' })
  47. }
  48. function getSignFrom(values) {
  49. if (Number.isFinite(values.reactivePower) && values.reactivePower !== 0) {
  50. return values.reactivePower < 0 ? -1 : 1
  51. }
  52. if (Number.isFinite(values.phaseAngle) && values.phaseAngle !== 0) {
  53. return values.phaseAngle < 0 ? -1 : 1
  54. }
  55. return 1
  56. }
  57. function getPreferredPowerKey(source, values) {
  58. const preferred = source.threePhasePowerDriver
  59. if (POWER_DRIVER_KEYS.includes(preferred)) {
  60. const valueName = {
  61. threePhaseActivePower: 'activePower',
  62. threePhaseReactivePower: 'reactivePower',
  63. threePhasePowerFactor: 'powerFactor',
  64. threePhasePhaseAngle: 'phaseAngle'
  65. }[preferred]
  66. if (Number.isFinite(values[valueName])) return preferred
  67. }
  68. return POWER_DRIVER_KEYS.find((key) => {
  69. const valueName = {
  70. threePhaseActivePower: 'activePower',
  71. threePhaseReactivePower: 'reactivePower',
  72. threePhasePowerFactor: 'powerFactor',
  73. threePhasePhaseAngle: 'phaseAngle'
  74. }[key]
  75. return Number.isFinite(values[valueName])
  76. }) || ''
  77. }
  78. function deriveFromKnownS(values, preferredKey) {
  79. const result = {
  80. activePower: values.activePower,
  81. phaseAngle: values.phaseAngle,
  82. powerFactor: values.powerFactor,
  83. reactivePower: values.reactivePower
  84. }
  85. const apparentPower = values.apparentPower
  86. const qSign = getSignFrom(values)
  87. const driver = preferredKey || getPreferredPowerKey({}, values)
  88. if (!Number.isFinite(apparentPower)) return result
  89. if (driver === 'threePhaseActivePower' && Number.isFinite(values.activePower)) {
  90. if (values.activePower > apparentPower) return { ...result, errorText: '实际功率不能大于视在功率' }
  91. result.powerFactor = apparentPower === 0 ? 0 : values.activePower / apparentPower
  92. result.reactivePower = qSign * Math.sqrt(Math.max(0, apparentPower * apparentPower - values.activePower * values.activePower))
  93. result.phaseAngle = Math.atan2(result.reactivePower, values.activePower) * DEG_PER_RAD
  94. return result
  95. }
  96. if (driver === 'threePhaseReactivePower' && Number.isFinite(values.reactivePower)) {
  97. if (Math.abs(values.reactivePower) > apparentPower) return { ...result, errorText: '无功功率绝对值不能大于视在功率' }
  98. result.activePower = Math.sqrt(Math.max(0, apparentPower * apparentPower - values.reactivePower * values.reactivePower))
  99. result.powerFactor = apparentPower === 0 ? 0 : result.activePower / apparentPower
  100. result.phaseAngle = Math.atan2(values.reactivePower, result.activePower) * DEG_PER_RAD
  101. return result
  102. }
  103. if (driver === 'threePhasePowerFactor' && Number.isFinite(values.powerFactor)) {
  104. result.activePower = apparentPower * values.powerFactor
  105. result.reactivePower = qSign * apparentPower * Math.sqrt(Math.max(0, 1 - values.powerFactor * values.powerFactor))
  106. result.phaseAngle = Math.atan2(result.reactivePower, result.activePower) * DEG_PER_RAD
  107. return result
  108. }
  109. if (driver === 'threePhasePhaseAngle' && Number.isFinite(values.phaseAngle)) {
  110. const angleRad = values.phaseAngle * RAD_PER_DEG
  111. result.powerFactor = Math.cos(angleRad)
  112. result.activePower = apparentPower * result.powerFactor
  113. result.reactivePower = apparentPower * Math.sin(angleRad)
  114. return result
  115. }
  116. return result
  117. }
  118. function derivePowerTriangle(values, preferredKey) {
  119. if (Number.isFinite(values.apparentPower)) {
  120. return deriveFromKnownS(values, preferredKey)
  121. }
  122. const result = {
  123. activePower: values.activePower,
  124. apparentPower: null,
  125. phaseAngle: values.phaseAngle,
  126. powerFactor: values.powerFactor,
  127. reactivePower: values.reactivePower
  128. }
  129. const p = values.activePower
  130. const q = values.reactivePower
  131. const pf = values.powerFactor
  132. const angle = values.phaseAngle
  133. if (Number.isFinite(p) && Number.isFinite(q)) {
  134. result.apparentPower = Math.sqrt(p * p + q * q)
  135. result.powerFactor = result.apparentPower === 0 ? 0 : p / result.apparentPower
  136. result.phaseAngle = Math.atan2(q, p) * DEG_PER_RAD
  137. return result
  138. }
  139. if (Number.isFinite(p) && Number.isFinite(pf)) {
  140. if (pf <= 0 && p > 0) return { ...result, errorText: '功率因素为 0 时实际功率只能为 0' }
  141. result.apparentPower = pf === 0 ? 0 : p / pf
  142. result.reactivePower = getSignFrom(values) * Math.sqrt(Math.max(0, result.apparentPower * result.apparentPower - p * p))
  143. result.phaseAngle = Math.atan2(result.reactivePower, p) * DEG_PER_RAD
  144. return result
  145. }
  146. if (Number.isFinite(p) && Number.isFinite(angle)) {
  147. const angleRad = angle * RAD_PER_DEG
  148. const cosValue = Math.cos(angleRad)
  149. if (cosValue <= 0 && p > 0) return { ...result, errorText: '相位角需小于 90° 才能由实际功率反推' }
  150. result.powerFactor = cosValue
  151. result.apparentPower = cosValue === 0 ? 0 : p / cosValue
  152. result.reactivePower = p * Math.tan(angleRad)
  153. return result
  154. }
  155. if (Number.isFinite(q) && Number.isFinite(pf)) {
  156. const reactiveRatio = Math.sqrt(Math.max(0, 1 - pf * pf))
  157. if (reactiveRatio === 0 && q !== 0) return { ...result, errorText: '功率因素为 1 时无功功率应为 0' }
  158. result.apparentPower = reactiveRatio === 0 ? 0 : Math.abs(q) / reactiveRatio
  159. result.activePower = result.apparentPower * pf
  160. result.phaseAngle = Math.atan2(q, result.activePower) * DEG_PER_RAD
  161. return result
  162. }
  163. if (Number.isFinite(q) && Number.isFinite(angle)) {
  164. const tanValue = Math.tan(angle * RAD_PER_DEG)
  165. if (Math.abs(tanValue) < 1e-12 && q !== 0) return { ...result, errorText: '相位角为 0° 时无功功率应为 0' }
  166. result.activePower = tanValue === 0 ? 0 : q / tanValue
  167. if (result.activePower < 0) return { ...result, errorText: '无功功率与相位角方向不一致' }
  168. result.apparentPower = Math.sqrt(result.activePower * result.activePower + q * q)
  169. result.powerFactor = result.apparentPower === 0 ? 0 : result.activePower / result.apparentPower
  170. return result
  171. }
  172. return result
  173. }
  174. function validate(values) {
  175. if ([values.lineVoltage, values.lineCurrent, values.phaseVoltage, values.phaseCurrent, values.apparentPower, values.activePower, values.reactivePower, values.powerFactor, values.phaseAngle]
  176. .some((value) => Number.isNaN(value))) {
  177. return '输入值格式无效'
  178. }
  179. if (Number.isFinite(values.lineVoltage) && values.lineVoltage <= 0) return '线电压需大于 0'
  180. if (Number.isFinite(values.lineCurrent) && values.lineCurrent <= 0) return '线电流需大于 0'
  181. if (Number.isFinite(values.phaseVoltage) && values.phaseVoltage <= 0) return '相电压需大于 0'
  182. if (Number.isFinite(values.phaseCurrent) && values.phaseCurrent <= 0) return '相电流需大于 0'
  183. if (Number.isFinite(values.apparentPower) && values.apparentPower < 0) return '视在功率不能为负数'
  184. if (Number.isFinite(values.activePower) && values.activePower < 0) return '实际功率不能为负数'
  185. if (Number.isFinite(values.powerFactor) && (values.powerFactor < 0 || values.powerFactor > 1)) return '功率因素范围为 0-1'
  186. if (Number.isFinite(values.phaseAngle) && Math.abs(values.phaseAngle) > 90) return '相位角范围为 -90° 到 90°'
  187. return ''
  188. }
  189. function assignVoltageFromLine(result, connectionKey, lineVoltage) {
  190. result.lineVoltage = lineVoltage
  191. result.phaseVoltage = connectionKey === 'star' ? lineVoltage / SQRT3 : lineVoltage
  192. }
  193. function assignVoltageFromPhase(result, connectionKey, phaseVoltage) {
  194. result.phaseVoltage = phaseVoltage
  195. result.lineVoltage = connectionKey === 'star' ? phaseVoltage * SQRT3 : phaseVoltage
  196. }
  197. function assignCurrentFromLine(result, connectionKey, lineCurrent) {
  198. result.lineCurrent = lineCurrent
  199. result.phaseCurrent = connectionKey === 'star' ? lineCurrent : lineCurrent / SQRT3
  200. }
  201. function assignCurrentFromPhase(result, connectionKey, phaseCurrent) {
  202. result.phaseCurrent = phaseCurrent
  203. result.lineCurrent = connectionKey === 'star' ? phaseCurrent : phaseCurrent * SQRT3
  204. }
  205. function resolveElectricalValues(connectionKey, values, preferredKey = '') {
  206. const result = {
  207. apparentPower: Number.isFinite(values.apparentPower) ? values.apparentPower : null,
  208. lineCurrent: null,
  209. lineVoltage: null,
  210. phaseCurrent: null,
  211. phaseVoltage: null
  212. }
  213. if (preferredKey === 'threePhasePhaseVoltage' && Number.isFinite(values.phaseVoltage)) {
  214. assignVoltageFromPhase(result, connectionKey, values.phaseVoltage)
  215. } else if (preferredKey === 'threePhaseLineVoltage' && Number.isFinite(values.lineVoltage)) {
  216. assignVoltageFromLine(result, connectionKey, values.lineVoltage)
  217. } else if (Number.isFinite(values.lineVoltage)) {
  218. assignVoltageFromLine(result, connectionKey, values.lineVoltage)
  219. } else if (Number.isFinite(values.phaseVoltage)) {
  220. assignVoltageFromPhase(result, connectionKey, values.phaseVoltage)
  221. }
  222. if (preferredKey === 'threePhasePhaseCurrent' && Number.isFinite(values.phaseCurrent)) {
  223. assignCurrentFromPhase(result, connectionKey, values.phaseCurrent)
  224. } else if (preferredKey === 'threePhaseLineCurrent' && Number.isFinite(values.lineCurrent)) {
  225. assignCurrentFromLine(result, connectionKey, values.lineCurrent)
  226. } else if (Number.isFinite(values.lineCurrent)) {
  227. assignCurrentFromLine(result, connectionKey, values.lineCurrent)
  228. } else if (Number.isFinite(values.phaseCurrent)) {
  229. assignCurrentFromPhase(result, connectionKey, values.phaseCurrent)
  230. }
  231. if (!Number.isFinite(result.apparentPower) && Number.isFinite(result.lineVoltage) && Number.isFinite(result.lineCurrent)) {
  232. result.apparentPower = SQRT3 * result.lineVoltage * result.lineCurrent
  233. }
  234. if (!Number.isFinite(result.lineCurrent) && Number.isFinite(result.apparentPower) && Number.isFinite(result.lineVoltage) && result.lineVoltage > 0) {
  235. assignCurrentFromLine(result, connectionKey, result.apparentPower / (SQRT3 * result.lineVoltage))
  236. }
  237. if (!Number.isFinite(result.lineVoltage) && Number.isFinite(result.apparentPower) && Number.isFinite(result.lineCurrent) && result.lineCurrent > 0) {
  238. assignVoltageFromLine(result, connectionKey, result.apparentPower / (SQRT3 * result.lineCurrent))
  239. }
  240. return result
  241. }
  242. function formatEditableValue(value) {
  243. return Number.isFinite(value) ? formatNumber(value) : ''
  244. }
  245. function buildRows(connectionKey, values, powerResult, preferredElectricalKey = '') {
  246. const electricalValues = resolveElectricalValues(connectionKey, {
  247. ...values,
  248. apparentPower: Number.isFinite(values.apparentPower) ? values.apparentPower : powerResult.apparentPower
  249. }, preferredElectricalKey)
  250. const displayValues = {
  251. activePower: Number.isFinite(powerResult.activePower) ? powerResult.activePower : values.activePower,
  252. apparentPower: electricalValues.apparentPower,
  253. lineCurrent: electricalValues.lineCurrent,
  254. lineVoltage: electricalValues.lineVoltage,
  255. phaseAngle: Number.isFinite(powerResult.phaseAngle) ? powerResult.phaseAngle : values.phaseAngle,
  256. phaseCurrent: electricalValues.phaseCurrent,
  257. phaseVoltage: electricalValues.phaseVoltage,
  258. powerFactor: Number.isFinite(powerResult.powerFactor) ? powerResult.powerFactor : values.powerFactor,
  259. reactivePower: Number.isFinite(powerResult.reactivePower) ? powerResult.reactivePower : values.reactivePower
  260. }
  261. const displayText = {
  262. activePower: formatEditableValue(displayValues.activePower),
  263. apparentPower: formatEditableValue(displayValues.apparentPower),
  264. lineCurrent: formatEditableValue(displayValues.lineCurrent),
  265. lineVoltage: formatEditableValue(displayValues.lineVoltage),
  266. phaseAngle: formatEditableValue(displayValues.phaseAngle),
  267. phaseCurrent: formatEditableValue(displayValues.phaseCurrent),
  268. phaseVoltage: formatEditableValue(displayValues.phaseVoltage),
  269. powerFactor: formatEditableValue(displayValues.powerFactor),
  270. reactivePower: formatEditableValue(displayValues.reactivePower)
  271. }
  272. return ROW_OPTIONS.map((row) => ({
  273. ...row,
  274. value: displayText[row.key] || (row.editable ? '' : '--')
  275. }))
  276. }
  277. function buildState(source = {}) {
  278. const connectionIndex = normalizeIndex(source.threePhaseConnectionIndex, CONNECTION_OPTIONS, 0)
  279. const connection = getOption(CONNECTION_OPTIONS, connectionIndex)
  280. const rawValues = INPUT_KEYS.reduce((result, key) => {
  281. result[key] = String(source[key] === undefined || source[key] === null ? '' : source[key])
  282. return result
  283. }, {})
  284. const values = {
  285. activePower: parseLooseNumber(rawValues.threePhaseActivePower),
  286. apparentPower: parseLooseNumber(rawValues.threePhaseApparentPower),
  287. lineCurrent: parseLooseNumber(rawValues.threePhaseLineCurrent),
  288. lineVoltage: parseLooseNumber(rawValues.threePhaseLineVoltage),
  289. phaseAngle: parseLooseNumber(rawValues.threePhasePhaseAngle),
  290. phaseCurrent: parseLooseNumber(rawValues.threePhasePhaseCurrent),
  291. phaseVoltage: parseLooseNumber(rawValues.threePhasePhaseVoltage),
  292. powerFactor: parseLooseNumber(rawValues.threePhasePowerFactor),
  293. reactivePower: parseLooseNumber(rawValues.threePhaseReactivePower)
  294. }
  295. const validationError = validate(values)
  296. const preferredElectricalKey = ELECTRICAL_INPUT_KEYS.includes(source.threePhaseElectricalDriver)
  297. ? source.threePhaseElectricalDriver
  298. : ''
  299. const electricalValues = validationError
  300. ? {}
  301. : resolveElectricalValues(connection.key, values, preferredElectricalKey)
  302. const preferredPowerKey = getPreferredPowerKey(source, values)
  303. const powerResult = validationError
  304. ? {}
  305. : derivePowerTriangle({
  306. ...values,
  307. ...electricalValues
  308. }, preferredPowerKey)
  309. const errorText = validationError || powerResult.errorText || ''
  310. return {
  311. ...rawValues,
  312. threePhaseConnectionIndex: connectionIndex,
  313. threePhaseConnectionKey: connection.key,
  314. threePhaseConnectionOptions: CONNECTION_OPTIONS,
  315. threePhaseElectricalDriver: preferredElectricalKey,
  316. threePhaseErrorText: errorText,
  317. threePhasePowerDriver: preferredPowerKey,
  318. threePhaseRows: buildRows(connection.key, values, powerResult || {}, preferredElectricalKey)
  319. }
  320. }
  321. function createInitialState() {
  322. return buildState({})
  323. }
  324. function updateState(state, changedData = {}) {
  325. return buildState({
  326. ...state,
  327. ...changedData
  328. })
  329. }
  330. function clearInputs(state = {}) {
  331. return updateState(state, INPUT_KEYS.reduce((result, key) => {
  332. result[key] = ''
  333. return result
  334. }, {
  335. threePhaseElectricalDriver: '',
  336. threePhasePowerDriver: ''
  337. }))
  338. }
  339. module.exports = {
  340. CONNECTION_OPTIONS,
  341. ELECTRICAL_INPUT_KEYS,
  342. POWER_DRIVER_KEYS,
  343. clearInputs,
  344. createInitialState,
  345. updateState
  346. }