index.vue 7.7 KB

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