stats-component.js 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  1. /*---------------------------------------------参数配置---------------------------------------*/
  2. const CONSTANTS = {
  3. KEY_CHANNEL_NAMES: 'channel_names', //上游的名字,通道分组用
  4. KEY_MERCHANT_NAMES: 'merchant_names', //下游的名字,机构分组用
  5. KEY_CHANNEL_FAST: 'channel_fast', //上下游余额统计:统计的快充通道
  6. KEY_CHANNEL_NORMAL: 'channel_normal', //上下游余额统计:统计的普充通道
  7. KEY_MERCHANT: 'merchant', //上下游余额统计:统计的机构
  8. KEY_DAILY_CHANNEL: 'daily_channel', //日对账单:统计的通道
  9. KEY_DAILY_MERCHANT: 'daily_merchant', //日对账单:统计的通道
  10. };
  11. /*---------------------------------------------公共方法---------------------------------------*/
  12. function formatDecimals(number, digit= 2) {//格式化金额
  13. if (typeof number === 'string') {
  14. number = Number(number);
  15. }
  16. if (number === 0) {
  17. return '0';
  18. }
  19. return parseFloat(number).toFixed(digit);
  20. }
  21. function isValidKey(obj, key) {
  22. if (obj.hasOwnProperty(key)) {
  23. if (obj[key] !== null && obj[key] !== undefined && obj[key] !== '') {
  24. return true;
  25. }
  26. }
  27. return false;
  28. }
  29. function deepCloneData(obj) {//数据深拷贝
  30. if (obj === null || typeof obj !== 'object') {
  31. return obj;
  32. }
  33. let clone = Array.isArray(obj) ? [] : {};
  34. for (let key in obj) {
  35. if (obj.hasOwnProperty(key)) {
  36. clone[key] = deepCloneData(obj[key]);
  37. }
  38. }
  39. return clone;
  40. }
  41. function sortSysData(sysData, sortData, key) {//sortData数据前置,sysData中未包含在sortData中的数据放在后面
  42. let sortedData = deepCloneData(sortData);
  43. const unselectedData = sysData.filter(function (sys) {
  44. const selected = sortData.find(function(cust) {
  45. return sys[key] === cust[key] && sys.tag === sys.tag;
  46. });
  47. return !selected;
  48. });
  49. return sortedData.concat(unselectedData);
  50. }
  51. /**
  52. * @param sortCustSet 排好序的元素
  53. * @param data 实时数据,未排序
  54. * @param groupKey 分组字段
  55. * @param nameKey 过滤字段,机构和通道均包含name字段,增加nameKey,比如store_name,mch_name,多一层过滤条件防止存在重名情况
  56. * @returns {*[]}
  57. */
  58. function sortCustData(sortCustSet, data, groupKey, nameKey) {
  59. // 去掉不在当前数据中的排序值
  60. const sortCust = sortCustSet.filter(function (item) {
  61. return data.find(function (litem) {
  62. return litem.name === item.name && litem[nameKey] === item[nameKey] && litem.tag === item.tag;
  63. });
  64. });
  65. const custMap = sortCust.reduce((acc, item, index) => {
  66. acc[item.name] = index;
  67. return acc;
  68. }, {});
  69. data.sort((a, b) => custMap[a.name] - custMap[b.name]);
  70. const groupMap = {};
  71. data.forEach(item => {
  72. const group = item[groupKey];
  73. if (!groupMap[group]) {
  74. groupMap[group] = [];
  75. }
  76. groupMap[group].push(item);
  77. });
  78. const sortedData = [];
  79. data.forEach(item => {
  80. const group = item[groupKey];
  81. if (groupMap[group]) {
  82. sortedData.push(...groupMap[group]);
  83. delete groupMap[group];
  84. }
  85. });
  86. return sortedData;
  87. }
  88. /*
  89. * 合并二维数据,表格数据合并/通道机构配置项合并
  90. */
  91. function mergeData(data1, data2, key) {
  92. let dataMormalMerge = [];
  93. for (let item of data1) {
  94. const p2Item = data2.find(function(p2) {
  95. return item[key] === p2[key] && item.tag === p2.tag;
  96. });
  97. if (!p2Item) {
  98. dataMormalMerge.push(item);
  99. } else {
  100. dataMormalMerge.push(p2Item);
  101. }
  102. }
  103. for (let item of data2) {
  104. const mItem = dataMormalMerge.find(function(m) {
  105. return item[key] === m[key] && item.tag === m.tag;
  106. });
  107. if (!mItem) {
  108. dataMormalMerge.push(item);
  109. }
  110. }
  111. return dataMormalMerge;
  112. }
  113. /*
  114. * 合并一维数组,上下游名称配置项合并
  115. */
  116. function mergeNames(data1, data2) {
  117. const notin = data2.filter(item => !data1.includes(item));
  118. return data1.concat(notin);
  119. }
  120. function handleInputd2(event) {//限制金额输入框
  121. let element = event.target;
  122. let value = element.textContent;
  123. let selection = window.getSelection();
  124. let range = selection.getRangeAt(0);
  125. let startOffset = range.startOffset;
  126. value = value.replace(/[^\d.-]/g, '');
  127. const parts = value.split('.');
  128. if (parts.length > 1) {
  129. parts[1] = parts[1].slice(0, 2);
  130. value = parts.join('.');
  131. }
  132. element.textContent = value;
  133. let newNode = element.childNodes[0] || element;
  134. range.setStart(newNode, Math.min(startOffset, value.length));
  135. range.setEnd(newNode, Math.min(startOffset, value.length));
  136. selection.removeAllRanges();
  137. selection.addRange(range);
  138. }
  139. function handleInputd4(event) {
  140. let element = event.target;
  141. let value = element.textContent;
  142. let selection = window.getSelection();
  143. let range = selection.getRangeAt(0);
  144. let startOffset = range.startOffset;
  145. value = value.replace(/[^\d.-]/g, '');
  146. const parts = value.split('.');
  147. if (parts.length > 1) {
  148. parts[1] = parts[1].slice(0, 4);
  149. value = parts.join('.');
  150. }
  151. element.textContent = value;
  152. let newNode = element.childNodes[0] || element;
  153. range.setStart(newNode, Math.min(startOffset, value.length));
  154. range.setEnd(newNode, Math.min(startOffset, value.length));
  155. selection.removeAllRanges();
  156. selection.addRange(range);
  157. }
  158. function isObjectEmpty(obj) {
  159. return Object.keys(obj).length === 0;
  160. }
  161. function showErr(error) {
  162. layer.alert(error, {
  163. icon: 2,
  164. title: '错误'
  165. });
  166. }
  167. function showSuccess(msg) {
  168. layer.msg(msg, {
  169. icon: 1,
  170. time: 2000
  171. });
  172. }
  173. function isNumeric(value) {
  174. return /^-?\d+(\.\d+)?$/.test(value);
  175. }
  176. /*----------------------------------------------统一请求定义---------------------------------------*/
  177. class ApiService {
  178. constructor(baseUrl) {
  179. this.baseUrl = baseUrl;
  180. }
  181. request(endpoint, method, data) {
  182. const that = this;
  183. return new Promise(function(resolve, reject) {
  184. $.ajax({
  185. url: that.baseUrl + endpoint,
  186. type: method,
  187. data: data,
  188. dataType: 'json',
  189. success: function(response) {
  190. if (response.state) {
  191. resolve(response);
  192. } else {
  193. reject(new Error('请求失败'));
  194. }
  195. },
  196. error: function(jqXHR, textStatus, errorThrown) {
  197. reject(new Error('请求失败: ' + textStatus + ', ' + errorThrown));
  198. }
  199. });
  200. });
  201. }
  202. getStatsSettings() {
  203. const endpoint = '?act=refill_amount_stats&op=stats_settings';
  204. return this.request(endpoint, 'GET', {});
  205. };
  206. setStatsSettings(key, value) {
  207. const endpoint = '?act=refill_amount_stats&op=stats_settings_save';
  208. let data = {
  209. key: key,
  210. value: value
  211. };
  212. return this.request(endpoint, 'POST', data);
  213. };
  214. getSysChannels() {
  215. const endpoint = '?act=refill_amount_stats&op=sys_channels';
  216. return this.request(endpoint, 'GET', {});
  217. };
  218. getSysMchs() {
  219. const endpoint = '?act=refill_amount_stats&op=sys_mchs';
  220. return this.request(endpoint, 'GET', {});
  221. };
  222. getChannelData(params) {
  223. const queryString = $.param(params);
  224. const endpoint = '?act=refill_amount_stats&op=channel_data&' + queryString;
  225. return this.request(endpoint, 'GET', {});
  226. };
  227. getMchData(params) {
  228. const queryString = $.param(params);
  229. const endpoint = '?act=refill_amount_stats&op=mch_data&' + queryString;
  230. return this.request(endpoint, 'GET', {});
  231. };
  232. getDailyProviderData(params) {
  233. const queryString = $.param(params);
  234. const endpoint = '?act=refill_amount_stats&op=daily_provider_data&' + queryString;
  235. return this.request(endpoint, 'GET', {});
  236. };
  237. getDailyMerchantData(params) {
  238. const queryString = $.param(params);
  239. const endpoint = '?act=refill_amount_stats&op=daily_merchant_data&' + queryString;
  240. return this.request(endpoint, 'GET', {});
  241. };
  242. }
  243. const statsApi = new ApiService('');
  244. /*------------------------------------------------------组件实现:调度类-------------------------------------------*/
  245. class ComponentBase {
  246. constructor(options) {
  247. this.el = document.querySelector(options.el);
  248. this.components = options.components || {};
  249. this.componentData = options.componentData || {};
  250. this.events = {};
  251. this.componentInstances = {};
  252. this.render();
  253. }
  254. render() {
  255. this.el.innerHTML = this.compile(this.el.innerHTML);
  256. }
  257. compile(template) {
  258. const componentNames = Object.keys(this.components);
  259. componentNames.forEach(name => {
  260. const regex = new RegExp(`<${name}([^>]*)>(.*?)<\/${name}>`, 'g');
  261. template = template.replace(regex, (match, attrs) => {
  262. const props = {};
  263. attrs.replace(/(\w+)="([^"]*)"/g, (match, key, value) => {
  264. if (CONSTANTS[value] !== undefined) {
  265. value = CONSTANTS[value];
  266. }
  267. props[key] = value;
  268. });
  269. const component = new this.components[name]({
  270. props: props,
  271. data: this.componentData[name] || {},
  272. parent: this
  273. });
  274. this.componentInstances[name] = component;
  275. return component.render();
  276. });
  277. });
  278. return template;
  279. }
  280. on(event, callback) {
  281. if (!this.events[event]) {
  282. this.events[event] = [];
  283. }
  284. this.events[event].push(callback);
  285. }
  286. emit(event, ...args) {
  287. if (this.events[event]) {
  288. this.events[event].forEach(callback => callback(...args));
  289. }
  290. }
  291. getComponent(name) {
  292. return this.componentInstances[name];
  293. }
  294. }
  295. /*------------------------------------------------------组件实现:组件基类-------------------------------------------*/
  296. class Component {
  297. constructor(options) {
  298. this.data = options.data || {};
  299. this.props = options.props || {};
  300. this.template = options.template || '';
  301. this.id = 'component-' + Math.random().toString(36).substr(2, 9);
  302. this.props['comp-id'] = this.id;
  303. }
  304. render() {
  305. let template = this.template;
  306. for (const key in this.props) {
  307. template = template.replace(new RegExp(`{{${key}}}`, 'g'), this.props[key]);
  308. }
  309. setTimeout(() => {
  310. this.bindEvents();
  311. this.mounted();
  312. }, 0);
  313. return template;
  314. }
  315. bindEvents() {
  316. const rootElement = document.querySelector(`#${this.id}`);
  317. if (!rootElement) {
  318. return;
  319. }
  320. this.unbindEvents();
  321. const supportedEvents = ['click', 'input', 'blur', 'change'];
  322. supportedEvents.forEach(eventType => {
  323. const elements = rootElement.querySelectorAll(`[v-on\\:${eventType}]`);
  324. elements.forEach(element => {
  325. const method = element.getAttribute(`v-on:${eventType}`);
  326. if (method && typeof this[method] === 'function') {
  327. const listener = this[method].bind(this);
  328. element.addEventListener(eventType, listener);
  329. if (!this.eventListeners[eventType]) {
  330. this.eventListeners[eventType] = [];
  331. }
  332. this.eventListeners[eventType].push({ element, listener });
  333. }
  334. });
  335. });
  336. }
  337. unbindEvents() {
  338. for (const eventType in this.eventListeners) {
  339. this.eventListeners[eventType].forEach(({ element, listener }) => {
  340. element.removeEventListener(eventType, listener);
  341. });
  342. }
  343. this.eventListeners = {};
  344. }
  345. mounted() {
  346. }
  347. }
  348. /*------------------------------------------------------layui表格基类-----------------------------------------------*/
  349. class LayuiTableBase {
  350. constructor(id) {
  351. this.tableId = id;
  352. }
  353. /*
  354. * 表格中可编辑列的处理
  355. */
  356. editEvent(editColumns = [], callback) {
  357. const tableItem = $('#' + this.tableId);
  358. const trs = tableItem.next('.layui-table-view').find('.layui-table-body tbody tr');
  359. trs.each(function (i, tr) {
  360. for (let col of editColumns) {
  361. const td = $(tr).find('td[data-field="' + col + '"]').not('.layui-hide');
  362. if (td.length > 0) {//合并隐藏的不处理
  363. const valDiv = td.find('div');
  364. valDiv.attr('contenteditable', true).css('background-color', 'lightblue');
  365. valDiv.off('input', handleInputd4)
  366. .off('keypress')
  367. .off('blur')
  368. .on('input', handleInputd4)
  369. .on('keypress', function (e) {
  370. if (e.keyCode === 13) {
  371. e.preventDefault();
  372. $(this).blur();
  373. }
  374. })
  375. .on('blur', function () {
  376. const val = valDiv.text().trim();
  377. if (!isNumeric(val)) {
  378. valDiv.html('0');
  379. }
  380. if (callback !== null && typeof callback === 'function') {
  381. const index = $(this).parent().parent().data('index');
  382. callback(index);
  383. }
  384. });
  385. }
  386. }
  387. });
  388. }
  389. switchEvent(switchColumns = [], callback) {
  390. const tableItem = $('#' + this.tableId);
  391. const trs = tableItem.next('.layui-table-view').find('.layui-table-body tbody tr');
  392. trs.each(function (i, tr) {
  393. for (let clo of switchColumns) {
  394. const td = $(tr).find('td[data-field="' + clo + '"]').not('.layui-hide');
  395. if (td.length > 0) {
  396. const buttons = td.find('.layui-btn');
  397. buttons.off('click').on('click', function (e) {
  398. e.preventDefault();
  399. e.stopPropagation();
  400. const currTr = $(this).closest('tr');
  401. const index = currTr.data('index');
  402. buttons.removeClass('selected').addClass('unselected');
  403. $(this).removeClass('unselected').addClass('selected');
  404. if (callback !== null && typeof callback === 'function') {
  405. callback(index);
  406. }
  407. });
  408. }
  409. }
  410. });
  411. }
  412. /*
  413. * 表格中金额字段样式,小于0的数值设置为红色
  414. */
  415. setValueClass(valColumns) {
  416. const trs = $('#' + this.tableId).next('.layui-table-view').find('.layui-table-body tbody tr');
  417. trs.each(function(i, tr) {
  418. for(let col of valColumns) {
  419. const td = $(tr).find('td[data-field="' + col + '"]').not('.layui-hide');
  420. if (td.length > 0) {//合并隐藏的不处理
  421. const valDiv = td.find('div');
  422. const val = valDiv.text().trim();
  423. if (parseFloat(val) < 0) {
  424. valDiv.addClass('negative-value');
  425. } else {
  426. valDiv.removeClass('negative-value');
  427. }
  428. }
  429. }
  430. });
  431. }
  432. }
  433. /*------------------------------------------------------组件:上下游名称设置-------------------------------------------*/
  434. class StreamNameComponent extends Component {
  435. constructor(options) {
  436. super(Object.assign(options, {
  437. template: `
  438. <div id="{{comp-id}}">
  439. <div class="bottom10">
  440. <span>{{title}}:</span>
  441. <span id="names-selected-{{comp-id}}" class="names_ca"></span>
  442. <button v-on:click="open" type="button" class="layui-btn layui-bg-green">修改</button>
  443. </div>
  444. <div id="group-names-model-{{comp-id}}" style="display: none;">
  445. <div class="input-group">
  446. <div class="input-item">
  447. <input type="text" id="input-{{comp-id}}" placeholder="多个名称使用,分隔">
  448. </div>
  449. <div class="input-item">
  450. <button v-on:click="save" class="layui-btn layui-btn-sm">保存</button>
  451. </div>
  452. </div>
  453. <div class="names-manager-container">
  454. </div>
  455. </div>
  456. </div>
  457. `
  458. }));
  459. this.cacheKey = this.props['type'];
  460. this.cacheData = [];
  461. }
  462. async mounted() {
  463. await this.loadData();
  464. this.updateUI();
  465. }
  466. async open() {
  467. const content = this.createModelContent();
  468. $('#group-names-model-' + this.id + ' .names-manager-container').html(content);
  469. this.bindEvents();
  470. layer.open({
  471. type: 1,
  472. title: '设置上游名称',
  473. content: $('#group-names-model-' + this.props['comp-id']),
  474. area: ['500px', '400px'],
  475. success: function() {
  476. }
  477. });
  478. }
  479. async loadData() {
  480. const statsSettings = await statsApi.getStatsSettings();
  481. if (statsSettings.state === true) {
  482. this.cacheData = statsSettings[this.cacheKey] || [];
  483. }
  484. }
  485. createModelContent() {
  486. const channelNames = this.cacheData;
  487. let html = '';
  488. for (let i = 0; i < channelNames.length; i++) {
  489. html += '<label class="ch-tags">' + channelNames[i] +
  490. '<span class="delete-btn" v-on:click="delete" data-index="' + i + '">&times;</span>' +
  491. '</label>';
  492. }
  493. return html;
  494. }
  495. save() {
  496. const that = this;
  497. const inputVal = $('#input-' + this.id).val().replace(/,/g, ',');
  498. const newNames = inputVal.split(',').map(item => item.trim()).filter(item => item);
  499. if (newNames.length > 0) {
  500. const cache = this.cacheData;
  501. const updatedNames = cache.concat(newNames);
  502. const uniqueNames = updatedNames.filter((item, index) => updatedNames.indexOf(item) === index);
  503. const loadIndex = layer.load(2, { shade: [0.2, '#000'] });
  504. that.updateCache(uniqueNames, [loadIndex]);
  505. }
  506. }
  507. delete(event) {
  508. const that = this;
  509. const index = event.currentTarget.getAttribute('data-index');
  510. const cache = this.cacheData;
  511. const tmpNames = deepCloneData(cache);
  512. const tag = tmpNames[index];
  513. layer.confirm('确定要删除 "' + tag + '" 吗?', {
  514. title: '删除确认',
  515. icon: 3,
  516. btn: ['删除', '取消'],
  517. btn1: function (btn_i) {
  518. tmpNames.splice(index, 1);
  519. const loadIndex = layer.load(2, { shade: [0.2, '#000'] });
  520. that.updateCache(tmpNames, [loadIndex, btn_i]);
  521. }
  522. });
  523. }
  524. updateCache(tmpNames, closer = []) {
  525. const that = this;
  526. const cacheKey = this.cacheKey;
  527. statsApi.setStatsSettings(cacheKey, tmpNames)
  528. .then(function (data) {
  529. closer.forEach(function (lr) {
  530. layer.close(lr);
  531. });
  532. if (data.state === true) {
  533. that.cacheData = data[cacheKey];
  534. that.updateUI();
  535. const content = that.createModelContent();
  536. $('#group-names-model-' + that.id + ' .names-manager-container').html(content);
  537. $('#input-' + that.id).val('');
  538. that.bindEvents();
  539. } else {
  540. showErr('保存失败,请重试');
  541. }
  542. })
  543. .catch(function (error) {
  544. showErr('保存失败,请重试');
  545. });
  546. }
  547. updateUI() {
  548. const selector = $('#names-selected-' + this.id);
  549. const cache = this.cacheData;
  550. let html = '';
  551. for(let item of cache) {
  552. html += '<label class="ch-tags">' +item+ '</label>';
  553. }
  554. selector.html(html);
  555. }
  556. }
  557. /*-------------------------------------------------------组件:设置统计的通道/机构-----------------------------------------*/
  558. class SelectedChannelComponent extends Component {
  559. constructor(options) {
  560. super(Object.assign(options, {
  561. template: `
  562. <div id="{{comp-id}}">
  563. <button class="layui-btn layui-bg-green" v-on:click="open">新增</button>
  564. <div id="select-modal-{{comp-id}}" style="display: none;">
  565. <form id="select-form-{{comp-id}}"></form>
  566. <div id="selected-items-{{comp-id}}" class="selected-items"></div>
  567. </div>
  568. </div>
  569. `
  570. }));
  571. this.parent = options.parent;
  572. this.cacheKey = this.props['type'];
  573. this.cacheData = []; //计入统计的通道
  574. this.lastCacheData = []; //打开弹层时的数据,用于对比是否做了修改
  575. this.sysData = []; //系统全量通道,初始时为空,数据在第一次打开弹层时加载
  576. }
  577. mounted() {
  578. const that = this;
  579. $('#selected-items-' + this.id).sortable({
  580. containment: "parent",
  581. cursor: "move",
  582. update: function(event, ui) {
  583. that.updateCheckboxOrder();
  584. }
  585. }).disableSelection();
  586. }
  587. async open() {
  588. await this.loadData();
  589. this.renderForm(true);
  590. const that = this;
  591. layer.open({
  592. type: 1,
  593. title: this.props['title'],
  594. area: ['960px', '600px'],
  595. content: $('#select-modal-' + this.id),
  596. btn: ['确定', '取消'],
  597. yes: function() {
  598. if (that.isEdit() === true) {
  599. statsApi.setStatsSettings(that.cacheKey, that.cacheData)
  600. .then(function (data) {
  601. layer.closeAll();
  602. })
  603. .catch(function (error) {
  604. layer.closeAll();
  605. showErr('保存失败,请重试');
  606. });
  607. } else {
  608. layer.closeAll();
  609. }
  610. },
  611. end: function() {
  612. if (that.isEdit() === true) {
  613. if (that.parent && typeof that.parent.emit === 'function') {
  614. that.parent.emit(that.props['emit'], that.cacheKey, that.cacheData);
  615. }
  616. }
  617. }
  618. });
  619. }
  620. async loadData() {
  621. const loadIndex = layer.load(2, { shade: [0.2, '#000'] });
  622. const sysChannels = await statsApi.getSysChannels();
  623. if (sysChannels.state === true) {
  624. this.sysData = sysChannels.list;
  625. }
  626. const statsSettings = await statsApi.getStatsSettings();
  627. this.cacheData = statsSettings[this.cacheKey] || [];
  628. this.sysData = sortSysData(this.sysData, this.cacheData, 'store_name');
  629. this.lastCacheData = deepCloneData(this.cacheData);
  630. layer.close(loadIndex);
  631. }
  632. isEdit() {
  633. if (this.lastCacheData.length !== this.cacheData.length) {
  634. return true;
  635. }
  636. for (let i = 0; i < this.lastCacheData.length; i++) {
  637. const u1 = this.cacheData[i].tag + '-' + this.cacheData[i].store_id;
  638. const u2 = this.lastCacheData[i].tag + '-' + this.lastCacheData[i].store_id;
  639. if (u1 !== u2) {
  640. return true;
  641. }
  642. }
  643. return false;
  644. }
  645. renderForm(updateSelectedItems) {
  646. let formHtml = '';
  647. for (let item of this.sysData) {
  648. let checked = '';
  649. const dataItem = this.cacheData.find(function (chan) {
  650. return chan.store_name === item.store_name && chan.tag === item.tag;
  651. });
  652. if (dataItem) {
  653. checked = 'checked="true"';
  654. }
  655. formHtml += '<label class="checkout-items">';
  656. formHtml += '<input type="checkbox" name="stats_channels-' + this.id + '" v-on:change="change" value="' + item.store_name + '" data-name="' + item.name + '" data-quality_text="' + item.quality_text + '" data-tag="' + item.tag + '" ' + checked + ' />' + item.store_name;
  657. formHtml += '</label>';
  658. }
  659. $('#select-form-' + this.id).html(formHtml);
  660. this.bindEvents();
  661. if (updateSelectedItems === true) {
  662. this.updateSelectedItems();
  663. }
  664. }
  665. change(event) {
  666. const tag = event.currentTarget.getAttribute('data-tag');
  667. const value = event.currentTarget.value;
  668. const checked = event.currentTarget.checked;
  669. this.updateCache(value, tag, checked);
  670. this.updateSelectedItems();
  671. }
  672. updateCache(storeName, tag, checked) {
  673. if (checked === true) {
  674. const item = this.sysData.find(function (chan) {
  675. return chan.store_name === storeName && chan.tag === tag;
  676. });
  677. if (item) {
  678. this.cacheData.push(item);
  679. }
  680. } else {
  681. let cusChannelsNew = [];
  682. for (let item of this.cacheData) {
  683. if (item.store_name !== storeName || (item.store_name === storeName && item.tag !== tag)) {
  684. cusChannelsNew.push(item);
  685. }
  686. }
  687. this.cacheData = cusChannelsNew;
  688. }
  689. }
  690. updateSelectedItems() {
  691. const selectedItems = $('#' + this.id).find('#selected-items-' + this.id);
  692. selectedItems.empty();
  693. for(let item of this.cacheData) {//选中项放入排序区
  694. const itemHtml = '<div class="sortable-item" data-tag="' + item.tag + '" id="' + item.store_name + '">' + item.store_name + '</div>';
  695. selectedItems.append(itemHtml);
  696. }
  697. this.updateCheckboxOrder();
  698. }
  699. updateCheckboxOrder() {
  700. const selectedItems = $('#' + this.id).find('#selected-items-' + this.id + ' .sortable-item');
  701. let sortedData = [];
  702. const that = this;
  703. selectedItems.each(function() {
  704. const value = $(this).text().trim();
  705. const tag = $(this).data('tag');
  706. const dataItem = that.sysData.find(function (item) {
  707. return item.store_name === value && item.tag === tag;
  708. });
  709. if (dataItem) {
  710. sortedData.push(dataItem);
  711. }
  712. });
  713. this.cacheData = deepCloneData(sortedData);
  714. //全局通道数据,已选中项,排在前面
  715. const unselectedData = that.sysData.filter(function (item) {
  716. return !sortedData.includes(item);
  717. });
  718. sortedData = sortedData.concat(unselectedData);
  719. this.sysData = sortedData;
  720. this.renderForm(false);
  721. }
  722. }
  723. /*-------------------------------------------------------统计的机构设置---------------------------------------------*/
  724. class SelectedMerchantComponent extends Component {
  725. constructor(options) {
  726. super(Object.assign(options, {
  727. template: `
  728. <div id="{{comp-id}}">
  729. <button class="layui-btn layui-bg-green" v-on:click="open">新增</button>
  730. <div id="select-modal-{{comp-id}}" style="display: none;">
  731. <form id="select-form-{{comp-id}}"></form>
  732. <div id="selected-items-{{comp-id}}" class="selected-items"></div>
  733. </div>
  734. </div>
  735. `
  736. }));
  737. this.parent = options.parent;
  738. this.cacheKey = this.props['type'];
  739. this.cacheData = []; //计入统计的机构
  740. this.lastCacheData = []; //打开弹层时的数据,用于对比是否做了修改
  741. this.sysData = []; //系统全量通道,初始时为空,数据在第一次打开弹层时加载
  742. }
  743. mounted() {
  744. const that = this;
  745. $('#selected-items-' + this.id).sortable({
  746. containment: "parent",
  747. cursor: "move",
  748. update: function(event, ui) {
  749. that.updateCheckboxOrder();
  750. }
  751. }).disableSelection();
  752. }
  753. async open() {
  754. await this.loadData();
  755. this.renderForm(true);
  756. const that = this;
  757. layer.open({
  758. type: 1,
  759. title: this.props['title'],
  760. area: ['960px', '600px'],
  761. content: $('#select-modal-' + this.id),
  762. btn: ['确定', '取消'],
  763. yes: function() {
  764. if (that.isEdit() === true) {
  765. statsApi.setStatsSettings(that.cacheKey, that.cacheData)
  766. .then(function (data) {
  767. layer.closeAll();
  768. })
  769. .catch(function (error) {
  770. layer.closeAll();
  771. showErr('保存失败,请重试');
  772. });
  773. } else {
  774. layer.closeAll();
  775. }
  776. },
  777. end: function() {
  778. if (that.isEdit() === true) {
  779. if (that.parent && typeof that.parent.emit === 'function') {
  780. that.parent.emit(that.props['emit'], that.cacheKey, that.cacheData);
  781. }
  782. }
  783. }
  784. });
  785. }
  786. async loadData() {
  787. const loadIndex = layer.load(2, { shade: [0.2, '#000'] });
  788. if (this.sysData.length === 0) { //系统全量机构数据,仅加载1次
  789. const sysMchs = await statsApi.getSysMchs();
  790. if (sysMchs.state === true) {
  791. this.sysData = sysMchs.list;
  792. }
  793. }
  794. const statsSettings = await statsApi.getStatsSettings();
  795. this.cacheData = statsSettings[this.cacheKey] || [];
  796. this.sysData = sortSysData(this.sysData, this.cacheData, 'mch_name');
  797. this.lastCacheData = deepCloneData(this.cacheData);
  798. layer.close(loadIndex);
  799. }
  800. isEdit() {
  801. if (this.lastCacheData.length !== this.cacheData.length) {
  802. return true;
  803. }
  804. for (let i = 0; i < this.lastCacheData.length; i++) {
  805. const u1 = this.cacheData[i].tag + '-' + this.cacheData[i].mch_id;
  806. const u2 = this.lastCacheData[i].tag + '-' + this.lastCacheData[i].mch_id;
  807. if (u1 !== u2) {
  808. return true;
  809. }
  810. }
  811. return false;
  812. }
  813. renderForm(updateSelectedItems) {
  814. let formHtml = '';
  815. for (let item of this.sysData) {
  816. let checked = '';
  817. const dataItem = this.cacheData.find(function (chan) {
  818. return chan.mch_name === item.mch_name && chan.tag === item.tag;
  819. });
  820. if (dataItem) {
  821. checked = 'checked="true"';
  822. }
  823. formHtml += '<label class="checkout-items">';
  824. formHtml += '<input type="checkbox" name="stats_mchs-' + this.id + '" v-on:change="change" value="' + item.mch_name + '" data-name="' + item.name + '" data-quality_text="' + item.quality_text + '" data-tag="' + item.tag + '" ' + checked + ' />' + item.mch_name;
  825. formHtml += '</label>';
  826. }
  827. $('#select-form-' + this.id).html(formHtml);
  828. this.bindEvents();
  829. if (updateSelectedItems === true) {
  830. this.updateSelectedItems();
  831. }
  832. }
  833. change(event) {
  834. const tag = event.currentTarget.getAttribute('data-tag');
  835. const value = event.currentTarget.value;
  836. const checked = event.currentTarget.checked;
  837. this.updateCache(value, tag, checked);
  838. this.updateSelectedItems();
  839. }
  840. updateCache(mchName, tag, checked) {
  841. if (checked === true) {
  842. const item = this.sysData.find(function (chan) {
  843. return chan.mch_name === mchName && chan.tag === tag;
  844. });
  845. if (item) {
  846. this.cacheData.push(item);
  847. }
  848. } else {
  849. let cusMchsNew = [];
  850. for (let item of this.cacheData) {
  851. if (item.mch_name !== mchName || (item.mch_name === mchName && item.tag !== tag)) {
  852. cusMchsNew.push(item);
  853. }
  854. }
  855. this.cacheData = cusMchsNew;
  856. }
  857. }
  858. updateSelectedItems() {
  859. const selectedItems = $('#' + this.id).find('#selected-items-' + this.id);
  860. selectedItems.empty();
  861. for(let item of this.cacheData) {//选中项放入排序区
  862. const itemHtml = '<div class="sortable-item" data-tag="' + item.tag + '" id="' + item.mch_name + '">' + item.mch_name + '</div>';
  863. selectedItems.append(itemHtml);
  864. }
  865. this.updateCheckboxOrder();
  866. }
  867. updateCheckboxOrder() {
  868. const selectedItems = $('#' + this.id).find('#selected-items-' + this.id + ' .sortable-item');
  869. let sortedData = [];
  870. const that = this;
  871. selectedItems.each(function() {
  872. const value = $(this).text().trim();
  873. const tag = $(this).data('tag');
  874. const dataItem = that.sysData.find(function (item) {
  875. return item.mch_name === value && item.tag === tag;
  876. });
  877. if (dataItem) {
  878. sortedData.push(dataItem);
  879. }
  880. });
  881. this.cacheData = deepCloneData(sortedData);
  882. //全局通道数据,已选中项,排在前面
  883. const unselectedData = that.sysData.filter(function (item) {
  884. return !sortedData.includes(item);
  885. });
  886. sortedData = sortedData.concat(unselectedData);
  887. this.sysData = sortedData;
  888. this.renderForm(false);
  889. }
  890. }