/*---------------------------------------------参数配置---------------------------------------*/ const CONSTANTS = { KEY_CHANNEL_NAMES: 'channel_names', //上游的名字,通道分组用 KEY_MERCHANT_NAMES: 'merchant_names', //下游的名字,机构分组用 KEY_CHANNEL_FAST: 'channel_fast', //上下游余额统计:统计的快充通道 KEY_CHANNEL_NORMAL: 'channel_normal', //上下游余额统计:统计的普充通道 KEY_MERCHANT: 'merchant', //上下游余额统计:统计的机构 KEY_DAILY_CHANNEL: 'daily_channel', //日对账单:统计的通道 KEY_DAILY_MERCHANT: 'daily_merchant', //日对账单:统计的通道 }; /*---------------------------------------------公共方法---------------------------------------*/ function formatDecimals(number, digit= 2) {//格式化金额 if (typeof number === 'string') { number = Number(number); } if (number === 0) { return '0'; } return parseFloat(number).toFixed(digit); } function isValidKey(obj, key) { if (obj.hasOwnProperty(key)) { if (obj[key] !== null && obj[key] !== undefined && obj[key] !== '') { return true; } } return false; } function deepCloneData(obj) {//数据深拷贝 if (obj === null || typeof obj !== 'object') { return obj; } let clone = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepCloneData(obj[key]); } } return clone; } function sortSysData(sysData, sortData, key) {//sortData数据前置,sysData中未包含在sortData中的数据放在后面 let sortedData = deepCloneData(sortData); const unselectedData = sysData.filter(function (sys) { const selected = sortData.find(function(cust) { return sys[key] === cust[key] && sys.tag === sys.tag; }); return !selected; }); return sortedData.concat(unselectedData); } /** * @param sortCustSet 排好序的元素 * @param data 实时数据,未排序 * @param groupKey 分组字段 * @param nameKey 过滤字段,机构和通道均包含name字段,增加nameKey,比如store_name,mch_name,多一层过滤条件防止存在重名情况 * @returns {*[]} */ function sortCustData(sortCustSet, data, groupKey, nameKey) { // 去掉不在当前数据中的排序值 const sortCust = sortCustSet.filter(function (item) { return data.find(function (litem) { return litem.name === item.name && litem[nameKey] === item[nameKey] && litem.tag === item.tag; }); }); const custMap = sortCust.reduce((acc, item, index) => { acc[item.name] = index; return acc; }, {}); data.sort((a, b) => custMap[a.name] - custMap[b.name]); const groupMap = {}; data.forEach(item => { const group = item[groupKey]; if (!groupMap[group]) { groupMap[group] = []; } groupMap[group].push(item); }); const sortedData = []; data.forEach(item => { const group = item[groupKey]; if (groupMap[group]) { sortedData.push(...groupMap[group]); delete groupMap[group]; } }); return sortedData; } /* * 合并二维数据,表格数据合并/通道机构配置项合并 */ function mergeData(data1, data2, key) { let dataMormalMerge = []; for (let item of data1) { const p2Item = data2.find(function(p2) { return item[key] === p2[key] && item.tag === p2.tag; }); if (!p2Item) { dataMormalMerge.push(item); } else { dataMormalMerge.push(p2Item); } } for (let item of data2) { const mItem = dataMormalMerge.find(function(m) { return item[key] === m[key] && item.tag === m.tag; }); if (!mItem) { dataMormalMerge.push(item); } } return dataMormalMerge; } /* * 合并一维数组,上下游名称配置项合并 */ function mergeNames(data1, data2) { const notin = data2.filter(item => !data1.includes(item)); return data1.concat(notin); } function handleInputd2(event) {//限制金额输入框 let element = event.target; let value = element.textContent; let selection = window.getSelection(); let range = selection.getRangeAt(0); let startOffset = range.startOffset; value = value.replace(/[^\d.-]/g, ''); const parts = value.split('.'); if (parts.length > 1) { parts[1] = parts[1].slice(0, 2); value = parts.join('.'); } element.textContent = value; let newNode = element.childNodes[0] || element; range.setStart(newNode, Math.min(startOffset, value.length)); range.setEnd(newNode, Math.min(startOffset, value.length)); selection.removeAllRanges(); selection.addRange(range); } function handleInputd4(event) { let element = event.target; let value = element.textContent; let selection = window.getSelection(); let range = selection.getRangeAt(0); let startOffset = range.startOffset; value = value.replace(/[^\d.-]/g, ''); const parts = value.split('.'); if (parts.length > 1) { parts[1] = parts[1].slice(0, 4); value = parts.join('.'); } element.textContent = value; let newNode = element.childNodes[0] || element; range.setStart(newNode, Math.min(startOffset, value.length)); range.setEnd(newNode, Math.min(startOffset, value.length)); selection.removeAllRanges(); selection.addRange(range); } function isObjectEmpty(obj) { return Object.keys(obj).length === 0; } function showErr(error) { layer.alert(error, { icon: 2, title: '错误' }); } function showSuccess(msg) { layer.msg(msg, { icon: 1, time: 2000 }); } function isNumeric(value) { return /^-?\d+(\.\d+)?$/.test(value); } /*----------------------------------------------统一请求定义---------------------------------------*/ class ApiService { constructor(baseUrl) { this.baseUrl = baseUrl; } request(endpoint, method, data) { const that = this; return new Promise(function(resolve, reject) { $.ajax({ url: that.baseUrl + endpoint, type: method, data: data, dataType: 'json', success: function(response) { if (response.state) { resolve(response); } else { reject(new Error('请求失败')); } }, error: function(jqXHR, textStatus, errorThrown) { reject(new Error('请求失败: ' + textStatus + ', ' + errorThrown)); } }); }); } getStatsSettings() { const endpoint = '?act=refill_amount_stats&op=stats_settings'; return this.request(endpoint, 'GET', {}); }; setStatsSettings(key, value) { const endpoint = '?act=refill_amount_stats&op=stats_settings_save'; let data = { key: key, value: value }; return this.request(endpoint, 'POST', data); }; getSysChannels() { const endpoint = '?act=refill_amount_stats&op=sys_channels'; return this.request(endpoint, 'GET', {}); }; getSysMchs() { const endpoint = '?act=refill_amount_stats&op=sys_mchs'; return this.request(endpoint, 'GET', {}); }; getChannelData(params) { const queryString = $.param(params); const endpoint = '?act=refill_amount_stats&op=channel_data&' + queryString; return this.request(endpoint, 'GET', {}); }; getMchData(params) { const queryString = $.param(params); const endpoint = '?act=refill_amount_stats&op=mch_data&' + queryString; return this.request(endpoint, 'GET', {}); }; getDailyProviderData(params) { const queryString = $.param(params); const endpoint = '?act=refill_amount_stats&op=daily_provider_data&' + queryString; return this.request(endpoint, 'GET', {}); }; getDailyMerchantData(params) { const queryString = $.param(params); const endpoint = '?act=refill_amount_stats&op=daily_merchant_data&' + queryString; return this.request(endpoint, 'GET', {}); }; } const statsApi = new ApiService(''); /*------------------------------------------------------组件实现:调度类-------------------------------------------*/ class ComponentBase { constructor(options) { this.el = document.querySelector(options.el); this.components = options.components || {}; this.componentData = options.componentData || {}; this.events = {}; this.componentInstances = {}; this.render(); } render() { this.el.innerHTML = this.compile(this.el.innerHTML); } compile(template) { const componentNames = Object.keys(this.components); componentNames.forEach(name => { const regex = new RegExp(`<${name}([^>]*)>(.*?)<\/${name}>`, 'g'); template = template.replace(regex, (match, attrs) => { const props = {}; attrs.replace(/(\w+)="([^"]*)"/g, (match, key, value) => { if (CONSTANTS[value] !== undefined) { value = CONSTANTS[value]; } props[key] = value; }); const component = new this.components[name]({ props: props, data: this.componentData[name] || {}, parent: this }); this.componentInstances[name] = component; return component.render(); }); }); return template; } on(event, callback) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(callback); } emit(event, ...args) { if (this.events[event]) { this.events[event].forEach(callback => callback(...args)); } } getComponent(name) { return this.componentInstances[name]; } } /*------------------------------------------------------组件实现:组件基类-------------------------------------------*/ class Component { constructor(options) { this.data = options.data || {}; this.props = options.props || {}; this.template = options.template || ''; this.id = 'component-' + Math.random().toString(36).substr(2, 9); this.props['comp-id'] = this.id; } render() { let template = this.template; for (const key in this.props) { template = template.replace(new RegExp(`{{${key}}}`, 'g'), this.props[key]); } setTimeout(() => { this.bindEvents(); this.mounted(); }, 0); return template; } bindEvents() { const rootElement = document.querySelector(`#${this.id}`); if (!rootElement) { return; } this.unbindEvents(); const supportedEvents = ['click', 'input', 'blur', 'change']; supportedEvents.forEach(eventType => { const elements = rootElement.querySelectorAll(`[v-on\\:${eventType}]`); elements.forEach(element => { const method = element.getAttribute(`v-on:${eventType}`); if (method && typeof this[method] === 'function') { const listener = this[method].bind(this); element.addEventListener(eventType, listener); if (!this.eventListeners[eventType]) { this.eventListeners[eventType] = []; } this.eventListeners[eventType].push({ element, listener }); } }); }); } unbindEvents() { for (const eventType in this.eventListeners) { this.eventListeners[eventType].forEach(({ element, listener }) => { element.removeEventListener(eventType, listener); }); } this.eventListeners = {}; } mounted() { } } /*------------------------------------------------------layui表格基类-----------------------------------------------*/ class LayuiTableBase { constructor(id) { this.tableId = id; } /* * 表格中可编辑列的处理 */ editEvent(editColumns = [], callback) { const tableItem = $('#' + this.tableId); const trs = tableItem.next('.layui-table-view').find('.layui-table-body tbody tr'); trs.each(function (i, tr) { for (let col of editColumns) { const td = $(tr).find('td[data-field="' + col + '"]').not('.layui-hide'); if (td.length > 0) {//合并隐藏的不处理 const valDiv = td.find('div'); valDiv.attr('contenteditable', true).css('background-color', 'lightblue'); valDiv.off('input', handleInputd4) .off('keypress') .off('blur') .on('input', handleInputd4) .on('keypress', function (e) { if (e.keyCode === 13) { e.preventDefault(); $(this).blur(); } }) .on('blur', function () { const val = valDiv.text().trim(); if (!isNumeric(val)) { valDiv.html('0'); } if (callback !== null && typeof callback === 'function') { const index = $(this).parent().parent().data('index'); callback(index); } }); } } }); } switchEvent(switchColumns = [], callback) { const tableItem = $('#' + this.tableId); const trs = tableItem.next('.layui-table-view').find('.layui-table-body tbody tr'); trs.each(function (i, tr) { for (let clo of switchColumns) { const td = $(tr).find('td[data-field="' + clo + '"]').not('.layui-hide'); if (td.length > 0) { const buttons = td.find('.layui-btn'); buttons.off('click').on('click', function (e) { e.preventDefault(); e.stopPropagation(); const currTr = $(this).closest('tr'); const index = currTr.data('index'); buttons.removeClass('selected').addClass('unselected'); $(this).removeClass('unselected').addClass('selected'); if (callback !== null && typeof callback === 'function') { callback(index); } }); } } }); } /* * 表格中金额字段样式,小于0的数值设置为红色 */ setValueClass(valColumns) { const trs = $('#' + this.tableId).next('.layui-table-view').find('.layui-table-body tbody tr'); trs.each(function(i, tr) { for(let col of valColumns) { const td = $(tr).find('td[data-field="' + col + '"]').not('.layui-hide'); if (td.length > 0) {//合并隐藏的不处理 const valDiv = td.find('div'); const val = valDiv.text().trim(); if (parseFloat(val) < 0) { valDiv.addClass('negative-value'); } else { valDiv.removeClass('negative-value'); } } } }); } } /*------------------------------------------------------组件:上下游名称设置-------------------------------------------*/ class StreamNameComponent extends Component { constructor(options) { super(Object.assign(options, { template: `