index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. /**
  2. * FeHelper 贷款计算器工具
  3. * @author zhaoxianlie
  4. */
  5. new Vue({
  6. el: '#containerPayback',
  7. data: {
  8. money: 10000,
  9. months: 12,
  10. yearRate: 24,
  11. paybackMode: 1,
  12. billList: [],
  13. formula: {
  14. "1": '等额本息:月供=贷款本金×[年化利率÷12×(1+年化利率÷12) ^ 还款月数]÷{[(1+年化利率÷12) ^ 还款月数]-1}',
  15. "2": '等额本金:月供=贷款本金÷还款月数x(1+年化利率÷12x剩余还款期数)'
  16. },
  17. calcMode: 'rate',
  18. calcModeText: {
  19. 'rate': '实际年化反推',
  20. 'loan': '月供账单计算'
  21. },
  22. revRate: 0,
  23. revAllAmount: 11347.20,
  24. revAllInterest: 0
  25. },
  26. mounted: function () {
  27. // 进制转换的初始化
  28. this.paybackConvert();
  29. },
  30. methods: {
  31. paybackConvert: function () {
  32. this.$nextTick(() => {
  33. if (!this.dataCheck()) {
  34. return;
  35. }
  36. if (this.calcMode === 'rate') {
  37. if (parseInt(this.paybackMode) === 1) {
  38. this.avgCapitalPlusInterest();
  39. } else {
  40. this.avgCapitalOnly();
  41. }
  42. } else {
  43. this.revCalcYearRate();
  44. }
  45. });
  46. },
  47. /**
  48. * 数据合法性校验
  49. */
  50. dataCheck: function () {
  51. if (!this.money || !/\d|\./.test(this.money) || parseInt(this.money) <= 0) {
  52. alert('请输入正确的贷款金额!');
  53. return false;
  54. }
  55. if (!this.months || /\D/.test(this.months) || parseInt(this.months) <= 0) {
  56. alert('请输入正确的贷款期限!');
  57. return false;
  58. }
  59. if (parseInt(this.months) > 360) {
  60. alert('在哪儿能贷30年?');
  61. return false;
  62. }
  63. if (this.calcMode === 'rate') {
  64. if (!this.yearRate || !/\d|\./.test(this.yearRate) || parseFloat(this.yearRate) <= 0) {
  65. alert('请输入正确的贷款年化利率!');
  66. return false;
  67. }
  68. } else {
  69. if (!this.revAllAmount || !/\d|\./.test(this.revAllAmount) || parseInt(this.revAllAmount) <= 0) {
  70. alert('请输入正确的总还款额!');
  71. return false;
  72. }
  73. if(parseInt(this.revAllAmount) < parseInt(this.money)) {
  74. alert('还款总额比贷款本金还低,这种情况就不用算了吧。。。');
  75. return false;
  76. }
  77. }
  78. return true;
  79. },
  80. /**
  81. * 等额本息计算方式
  82. * 每月还款额=贷款本金×[月利率×(1+月利率) ^ 还款月数]÷{[(1+月利率) ^ 还款月数]-1}
  83. */
  84. avgCapitalPlusInterest: function () {
  85. let rate = this.yearRate / 12 / 100;
  86. let mRate = Math.pow(rate + 1, this.months);
  87. // 每月账单金额
  88. let bill = Math.round(this.money * rate * mRate / (mRate - 1) * 100) / 100;
  89. // 累计还款额
  90. let allBillsAmount = bill * this.months;
  91. // 总利息
  92. let allInterest = allBillsAmount - this.money;
  93. // 剩余本金
  94. let leftOver = this.money;
  95. // 剩余利息
  96. let leftInterest = allInterest;
  97. // 剩余期限
  98. let leftTime = this.months;
  99. // 每期利息
  100. let interest = 0;
  101. // 每期本金
  102. let amount = 0;
  103. // 累计数据先入队
  104. this.billList = [{
  105. name: '合计',
  106. amount: Number(this.money).toFixed(2),
  107. interest: (Math.round(allInterest * 100) / 100).toFixed(2),
  108. bill: (Math.round(allBillsAmount * 100) / 100).toFixed(2),
  109. totalAmount: '-',
  110. totalInterest: '-',
  111. leftOver: (Math.round(leftOver * 100) / 100).toFixed(2),
  112. leftInterest: (Math.round(allInterest * 100) / 100).toFixed(2)
  113. }];
  114. // 生成账单列表
  115. for (; leftTime > 0; leftTime--) {
  116. mRate = Math.pow(rate + 1, leftTime || 0);
  117. // 特殊处理最后一期
  118. if (leftTime === 1) {
  119. interest = leftInterest;
  120. amount = leftOver;
  121. } else {
  122. // 月供利息
  123. interest = leftOver * rate;
  124. // 月供本金
  125. amount = bill - interest;
  126. }
  127. leftOver -= amount;
  128. leftInterest -= interest;
  129. this.billList.push({
  130. name: `第${this.months - leftTime + 1}期`,
  131. amount: (Math.round(amount * 100) / 100).toFixed(2),
  132. interest: (Math.round(interest * 100) / 100).toFixed(2),
  133. bill: (Math.round(bill * 100) / 100).toFixed(2),
  134. totalAmount: (Math.round((this.money - leftOver) * 100) / 100).toFixed(2),
  135. totalInterest: (Math.round((allInterest - leftInterest) * 100) / 100).toFixed(2),
  136. leftOver: (Math.round(leftOver * 100) / 100).toFixed(2),
  137. leftInterest: (Math.round(leftInterest * 100) / 100).toFixed(2)
  138. });
  139. }
  140. },
  141. /**
  142. * 等额本金还款公式
  143. *
  144. * 月供本金=贷款本金÷还款月数
  145. * 月供利息=月供本金×月利率x剩余还款期数
  146. * 月供总额=月供本金+月供利率 = 贷款本金÷还款月数x(1+月利率x剩余还款期数)
  147. *
  148. */
  149. avgCapitalOnly: function () {
  150. let rate = this.yearRate / 12 / 100;
  151. let amount = this.money / this.months;
  152. let deltaInterest = amount * rate;
  153. let allBillsAmount = (amount + this.money * rate + amount * (1 + rate)) / 2 * this.months;
  154. let allInterest = allBillsAmount - this.money;
  155. // 剩余本金
  156. let leftOver = this.money;
  157. // 剩余利息
  158. let leftInterest = allInterest;
  159. // 累计数据先入队
  160. this.billList = [{
  161. name: '合计',
  162. amount: Number(this.money).toFixed(2),
  163. interest: (Math.round(allInterest * 100) / 100).toFixed(2),
  164. bill: (Math.round(allBillsAmount * 100) / 100).toFixed(2),
  165. totalAmount: '-',
  166. totalInterest: '-',
  167. leftOver: (Math.round(leftOver * 100) / 100).toFixed(2),
  168. leftInterest: (Math.round(allInterest * 100) / 100).toFixed(2)
  169. }];
  170. // 每期利息
  171. let interest = 0;
  172. // 生成账单列表
  173. for (let leftTime = this.months; leftTime > 0; leftTime--) {
  174. interest = leftTime * deltaInterest;
  175. leftOver -= amount;
  176. leftInterest -= interest;
  177. this.billList.push({
  178. name: `第${this.months - leftTime + 1}期`,
  179. amount: (Math.round(amount * 100) / 100).toFixed(2),
  180. interest: (Math.round(interest * 100) / 100).toFixed(2),
  181. bill: (Math.round((amount + interest) * 100) / 100).toFixed(2),
  182. totalAmount: (Math.round((this.money - leftOver) * 100) / 100).toFixed(2),
  183. totalInterest: (Math.round((allInterest - leftInterest) * 100) / 100).toFixed(2),
  184. leftOver: (Math.round(leftOver * 100) / 100).toFixed(2),
  185. leftInterest: (Math.round(leftInterest * 100) / 100).toFixed(2)
  186. });
  187. }
  188. },
  189. /**
  190. * 计算模式切换
  191. */
  192. exchange: function () {
  193. this.calcMode = this.calcMode === 'rate' ? 'loan' : 'rate';
  194. this.$nextTick(() => {
  195. if (this.calcMode === 'rate') {
  196. this.paybackConvert();
  197. } else {
  198. this.revCalcYearRate();
  199. }
  200. });
  201. },
  202. /**
  203. * 通过给定的贷款本金、期限、总利息,进行真实年后利率反推
  204. */
  205. revCalcYearRate: function () {
  206. let retryArr = [];
  207. // 近似二分法定利率
  208. let getRate = (cur, curMore) => {
  209. // 找到上一次迭代时候的结果
  210. let rt = retryArr[retryArr.length - 2];
  211. if (curMore) {
  212. if (rt[1]) { // 上次大,这次还大,直接找到最后一次「小」的值做二分,否则-2处理
  213. for (let i = retryArr.length - 3; i >= 0; i--) {
  214. if (!retryArr[i][1]) {
  215. return (cur + retryArr[i][0]) / 2;
  216. }
  217. }
  218. return cur - 2;
  219. } else { // 上次小,这次大,直接两次结果做二分
  220. return (cur + rt[0]) / 2;
  221. }
  222. } else {
  223. if (rt[1]) { // 上次小,这次还大,直接两次结果做二分
  224. return (cur + rt[0]) / 2;
  225. } else { // 上次小,这次还小,直接找到最后一次「大」的值做二分,否则+2处理
  226. for (let i = retryArr.length - 3; i >= 0; i--) {
  227. if (retryArr[i][1]) {
  228. return (cur + retryArr[i][0]) / 2;
  229. }
  230. }
  231. return cur + 2;
  232. }
  233. }
  234. };
  235. // 利率近似值计算
  236. let calcTotal = (money, month, year, target, method) => {
  237. let rate = year / 12 / 100;
  238. let total = 0;
  239. if (method === 1) { // 等额本息
  240. let mRate = Math.pow(rate + 1, month);
  241. total = month * Math.round(money * rate * mRate / (mRate - 1) * 100) / 100;
  242. } else { // 等额本金
  243. total = (money / month + money * rate + money / month * (1 + rate)) / 2 * month;
  244. }
  245. let delta = Math.abs(total - target);
  246. return delta >= 0 && delta <= ((target - money) / money * month / 12 / 0.1374) ? [year] : (total > target ? [year, 1] : [year, 0]);
  247. };
  248. // 迭代推演
  249. let guessRate = (money, month, total, method) => {
  250. retryArr = [[0, 0], [0, 0]];
  251. let cur = (total - money) / money / month * 12 * 100;
  252. let arr = calcTotal(money, month, cur, total, method);
  253. let rt, time = 0, result = 'unknown';
  254. while (time < 1000) {
  255. time++;
  256. if (arr.length === 1) {
  257. result = Math.round(arr[0] * 10) / 10 + '%';
  258. break;
  259. }
  260. else {
  261. retryArr.push(arr);
  262. cur = getRate(arr[0], arr[1]);
  263. arr = calcTotal(money, month, cur, total, method);
  264. }
  265. }
  266. return result;
  267. };
  268. this.revAllInterest = Math.round((this.revAllAmount - this.money) * 100) / 100;
  269. this.revRate = guessRate(this.money, this.months, this.revAllAmount, parseInt(this.paybackMode));
  270. }
  271. }
  272. });