index.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. /**
  2. * FeHelper 贷款计算器工具
  3. * @author zhaoxianlie
  4. */
  5. new Vue({
  6. el: '#pageContainer',
  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. this.loadPatchHotfix();
  31. },
  32. methods: {
  33. paybackConvert: function () {
  34. this.$nextTick(() => {
  35. if (!this.dataCheck()) {
  36. return;
  37. }
  38. if (this.calcMode === 'rate') {
  39. if (parseInt(this.paybackMode) === 1) {
  40. this.avgCapitalPlusInterest();
  41. } else {
  42. this.avgCapitalOnly();
  43. }
  44. } else {
  45. this.revCalcYearRate();
  46. }
  47. });
  48. },
  49. /**
  50. * 数据合法性校验
  51. */
  52. dataCheck: function () {
  53. if (!this.money || !/\d|\./.test(this.money) || parseInt(this.money) <= 0) {
  54. alert('请输入正确的贷款金额!');
  55. return false;
  56. }
  57. if (!this.months || /\D/.test(this.months) || parseInt(this.months) <= 0) {
  58. alert('请输入正确的贷款期限!');
  59. return false;
  60. }
  61. if (parseInt(this.months) > 360) {
  62. alert('在哪儿能贷30年?');
  63. return false;
  64. }
  65. if (this.calcMode === 'rate') {
  66. if (!this.yearRate || !/\d|\./.test(this.yearRate) || parseFloat(this.yearRate) <= 0) {
  67. alert('请输入正确的贷款年化利率!');
  68. return false;
  69. }
  70. } else {
  71. if (!this.revAllAmount || !/\d|\./.test(this.revAllAmount) || parseInt(this.revAllAmount) <= 0) {
  72. alert('请输入正确的总还款额!');
  73. return false;
  74. }
  75. if(parseInt(this.revAllAmount) < parseInt(this.money)) {
  76. alert('还款总额比贷款本金还低,这种情况就不用算了吧。。。');
  77. return false;
  78. }
  79. }
  80. return true;
  81. },
  82. /**
  83. * 等额本息计算方式
  84. * 每月还款额=贷款本金×[月利率×(1+月利率) ^ 还款月数]÷{[(1+月利率) ^ 还款月数]-1}
  85. */
  86. avgCapitalPlusInterest: function () {
  87. let rate = this.yearRate / 12 / 100;
  88. let mRate = Math.pow(rate + 1, this.months);
  89. // 每月账单金额
  90. let bill = Math.round(this.money * rate * mRate / (mRate - 1) * 100) / 100;
  91. // 累计还款额
  92. let allBillsAmount = bill * this.months;
  93. // 总利息
  94. let allInterest = allBillsAmount - this.money;
  95. // 剩余本金
  96. let leftOver = this.money;
  97. // 剩余利息
  98. let leftInterest = allInterest;
  99. // 剩余期限
  100. let leftTime = this.months;
  101. // 每期利息
  102. let interest = 0;
  103. // 每期本金
  104. let amount = 0;
  105. // 累计数据先入队
  106. this.billList = [{
  107. name: '合计',
  108. amount: Number(this.money).toFixed(2),
  109. interest: (Math.round(allInterest * 100) / 100).toFixed(2),
  110. bill: (Math.round(allBillsAmount * 100) / 100).toFixed(2),
  111. totalAmount: '-',
  112. totalInterest: '-',
  113. leftOver: (Math.round(leftOver * 100) / 100).toFixed(2),
  114. leftInterest: (Math.round(allInterest * 100) / 100).toFixed(2)
  115. }];
  116. // 生成账单列表
  117. for (; leftTime > 0; leftTime--) {
  118. mRate = Math.pow(rate + 1, leftTime || 0);
  119. // 特殊处理最后一期
  120. if (leftTime === 1) {
  121. interest = leftInterest;
  122. amount = leftOver;
  123. } else {
  124. // 月供利息
  125. interest = leftOver * rate;
  126. // 月供本金
  127. amount = bill - interest;
  128. }
  129. leftOver -= amount;
  130. leftInterest -= interest;
  131. this.billList.push({
  132. name: `第${this.months - leftTime + 1}期`,
  133. amount: (Math.round(amount * 100) / 100).toFixed(2),
  134. interest: (Math.round(interest * 100) / 100).toFixed(2),
  135. bill: (Math.round(bill * 100) / 100).toFixed(2),
  136. totalAmount: (Math.round((this.money - leftOver) * 100) / 100).toFixed(2),
  137. totalInterest: (Math.round((allInterest - leftInterest) * 100) / 100).toFixed(2),
  138. leftOver: (Math.round(leftOver * 100) / 100).toFixed(2),
  139. leftInterest: (Math.round(leftInterest * 100) / 100).toFixed(2)
  140. });
  141. }
  142. },
  143. /**
  144. * 等额本金还款公式
  145. *
  146. * 月供本金=贷款本金÷还款月数
  147. * 月供利息=月供本金×月利率x剩余还款期数
  148. * 月供总额=月供本金+月供利率 = 贷款本金÷还款月数x(1+月利率x剩余还款期数)
  149. *
  150. */
  151. avgCapitalOnly: function () {
  152. let rate = this.yearRate / 12 / 100;
  153. let amount = this.money / this.months;
  154. let deltaInterest = amount * rate;
  155. let allBillsAmount = (amount + this.money * rate + amount * (1 + rate)) / 2 * this.months;
  156. let allInterest = allBillsAmount - this.money;
  157. // 剩余本金
  158. let leftOver = this.money;
  159. // 剩余利息
  160. let leftInterest = allInterest;
  161. // 累计数据先入队
  162. this.billList = [{
  163. name: '合计',
  164. amount: Number(this.money).toFixed(2),
  165. interest: (Math.round(allInterest * 100) / 100).toFixed(2),
  166. bill: (Math.round(allBillsAmount * 100) / 100).toFixed(2),
  167. totalAmount: '-',
  168. totalInterest: '-',
  169. leftOver: (Math.round(leftOver * 100) / 100).toFixed(2),
  170. leftInterest: (Math.round(allInterest * 100) / 100).toFixed(2)
  171. }];
  172. // 每期利息
  173. let interest = 0;
  174. // 生成账单列表
  175. for (let leftTime = this.months; leftTime > 0; leftTime--) {
  176. interest = leftTime * deltaInterest;
  177. leftOver -= amount;
  178. leftInterest -= interest;
  179. this.billList.push({
  180. name: `第${this.months - leftTime + 1}期`,
  181. amount: (Math.round(amount * 100) / 100).toFixed(2),
  182. interest: (Math.round(interest * 100) / 100).toFixed(2),
  183. bill: (Math.round((amount + interest) * 100) / 100).toFixed(2),
  184. totalAmount: (Math.round((this.money - leftOver) * 100) / 100).toFixed(2),
  185. totalInterest: (Math.round((allInterest - leftInterest) * 100) / 100).toFixed(2),
  186. leftOver: (Math.round(leftOver * 100) / 100).toFixed(2),
  187. leftInterest: (Math.round(leftInterest * 100) / 100).toFixed(2)
  188. });
  189. }
  190. },
  191. /**
  192. * 计算模式切换
  193. */
  194. exchange: function () {
  195. this.calcMode = this.calcMode === 'rate' ? 'loan' : 'rate';
  196. this.$nextTick(() => {
  197. if (this.calcMode === 'rate') {
  198. this.paybackConvert();
  199. } else {
  200. this.revCalcYearRate();
  201. }
  202. });
  203. },
  204. /**
  205. * 通过给定的贷款本金、期限、总利息,进行真实年后利率反推
  206. */
  207. revCalcYearRate: function () {
  208. let retryArr = [];
  209. // 近似二分法定利率
  210. let getRate = (cur, curMore) => {
  211. // 找到上一次迭代时候的结果
  212. let rt = retryArr[retryArr.length - 2];
  213. if (curMore) {
  214. if (rt[1]) { // 上次大,这次还大,直接找到最后一次「小」的值做二分,否则-2处理
  215. for (let i = retryArr.length - 3; i >= 0; i--) {
  216. if (!retryArr[i][1]) {
  217. return (cur + retryArr[i][0]) / 2;
  218. }
  219. }
  220. return cur - 2;
  221. } else { // 上次小,这次大,直接两次结果做二分
  222. return (cur + rt[0]) / 2;
  223. }
  224. } else {
  225. if (rt[1]) { // 上次小,这次还大,直接两次结果做二分
  226. return (cur + rt[0]) / 2;
  227. } else { // 上次小,这次还小,直接找到最后一次「大」的值做二分,否则+2处理
  228. for (let i = retryArr.length - 3; i >= 0; i--) {
  229. if (retryArr[i][1]) {
  230. return (cur + retryArr[i][0]) / 2;
  231. }
  232. }
  233. return cur + 2;
  234. }
  235. }
  236. };
  237. // 利率近似值计算
  238. let calcTotal = (money, month, year, target, method) => {
  239. let rate = year / 12 / 100;
  240. let total = 0;
  241. if (method === 1) { // 等额本息
  242. let mRate = Math.pow(rate + 1, month);
  243. total = month * Math.round(money * rate * mRate / (mRate - 1) * 100) / 100;
  244. } else { // 等额本金
  245. total = (money / month + money * rate + money / month * (1 + rate)) / 2 * month;
  246. }
  247. let delta = Math.abs(total - target);
  248. return delta >= 0 && delta <= ((target - money) / money * month / 12 / 0.1374) ? [year] : (total > target ? [year, 1] : [year, 0]);
  249. };
  250. // 迭代推演
  251. let guessRate = (money, month, total, method) => {
  252. retryArr = [[0, 0], [0, 0]];
  253. let cur = (total - money) / money / month * 12 * 100;
  254. let arr = calcTotal(money, month, cur, total, method);
  255. let rt, time = 0, result = 'unknown';
  256. while (time < 1000) {
  257. time++;
  258. if (arr.length === 1) {
  259. result = Math.round(arr[0] * 10) / 10 + '%';
  260. break;
  261. }
  262. else {
  263. retryArr.push(arr);
  264. cur = getRate(arr[0], arr[1]);
  265. arr = calcTotal(money, month, cur, total, method);
  266. }
  267. }
  268. return result;
  269. };
  270. this.money = this.revMoney;
  271. this.revAllInterest = Math.round((this.revAllAmount - this.money) * 100) / 100;
  272. this.revRate = guessRate(this.money, this.months, this.revAllAmount, parseInt(this.paybackMode));
  273. },
  274. openDonateModal: function(event) {
  275. event.preventDefault();
  276. event.stopPropagation();
  277. chrome.runtime.sendMessage({
  278. type: 'fh-dynamic-any-thing',
  279. thing: 'open-donate-modal',
  280. params: { toolName: 'loan-rate' }
  281. });
  282. },
  283. openOptionsPage: function(event) {
  284. event.preventDefault();
  285. event.stopPropagation();
  286. chrome.runtime.openOptionsPage();
  287. },
  288. loadPatchHotfix() {
  289. // 页面加载时自动获取并注入页面的补丁
  290. chrome.runtime.sendMessage({
  291. type: 'fh-dynamic-any-thing',
  292. thing: 'fh-get-tool-patch',
  293. toolName: 'loan-rate'
  294. }, patch => {
  295. if (patch) {
  296. if (patch.css) {
  297. const style = document.createElement('style');
  298. style.textContent = patch.css;
  299. document.head.appendChild(style);
  300. }
  301. if (patch.js) {
  302. try {
  303. if (window.evalCore && window.evalCore.getEvalInstance) {
  304. window.evalCore.getEvalInstance(window)(patch.js);
  305. }
  306. } catch (e) {
  307. console.error('loan-rate补丁JS执行失败', e);
  308. }
  309. }
  310. }
  311. });
  312. },
  313. }
  314. });