stats-quota.js 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225
  1. class DataHandler {
  2. constructor() {
  3. this[CONSTANTS.KEY_QUOTA_COMPANY] = [];
  4. this[CONSTANTS.KEY_QUOTA_MERCHANT] = [];
  5. this[CONSTANTS.KEY_QUOTA_CHANNEL] = [];
  6. this[CONSTANTS.KEY_QUOTA_SUMMARY] = [];
  7. this[CONSTANTS.KEY_QUOTA_CONDITION] = [];
  8. this.structureMapping = {
  9. // company结构: 排序(sort_order), 主体名称(name), 总额度(total_quota), 已用额度(used_quota), 备注(remark)
  10. [CONSTANTS.KEY_QUOTA_COMPANY]: ['sort', 'name', 'total_quota', 'used_quota', 'remark'],
  11. // merchant结构: 对应主体(subject), 机构id(mch_id), 已开票总金额(total_invoiced), 上次开票日期(last_invoice_date)
  12. [CONSTANTS.KEY_QUOTA_MERCHANT]: ['sort','subject', 'mch_id', 'total_invoiced', 'last_invoice_date', 'remark', 'month_opened'],
  13. // channel结构: 对应主体(subject), 通道id(store_id), 已开票总金额(total_invoiced), 上次开票日期(last_invoice_date)
  14. [CONSTANTS.KEY_QUOTA_CHANNEL]: ['sort', 'subject', 'store_id', 'total_invoiced', 'last_invoice_date', 'remark', 'month_opened'],
  15. //summary结构: 主体(subject)
  16. [CONSTANTS.KEY_QUOTA_SUMMARY]: ['sort', 'subject', 'invoice_remaining_amount'],
  17. //condition结构: 月份(month)
  18. [CONSTANTS.KEY_QUOTA_CONDITION]: ['month']
  19. };
  20. }
  21. translateToIndex(structure, data) {
  22. return this.structureMapping[structure].map((key, index) => data[key]);
  23. }
  24. translateToField(structure, data) {
  25. let translatedData = {};
  26. this.structureMapping[structure].forEach((key, index) => {
  27. translatedData[key] = data[index];
  28. });
  29. return translatedData;
  30. }
  31. getCompanyList() {
  32. const list = [], that = this;
  33. this[CONSTANTS.KEY_QUOTA_COMPANY].forEach(v => {
  34. list.push(that.translateToField(CONSTANTS.KEY_QUOTA_COMPANY, v));
  35. });
  36. return list;
  37. }
  38. getMerchantList() {
  39. const list = [], that = this;
  40. this[CONSTANTS.KEY_QUOTA_MERCHANT].forEach((v) => {
  41. list.push(that.translateToField(CONSTANTS.KEY_QUOTA_MERCHANT, v));
  42. });
  43. return list;
  44. }
  45. getChannelList() {
  46. const list = [], that = this;
  47. this[CONSTANTS.KEY_QUOTA_CHANNEL].forEach((v) => {
  48. list.push(that.translateToField(CONSTANTS.KEY_QUOTA_CHANNEL, v));
  49. });
  50. return list;
  51. }
  52. getSummaryList() {
  53. const list = [], that = this;
  54. this[CONSTANTS.KEY_QUOTA_SUMMARY].forEach((v) => {
  55. list.push(that.translateToField(CONSTANTS.KEY_QUOTA_SUMMARY, v));
  56. });
  57. return list;
  58. }
  59. getConditionList() { //条件设置只会有一条数据
  60. const list = [], that = this;
  61. this[CONSTANTS.KEY_QUOTA_CONDITION].forEach((v) => {
  62. list.push(that.translateToField(CONSTANTS.KEY_QUOTA_CONDITION, v));
  63. });
  64. return list;
  65. }
  66. async addOrUpdateData(structure, newData, uni_key) {
  67. let list = this[structure];
  68. let translatedData = this.translateToIndex(structure, newData);
  69. let index = list.findIndex(item => item[uni_key] === translatedData[uni_key]);
  70. if (index !== -1) {
  71. list[index] = translatedData;
  72. } else {
  73. list.push(translatedData);
  74. }
  75. await this.saveDataToBackend(structure);
  76. }
  77. async updateAllData(structure, dataList) {
  78. let list = this[structure];
  79. list.length = 0;
  80. dataList.forEach(data => {
  81. let translatedData = this.translateToIndex(structure, data);
  82. list.push(translatedData);
  83. });
  84. await this.saveDataToBackend(structure);
  85. }
  86. async removeData(structure, index) {
  87. let list = this[structure];
  88. if (index >= 0 && index < list.length) {
  89. list.splice(index, 1);
  90. await this.saveDataToBackend(structure);
  91. }
  92. }
  93. async saveDataToBackend(structure) {
  94. this.checkAndFixData(structure);
  95. const compressedData = this.compressData(structure);
  96. await statsApi.setStatsSettings(structure, compressedData);
  97. }
  98. checkAndFixData(structure) {
  99. let list = this[structure];
  100. let structureLength = this.structureMapping[structure].length;
  101. list.forEach(item => {
  102. if (item.length < structureLength) {
  103. for (let i = item.length; i < structureLength; i++) {
  104. item.push('');
  105. }
  106. }
  107. });
  108. }
  109. compressData(structure) {
  110. return this[structure];
  111. }
  112. decompressData(structure, data) {
  113. this[structure] = data[structure] || [];
  114. this.checkAndFixData(structure);
  115. }
  116. async loadComapny() {
  117. const data = await statsApi.getStatsSettings([CONSTANTS.KEY_QUOTA_COMPANY]);
  118. if (data.state !== true) {
  119. showErr('网络异常,请刷新页面重试');
  120. return;
  121. }
  122. this.decompressData(CONSTANTS.KEY_QUOTA_COMPANY, data);
  123. }
  124. async loadMerchant() {
  125. const data = await statsApi.getStatsSettings([CONSTANTS.KEY_QUOTA_MERCHANT]);
  126. if (data.state !== true) {
  127. showErr('网络异常,请刷新页面重试');
  128. return;
  129. }
  130. this.decompressData(CONSTANTS.KEY_QUOTA_MERCHANT, data);
  131. }
  132. async loadChannel() {
  133. const data = await statsApi.getStatsSettings([CONSTANTS.KEY_QUOTA_CHANNEL]);
  134. if (data.state !== true) {
  135. showErr('网络异常,请刷新页面重试');
  136. return;
  137. }
  138. this.decompressData(CONSTANTS.KEY_QUOTA_CHANNEL, data);
  139. }
  140. async loadSummary() {
  141. const data = await statsApi.getStatsSettings([CONSTANTS.KEY_QUOTA_SUMMARY]);
  142. if (data.state !== true) {
  143. showErr('网络异常,请刷新页面重试');
  144. return;
  145. }
  146. this.decompressData(CONSTANTS.KEY_QUOTA_SUMMARY, data);
  147. }
  148. async loadCondition() {
  149. const data = await statsApi.getStatsSettings([CONSTANTS.KEY_QUOTA_CONDITION]);
  150. if (data.state !== true) {
  151. showErr('网络异常,请刷新页面重试');
  152. return;
  153. }
  154. this.decompressData(CONSTANTS.KEY_QUOTA_CONDITION, data);
  155. }
  156. }
  157. class QuotaApiService extends BaseService {
  158. getStatsSettings(keys = []) {
  159. const endpoint = '?act=refill_tax_quota_stats&op=stats_settings&key=' + keys.join(',');
  160. return this.request(endpoint, 'GET', {});
  161. };
  162. setStatsSettings(key, value) {
  163. const endpoint = '?act=refill_tax_quota_stats&op=stats_settings_save';
  164. let data = {
  165. key: key,
  166. value: value
  167. };
  168. return this.request(endpoint, 'POST', data);
  169. };
  170. getSysMchs() {
  171. const endpoint = '?act=refill_tax_quota_stats&op=sys_mchs';
  172. return this.request(endpoint, 'GET', {});
  173. };
  174. getSysMchAmounts(mchids, month) {
  175. const endpoint = `?act=refill_tax_quota_stats&op=sys_mchs_amounts&mchids=${mchids}&month=${month}` ;
  176. return this.request(endpoint, 'GET', {});
  177. };
  178. getSysChans() {
  179. const endpoint = '?act=refill_tax_quota_stats&op=sys_chans';
  180. return this.request(endpoint, 'GET', {});
  181. };
  182. getSysChanAmounts(storeIds, month) {
  183. const endpoint = `?act=refill_tax_quota_stats&op=sys_chans_amounts&store_ids=${storeIds}&month=${month}`;
  184. return this.request(endpoint, 'GET', {});
  185. };
  186. }
  187. const statsApi = new QuotaApiService('');
  188. const dataHelper = new DataHandler();
  189. class CompanyComponent extends Component {
  190. constructor(options) {
  191. super(Object.assign(options, {
  192. template: `
  193. <div id="{{comp-id}}">
  194. <div class="quota-tablebar">
  195. <button v-on:click="open" type="button" class="layui-btn layui-bg-green">新增</button>
  196. <span>1、主体额度说明:(按月统计)</span>
  197. </div>
  198. <div id="company-model-{{comp-id}}" class="company-model" style="display: none;">
  199. <div class="leftmenu-manager-container selected-items" id="selected-items-{{comp-id}}"></div>
  200. <div class="model-form">
  201. <div class="form-group">
  202. <label for="name-{{comp-id}}"><span class="req-flag">*</span>主体名称:</label>
  203. <input type="text" id="name-{{comp-id}}" />
  204. </div>
  205. <div class="form-group">
  206. <label for="total_quota-{{comp-id}}">总额度(元):</label>
  207. <input type="text" id="total_quota-{{comp-id}}" />
  208. <span style="display: inline-block;margin-left: 10px;color: red;" id="total_quota_cn-{{comp-id}}"></span>
  209. </div>
  210. <div class="form-group">
  211. <label for="used_quota-{{comp-id}}">已用额度(元):</label>
  212. <input type="text" id="used_quota-{{comp-id}}" />
  213. <span style="display: inline-block;margin-left: 10px;color: red;" id="used_quota-{{comp-id}}"></span>
  214. </div>
  215. <div class="form-group">
  216. <label for="remark-{{comp-id}}">备注:</label>
  217. <textarea id="remark-{{comp-id}}"></textarea>
  218. </div>
  219. <button v-on:click="save" class="layui-btn layui-btn-sm">保存</button>
  220. </div>
  221. </div>
  222. </div>
  223. `
  224. }));
  225. this.parent = options.parent;
  226. this.cacheKey = this.props['type'];
  227. this.lastData = undefined;
  228. this.sortChange = false;
  229. }
  230. async mounted() {
  231. const that = this;
  232. $('#selected-items-' + this.id).sortable({
  233. containment: "parent",
  234. cursor: "move",
  235. update: function(event, ui) {
  236. that.sortChange = true;
  237. }
  238. }).disableSelection();
  239. this.inputEvent($('#total_quota-' + this.id));
  240. this.inputEvent($('#used_quota-' + this.id));
  241. }
  242. async open() {
  243. this.companyLeftList();
  244. const that = this;
  245. layer.open({
  246. type: 1,
  247. title: '新增主体',
  248. content: $('#company-model-' + this.props['comp-id']),
  249. area: ['800px', '500px'],
  250. success: function () {
  251. },
  252. end: async function () {
  253. if (that.sortChange === true) {
  254. const loadIndex = layer.load(2, {shade: [0.2, '#000']});
  255. await that.updateSort();
  256. layer.close(loadIndex);
  257. }
  258. if (that.isEdit() === true || that.sortChange === true) {
  259. if (that.parent && typeof that.parent.emit === 'function') {
  260. that.parent.emit(that.props['emit'], that.cacheKey, that.cacheData);
  261. }
  262. }
  263. }
  264. });
  265. }
  266. isEdit() {
  267. const companyList = dataHelper.getCompanyList();
  268. if (this.lastData.length !== companyList.length) {
  269. return true;
  270. }
  271. for (let i = 0; i < this.lastData.length; i++) {
  272. if (this.lastData[i].name !== companyList[i].name) {
  273. return true;
  274. }
  275. }
  276. return false;
  277. }
  278. companyLeftList() {
  279. const content = this.createModelContent();
  280. $('#company-model-' + this.id + ' .leftmenu-manager-container').html(content);
  281. this.bindEvents(); //左列表刷新后需要重新绑定v-on事件
  282. }
  283. createModelContent() {
  284. const companyList = dataHelper.getCompanyList();
  285. if (this.lastData === undefined) {
  286. this.lastData = deepCloneData(companyList);
  287. }
  288. let html = '';
  289. for (let i = 0; i < companyList.length; i++) {
  290. html += '<label class="ch-tags sortable-item" data-name="' + companyList[i].name + '">' +
  291. companyList[i].name +
  292. '<span class="delete-btn" v-on:click="delete" data-index="' + i + '">&times;</span>' +
  293. '</label>';
  294. }
  295. return html;
  296. }
  297. async save() {
  298. const that = this;
  299. const name = $('#name-' + this.id).val().trim();
  300. let total_quota = $('#total_quota-' + this.id).val().trim();
  301. let used_quota = $('#used_quota-' + this.id).val().trim();
  302. let remark = $('#remark-' + this.id).val().trim();
  303. if (name === '') {
  304. showErr('主体名称 未填写');
  305. return;
  306. }
  307. const companyList = dataHelper.getCompanyList();
  308. const used = companyList.find(item => item.name === name);
  309. if (used !== undefined) {
  310. showErr('不能重复设置主体');
  311. return;
  312. }
  313. total_quota = total_quota === '' ? 0 : parseFloat(total_quota);
  314. used_quota = used_quota === '' ? 0 : parseFloat(used_quota);
  315. const data = {
  316. sort: 0,
  317. name: name,
  318. total_quota: formatDecimals(total_quota, 2),
  319. used_quota: formatDecimals(used_quota, 2),
  320. remark: remark
  321. }
  322. await dataHelper.addOrUpdateData(CONSTANTS.KEY_QUOTA_COMPANY, data, 1);
  323. this.companyLeftList();
  324. }
  325. delete(event) {
  326. const that = this;
  327. const index = event.currentTarget.getAttribute('data-index');
  328. const list = dataHelper.getCompanyList();
  329. const company = list[index].name;
  330. const merchantList = dataHelper.getMerchantList();
  331. const usedMch = merchantList.find(item => item.subject === company);
  332. if (usedMch !== undefined) {
  333. showErr('机构统计中有该主体关联,不能删除');
  334. return;
  335. }
  336. const channelList = dataHelper.getChannelList();
  337. const usedChan = channelList.find(item => item.subject === company);
  338. if (usedChan !== undefined) {
  339. showErr('上游统计中有该主体关联,不能删除');
  340. return;
  341. }
  342. const summaryList = dataHelper.getSummaryList();
  343. const usedSummary = summaryList.find(item => item.subject === company);
  344. if (usedSummary !== undefined) {
  345. showErr('综合统计中有该主体关联,不能删除');
  346. return;
  347. }
  348. layer.confirm('确定要删除 "' + company + '" 吗?', {
  349. title: '删除确认',
  350. icon: 3,
  351. btn: ['删除', '取消'],
  352. btn1: async function (btn_i) {
  353. const loadIndex = layer.load(2, {shade: [0.2, '#000']});
  354. await dataHelper.removeData(CONSTANTS.KEY_QUOTA_COMPANY, index);
  355. that.companyLeftList();
  356. layer.close(loadIndex);
  357. layer.close(btn_i);
  358. }
  359. });
  360. }
  361. async updateSort() {
  362. const selectedItems = $('#' + this.id).find('#selected-items-' + this.id + ' .sortable-item');
  363. const that = this;
  364. const companyList = dataHelper.getCompanyList();
  365. selectedItems.each(function (index) {
  366. const value = $(this).data('name');
  367. const currIndex = companyList.findIndex(item => item.name === value);
  368. if (currIndex !== -1) {
  369. companyList[currIndex].sort = index;
  370. }
  371. });
  372. companyList.sort((a, b) => a.sort - b.sort);
  373. await dataHelper.updateAllData(CONSTANTS.KEY_QUOTA_COMPANY, companyList);
  374. }
  375. inputEvent(jqObj) {
  376. jqObj.off('input', handleInputNormal2)
  377. .off('keypress')
  378. .off('blur')
  379. .on('input', handleInputNormal2)
  380. .on('keypress', function (e) {
  381. if (e.keyCode === 13) {
  382. e.preventDefault();
  383. jqObj.blur();
  384. }
  385. })
  386. .on('blur', function () {
  387. let val = jqObj.val();
  388. if (!isNumeric(val)) {
  389. jqObj.val('0');
  390. return;
  391. }
  392. val = normalizeAmount(val);//过滤,比如前导 0
  393. jqObj.val(formatDecimals(val));
  394. const $span = jqObj.next('span');
  395. if ($span.length > 0) {
  396. $span.text('(' + cnMoneyFormat(val) + ')');
  397. }
  398. });
  399. }
  400. }
  401. class MerchantComponent extends Component {
  402. constructor(options) {
  403. super(Object.assign(options, {
  404. template: `
  405. <div id="{{comp-id}}">
  406. <div class="quota-tablebar">
  407. <button v-on:click="open" type="button" class="layui-btn layui-bg-green">新增</button>
  408. <span>2、下游(机构)</span>
  409. </div>
  410. <div id="merchant-model-{{comp-id}}" class="merchant-model" style="display: none;">
  411. <div class="leftmenu-manager-container selected-items" id="selected-items-{{comp-id}}"></div>
  412. <div class="model-form">
  413. <div class="form-group">
  414. <label for="company-{{comp-id}}"><span class="req-flag">*</span>主体:</label>
  415. <select id="company-{{comp-id}}">
  416. </select>
  417. </div>
  418. <div class="form-group">
  419. <label for="merchant-{{comp-id}}"><span class="req-flag">*</span>机构:</label>
  420. <div class="layui-form" lay-filter="mch_select_{{comp-id}}" style="width:220px;">
  421. <select id="merchant-{{comp-id}}" lay-search style="width:220px;">
  422. </select>
  423. </div>
  424. </div>
  425. <div class="form-group">
  426. <label for="total_invoiced-{{comp-id}}">已开票金额(元):</label>
  427. <input type="text" id="total_invoiced-{{comp-id}}" />
  428. </div>
  429. <div class="form-group">
  430. <label for="last_invoice_date-{{comp-id}}">上次开票日期:</label>
  431. <input type="text" id="last_invoice_date-{{comp-id}}" class="date" />
  432. </div>
  433. <div class="form-group">
  434. <label for="remark-{{comp-id}}">备注:</label>
  435. <textarea id="remark-{{comp-id}}"></textarea>
  436. </div>
  437. <button v-on:click="save" class="layui-btn layui-btn-sm">保存</button>
  438. </div>
  439. </div>
  440. </div>
  441. `
  442. }));
  443. this.parent = options.parent;
  444. this.cacheKey = this.props['type'];
  445. this.lastData = undefined;
  446. this.sortChange = false;
  447. this.sysMchs = [];
  448. }
  449. async mounted() {
  450. const that = this;
  451. $('#selected-items-' + this.id).sortable({
  452. containment: "parent",
  453. cursor: "move",
  454. update: function(event, ui) {
  455. that.sortChange = true;
  456. }
  457. }).disableSelection();
  458. this.inputEvent($('#total_invoiced-' + this.id));
  459. laydate.render({
  460. elem: '#last_invoice_date-' + this.id,
  461. type: 'date',
  462. trigger: 'click',
  463. max: new Date().setDate(new Date().getDate() - 1)
  464. });
  465. }
  466. async open() {
  467. this.companySelector();
  468. await this.mchSelector();
  469. this.merchantLeftList();
  470. const that = this;
  471. layer.open({
  472. type: 1,
  473. title: '新增下游',
  474. content: $('#merchant-model-' + this.props['comp-id']),
  475. area: ['800px', '580px'],
  476. success: function () {
  477. },
  478. end: async function () {
  479. if (that.sortChange === true) {
  480. const loadIndex = layer.load(2, {shade: [0.2, '#000']});
  481. await that.updateSort();
  482. layer.close(loadIndex);
  483. }
  484. if (that.isEdit() === true) {
  485. if (that.parent && typeof that.parent.emit === 'function') {
  486. that.parent.emit(that.props['emit'], that.cacheKey, that.cacheData);
  487. }
  488. }
  489. }
  490. });
  491. }
  492. isEdit() {
  493. const merchantList = dataHelper.getMerchantList();
  494. if (this.lastData.length !== merchantList.length) {
  495. return true;
  496. }
  497. for (let i = 0; i < this.lastData.length; i++) {
  498. if (this.lastData[i].mch_id !== merchantList[i].mch_id) {
  499. return true;
  500. }
  501. }
  502. return false;
  503. }
  504. merchantLeftList() {
  505. const content = this.createModelContent();
  506. $('#merchant-model-' + this.id + ' .leftmenu-manager-container').html(content);
  507. this.bindEvents();
  508. }
  509. createModelContent() {
  510. const merchantList = dataHelper.getMerchantList();
  511. if (this.lastData === undefined) {
  512. this.lastData = deepCloneData(merchantList);
  513. }
  514. let html = '';
  515. for (let i = 0; i < merchantList.length; i++) {
  516. const sysItem = this.sysMchs.find(sItem => sItem.mch_id === merchantList[i].mch_id);
  517. let mchName = '';
  518. if (sysItem !== undefined) {
  519. mchName = sysItem.mch_name;
  520. } else {
  521. mchName = merchantList[i].mch_id + '(无效项)';
  522. }
  523. html += '<label class="ch-tags sortable-item" data-name="' + mchName + '">' +
  524. mchName +
  525. '<span class="delete-btn" v-on:click="delete" data-index="' + i + '">&times;</span>' +
  526. '</label>';
  527. }
  528. return html;
  529. }
  530. companySelector() {
  531. let content = '';
  532. const companyList = dataHelper.getCompanyList();
  533. companyList.forEach(item=> {
  534. content += '<option>' + item.name + '</option>';
  535. });
  536. $('#merchant-model-' + this.id + ' #company-' + this.id).html(content);
  537. }
  538. async mchSelector() {
  539. let content = '';
  540. const mchData = await statsApi.getSysMchs();
  541. if (mchData.state === true) {
  542. mchData.list.forEach(item => {
  543. content += '<option value="' + item.mch_id + '">' + item.mch_name + '</option>';
  544. });
  545. $('#merchant-model-' + this.id + ' #merchant-' + this.id).html(content);
  546. this.sysMchs = mchData.list;
  547. }
  548. //只能在渲染option之后才可以设置select搜索组件
  549. const componentId = this.id;
  550. layui.use('form', function() {
  551. const form = layui.form;
  552. form.render('select', 'mch_select_' + componentId);
  553. });
  554. }
  555. async save() {
  556. const subject = $('#company-' + this.id).val();
  557. let mch_id = $('#merchant-' + this.id).val();
  558. let total_invoiced = $('#total_invoiced-' + this.id).val().trim();
  559. let last_invoice_date = $('#last_invoice_date-' + this.id).val();
  560. let remark = $('#remark-' + this.id).val().trim();
  561. if (subject === '') {
  562. showErr('主体未选择');
  563. return;
  564. }
  565. if (mch_id === '') {
  566. showErr('机构未选择');
  567. return;
  568. }
  569. if (last_invoice_date !== '' && !isValidDate(last_invoice_date)) {
  570. showErr('开票日期格式错误');
  571. return;
  572. }
  573. const merchantList = dataHelper.getMerchantList();
  574. const used = merchantList.find(item => item.mch_id === mch_id);
  575. if (used !== undefined) {
  576. showErr('不能重复设置机构');
  577. return;
  578. }
  579. total_invoiced = total_invoiced === '' ? 0 : parseFloat(total_invoiced);
  580. const data = {
  581. sort: 0,
  582. subject: subject,
  583. mch_id: mch_id,
  584. total_invoiced: formatDecimals(total_invoiced, 2),
  585. last_invoice_date: last_invoice_date,
  586. remark: remark
  587. }
  588. await dataHelper.addOrUpdateData(CONSTANTS.KEY_QUOTA_MERCHANT, data, 2);
  589. this.merchantLeftList();
  590. }
  591. delete(event) {
  592. const that = this;
  593. const index = event.currentTarget.getAttribute('data-index');
  594. const list = dataHelper.getMerchantList();
  595. const mch_id = list[index].mch_id;
  596. let mch_name = '';
  597. const sysItem = this.sysMchs.find(sItem => sItem.mch_id === mch_id);
  598. if (sysItem !== undefined) {
  599. mch_name = sysItem.mch_name;
  600. } else {
  601. mch_name = `${mch_id}(无效项)`;
  602. }
  603. layer.confirm('确定要删除 "' + mch_name + '" 吗?', {
  604. title: '删除确认',
  605. icon: 3,
  606. btn: ['删除', '取消'],
  607. btn1: async function (btn_i) {
  608. const loadIndex = layer.load(2, {shade: [0.2, '#000']});
  609. await dataHelper.removeData(CONSTANTS.KEY_QUOTA_MERCHANT, index);
  610. that.merchantLeftList();
  611. layer.close(loadIndex);
  612. layer.close(btn_i);
  613. }
  614. });
  615. }
  616. async updateSort() {
  617. const selectedItems = $('#' + this.id).find('#selected-items-' + this.id + ' .sortable-item');
  618. const that = this;
  619. const merchantList = dataHelper.getMerchantList();
  620. selectedItems.each(function (index) {
  621. const value = $(this).data('name');
  622. const sysItem = that.sysMchs.find(sItem => sItem.mch_name === value);
  623. if (sysItem === undefined) {
  624. return;
  625. }
  626. const currIndex = merchantList.findIndex(item => item.mch_id === sysItem.mch_id);
  627. if (currIndex !== -1) {
  628. merchantList[currIndex].sort = index;
  629. }
  630. });
  631. merchantList.sort((a, b) => a.sort - b.sort);
  632. await dataHelper.updateAllData(CONSTANTS.KEY_QUOTA_MERCHANT, merchantList);
  633. }
  634. inputEvent(jqObj) {
  635. jqObj.off('input', handleInputNormal2)
  636. .off('keypress')
  637. .off('blur')
  638. .on('input', handleInputNormal2)
  639. .on('keypress', function (e) {
  640. if (e.keyCode === 13) {
  641. e.preventDefault();
  642. jqObj.blur();
  643. }
  644. })
  645. .on('blur', function () {
  646. let val = jqObj.val();
  647. if (!isNumeric(val)) {
  648. jqObj.val('0');
  649. return;
  650. }
  651. val = normalizeAmount(val);//过滤,比如前导 0
  652. jqObj.val(formatDecimals(val));
  653. });
  654. }
  655. }
  656. class ChannelComponent extends Component {
  657. constructor(options) {
  658. super(Object.assign(options, {
  659. template: `
  660. <div id="{{comp-id}}">
  661. <div class="quota-tablebar">
  662. <button v-on:click="open" type="button" class="layui-btn layui-bg-green">新增</button>
  663. <span>3、上游</span>
  664. </div>
  665. <div id="channel-model-{{comp-id}}" class="channel-model" style="display: none;">
  666. <div class="leftmenu-manager-container selected-items" id="selected-items-{{comp-id}}"></div>
  667. <div class="model-form">
  668. <div class="form-group">
  669. <label for="company-{{comp-id}}"><span class="req-flag">*</span>主体:</label>
  670. <select id="company-{{comp-id}}">
  671. </select>
  672. </div>
  673. <div class="form-group">
  674. <label for="channel-{{comp-id}}"><span class="req-flag">*</span>通道:</label>
  675. <div class="layui-form" lay-filter="chan_select_{{comp-id}}" style="width:220px;">
  676. <select id="channel-{{comp-id}}" lay-search style="width:220px;">
  677. </select>
  678. </div>
  679. </div>
  680. <div class="form-group">
  681. <label for="total_invoiced-{{comp-id}}">已开票金额(元):</label>
  682. <input type="text" id="total_invoiced-{{comp-id}}" />
  683. </div>
  684. <div class="form-group">
  685. <label for="last_invoice_date-{{comp-id}}">上次开票日期:</label>
  686. <input type="text" id="last_invoice_date-{{comp-id}}" class="date" />
  687. </div>
  688. <div class="form-group">
  689. <label for="remark-{{comp-id}}">备注:</label>
  690. <textarea id="remark-{{comp-id}}"></textarea>
  691. </div>
  692. <button v-on:click="save" class="layui-btn layui-btn-sm">保存</button>
  693. </div>
  694. </div>
  695. </div>
  696. `
  697. }));
  698. this.parent = options.parent;
  699. this.cacheKey = this.props['type'];
  700. this.lastData = undefined;
  701. this.sortChange = false;
  702. this.sysChans = [];
  703. }
  704. async mounted() {
  705. const that = this;
  706. $('#selected-items-' + this.id).sortable({
  707. containment: "parent",
  708. cursor: "move",
  709. update: function(event, ui) {
  710. that.sortChange = true;
  711. }
  712. }).disableSelection();
  713. this.inputEvent($('#total_invoiced-' + this.id));
  714. laydate.render({
  715. elem: '#last_invoice_date-' + this.id,
  716. type: 'date',
  717. trigger: 'click',
  718. max: new Date().setDate(new Date().getDate() - 1)
  719. });
  720. }
  721. async open() {
  722. this.companySelector();
  723. await this.chanSelector();
  724. this.channelLeftList();
  725. const that = this;
  726. layer.open({
  727. type: 1,
  728. title: '新增上游',
  729. content: $('#channel-model-' + this.props['comp-id']),
  730. area: ['800px', '580px'],
  731. success: function () {
  732. },
  733. end: async function () {
  734. if (that.sortChange === true) {
  735. const loadIndex = layer.load(2, {shade: [0.2, '#000']});
  736. await that.updateSort();
  737. layer.close(loadIndex);
  738. }
  739. if (that.isEdit() === true) {
  740. if (that.parent && typeof that.parent.emit === 'function') {
  741. that.parent.emit(that.props['emit'], that.cacheKey, that.cacheData);
  742. }
  743. }
  744. }
  745. });
  746. }
  747. isEdit() {
  748. const channelList = dataHelper.getChannelList();
  749. if (this.lastData.length !== channelList.length) {
  750. return true;
  751. }
  752. for (let i = 0; i < this.lastData.length; i++) {
  753. if (this.lastData[i].store_id !== channelList[i].store_id) {
  754. return true;
  755. }
  756. }
  757. return false;
  758. }
  759. channelLeftList() {
  760. const content = this.createModelContent();
  761. $('#channel-model-' + this.id + ' .leftmenu-manager-container').html(content);
  762. this.bindEvents();
  763. }
  764. createModelContent() {
  765. const channelList = dataHelper.getChannelList();
  766. if (this.lastData === undefined) {
  767. this.lastData = deepCloneData(channelList);
  768. }
  769. let html = '';
  770. for (let i = 0; i < channelList.length; i++) {
  771. const sysItem = this.sysChans.find(sItem => sItem.store_id === channelList[i].store_id);
  772. let chanName = '';
  773. if (sysItem !== undefined) {
  774. chanName = sysItem.store_name;
  775. } else {
  776. chanName = channelList[i].store_id + '(无效项)';
  777. }
  778. html += '<label class="ch-tags sortable-item" data-name="' + chanName + '">' +
  779. chanName +
  780. '<span class="delete-btn" v-on:click="delete" data-index="' + i + '">&times;</span>' +
  781. '</label>';
  782. }
  783. return html;
  784. }
  785. companySelector() {
  786. let content = '';
  787. const companyList = dataHelper.getCompanyList();
  788. companyList.forEach(item=> {
  789. content += '<option>' + item.name + '</option>';
  790. });
  791. $('#channel-model-' + this.id + ' #company-' + this.id).html(content);
  792. }
  793. async chanSelector() {
  794. let content = '';
  795. const chanData = await statsApi.getSysChans();
  796. if (chanData.state === true) {
  797. chanData.list.forEach(item => {
  798. content += '<option value="' + item.store_id + '">' + item.store_name + '</option>';
  799. });
  800. $('#channel-model-' + this.id + ' #channel-' + this.id).html(content);
  801. this.sysChans = chanData.list;
  802. }
  803. //只能在渲染option之后才可以设置select搜索组件
  804. const componentId = this.id;
  805. layui.use('form', function() {
  806. const form = layui.form;
  807. form.render('select', 'chan_select_' + componentId);
  808. });
  809. }
  810. async save() {
  811. const subject = $('#company-' + this.id).val();
  812. let store_id = $('#channel-' + this.id).val();
  813. let total_invoiced = $('#total_invoiced-' + this.id).val().trim();
  814. let last_invoice_date = $('#last_invoice_date-' + this.id).val();
  815. let remark = $('#remark-' + this.id).val().trim();
  816. if (subject === '') {
  817. showErr('主体未选择');
  818. return;
  819. }
  820. if (store_id === '') {
  821. showErr('通道未选择');
  822. return;
  823. }
  824. if (last_invoice_date !== '' && !isValidDate(last_invoice_date)) {
  825. showErr('开票日期格式错误');
  826. return;
  827. }
  828. const channelList = dataHelper.getChannelList();
  829. const used = channelList.find(item => item.store_id === store_id);
  830. if (used !== undefined) {
  831. showErr('不能重复设置通道')
  832. }
  833. total_invoiced = total_invoiced === '' ? 0 : parseFloat(total_invoiced);
  834. const data = {
  835. sort: 0,
  836. subject: subject,
  837. store_id: store_id,
  838. total_invoiced: formatDecimals(total_invoiced, 2),
  839. last_invoice_date: last_invoice_date,
  840. remark: remark
  841. }
  842. await dataHelper.addOrUpdateData(CONSTANTS.KEY_QUOTA_CHANNEL, data, 2);
  843. this.channelLeftList();
  844. }
  845. delete(event) {
  846. const that = this;
  847. const index = event.currentTarget.getAttribute('data-index');
  848. const list = dataHelper.getChannelList();
  849. const store_id = list[index].store_id;
  850. let store_name = '';
  851. const sysItem = this.sysChans.find(sItem => sItem.store_id === store_id);
  852. if (sysItem !== undefined) {
  853. store_name = sysItem.store_name;
  854. } else {
  855. store_name = `${store_id}(无效项)`;
  856. }
  857. layer.confirm('确定要删除 "' + store_name + '" 吗?', {
  858. title: '删除确认',
  859. icon: 3,
  860. btn: ['删除', '取消'],
  861. btn1: async function (btn_i) {
  862. const loadIndex = layer.load(2, {shade: [0.2, '#000']});
  863. await dataHelper.removeData(CONSTANTS.KEY_QUOTA_CHANNEL, index);
  864. that.channelLeftList();
  865. layer.close(loadIndex);
  866. layer.close(btn_i);
  867. }
  868. });
  869. }
  870. async updateSort() {
  871. const selectedItems = $('#' + this.id).find('#selected-items-' + this.id + ' .sortable-item');
  872. const that = this;
  873. const channelList = dataHelper.getChannelList();
  874. selectedItems.each(function (index) {
  875. const value = $(this).data('name');
  876. const sysItem = that.sysChans.find(sItem => sItem.store_name === value);
  877. if (sysItem === undefined) {
  878. return;
  879. }
  880. const currIndex = channelList.findIndex(item => item.store_id === sysItem.store_id);
  881. if (currIndex !== -1) {
  882. channelList[currIndex].sort = index;
  883. }
  884. });
  885. channelList.sort((a, b) => a.sort - b.sort);
  886. await dataHelper.updateAllData(CONSTANTS.KEY_QUOTA_CHANNEL, channelList);
  887. }
  888. inputEvent(jqObj) {
  889. jqObj.off('input', handleInputNormal2)
  890. .off('keypress')
  891. .off('blur')
  892. .on('input', handleInputNormal2)
  893. .on('keypress', function (e) {
  894. if (e.keyCode === 13) {
  895. e.preventDefault();
  896. jqObj.blur();
  897. }
  898. })
  899. .on('blur', function () {
  900. let val = jqObj.val();
  901. if (!isNumeric(val)) {
  902. jqObj.val('0');
  903. return;
  904. }
  905. val = normalizeAmount(val);//过滤,比如前导 0
  906. jqObj.val(formatDecimals(val));
  907. });
  908. }
  909. }
  910. class SummaryComponent extends Component {
  911. constructor(options) {
  912. super(Object.assign(options, {
  913. template: `
  914. <div id="{{comp-id}}">
  915. <div class="quota-tablebar">
  916. <button v-on:click="open" type="button" class="layui-btn layui-bg-green">新增</button>
  917. <span>4、综合统计</span>
  918. </div>
  919. <div id="summary-model-{{comp-id}}" class="summary-model" style="display: none;">
  920. <div class="leftmenu-manager-container selected-items" id="selected-items-{{comp-id}}"></div>
  921. <div class="model-form" style="height: 400px">
  922. <div class="form-group">
  923. <label for="company-{{comp-id}}"><span class="req-flag">*</span>主体:</label>
  924. <div id="company-{{comp-id}}">
  925. </div>
  926. </div>
  927. <button v-on:click="save" class="layui-btn layui-btn-sm">保存</button>
  928. </div>
  929. </div>
  930. </div>
  931. `
  932. }));
  933. this.parent = options.parent;
  934. this.cacheKey = this.props['type'];
  935. this.lastData = undefined;
  936. this.sortChange = false;
  937. }
  938. async mounted() {
  939. const that = this;
  940. $('#selected-items-' + this.id).sortable({
  941. containment: "parent",
  942. cursor: "move",
  943. update: function(event, ui) {
  944. that.sortChange = true;
  945. }
  946. }).disableSelection();
  947. }
  948. async open() {
  949. this.companySelector();
  950. this.summaryLeftList();
  951. const that = this;
  952. layer.open({
  953. type: 1,
  954. title: '设置综合统计主体',
  955. content: $('#summary-model-' + this.props['comp-id']),
  956. area: ['800px', '580px'],
  957. success: function () {
  958. },
  959. end: async function () {
  960. if (that.sortChange === true) {
  961. const loadIndex = layer.load(2, {shade: [0.2, '#000']});
  962. await that.updateSort();
  963. layer.close(loadIndex);
  964. }
  965. if (that.isEdit() === true) {
  966. if (that.parent && typeof that.parent.emit === 'function') {
  967. that.parent.emit(that.props['emit'], that.cacheKey, that.cacheData);
  968. }
  969. }
  970. }
  971. });
  972. }
  973. isEdit() {
  974. const companyList = dataHelper.getCompanyList();
  975. if (this.lastData.length !== companyList.length) {
  976. return true;
  977. }
  978. for (let i = 0; i < this.lastData.length; i++) {
  979. if (this.lastData[i].store_id !== companyList[i].store_id) {
  980. return true;
  981. }
  982. }
  983. return false;
  984. }
  985. summaryLeftList() {
  986. const content = this.createModelContent();
  987. $('#summary-model-' + this.id + ' .leftmenu-manager-container').html(content);
  988. this.bindEvents();
  989. }
  990. createModelContent() {
  991. const summaryList = dataHelper.getSummaryList();
  992. if (this.lastData === undefined) {
  993. this.lastData = deepCloneData(summaryList);
  994. }
  995. let html = '';
  996. for (let i = 0; i < summaryList.length; i++) {
  997. html += '<label class="ch-tags sortable-item" data-name="' + summaryList[i].subject + '">' +
  998. summaryList[i].subject +
  999. '<span class="delete-btn" v-on:click="delete" data-index="' + i + '">&times;</span>' +
  1000. '</label>';
  1001. }
  1002. return html;
  1003. }
  1004. companySelector() {
  1005. let content = '';
  1006. const companyList = dataHelper.getCompanyList();
  1007. const summaryList = dataHelper.getSummaryList();
  1008. const selectedSubjects = summaryList.map(item => item.subject);
  1009. companyList.forEach(item=> {
  1010. const isChecked = selectedSubjects.includes(item.name) ? 'checked' : '';
  1011. content += `
  1012. <label class="company-checkbox">
  1013. <input type="checkbox" name="summary_company" value="${item.name}" ${isChecked}>
  1014. <span>${item.name}</span>
  1015. </label>
  1016. `;
  1017. });
  1018. $('#summary-model-' + this.id + ' #company-' + this.id).html(content);
  1019. }
  1020. async save() {
  1021. const selectedValues = [];
  1022. $('#summary-model-' + this.id + ' #company-' + this.id + ' input[type="checkbox"]:checked').each(function() {
  1023. selectedValues.push($(this).val());
  1024. });
  1025. const summaryList = dataHelper.getSummaryList();
  1026. selectedValues.forEach(item => {
  1027. const activeItem = summaryList.find(sItem => sItem.subject === item);
  1028. if (activeItem === undefined) {
  1029. summaryList.push({
  1030. sort: 0,
  1031. subject: item
  1032. });
  1033. }
  1034. });
  1035. await dataHelper.updateAllData(CONSTANTS.KEY_QUOTA_SUMMARY, summaryList);
  1036. this.summaryLeftList();
  1037. }
  1038. delete(event) {
  1039. const that = this;
  1040. const index = event.currentTarget.getAttribute('data-index');
  1041. const list = dataHelper.getSummaryList();
  1042. const subject = list[index].subject;
  1043. layer.confirm('确定要删除 "' + subject + '" 吗?', {
  1044. title: '删除确认',
  1045. icon: 3,
  1046. btn: ['删除', '取消'],
  1047. btn1: async function (btn_i) {
  1048. const loadIndex = layer.load(2, {shade: [0.2, '#000']});
  1049. await dataHelper.removeData(CONSTANTS.KEY_QUOTA_SUMMARY, index);
  1050. that.summaryLeftList();
  1051. layer.close(loadIndex);
  1052. layer.close(btn_i);
  1053. }
  1054. });
  1055. }
  1056. async updateSort() {
  1057. const selectedItems = $('#' + this.id).find('#selected-items-' + this.id + ' .sortable-item');
  1058. const that = this;
  1059. const summaryList = dataHelper.getSummaryList();
  1060. selectedItems.each(function (index) {
  1061. const value = $(this).data('name');
  1062. const currIndex = summaryList.findIndex(item => item.subject === value);
  1063. if (currIndex !== -1) {
  1064. summaryList[currIndex].sort = index;
  1065. }
  1066. });
  1067. summaryList.sort((a, b) => a.sort - b.sort);
  1068. await dataHelper.updateAllData(CONSTANTS.KEY_QUOTA_SUMMARY, summaryList);
  1069. }
  1070. }
  1071. function filterSubjectPrefix(str) {
  1072. return str.replace(/^\d+-/, '');
  1073. }