index.js 12 KB

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