index.vue 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. <template>
  2. <view class="jnpf-calculation jnpf-calculation-right">
  3. <u-input input-align='right' v-model="innerValue" disabled placeholder='0.00' />
  4. <view class="tips" v-if="isAmountChinese">{{rmbText}}</view>
  5. </view>
  6. </template>
  7. <script>
  8. /**
  9. * 中缀转后缀(逆波兰 Reverse Polish Notation)
  10. * @param {Array} exps - 中缀表达式数组
  11. */
  12. const toRPN = exps => {
  13. const s1 = [] // 符号栈
  14. const s2 = [] // 输出栈
  15. const getTopVal = (stack) => stack.length > 0 ? stack[stack.length - 1] : null
  16. const levelCompare = (c1, c2) => {
  17. const getIndex = c => ['+-', '×÷', '()'].findIndex(t => t.includes(c))
  18. return getIndex(c1) - getIndex(c2)
  19. }
  20. exps.forEach(t => {
  21. if (typeof t === 'string' && Number.isNaN(Number(t))) { // 是符号
  22. if (t === '(') {
  23. s1.push(t)
  24. } else if (t === ')') {
  25. let popVal
  26. do {
  27. popVal = s1.pop()
  28. popVal !== '(' && s2.push(popVal)
  29. } while (s1.length && popVal !== '(')
  30. } else {
  31. let topVal = getTopVal(s1)
  32. if (!topVal) { // s1 为空 直接push
  33. s1.push(t)
  34. } else {
  35. while (topVal && topVal !== '(' && levelCompare(topVal, t) >= 0) { // 优先级 >= t 弹出到s2
  36. s2.push(s1.pop())
  37. topVal = getTopVal(s1)
  38. }
  39. s1.push(t)
  40. }
  41. }
  42. return
  43. }
  44. s2.push(t) // 数字直接入栈
  45. })
  46. while (s1.length) {
  47. s2.push(s1.pop())
  48. }
  49. return s2
  50. }
  51. const calcRPN = rpnExps => {
  52. rpnExps = rpnExps.concat()
  53. const calc = (x, y, type) => {
  54. let a1 = Number(x),
  55. a2 = Number(y)
  56. switch (type) {
  57. case '+':
  58. return a1 + a2;
  59. case '-':
  60. return a1 - a2;
  61. case '×':
  62. return a1 * a2;
  63. case '÷':
  64. return a1 / a2;
  65. }
  66. }
  67. for (let i = 2; i < rpnExps.length; i++) {
  68. if ('+-×÷'.includes(rpnExps[i])) {
  69. let val = calc(rpnExps[i - 2], rpnExps[i - 1], rpnExps[i])
  70. rpnExps.splice(i - 2, 3, val)
  71. i = i - 2
  72. }
  73. }
  74. return rpnExps[0]
  75. }
  76. const mergeNumberOfExps = expressions => {
  77. const res = []
  78. const isNumChar = n => /^[\d|\.]$/.test(n)
  79. for (let i = 0; i < expressions.length; i++) {
  80. if (i > 0 && isNumChar(expressions[i - 1]) && isNumChar(expressions[i])) {
  81. res[res.length - 1] += expressions[i]
  82. continue
  83. }
  84. res.push(expressions[i])
  85. }
  86. return res
  87. }
  88. export default {
  89. name: 'jnpf-calculation',
  90. props: {
  91. modelValue: {
  92. type: [String, Number],
  93. default: ''
  94. },
  95. thousands: {
  96. type: Boolean,
  97. default: false
  98. },
  99. precision: {
  100. default: 0
  101. },
  102. isAmountChinese: {
  103. type: Boolean,
  104. default: false
  105. },
  106. expression: {
  107. type: Array,
  108. default: []
  109. },
  110. config: {
  111. type: Object,
  112. default: {}
  113. },
  114. formData: {
  115. type: Object,
  116. default: {}
  117. },
  118. rowIndex: {
  119. type: [String, Number],
  120. default: ''
  121. },
  122. roundType: {
  123. type: [String, Number],
  124. default: 1
  125. }
  126. },
  127. data() {
  128. return {
  129. innerValue: '',
  130. RPN_EXP: toRPN(mergeNumberOfExps(this.expression)),
  131. rmbText: '',
  132. subValue: 0
  133. }
  134. },
  135. watch: {
  136. formData: {
  137. handler(val, oldVal) {
  138. setTimeout(() => {
  139. this.execRPN()
  140. }, 0)
  141. },
  142. deep: true,
  143. immediate: true
  144. },
  145. modelValue: {
  146. handler(val, oldVal) {
  147. this.innerValue = val
  148. },
  149. deep: true,
  150. immediate: true
  151. },
  152. },
  153. methods: {
  154. getRoundValue(val) {
  155. const precision = this.precision || 0;
  156. let truncatedNumber
  157. if (this.roundType == 2) {
  158. if (precision === 0) Math.trunc(val);
  159. const factor = Math.pow(10, precision);
  160. truncatedNumber = Math.trunc(val * factor) / factor;
  161. return truncatedNumber
  162. }
  163. if (this.roundType == 3) return Math.ceil(val)
  164. if (this.roundType == 4) return Math.floor(val);
  165. return val.toFixed(precision)
  166. },
  167. /**
  168. * 计算表达式
  169. */
  170. execRPN() {
  171. const temp = this.RPN_EXP.map(t => typeof t === 'object' ? this.getFormVal(t.__vModel__) : t)
  172. this.setValue(temp)
  173. this.subValue = JSON.parse(JSON.stringify(this.innerValue))
  174. if (isNaN(this.innerValue)) this.innerValue = 0
  175. this.rmbText = this.jnpf.getAmountChinese(Number(this.subValue) || 0)
  176. this.$emit('update:modelValue', this.subValue)
  177. if (this.thousands) this.innerValue = this.numFormat(this.innerValue)
  178. },
  179. setValue(temp) {
  180. let result = calcRPN(temp);
  181. if (isNaN(result) || !isFinite(result)) {
  182. this.innerValue = 0;
  183. } else {
  184. let num = Number(result);
  185. if (this.roundType == 2) {
  186. this.innerValue = num;
  187. } else {
  188. this.innerValue = Number(num.toFixed(this.precision ||
  189. 0));
  190. }
  191. }
  192. this.innerValue = this.getRoundValue(parseFloat(this.innerValue));
  193. },
  194. /**
  195. * 千分符
  196. */
  197. numFormat(num) {
  198. num = num.toString().split("."); // 分隔小数点
  199. let arr = num[0].split("").reverse(); // 转换成字符数组并且倒序排列
  200. let res = [];
  201. for (let i = 0, len = arr.length; i < len; i++) {
  202. if (i % 3 === 0 && i !== 0) res.push(","); // 添加分隔符
  203. res.push(arr[i]);
  204. }
  205. res.reverse(); // 再次倒序成为正确的顺序
  206. if (num[1]) { // 如果有小数的话添加小数部分
  207. res = res.join("").concat("." + num[1]);
  208. } else {
  209. res = res.join("");
  210. }
  211. return res
  212. },
  213. /**
  214. * 获取指定组件的值
  215. */
  216. getFormVal(vModel) {
  217. try {
  218. if (vModel.indexOf('.') > -1) {
  219. let [tableVModel, cmpVModel] = vModel.split('.');
  220. if (typeof this.rowIndex === 'number') {
  221. if (!Array.isArray(this.formData[tableVModel]) || this.formData[tableVModel].length < this
  222. .rowIndex + 1) return 0;
  223. return this.formData[tableVModel][this.rowIndex][cmpVModel] || 0;
  224. } else {
  225. if (!this.formData[tableVModel].length) return 0;
  226. return this.formData[tableVModel].reduce((sum, c) => (c[cmpVModel] ? Number(c[cmpVModel]) :
  227. 0) + sum, 0);
  228. }
  229. }
  230. return this.formData[vModel] || 0
  231. } catch (error) {
  232. console.warn('计算公式出错, 可能包含无效的组件值', error)
  233. return 0
  234. }
  235. },
  236. }
  237. }
  238. </script>
  239. <style lang="scss" scoped>
  240. .jnpf-calculation {
  241. width: 100%;
  242. &.jnpf-calculation-right {
  243. text-align: right;
  244. }
  245. .tips {
  246. color: #999999;
  247. line-height: 40rpx;
  248. }
  249. }
  250. </style>