import { parse } from 'querystring'; import moment from 'moment'; import numeral from 'numeral'; import ExportJsonExcel from 'js-export-excel'; import type { RangePickerDateProps } from 'antd/es/date-picker/generatePicker'; import type { ProColumns } from '@ant-design/pro-table'; type RangePickerValue = RangePickerDateProps['value'] /* eslint no-useless-escape:0 import/prefer-default-export:0 */ const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/; export const isUrl = (path: string): boolean => reg.test(path); export const isAntDesignPro = (): boolean => { if (ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') { return true; } return window.location.hostname === 'preview.pro.ant.design'; }; // For the official demo site, it is used to turn off features that are not needed in the real development environment export const isAntDesignProOrDev = (): boolean => { const { NODE_ENV } = process.env; if (NODE_ENV === 'development') { return true; } return isAntDesignPro(); }; export const getPageQuery = () => parse(window.location.href.split('?')[1]); export function getTimeDistance(type: 'today' | 'week' | 'month' | 'year'): RangePickerValue { const now = new Date(); const oneDay = 1000 * 60 * 60 * 24; if (type === 'today') { now.setHours(0); now.setMinutes(0); now.setSeconds(0); return [moment(now), moment(now.getTime() + (oneDay - 1000))]; } if (type === 'week') { let day = now.getDay(); now.setHours(0); now.setMinutes(0); now.setSeconds(0); if (day === 0) { day = 6; } else { day -= 1; } const beginTime = now.getTime() - day * oneDay; return [moment(beginTime), moment(beginTime + (7 * oneDay - 1000))]; } const year = now.getFullYear(); if (type === 'month') { const month = now.getMonth(); const nextDate = moment(now).add(1, 'months'); const nextYear = nextDate.year(); const nextMonth = nextDate.month(); return [ moment(`${year}-${fixedZero(month + 1)}-01 00:00:00`), moment(moment(`${nextYear}-${fixedZero(nextMonth + 1)}-01 00:00:00`).valueOf() - 1000), ]; } return [moment(`${year}-01-01 00:00:00`), moment(`${year}-12-31 23:59:59`)]; } export function fixedZero(val: number) { return val * 1 < 10 ? `0${val}` : val; } // 拆分小数点前后数字 export const splitAmount = (value: any, fixedType?: string) => { const formatType = fixedType || '0,0' const stringValue = numeral(value).format(formatType) const [intValue, floatValue] = stringValue.split(".") return [intValue, floatValue] } // 报表导出excel 无多级表头 export const exportExcel = async ( outColumn: ProColumns[], reqDetailList: any[], fileName?: string, ) => { // 网络请求命名空间 // const outColumn: ProColumns[] = columns.slice(3); // 需要放在state里边,Table,Columns const option: { fileName: string; datas: any[] } = { fileName: fileName || '新建文件', // : '收营员报表' + moment().format('YYYYMMDDHHmmss'), datas: [], }; if (reqDetailList?.length) { const getList = (data = [], index: number) => { const wrapData: any = data.map((item: any) => { const node = { ...item }; if (item.children && item.children.length > 0) { const childNode = getList(item.children, index) return [node, ...childNode.data] } return node }); return { data: wrapData, index: index + 1 }; } const data = getList(reqDetailList as [], 0) const outList = data.data.flat(data.index + 1) option.datas = [ { sheetData: outList.map((item: any, index: number) => { const result = {}; outColumn.forEach((c: any, i) => { if (c.hideInTable) return; if (c.dataIndex === 'index') { result[c.dataIndex] = index + 1; } else if (c.render) { result[c.dataIndex] = typeof c.render(item[c.dataIndex], item) === 'object' ? item[c.dataIndex] : c.render(item[c.dataIndex], item) } else { result[c.dataIndex] = item[c.dataIndex] ? item[c.dataIndex] : item[c.dataIndex] === 0 ? 0 : '' } }); // if(item.children) { // const childrenColums = item.children.map((child, index) => { // const res = {}; // outColumn.forEach((c: any, i) => { // if (c.hideInTable) return; // if (i !== 0) { // res[c.dataIndex] = c.render ? c.render(child[c.dataIndex],item) : child[c.dataIndex] ; // } else { // res[c.dataIndex] = index + 1; // } // }); // return res // }) // console.log(childrenColums) // // return {childrenColums} // } // console.log('result',result) return result; }), sheetName: 'sheet1', // Excel文件名称 sheetFilter: outColumn.map((item) => item.dataIndex), sheetHeader: outColumn.map((item) => item.title), columnWidths: outColumn.map(() => 10), }, ]; // function warpNode(data) { // data.map((item, index) => { // const result = {}; // if(item.children) { // } // outColumn.forEach((c: any, i) => { // if (c.hideInTable) return; // if (i !== 0) { // result[c.dataIndex] = item[c.dataIndex]; // } else { // result[c.dataIndex] = index + 1; // } // }); // return result; // }) // } // } const toExcel = new ExportJsonExcel(option); toExcel.saveExcel(); return { message: 'ok' }; } return { message: '没有可导出的数据' }; }; // 合作商户资金到账汇总报表导出的修改 export const exportExcelReceivedSum = ( outColumn: ProColumns[], reqDetailList: any[], fileName?: string,) => { const option: { fileName: string; datas: any[] } = { fileName: fileName || '新建文件', // : '收营员报表' + moment().format('YYYYMMDDHHmmss'), datas: [], }; if (reqDetailList?.length) { const getList = (data = [], index: number) => { const wrapData: any = data.map((item: any) => { const node = { ...item }; if (item.children && item.children.length > 0) { const childNode = getList(item.children, index) return [node, ...childNode.data] } return node }); return { data: wrapData, index: index + 1 }; } const data = getList(reqDetailList as [], 0) const outList = data.data.flat(data.index + 1) console.log('outList', outList) option.datas = [ { sheetData: outList.map((item: any, index: number) => { const result = {}; // 判断这个对象是否都有值 outColumn.forEach((c: any, i) => { if (c.hideInTable) return; if (c.render) { result[c.dataIndex] = typeof c.render(item[c.dataIndex], item) === 'object' ? item[c.dataIndex] : c.render(item[c.dataIndex], item) } else { result[c.dataIndex] = item[c.dataIndex] ? item[c.dataIndex] : item[c.dataIndex] === 0 ? 0 : '' } }); // if(item.children) { // const childrenColums = item.children.map((child, index) => { // const res = {}; // outColumn.forEach((c: any, i) => { // if (c.hideInTable) return; // if (i !== 0) { // res[c.dataIndex] = c.render ? c.render(child[c.dataIndex],item) : child[c.dataIndex] ; // } else { // res[c.dataIndex] = index + 1; // } // }); // return res // }) // // return {childrenColums} // } return result; }), sheetName: 'sheet1', // Excel文件名称 sheetFilter: outColumn.map((item) => item.dataIndex), sheetHeader: outColumn.map((item) => item.title), columnWidths: outColumn.map(() => 10), }, ]; console.log('option.datas', option.datas) // function warpNode(data) { // data.map((item, index) => { // const result = {}; // if(item.children) { // } // outColumn.forEach((c: any, i) => { // if (c.hideInTable) return; // if (i !== 0) { // result[c.dataIndex] = item[c.dataIndex]; // } else { // result[c.dataIndex] = index + 1; // } // }); // return result; // }) // } // } const toExcel = new ExportJsonExcel(option); toExcel.saveExcel(); return { message: 'ok' }; } return { message: '没有可导出的数据' }; } // 处理好数据 可作为公共的导出方法 // outColumn 排除hideInTable和 再设置中隐藏的字段 变成一个数组 // reqDetailList 无论多少级 转成一层数组用于导出 // 合作商户资金到账汇总报表导出的修改 export const exportExcelaccountMonthly = ( outColumn: ProColumns[], reqDetailList: any[], fileName?: string,) => { const option: { fileName: string; datas: any[] } = { fileName: fileName || '新建文件', // : '收营员报表' + moment().format('YYYYMMDDHHmmss'), datas: [], }; if (reqDetailList?.length) { const data = reqDetailList const outList = data.flat(data.index + 1) option.datas = [ { sheetData: outList.map((item: any, index: number) => { const result = {}; // 判断这个对象是否都有值 outColumn.forEach((c: any, i) => { if (c.hideInTable) return; if (c.render) { result[c.dataIndex] = typeof c.render(item[c.dataIndex], item) === 'object' ? item[c.dataIndex] : c.render(item[c.dataIndex], item) } else { result[c.dataIndex] = item[c.dataIndex] ? item[c.dataIndex] : item[c.dataIndex] === 0 ? 0 : '' } }); return result; }), sheetName: 'sheet1', // Excel文件名称 sheetFilter: outColumn.map((item) => item.dataIndex), sheetHeader: outColumn.map((item) => item.title), columnWidths: outColumn.map(() => 10), }, ]; const toExcel = new ExportJsonExcel(option); toExcel.saveExcel(); return { message: 'ok' }; } return { message: '没有可导出的数据' }; } // 打印页面配置内容 export const printOutBody = (ele: any, content: string) => { const dateEle = document.createElement('div'); const printConent = document.createElement('div'); if (typeof ele === 'object' && ele.length > 1) { ele.forEach(n => { if (n) { printConent.appendChild(n) } }) } else { const title = ele?.getElementsByClassName('ant-table-wrapper')[0]; printConent.appendChild(ele) if (title) { dateEle.setAttribute('style', 'text-align:right;font-size:20px;margin-bottom: 20px'); dateEle.innerHTML = content; ele.insertBefore(dateEle, title); } } // dateEle.setAttribute('style', 'text-align:right;font-size:20px;margin-bottom: 20px;'); // dateEle.innerHTML = content; return printConent; }; // 打印合同配置的内容 export const printContract = (ele: any, content: string) => { const dateEle = document.createElement('div'); const printConent = document.createElement('div'); if (typeof ele === 'object' && ele.length > 1) { ele.forEach(n => printConent.appendChild(n)) } else { const title = ele printConent.appendChild(ele) if (title) { dateEle.setAttribute('style', 'text-align:center;font-size:20px;margin-bottom: 20px;'); dateEle.innerHTML = content; ele.insertBefore(dateEle, title); } } return printConent } // 打印低代码生成页面配置内容 // 由于不同页面表单所在区域的dom元素不同 此方法建议只在低代码生成的页面中 做打印功能 export const printOutInternal = (ele: any, content: string) => { // 先创建一个包裹在最外层的dom 再在这里面新建一个dom用来放需要打印的内容 const dateEle = document.createElement('div'); const printConent = document.createElement('div'); // 增加一些样式 printConent.setAttribute('style', 'width:100%;padding:25px') // 顶部内容dom 用于放标题内容 const top = document.createElement('div'); // 顶部dom框样式 top.setAttribute('style', 'width:100%;display:flex;just-content:center;align-items:center;margin-bottom:20px') // 找title的文本信息dom拿到值 const title = ele?.getElementsByClassName('ant-pro-table-list-toolbar-title')[0]; // 标题的样式 title.setAttribute('style', 'font-size:20px;margin: 0 auto') // 把title的dom放到顶部dom中 顶部dom加入到需要打印内容的dom中 添加的顺序就是显示的顺序 top.appendChild(title) dateEle.appendChild(top) // 拿到表格的整体dom const body = ele?.getElementsByClassName('ant-table')[0] body.setAttribute('style', 'height:auto') const colgroup = body.getElementsByTagName('colgroup')[0] const colList = colgroup.getElementsByTagName('col') for (let i = colList.length - 1; i >= 0; i--) { colList[i].remove() } // 表格数据的dom // const listDom = body.getElementsByClassName('ant-table-body')[0] // listDom.setAttribute('style', '') // 拿到表头 const th = body.getElementsByTagName('th') // 判断表头里面有没有操作列 有操作列的话 每行数据的最后一列要去除 let isOption = false th.forEach((n: any) => { if (n.innerHTML === '操作') { n.remove() isOption = true } }) th.forEach((n: any, index: number) => { if (index === th.length - 1) { n.setAttribute('style', 'width:1px') // n.remove() } // else { // n.setAttribute('style',`width:calc((100% - 1px)/${th.length - 1}),text-align:center`) // } }) // 拿到表格数据的dom const tbody = body.getElementsByClassName('ant-table-tbody')[0] const list = tbody.getElementsByTagName('tr') // 删除操作列的数据 if (isOption) { list.forEach((n: any, index: number) => { const td = n.getElementsByTagName('td') const { length } = td td[length - 1].remove() }) } // 判断是否有传入时间 有时间 显示在表格和头部中间偏右 if (content) { const time = document.createElement('div') time.innerText = content time.setAttribute('style', 'width:100%;text-align:right') dateEle.appendChild(time) } // 添加处理过的数据 放到打印内容中 dateEle.appendChild(body) printConent.appendChild(dateEle) return printConent; }; // 封装导出的方法 尽量适配报表的全部页面 export const handlePrint = (ele: any) => { const printContent = document.createElement('div'); const cloneNode = ele.cloneNode(true) const tableContent = cloneNode.getElementsByClassName('ant-card')[0] printContent.appendChild(tableContent) return printContent } // 获取临时查看图片的base4码 export const getBase64 = async (file: any): Promise => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result); reader.onerror = error => reject(error); }); } // 打印合作商户合同期结算明细表 有流程进度的特殊情况 export const printSettlementDetails = (ele: any, content: string) => { console.log('ele', ele) const dateEle = document.createElement('div'); const printConent = document.createElement('div'); if (typeof ele === 'object' && ele.length > 1) { ele.forEach(n => printConent.appendChild(n)) } else { const title = ele printConent.appendChild(ele) if (title) { dateEle.setAttribute('style', 'text-align:center;font-size:20px;margin-bottom: 20px;'); dateEle.innerHTML = content; ele.insertBefore(dateEle, title); } } console.log('printConent', printConent) return printConent } // 处理高精度问题 export const handleHighPrecision = (num: number) => { // 处理负数情况 const isNegative = num < 0; const newNumber = Number(Math.abs(num).toFixed(2)) const str: string = newNumber.toString(); // 取绝对值进行处理 // 取整数部分 let integer: string = str.split('.')[0]; // 确保整数部分至少两位(前面补 0) if (integer.length < 3) { integer = integer.padStart(3, '0'); } // 取最后两位作为小数部分 const lastTwo: string = integer.substring(integer.length - 2); // 取前面的整数部分 const startStr: string = integer.substring(0, integer.length - 2); // 组合字符串,转换为数字 const result = parseFloat(`${startStr}.${lastTwo}`); // 如果原数是负数,返回负值 return isNegative ? -result : result; } // 将多层级树组 按照label value 变为单层的对象 export const handleGetListObj = (list: any) => { let res: any = {}; if (!list || list.length === 0) { return res; } list.forEach((item: any) => { if (item.children?.length) { const childObj = handleGetListObj(item.children); res = { ...res, ...childObj }; } if (item.value !== undefined && item.label !== undefined) { res[item.value] = item.label; } }); return res; } // 将多层的columns 按照key value 变为单层对象 export const handleColumnsToObj = (list: any, parentTitle?: string) => { let res: any = {} if (!list || list.length === 0) { return res; } list.forEach((item: any) => { const currentTitle = typeof item.title === 'string' ? item.title : item.aiTitle || ''; const fullTitle = parentTitle ? `${currentTitle}${parentTitle} ` : currentTitle; if (item.children && item.children.length > 0) { // 如果有子节点,递归处理子节点,并传入当前拼接后的标题 const childResults = handleColumnsToObj(item.children, fullTitle); res = { ...res, ...childResults }; } else { // 如果没有子节点,添加到结果对象中 if(item.renderHaveText){ res[item.renderHaveText] = fullTitle; }else if (item.aiDataIndex) { res[item.aiDataIndex] = fullTitle; } } }) return res; } // 将多层的表格数据 他里面的对象数据 全部解构出来 变层父级key拼上当级key的格式 与原先的父级同级 export const handleExportListToObj = (data: any) => { if (!Array.isArray(data)) return []; const processNode = (node: any) => { // 处理当前节点的数据 const processedNode: any = {}; // 处理所有字段 Object.entries(node).forEach(([key, value]) => { // 保留children和ShopINCList结构 if (key === 'children' || key === 'ShopINCList') { return; } // 处理对象类型字段 if (value && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) { Object.entries(value).forEach(([innerKey, innerValue]) => { processedNode[`${key}${innerKey}`] = innerValue; }); } else { processedNode[key] = value; } }); // 处理子节点 const children = node.children && node.children.length > 0 ? node.children : node.ShopINCList && node.ShopINCList.length > 0 ? node.ShopINCList : []; if (children.length > 0) { processedNode.children = children.map((child: any) => processNode(child)); } return processedNode; }; return data.map(node => processNode(node)); }; // 有一个对象 key的值为跟表格的每一层对象的key对应 value值为这个key所代表的意思 export const handleChangeItemKeyToValue = (list: any, keyMapping: Record) => { if (!Array.isArray(list)) return []; const processNode = (node: any) => { const newNode: any = {}; // 处理当前节点的所有属性 Object.entries(node).forEach(([key, value]) => { // 特殊处理children数组 if (key === 'children') { const processedChildren = value.map((child: any) => processNode(child)); if (processedChildren.length > 0) { newNode.children = processedChildren; } return; } // 检查是否有映射关系 if (key in keyMapping) { const newKey = keyMapping[key]; // 处理值 if (value && typeof value === 'object' && !Array.isArray(value)) { // 如果是对象,递归处理 const processedValue = processNode(value); if (Object.keys(processedValue).length > 0) { newNode[newKey] = processedValue; } } else { // 普通值直接赋值 newNode[newKey] = value; } } }); return newNode; }; return list.map(node => processNode(node)).filter(node => Object.keys(node).length > 0); }; // export const handleChangeItemKeyToValue = (list: any, keyMapping: any) => { // if (!Array.isArray(list)) return []; // const processNode = (node: any) => { // const newNode: any = {}; // // 处理当前节点的所有属性 // Object.entries(node).forEach(([key, value]: any) => { // // 特殊处理children数组 // if (key === 'children') { // newNode.children = value.map((child: any) => processNode(child)); // return; // } // // 检查是否有映射关系 // const newKey = keyMapping[key] || key; // // 处理值 // if (value && typeof value === 'object' && !Array.isArray(value)) { // // 如果是对象,递归处理 // newNode[newKey] = processNode(value); // } else { // // 普通值直接赋值 // newNode[newKey] = value; // } // }); // return newNode; // }; // return list.map(node => processNode(node)); // } // 多层级数组 变为同一级 export const handleFlattenTree = (treeData: any) => { const result: any = []; function traverse(nodes) { if (!Array.isArray(nodes)) return; nodes.forEach(node => { // 将当前节点加入结果(排除嵌套字段) const { children, ShopINCList, ...rest } = node; result.push(rest); // 递归处理嵌套节点 if (children && children.length > 0) traverse(children); if (ShopINCList && ShopINCList.length > 0) traverse(ShopINCList); }); } traverse(treeData); return result; } export const findNodeByPath = (treeData: any[], targetPath: string): any | undefined => { // 边界检查 if (!Array.isArray(treeData) || !targetPath) return undefined; for (const node of treeData) { // 检查当前节点是否匹配 if (node.SYSTEMMODULE_URL === targetPath) { return node; } // 如果有子节点且不是空数组,递归查找 if (node.SystemModuleList?.length > 0) { const found = findNodeByPath(node.SystemModuleList, targetPath); if (found) return found; } } return undefined; // 未找到 } // 封装一个方法 将传入的树形字段 分别变为 片区层 服务区层 门店层 export const handleGetSpecifyFormatLevelRes = (data: any) => { const result = { regions: [], // 片区层(最高级) serviceAreas: [], // 服务区层 shops: [] // 门店层 }; if (!Array.isArray(data)) return result; const cleanItem = (item: any): any => { const cleaned: any = {}; Object.keys(item).forEach(key => { const value = item[key]; // 保留非对象且非数组的字段 if (typeof value !== 'object' || value === null) { cleaned[key] = value; } }); return cleaned; }; // 递归处理每一项 const processItem = (item: any) => { const keys = Object.keys(item); const lowerCaseKeys: any = keys.map(key => key.toLowerCase()); // 获取字段值(不区分大小写) const spRegionTypeId = keys.find(k => k.toLowerCase() === 'spregiontypeid'); const serverpartId = keys.find(k => k.toLowerCase() === 'serverpartid'); const serverpartShopId = keys.find(k => k.toLowerCase() === 'serverpartshopid'); const hasSPRegionTypeId = spRegionTypeId != null && item[spRegionTypeId] != null; const hasServerpartId = serverpartId != null && item[serverpartId] != null; const hasServerpartShopId = serverpartShopId != null && item[serverpartShopId] != null; // 严格层级判断 if (hasServerpartShopId) { result.shops.push(cleanItem(item)); // 规则3:只要 ServerpartShopId 有值就是门店层 } else if (hasSPRegionTypeId && hasServerpartId) { result.serviceAreas.push(cleanItem(item)); // 规则2:SPRegionTypeId + ServerpartId(无 ServerpartShopId) } else if (hasSPRegionTypeId && !hasServerpartId && !hasServerpartShopId) { result.regions.push(cleanItem(item)); // 规则1:仅 SPRegionTypeId } // 递归处理子集(children 或 ShopINCList,字段名大小写敏感) if (item.children && Array.isArray(item.children)) { item.children.forEach(child => processItem(child)); } if (item.ShopINCList && Array.isArray(item.ShopINCList)) { item.ShopINCList.forEach(child => processItem(child)); } }; // 遍历初始数组 data.forEach(item => processItem(item)); return result; } export const checkAndCompleteFields = (targetObj, sourceObj) => { // 获取目标对象所有键(转换为小写) const targetKeys = Object.keys(targetObj).map(key => key.toLowerCase()); // 遍历源对象的所有键 for (const sourceKey in sourceObj) { if (sourceObj.hasOwnProperty(sourceKey)) { const lowerSourceKey = sourceKey.toLowerCase(); // 检查目标对象是否包含当前键(不区分大小写) if (!targetKeys.includes(lowerSourceKey)) { // 找到源对象中原始键名(保留原始大小写) const originalKey = Object.keys(sourceObj).find( key => key.toLowerCase() === lowerSourceKey ); // 将缺失的字段添加到目标对象 targetObj[originalKey] = sourceObj[originalKey]; } } } return targetObj; } // 字符串计数 控制跟word一样 export const countLikeWordAccurate = (text: string) => { if (!text) return 0; // 将文本标准化:合并连续空格,去除首尾空格 const normalizedText = text.replace(/\s+/g, ' ').trim(); if (!normalizedText) return 0; // 使用Unicode属性转义更准确地匹配中文字符 const chineseRegex = /(\p{Script=Han}|\p{sc=Hani})/gu; const chineseCount = [...normalizedText.matchAll(chineseRegex)].length; // 统计非中文部分 const nonChineseText = normalizedText.replace(chineseRegex, ' '); // 统计英文单词和数字序列 const wordCount = nonChineseText.split(/\s+/) .filter(word => word.length > 0) .length; return chineseCount + wordCount; }