import dayjs from 'dayjs'; import { utils, writeFile } from "xlsx"; import * as XLSX from 'xlsx-js-style/dist/xlsx.bundle.js'; /* * Harmory 3.0 Util Plugin * Version : 1.0 * Make By Jason 2022 */ let util = { /** * @TODO 함수설명 필요 **/ toStr(str){ return str + '' }, /** * @TODO 함수설명 필요 **/ nvl(str, restr){ if (str === '' || str === null || typeof(str) === 'undefined' || str === 'undefined' || str === 'null'){ if (restr === ''){ return '' } else { return restr } }else{ return str } }, /** * @TODO 함수설명 필요 */ isNull(str){ if ( str === '' || str === null || str === undefined || typeof(str) === 'undefined' || str ==='undefined' || str ==='null'){ return true }else{ return false } }, /** * @TODO 함수설명 필요 * 로깅처리 하는 글로벌 Util * LOGGING =='YES' 일때만 처리함 **/ log(msg, level){ console.log(level+'|', msg) }, colorLog(msg){ // console.log("%c"+dayjs().format('YYYY-MM-DD HH:mm:ss')+" "+msg, 'color:#02f7eb') }, /** * @TODO 좌우 Trim **/ trim(data){ return data.replace(/^\s+|\s+$/g, '') }, /** * @TODO 왼쪽으로 Trim **/ ltrim(data){ return data.replace(/^\s+/, '') }, /** * @TODO 오른쪽으로 trim **/ rtrim(data){ return data.replace(/\s+$/, '') }, /** * @TODO 소수점 자리숫를 파싱한다. **/ toRoundFix(data, digit){ if(Number.isInteger(parseFloat(data))){ return parseFloat(data) } else { return parseFloat(data).toFixed(digit) } }, fillZero(number, width){ width -= number.toString().length if ( width > 0 ){ return new Array( width + (/\./.test( number ) ? 2 : 1) ).join( '0' ) + number } return number + '' // always return a strin }, hex2Float(number, underDigit){ let sNum = '' let num = parseInt(number, 16) sNum += parseFloat(num/100).toFixed(underDigit) return sNum }, decimalToHex(d, padding){ let hex = Number(d).toString(16) padding = typeof (padding) === 'undefined' || padding === null ? padding = 2 : padding while (hex.length < padding){ hex = '0' + hex } return hex.toUpperCase() }, left(str, n){ if (n <= 0){ return '' }else if (n > String(str).length){ return str }else{ return String(str).substring(0, n) } }, right(str, n){ if (n <= 0){ return '' }else if (n > String(str).length){ return str }else{ let iLen = String(str).length return String(str).substring(iLen, iLen - n) } }, convert12Hour(hour){ return ''+util.fillZero((parseInt(hour, 10) + 24) % 12 || 12, 2) }, getStrLength(str){ let strLength = 0 let i for (i = 0; i < str.length; i++){ let code = str.charCodeAt(i) let ch = str.substr(i, 1).toUpperCase() code = parseInt(code) if ((ch < '0' || ch > '9') && (ch < 'A' || ch > 'Z') && ((code > 255) || (code < 0))) strLength = strLength + 2 else strLength = strLength + 1 } return strLength }, str2Hex(instr){ let str = ''+instr let hex = '' for(let i = 0; i 4){ bytes += 2 }else if(ch === '\n'){ if(str.charAt(i-1) != '\r'){ bytes += 1 } }else if(ch === '<' || ch === '>'){ bytes += 4 }else{ bytes += 1 } if(bytes > len){ str_len-- $(obj).val(str.substring(0, str_len)) } } if(bytes > len){ result = false } return result }, getOS(){ let ua = String( navigator.userAgent ).toLowerCase() if(/iphone|ipad/.test(ua)){ return 'ios' }else if(/android/.test(ua)){ return 'android' }else{ return 'android' } }, setComma(num){ let sign = '' let rnum = String(num) if(rnum.substring(0, 1) == '-'){ sign = '-' rnum = rnum.substring(1) } let spNum = rnum.split('.') return sign + spNum[0].replace(/\D/g, '').replace(/\B(?=(\d{3})+(?!\d))/g, ',') + (spNum[1] ? '.' + spNum[1] : '') }, setUnComma(num){ num = String(num) return num.replace(/[^\d]+/g, '') }, setInputNumberComma(obj){ return util.setComma(util.setUnComma(obj)) }, get24To12(hour){ var _result = 0 var _hour = parseInt(hour, 10) if(_hour === 0){ _result = 12 }else if(_hour > 12){ _result = _hour - 12 }else{ _result = _hour } return _result }, /** * @param2 : 0: 오전 / 1: 오후 */ get12To24(hour, ampm){ let _result = 0 let _hour = parseInt(hour, 10) if(ampm === 0 ){ if(_hour === 12){ _result = 0 }else{ _result = _hour } }else if(ampm === 1){ if(_hour != 12){ _result = _hour + 12 }else{ _result = 12 } }else{ return _hour } return _result }, /** * 연락처 자동 파이프(-)생성 계산식 * @param {*} data */ getPhoneMask(data){ if(!util.isNull(data)){ data = data.replace(/[^0-9]/g, '') let res = '' if(data.length < 3){ res = data } else { if(data.substr(0, 2) =='02'){ if(data.length <= 5){//02-123-5678 res = data.substr(0, 2) + '-' + data.substr(2, 3) } else if(data.length > 5 && data.length <= 9){//02-123-5678 res = data.substr(0, 2) + '-' + data.substr(2, 3) + '-' + data.substr(5) } else {//02-1234-5678 data = data.substr(0, 10) res = data.substr(0, 2) + '-' + data.substr(2, 4) + '-' + data.substr(6) } } else { if(data.length < 8){ res = data } else if(data.length == 8){ res = data.substr(0, 4) + '-' + data.substr(4) } else if(data.length == 9){ res = data.substr(0, 3) + '-' + data.substr(3, 3) + '-' + data.substr(6) } else if(data.length == 10){ res = data.substr(0, 3) + '-' + data.substr(3, 3) + '-' + data.substr(6) } else if(data.length == 11){ //010-1234-5678 res = data.substr(0, 3) + '-' + data.substr(3, 4) + '-' + data.substr(7) }else { data = data.substr(0, 11) res = data.substr(0, 3) + '-' + data.substr(3, 4) + '-' + data.substr(7) } } } return res }else{ return data } }, /** * 사업자 등록번호 파이프(-) 자동입력 : * ex) 123-45-67890 */ getBisinessMask(data){ if(!data) return data data = data.replace(/[^0-9]/g, '') let res = '' if(data.length < 3){ res = data } else { if(data.length <= 5){ res = data.substr(0, 3) + '-' + data.substr(3, 2) }else if(data.length >= 6){ res = data.substr(0, 3) + '-' + data.substr(3, 2) + '-' + data.substr(5) } } return res }, replaceAll(str, searchStr, replaceStr){ if(util.nvl(str, '') === ''){ return '' } return str.split(searchStr).join(replaceStr) }, setDragged(obj){ let el = $(obj.el).parent().parent()[0] if (!$(el).hasClass('laypop_renew')){ el = $(obj.el).parent().parent().parent()[0] } el.style.left =obj.offsetX+'px' el.style.top =obj.offsetY+'px' }, //Blob형태로 다운로드 할때 사용 downLoadBlob(fileName, blob){ let downloadLink = document.createElement('a') downloadLink.download = fileName downloadLink.innerHTML = '' downloadLink.href = window.URL.createObjectURL(blob) downloadLink.onclick = function (event){ document.body.removeChild(event.target) } downloadLink.style.visibility = 'hidden' document.body.appendChild(downloadLink) downloadLink.click() }, async setPageMove(url){ const { $log } = useNuxtApp() if(useRoute().path === url) { $log.debug('현재 경로와 이동하려는 경로가 같다면 새로고침') window.location.reload() }else{ useRouter().push(url) } }, isMatch(data, clone){ return JSON.stringify(data) === JSON.stringify(clone) }, /** * 휴대폰 하이픈 처리 */ getPhoneHyphen(num){ const hyphenNum = num.replace(/[^0-9]/g, "") let strNum = '' if(hyphenNum.length >= 4 && hyphenNum.length<=7){ strNum = hyphenNum.replace(/(\d{3})(\d{1,3})/, '$1-$2') } else if(hyphenNum.length>=8 && hyphenNum.length <= 11){ strNum = hyphenNum.replace(/(\d{3})(\d{4})(\d{1,4})/, '$1-$2-$3') } else{ strNum = hyphenNum.substring(0,11).replace(/(\d{3})(\d{4})(\d{1,4})/, '$1-$2-$3') strNum = strNum.substring(0,13) } return strNum }, /** * 객체배열 오름차순 정렬 */ sortAsc(arr, key){ return arr.sort((a,b)=>{ var x = a[key]; var y = b[key]; return((xy)?1:0)); }) }, /** * 객체배열 내림차순 정렬 */ sortDesc(arr, key){ return arr.sort((a,b)=>{ var x = a[key]; var y = b[key]; return((x>y)?-1:((x>y)?1:0)); }) }, /** * 숫자 포맷 세팅 * @param {*} num 변경할 숫자 * @param {*} decimalPlaces 소수점 개수 * @param {*} nonNumberStr 숫자가 아닐경우 표현할 문자 * @param {*} unit 단위 */ fnFormatNumber(num, decimalPlaces = 2, nonNumberStr = 0, unit = '') { let number = Number(num) if(isNaN(number) || typeof number !== 'number' || num == null){ return nonNumberStr } const hasDecimal = number % 1 !== 0 const fixedNumber = hasDecimal ? parseFloat(number.toFixed(decimalPlaces)) : number const [integerPart, decimalPart] = fixedNumber.toString().split('.') const formattedIntegerPart = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',') if(decimalPart){ return `${formattedIntegerPart}.${decimalPart}${unit}` } else { return `${formattedIntegerPart}${unit}` } }, /** * Excel 다운로드 (클라이언트 데이터 기반) * * @param {*} params Excel 생성 설정값 * @param {*} headers 테이블 Header 데이터. format: [{title: '타이틀', key: '데이터Key'}, ....] * @param {*} tableList 테이블 row 데이터. format: [{ '데이터Key': value, ...}, ....] * @param {*} firstRow 테이블 최상위에 표시할 테이터 (없으면 표시하지 않음) * @param {*} specialStyle firstRow, 특정 셀 스타일을 주고 싶을 때 */ fnExcelMergeDownLoad(params, headers, tableList, firstRow, specialStyle){ const { $dayjs } = useNuxtApp() //excel 파일명 let date = $dayjs(new Date()).format('YYYYMMDD') let excelTitle = params.title || 'download' let excelBody = JSON.parse(JSON.stringify(tableList)) let merge = [] // 행, 열 결합 let isFirstRow = !util.isNull(firstRow) // 헤더 스타일 let headerStyle = { fill: { fgColor: { rgb: '878fa2' } }, font: { bold: true, color: { rgb: 'fafafa' } }, alignment: { vertical: 'center', horizontal: 'center' } } // firstRow가 있으면 table 정보에 첫 Row에 삽입 if(isFirstRow) { excelBody.unshift(firstRow) } //excel 생성 데이터 let excelData = [] excelBody.forEach((item) => { let obj = {} headers.forEach((names, namesIdx) => { let objValue = item[headers[namesIdx].key] obj[names.title] = (util.isNull(objValue)) ? '': objValue.toString().replace('_NONE', '') }) excelData.push(obj) }) // 셀병합 데이터 생성(셀병합의 경우 우선 최상단 헤더에만 존재함) headers.forEach((names, namesIdx) => { // rowspan if(names.hasOwnProperty('rowspan')){ merge.push({ s: { r: 0, c: namesIdx }, e: { r: names.rowspan - 1, c: namesIdx } }) } // colspan if(names.hasOwnProperty('colspan')){ merge.push({ s: { r: 0, c: namesIdx }, e: { r: 0, c: namesIdx + names.colspan - 1 } }) } }) let excelFileName = [date, excelTitle].join('_') + '.xlsx' let workbook = XLSX.utils.book_new() let worksheet = XLSX.utils.json_to_sheet(excelData) // 셀 병합 // merge = [ { s: { r: 1, c: 1 }, e: { r: 2, c: 3 } }; // A1부터 C1까지 병합 ] const styles = { '!merges': merge // 병합된 셀에 스타일 적용 } // 스타일 지정 // 헤더, firstRow 영역 스타일 설정(유니코드 65 => A, 66 => B .... 헤더 cell 스타일 적용) headers.forEach((header, index) => { let val = index / 26 let remain = index % 26 let doubleIndex = val >= 1 ? String.fromCharCode(65 + Math.floor(val) - 1) : '' worksheet[doubleIndex + String.fromCharCode(65 + remain) + '1'].s = headerStyle if(isFirstRow) worksheet[doubleIndex + String.fromCharCode(65 + remain) + '2'].s = specialStyle.hasOwnProperty('totalRowStyle') ? specialStyle.totalRowStyle : headerStyle }) // 특정 영역에 셀 스타일 적용 if(specialStyle.hasOwnProperty('cellStyleObj')){ specialStyle.cellStyleObj.target.forEach((cell) => { worksheet[cell].s = specialStyle.cellStyleObj.cellStyle }) } worksheet['!merges'] = styles['!merges']; // 병합 정보 업데이트 XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1') XLSX.writeFile(workbook, excelFileName) return }, /** * KREMS > 분석통계 => 그래프&테이블 영역의 파라미터 가공(start_dt, end_dt) 공통 함수 * @param {*} periodKey : UI상 일,주,월,년,기간에 대한 파라미터 * @param {*} start_dt : 시작일 * @param {*} end_dt : 종료일 * @returns */ statisticsDateFormat(periodKey, start_dt, end_dt) { let month = 0 let start = '' let end = '' if(periodKey === 'day') { // 일 start = dayjs(start_dt).format('YYYY-MM-DD') end = dayjs(start_dt).format('YYYY-MM-DD') } else if(periodKey === 'week') { // 주 if(dayjs(start_dt).format('dddd') === 'Sunday') { start = dayjs(start_dt).subtract(6, 'day').format('YYYY-MM-DD') end = dayjs(start_dt).format('YYYY-MM-DD') } else { start = dayjs(start_dt).startOf('week').subtract(-1, 'day').format('YYYY-MM-DD') end = dayjs(start_dt).endOf('week').subtract(-1, 'day').format('YYYY-MM-DD') } } else if(periodKey === 'month') { //월 if(util.isNull(start_dt.month)) { // true 일때 start = dayjs(start_dt).startOf('month').format('YYYY-MM-DD') end = dayjs(start_dt).endOf('month').format('YYYY-MM-DD') } else { month = start_dt.month + 1 start = dayjs(start_dt.year+'-'+month).startOf('month').format('YYYY-MM-DD') end = dayjs(start_dt.year+'-'+month).endOf('month').format('YYYY-MM-DD') } } else if(periodKey === 'year') { start = start_dt+'-01-01' end = start_dt+'-12-31' } else if(periodKey === 'period') { start = dayjs(start_dt).format('YYYY-MM-DD') end = dayjs(end_dt).format('YYYY-MM-DD') } return [start, end] }, /** * 엑셀다운로드 공통 * @returns 엑셀파일 * @param {*} params Excel 생성 설정값 * @param {*} headers 테이블 Header 데이터. format: [{heder: '타이틀', dataKey: '데이터Key'}, ....] * @param {*} tableList 테이블 row 데이터. format: [{ '데이터Key': value, ...}, ....] * @param {*} firstRow 테이블 최상위에 표시할 테이터 (없으면 표시하지 않음) */ fnExcelDownLoad(params, headers, tableList, firstRow){ let date = dayjs(new Date()).format('YYYYMMDD') let excelTitle = params.title || 'download'; let excelBody = JSON.parse(JSON.stringify(tableList)); //firstRow가 있으면 table 정보에 첫 Row에 삽입 if(util.isNull(firstRow) === false) { excelBody.unshift(firstRow); } //excel 생성 데이터 let excelData = [] excelBody.forEach((item) => { let obj = {} headers.forEach((names, namesIdx) => { let objValue = item[headers[namesIdx].dataKey] obj[names.header] = (util.isNull(objValue)) ? '': objValue.toString().replace('_NONE', '') }) excelData.push(obj) }); // var excelData = XLSX.utils.table_to_sheet(tableList); // table id를 넣어주면된다 let excelFileName = [date, excelTitle].join('_') + '.xlsx' let workbook = XLSX.utils.book_new() let worksheet = XLSX.utils.json_to_sheet(excelData) // 헤더 스타일 설정 const headerStyle = { alignment: { horizontal: "center", vertical: "center" }, fill: { fgColor: { rgb: '878fa2' } }, font: { bold: true } }; // 자동 너비 계산 const maxLengths = headers.map(header => Math.max(header.header.length, ...excelData.map(row => (row[header.header] ? row[header.header].toString().length : 0))) ) worksheet['!cols'] = maxLengths.map(length => ({ wch: length + 20 })); // 헤더 스타일을 시트에 적용 headers.forEach((header, colIdx) => { const cellAddress = XLSX.utils.encode_cell({ c: colIdx, r: 0 }); if (!worksheet[cellAddress]) { worksheet[cellAddress] = { v: header.header }; } worksheet[cellAddress].s = headerStyle; }); XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1') XLSX.writeFile(workbook, excelFileName) return; }, /*************** * p5g util ***************/ fnExcelFormDown(arrForm, arrGuide, sheetName, fileName){ // 엑셀 파일 생성 const book = utils.book_new() // data get > 실 개발시 api 호출 const userDataByAoa = arrForm const guideByAoa = arrGuide // sheet 생성 - aoa_to_sheet 방식 const worksheetUserData = utils.aoa_to_sheet(userDataByAoa) const worksheetGuide = utils.aoa_to_sheet(guideByAoa) // sheet 생성 - json_to_sheet 방식 //const worksheetByJson = xlsx.utils.json_to_sheet(fruitDataByJson) // 엑셀 파일에 sheet set(엑셀파일, 시트데이터, 시트명) utils.book_append_sheet(book, worksheetUserData, sheetName) utils.book_append_sheet(book, worksheetGuide, "GUIDE") // 엑셀 다운로드 writeFile(book, `${fileName}.xlsx`); }, /** * 필수 입력 필드 체크 * @param {Array} fields 필수 입력 필드값 * @param {Object} obj 체크할 대상 * @returns */ isAllFieldsFilled(fields, obj) { return fields.every(field => !util.isNull(obj[field])) }, /** * 위도, 경도 입력 제한 */ fnIsValidLatlngKey(event){ const key = event.key const value = event.target.value // 입력가능한 특수 키 const allowedKeys = ['Backspace', 'Tab', 'Delete', 'ArrowLeft', 'ArrowRight', '-', '.'] if (allowedKeys.includes(key) || (!isNaN(Number(key)) && key !== ' ')) { // '.' 한번만 입력되도록 제한 if (key === '.' && value.includes('.')) { event.preventDefault() return false } // '-' 첫번째 위치가 아닌 경우 제한 if (key === '-' && value.length > 0) { event.preventDefault(); return false } return true } event.preventDefault() return false }, /** * keydown이벤트 > 위도 입력 체크 * @param {*} event keydown 이벤트 값 */ fnKeydownLatitude(event){ const isValid = util.fnIsValidLatlngKey(event) if (!isValid) return const value = event.target.value const newValue = parseFloat(value + event.key) if (newValue > 90 || newValue < -90) { event.preventDefault() } }, /** * keydown이벤트 > 경도 입력 체크 * @param {*} event keydown 이벤트 값 */ fnKeydownLongitude(event){ const isValid = util.fnIsValidLatlngKey(event, true) if (!isValid) return const value = event.target.value const newValue = parseFloat(value + event.key) if (newValue > 180 || newValue < -180) { event.preventDefault() } }, // 정수 fnReplaceValidateInteger(value){ return value.replace(/[^0-9]/g, '') }, // 위도, 경도 fnReplaceValidateLatlng(value){ return value.replace(/[^0-9.-]/g, '') }, /** * 기간 주기 변경 포맷 * @param {*} periodKey * @returns */ fnChangePeriodKey(periodKey){ const today = dayjs().format('YYYY-MM-DD HH:mm:ss') let startDate = '' let endDate = '' if(periodKey === 'Now'){ startDate = today endDate = today }else if(periodKey === '1H'){ startDate = dayjs().subtract(1, 'hour').format('YYYY-MM-DD HH:mm:ss') endDate = today }else if(periodKey === '6H'){ startDate = dayjs().subtract(6, 'hour').format('YYYY-MM-DD HH:mm:ss') endDate = today }else if(periodKey === '1D'){ startDate = dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss') endDate = today }else if(periodKey === '1W'){ startDate = dayjs().subtract(1, 'week').format('YYYY-MM-DD HH:mm:ss') endDate = today }else{ } return [startDate, endDate] }, /** * 캘린더 종료날짜에 따른 시작날짜와 시작날짜의 최소선택가능날짜 설정 * @param {*} periodKey 기간 * @param {*} startDate 시작날짜 * @param {*} endDate 종료날짜 * @returns */ setUpdateStartDate(periodKey, startDate, endDate){ let dateFormat = 'YYYY-MM-DD HH:mm:ss' let diffDay = endDate.diff(startDate, "day", true) let dDay = Math.floor(diffDay) let newStartDate = startDate let startMinDate = dayjs(endDate).subtract(1, 'months').format(dateFormat) if(dDay < 0) { // 종료날짜가 시작날짜 이전인 경우 시작날짜 재설정 if(periodKey == '1H') { newStartDate = dayjs(endDate).subtract(1, 'hours').format(dateFormat) }else if(periodKey == '6H'){ newStartDate = dayjs(endDate).subtract(6, 'hours').format(dateFormat) }else if(periodKey == '1D'){ newStartDate = dayjs(endDate).subtract(1, 'days').format(dateFormat) }else if(periodKey == '1W'){ newStartDate = dayjs(endDate).subtract(1, 'weeks').format(dateFormat) } } return [newStartDate, startMinDate] }, /** * 페이지 사이즈 목록 리턴 */ fnGetPageSizeList(){ return [10, 20, 50] }, fnNullCheckFormatDate(val){ if(!util.isNull(val)){ return dayjs(val).format('YYYY-MM-DD HH:mm:ss') }else{ return '-' } }, /** * 현재 로그인한 계정의 권한에 맞는 권한목록 설정 * @param {*} myRole 나의 권한 * @param {*} roleList 권한 목록 enum * @returns */ setAccountRoleList(myRole, roleList){ let result = [] if(myRole == 'SUPER') result = roleList else if(myRole == 'ADMIN') result = roleList.slice(1) else if(myRole == 'MANAGER') result = roleList.slice(2) else result = [] return result }, /** * 지역코드 반환 */ getRegionCode(sidoList, regionName){ let regionCode = '' sidoList.forEach((item) => { if(item.title.includes(regionName)) { regionCode = item.value } }) return regionCode } } export default util