excelUpload.vue 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863
  1. <template>
  2. <v-dialog v-model="props.isExcelUploadModal" persistent width="32.8125rem">
  3. <div class="v-common-dialog-wrapper custom-dialog">
  4. <div class="modal-tit">
  5. <strong>엑셀업로드</strong>
  6. <button class="btn-close" @click="$emit('closeModal')"></button>
  7. </div>
  8. <div class="v-common-dialog-content">
  9. <div class="excel-step">
  10. <div class="excel-step-box">
  11. <div class="excel-step-top">
  12. <span class="step" style="width:3.9375rem">Step1</span>
  13. <strong>양식 다운로드</strong>
  14. </div>
  15. <div class="excel-step-btm">
  16. <div class="excel--inner--content">
  17. <div class="ico"></div>
  18. <div class="desc">
  19. <p>일괄 등록 양식을 다운로드 받아 양식에 맞게 데이터를 입력 하고 하단의 첨부파일 등록을 통해 일괄 등록 신청을 진행하세요.</p>
  20. <v-btn class="custom-btn mini btn-gray" @click="fnExcelFormDownload">엑셀 다운로드</v-btn>
  21. </div>
  22. </div>
  23. </div>
  24. </div>
  25. <div class="excel-step-box">
  26. <div class="excel-step-top">
  27. <span class="step">Step2</span>
  28. <strong>엑셀 일괄 업로드</strong>
  29. </div>
  30. <div class="excel-step-btm">
  31. <div class="step-bg-box type2">
  32. <p>양식에 맞게 작성을 하였다면 해당 파일을 업로드하세요.</p>
  33. <p class="txt2">(파일 등록 시 STEP 03에서 결과 확인 가능)</p>
  34. <div class="add-file">
  35. <v-file-input
  36. hide-details
  37. @change="fnFileChange"
  38. @click:clear="fnClearFile"
  39. >
  40. <template v-slot:label>
  41. <v-btn class="custom-btn mini btn-gray" style="max-width:7.0625rem!important; height:2.25rem;">엑셀 일괄 등록</v-btn>
  42. </template>
  43. </v-file-input>
  44. </div>
  45. </div>
  46. </div>
  47. </div>
  48. <div class="excel-step-box">
  49. <div class="excel-step-top">
  50. <span class="step">Step3</span>
  51. <strong>일괄 등록 결과 확인</strong>
  52. </div>
  53. <div class="excel-step-btm">
  54. <div class="tbl-wrapper">
  55. <div class="tbl-wrap">
  56. <!-- ag grid -->
  57. <ag-grid-vue
  58. style="width:100%; height:calc(5 * 3.15rem);"
  59. class="ag-theme-quartz"
  60. :gridOptions="gridOptions2"
  61. :defaultColDef="defaultColDef"
  62. :columnDefs="tblHeaders2"
  63. :rowData="tblItems2"
  64. :suppressPaginationPanel="true"
  65. :overlayNoRowsTemplate="getExcelResultMsg"
  66. @grid-ready="fnOnGridReady"
  67. >
  68. </ag-grid-vue>
  69. </div>
  70. </div>
  71. </div>
  72. </div>
  73. </div>
  74. </div>
  75. <div class="btn-wrap">
  76. <v-btn
  77. class="custom-btn btn-white mini"
  78. @click="$emit('closeModal')"
  79. >
  80. <i class="ico"></i>
  81. 취소
  82. </v-btn>
  83. <v-btn class="custom-btn btn-blue mini" :disabled="isUploadStep3 === false" @click="fnUploadProc">
  84. <i class="ico"></i>
  85. 확인
  86. </v-btn>
  87. </div>
  88. </div>
  89. </v-dialog>
  90. </template>
  91. <script setup>
  92. /***********************
  93. * import
  94. ************************/
  95. import { read, utils, writeFile } from "xlsx";
  96. import { useI18n } from "vue-i18n"
  97. import useUtil from "@/composables/useUtil"
  98. import useValid from "@/composables/useValid"
  99. import "ag-grid-community/styles/ag-grid.css"
  100. import "ag-grid-community/styles/ag-theme-quartz.css"
  101. import { AgGridVue } from "ag-grid-vue3"
  102. import customTextDiv from '@/components/design/customTextDiv'
  103. /***********************
  104. * plugins inject
  105. ************************/
  106. const { $toast, $log, $dayjs, $eventBus } = useNuxtApp()
  107. // props
  108. const props = defineProps({
  109. isExcelUploadModal: Boolean,
  110. menu: String,
  111. imsiPrefixList: Array
  112. });
  113. // 참조가능 데이터 설정
  114. defineExpose({
  115. fnInit,
  116. })
  117. // 발신 이벤트 선언
  118. const emit = defineEmits(["closeModal", "uploadProc"])
  119. const i18n = useI18n()
  120. /***********************
  121. * data & created
  122. ************************/
  123. const tblHeaders2 = ref([
  124. { headerName: 'No', field: 'no'},
  125. { headerName: '엑셀 시트', field: 'headerName'},
  126. { headerName: '엑셀 행', field: 'cellNum'},
  127. { headerName: '실패 원인', field: 'errorMsg', cellRenderer : customTextDiv},
  128. ])
  129. const tblItems2 = ref([])
  130. const excelFileNames = ref([])
  131. const invalidData = ref([]) // 등록실패 데이터
  132. const isUploadStep2 = ref(false) // 파일 등록 여부
  133. const isUploadStep3 = ref(false) // 파일 데이터 유효성 통과 여부
  134. const uploadParams = ref(null)
  135. const remToPx = () => parseFloat(getComputedStyle(document.documentElement).fontSize);
  136. const rowHeightRem = 2.65; // 원하는 rem 값
  137. const rowHeightPx = rowHeightRem * remToPx();
  138. // gridOption2
  139. const gridOptions2 = {
  140. columnDefs: tblHeaders2.value,
  141. rowData: tblItems2.value, // 테이블 데이터
  142. rowSelection : 'multiple',
  143. autoSizeStrategy: {
  144. type: "fitGridWidth", // width맞춤
  145. },
  146. headerHeight : rowHeightPx,
  147. rowHeight: rowHeightPx,
  148. pagination: true,
  149. // overlayNoRowsTemplate: '<span class="no--rows--custom"><i class="ico-excel"></i>※ 엑셀 파일을 일괄 등록하세요.</span>',
  150. suppressPaginationPanel: true, // 하단 default 페이징 컨트롤 숨김
  151. suppressRowClickSelection: true, // 행 클릭 체크박스 무시
  152. localeText: {
  153. noRowsToShow: i18n.t('common.noData')
  154. },
  155. }
  156. const excelResultMsg = ref('<span class="no--rows--custom"><i class="ico-excel"></i>※ 엑셀 파일을 일괄 등록하세요.</span>')
  157. // grid default 옵션
  158. const defaultColDef = ref({
  159. lockVisible: true, // 열을 그리드 밖으로 꺼내지 않음
  160. })
  161. const gridApi = shallowRef()
  162. const isExcel = ref(false)
  163. const getExcelResultMsg = computed(() => {
  164. if(!isExcel.value) {
  165. return '<span class="no--rows--custom"><i class="ico-excel"></i>※ 엑셀 파일을 일괄 등록하세요.</span>'
  166. }else{
  167. return '<span class="no--rows--custom"><i class="ico-excel"></i>※ 등록한 파일의 데이터가 정상입니다.</span>'
  168. }
  169. })
  170. /***********************
  171. * Methods
  172. ************************/
  173. function fnInit() {
  174. tblItems2.value = []
  175. isUploadStep2.value = false
  176. isUploadStep3.value = false
  177. uploadParams.value = null
  178. invalidData.value = []
  179. }
  180. /**
  181. * 엑셀 양식 다운로드
  182. */
  183. function fnExcelFormDownload(){
  184. let arrForm = ''
  185. let arrGuide = ''
  186. let sheetTitle = ''
  187. let fileName = ''
  188. if(props.menu === 'userMgmt') {
  189. arrForm = fnSetUserInsertForm()
  190. arrGuide = fnSetUserGuideForm()
  191. sheetTitle = 'USERS'
  192. fileName = '가입자일괄등록양식'
  193. }else{
  194. arrForm = fnSetNeInsertForm()
  195. arrGuide = fnSetNeGuideForm()
  196. sheetTitle = 'NE'
  197. fileName = 'NE일괄등록양식'
  198. }
  199. useUtil.fnExcelFormDown(arrForm, arrGuide, sheetTitle, fileName)
  200. }
  201. // 가입자등록 양식 데이터
  202. function fnSetUserInsertForm() {
  203. let arr = []
  204. arr.push([
  205. "tenantName",
  206. "imsi",
  207. "msisdns",
  208. "skey",
  209. "opc",
  210. "uplink",
  211. "downlink",
  212. "singleNssais",
  213. "defaultNssais",
  214. "dnnConf_1_nssai",
  215. "dnnConf_1_dnn",
  216. "dnnConf_2_nssai",
  217. "dnnConf_2_dnn",
  218. "dnnConf_3_nssai",
  219. "dnnConf_3_dnn",
  220. "dnnConf_4_nssai",
  221. "dnnConf_4_dnn",
  222. "dnnConf_5_nssai",
  223. "dnnConf_5_dnn",
  224. "dnnConf_6_nssai",
  225. "dnnConf_6_dnn",
  226. "dnnConf_7_nssai",
  227. "dnnConf_7_dnn",
  228. "dnnConf_8_nssai",
  229. "dnnConf_8_dnn",
  230. "dnnConf_9_nssai",
  231. "dnnConf_9_dnn",
  232. "dnnConf_10_nssai",
  233. "dnnConf_10_dnn",
  234. ])
  235. arr.push(["KT", "45043000", "", "", "", ""])
  236. return arr
  237. }
  238. // 가입자등록 양식 유효성 데이터
  239. function fnSetUserGuideForm() {
  240. let arr = []
  241. arr.push(["COLUMN NAME", "입력 설명"])
  242. arr.push(["tenantName", "테넌트 이름"])
  243. arr.push(["imsi","IMSI, 앞 자리 값은 테넌트에 할당된 번호여야 하고 테넌트 이름과 함께 유효성 체크를 한다.",])
  244. arr.push(["msisdns","pattern: '(msisdn-[0-9]{5,15}|extid-.+@.+|.+)' 콤마(,)로 구분하여 여러개 입력이 가능하다. -- ex) 1000,2000,3000",])
  245. arr.push(["skey", ""])
  246. arr.push(["opc", ""])
  247. arr.push(["uplink","subscribed-UeAmbr Uplink, pattern: '^\\d+(\\.\\d+)? (bps|Kbps|Mbps|Gbps|Tbps)$' -- ex) 1024 Mbps",])
  248. arr.push(["downlink","subscribed-UeAmbr Downlink, pattern: '^\\d+(\\.\\d+)? (bps|Kbps|Mbps|Gbps|Tbps)$' -- ex) 1024 Mbps",])
  249. arr.push(["singleNssais","List of non default Single Nssais. 콤마(,)로 구분하여 여러개 입력이 가능하다. -- ex) 1000,2000,3000",])
  250. arr.push(["defaultNssais","A list of Single Nssais used as default. 콤마(,)로 구분하여 여러개 입력이 가능하다. -- ex) 1000,2000,3000",])
  251. arr.push(["dnnConf/{index}/nssai","dnnConf는 최대 5개의 nssai 와 dnn 쌍으로 입력 가능하다.",])
  252. arr.push(["dnnConf/{index}/dnn","반드시 1쌍 이상으로 index를 유의해서 입력해야한다. 쌍이 성립되지 않으면 데이터는 처리하지 않는다.",])
  253. return arr
  254. }
  255. // NE등록 양식 데이터
  256. function fnSetNeInsertForm(){
  257. let arr = []
  258. arr.push([
  259. 'NE Name',
  260. 'Tenant Name',
  261. 'NE Group Name',
  262. 'NE Type',
  263. 'NE ID',
  264. 'Connected UPF',
  265. 'EMS Name',
  266. 'NE Address',
  267. 'NE Location (Latitude)',
  268. 'NE Location (Longitude)',
  269. 'Description'
  270. ])
  271. arr.push(["nename01", "", "", "", "", "", "", "", "", "", ""])
  272. return arr
  273. }
  274. // NE등록 양식 유효성 데이터
  275. function fnSetNeGuideForm(){
  276. let arr = []
  277. arr.push(["COLUMN NAME", "입력 설명"])
  278. arr.push(["NE Name", "NE 이름"])
  279. arr.push(["Tenant Name","테넌트 이름",])
  280. arr.push(["NE Group Name","NE 그룹 이름",])
  281. arr.push(["NE Type", "NE 유형"])
  282. arr.push(["NE ID", ""])
  283. arr.push(["Connected UPF","연동 UPF",])
  284. arr.push(["EMS Name","수집 시스템",])
  285. arr.push(["NE Address","NE 주소",])
  286. arr.push(["NE Location (Latitude)","NE 주소 위도",])
  287. arr.push(["NE Location (Longitude)","NE 주소 경도",])
  288. arr.push(["Description","설명",])
  289. return arr
  290. }
  291. /**
  292. * 파일 업로드 이벤트
  293. */
  294. function fnFileChange(event) {
  295. let isFiles = event.target.files.length > 0
  296. if(isFiles) {
  297. isUploadStep2.value = true // 파일 업로드한 상태
  298. isUploadStep3.value = false
  299. let excelFileName = event.target.files[0].name;
  300. excelFileNames.value = excelFileName
  301. if (excelFileName.includes("xlsx") === true ||excelFileName.includes("xls") === true) {
  302. const file = event.target.files[0]
  303. let reader = new FileReader()
  304. reader.onload = () => {
  305. let data = reader.result
  306. let workbook
  307. try {
  308. workbook = read(data, { type: "binary" })
  309. } catch (error) {
  310. alert("파일을 읽을 수 없습니다.")
  311. return;
  312. }
  313. let sheetName = workbook.SheetNames[0]
  314. let worksheet = workbook.Sheets[sheetName]
  315. const rowObject = utils.sheet_to_json(worksheet)
  316. // 엑셀 데이터를 JSON으로 변환하면서 셀 위치 정보를 추가
  317. const jsonData = utils.sheet_to_json(worksheet, { header: 1 })
  318. console.log('%c rowObject########' ,'color:#bada55', rowObject)
  319. console.log('%c jsonData' ,'color:#bada55', jsonData)
  320. // 빈배열제거
  321. const newData = jsonData.filter(el => el.length)
  322. const headers = newData[0]
  323. let rowData = newData.slice(1).map((row, rowIndex) => {
  324. const obj = {}
  325. headers.forEach((header, colIndex) => {
  326. const cell = row[colIndex] !== undefined ? row[colIndex] : '' // 값을 설정하고 빈 값은 빈 문자열로
  327. obj[header] = {
  328. value: cell,
  329. cell: utils.encode_cell({ r: rowIndex + 1, c: colIndex }),
  330. }
  331. })
  332. return obj
  333. })
  334. let invalidDataList = []
  335. if(props.menu === 'userMgmt') {
  336. // 사용자 일괄 등록
  337. invalidDataList = userValidateData(headers, rowData)
  338. if(invalidDataList.length === 0){
  339. // 유효성 모두 통과
  340. let param = fnUserMakeParam(rowData)
  341. uploadParams.value = _cloneDeep(param)
  342. } else {
  343. uploadParams.value = null
  344. }
  345. }else{
  346. // NE 일괄 등록
  347. invalidDataList = neValidateData(headers, rowData)//neValidateData(rowData)
  348. if(invalidDataList.length === 0){
  349. let param = fnNeMakeParam(rowData)
  350. uploadParams.value = _cloneDeep(param)
  351. } else {
  352. uploadParams.value = null
  353. }
  354. }
  355. // 유효성 체크 리스트
  356. invalidData.value = _cloneDeep(invalidDataList)
  357. if(invalidDataList.length === 0) {
  358. isExcel.value = true
  359. // gridApi.value.api.setRowData(rowData.value);
  360. setTimeout(() => {
  361. gridApi.value.showNoRowsOverlay();
  362. }, 1);
  363. }else{
  364. isExcel.value = false
  365. }
  366. // 양식에러 테이블 데이터 목록
  367. tblItems2.value = _cloneDeep(invalidData.value)
  368. isUploadStep3.value = invalidData.value.length === 0
  369. }
  370. reader.readAsBinaryString(file)
  371. } else {
  372. alert("파일 확장자를 확인하세요(.xlsx, .xls)")
  373. }
  374. }
  375. else{
  376. isUploadStep2.value = false
  377. isUploadStep3.value = false
  378. fnClearFile()
  379. }
  380. }
  381. /**
  382. * 사용자 일괄등록 유효성 검사
  383. */
  384. function userValidateData(headers, data) {
  385. let invalidData = [];
  386. // 유효성 체크 항목들
  387. let regexMap = {
  388. tenantName: fnBusinessNameChk, // 테넌트명 체크
  389. uplink: fnLinkValidChk, // 업로드 속도값 체크
  390. downlink: fnLinkValidChk, // 다운로드 속도값 체크
  391. imsi: fnImsiChk, // IMSI체크
  392. msisdns: fnMsisdnsChk, // MSISDN 체크
  393. // defaultNssais: fnDefaultNssaisChk, // Default NSSAI 체크
  394. };
  395. const dnnConf = [
  396. 'dnnConf_1_nssai', 'dnnConf_1_dnn',
  397. 'dnnConf_2_nssai', 'dnnConf_2_dnn',
  398. 'dnnConf_3_nssai', 'dnnConf_3_dnn',
  399. 'dnnConf_4_nssai', 'dnnConf_4_dnn',
  400. 'dnnConf_5_nssai', 'dnnConf_5_dnn',
  401. 'dnnConf_6_nssai', 'dnnConf_6_dnn',
  402. 'dnnConf_7_nssai', 'dnnConf_7_dnn',
  403. 'dnnConf_8_nssai', 'dnnConf_8_dnn',
  404. 'dnnConf_9_nssai', 'dnnConf_9_dnn',
  405. 'dnnConf_10_nssai', 'dnnConf_10_dnn',
  406. ]
  407. // dnnConf키 수집 수
  408. let dnnConfKeyCnt = 0
  409. // 필수 키값 목록
  410. const requiredFields = ['tenantName', 'imsi', 'msisdns', 'skey', 'opc', 'uplink', 'downlink', 'singleNssais', 'defaultNssais']
  411. // IMSI 중복체크 데이터
  412. const imsiList = data.map(row => {
  413. if(row.hasOwnProperty('imsi')) {
  414. return row.imsi.value.toString()
  415. }else return []
  416. })
  417. // flatMap으로 평탄화 작업
  418. let num = 0
  419. invalidData = data.flatMap((item, index) => {
  420. // row 한바퀴 수행 후 초기화
  421. dnnConfKeyCnt = 0
  422. validDnnConf.value = []
  423. dnnConfigCnt.value = 0
  424. return Object.entries(item).flatMap(([key, { value, cell }]) => {
  425. // 필수 값 체크 dnnConf키가 아니고 필수키가 없는 경우
  426. if (!dnnConf.includes(key) && !requiredFields.includes(key)) {
  427. return { no: ++num, ROW_NUM: index + 2, headerName: key, value, cellNum: cell, errorMsg: '형식에러' }
  428. }
  429. if (requiredFields.includes(key) && useUtil.isNull(value)) {
  430. // 헤더 키는 존재하나 데이터가 없는 경우
  431. return { no: ++num, ROW_NUM: index + 2, headerName: key, value, cellNum: cell, errorMsg: 'noData' }
  432. }
  433. if(dnnConf.includes(key)) {
  434. dnnConfKeyCnt++
  435. }
  436. if(dnnConfKeyCnt == 19) {
  437. const result = fnDnnValidation(item)
  438. if(result !==true){
  439. return { no: ++num, ROW_NUM: index + 2, headerName: key, value, cellNum: cell, errorMsg: result }
  440. }
  441. }
  442. // IMSI 중복 체크
  443. if (key === 'imsi') {
  444. const result = fnImsiDupChk(item, key, imsiList);
  445. if (result !== true) {
  446. return { no: ++num, ROW_NUM: index + 2, headerName: key, value, cellNum: cell, errorMsg: result }
  447. }
  448. }
  449. const regex = regexMap[key];
  450. if (regex) {
  451. const result = regex(item, key);
  452. if (result !== true) {
  453. // 정규식 통과 실패
  454. return { no: ++num, ROW_NUM: index + 2, headerName: key, value, cellNum: cell, errorMsg: result }
  455. }
  456. }
  457. return []
  458. })
  459. })
  460. return invalidData
  461. }
  462. /**
  463. * 사용자일괄등록 param목록
  464. */
  465. function fnUserMakeParam(rowData){
  466. let params = []
  467. rowData.forEach((row, idx) => {
  468. let msisdnsArray = row.msisdns.value.toString().replace(/ /g, "").split(",") // 공백제거 후, split
  469. let singleNssaisArray = []
  470. if (row.singleNssais.value != undefined)
  471. singleNssaisArray = row.singleNssais.value.toString().replace(/ /g, "").split(",")
  472. let defaultNssaisArray = row.defaultNssais.value.toString().replace(/ /g, "").split(",")
  473. let item = {
  474. accountId: useAuthStore().getAccountId,
  475. tenantName: row.tenantName.value,
  476. imsi: row.imsi.value.toString(),
  477. msisdns: msisdnsArray, //item.msisdns,
  478. skey: row.skey.value.toString(),
  479. opc: row.opc.value.toString(),
  480. singleNssais: singleNssaisArray, //item.singleNssais,
  481. defaultNssais: defaultNssaisArray, //item.defaultNssais,
  482. uplink: row.uplink.value,
  483. downlink: row.downlink.value,
  484. dnnConf: row.dnnConf
  485. }
  486. params.push(item)
  487. })
  488. return params
  489. }
  490. /**
  491. * 사업자명 체크
  492. */
  493. function fnBusinessNameChk(row, key){
  494. const userPriority = useAuthStore().getAccountRole
  495. const userBusinessInfo = useAuthStore().getAuth.tenantName
  496. if (userPriority == "ADMIN") {
  497. // ADMIN 권한인 경우 등록가능, 테넌트 명이 존재하고 나의 테넌트명과 다르면 등록안된다.
  498. if (userBusinessInfo.length > 0 && userBusinessInfo != row[key].value) {
  499. return 'invalid_tenantName'
  500. }else{
  501. return true
  502. }
  503. // return true
  504. }else{
  505. return 'No permission'
  506. }
  507. }
  508. /**
  509. * 업다운링크 체크
  510. */
  511. function fnLinkValidChk(row, key){
  512. let linkRegex = /^\d+(\.\d+)? (bps|Kbps|Mbps|Gbps|Tbps)$/
  513. return linkRegex.test(row[key].value) ? true : 'Invalid link'
  514. }
  515. // 현재 업로드한 엑셀파일 데이터에서 imsi값 중복체크
  516. function fnImsiDupChk(row, key, imsiList ){
  517. const imsiValue = row[key].value.toString()
  518. return imsiList.indexOf(imsiValue) !== imsiList.lastIndexOf(imsiValue) ? 'Duplicate IMSI' : true
  519. }
  520. /**
  521. * imsi체크
  522. * key = 'imsi'
  523. */
  524. function fnImsiChk(row, key){
  525. let imsiPrefix = row[key].value.toString().substr(0, 8) // 엑셀에 입력한 imsi값 앞에서 8자리 가져온다
  526. let notMatchFlag = false
  527. props.imsiPrefixList.some((element) => {
  528. if (element.key == row.tenantName.value) {
  529. // imsiPrefix의 테넌트명과 엑셀에 입력된 테넌트명이 같은 경우
  530. if (imsiPrefix == element.value) {
  531. // 입력한 고정imsi값이 imsiPrefix값과 같은 경우
  532. notMatchFlag = false
  533. }
  534. return true
  535. }
  536. })
  537. return notMatchFlag ? 'Invalid_imsi' : true
  538. }
  539. function fnMsisdnsChk(row, key){
  540. const regex = /(msisdn-\d{5,15}|extid-.+@.+|.+)/
  541. const items = row[key].value.split(',').map(item => item.trim())
  542. for (const item of items) {
  543. if (!regex.test(item)) {
  544. return `Invalid entry found: "${item}"`
  545. }
  546. }
  547. return true
  548. }
  549. /**
  550. * dnnConfig 유효성 검사
  551. */
  552. const validDnnConf = ref([])
  553. const dnnConfigCnt = ref(0)
  554. function fnDnnValidation(row){
  555. const pairs = [
  556. { key1: 'dnnConf_1_nssai', key2: 'dnnConf_1_dnn' },
  557. { key1: 'dnnConf_2_nssai', key2: 'dnnConf_2_dnn' },
  558. { key1: 'dnnConf_3_nssai', key2: 'dnnConf_3_dnn' },
  559. { key1: 'dnnConf_4_nssai', key2: 'dnnConf_4_dnn' },
  560. { key1: 'dnnConf_5_nssai', key2: 'dnnConf_5_dnn' },
  561. { key1: 'dnnConf_6_nssai', key2: 'dnnConf_6_dnn' },
  562. { key1: 'dnnConf_7_nssai', key2: 'dnnConf_7_dnn' },
  563. { key1: 'dnnConf_8_nssai', key2: 'dnnConf_8_dnn' },
  564. { key1: 'dnnConf_9_nssai', key2: 'dnnConf_9_dnn' },
  565. { key1: 'dnnConf_10_nssai', key2: 'dnnConf_10_dnn' },
  566. ]
  567. pairs.forEach((item, idx) => {
  568. if (!useUtil.isNull(row[item.key1].value) && !useUtil.isNull(row[item.key2].value)) {
  569. validDnnConf.value[dnnConfigCnt.value++] = {
  570. nssai: row[item.key1].value.toString(),
  571. dnn: row[item.key2].value.toString(),
  572. }
  573. }
  574. })
  575. if (dnnConfigCnt.value < 1) {
  576. return 'one NSSAI-DNN pair required'
  577. }else{
  578. row.dnnConf=_cloneDeep(validDnnConf.value)
  579. }
  580. return true
  581. }
  582. /**
  583. * ne일괄등록 param목록
  584. */
  585. function fnNeMakeParam(rowData){
  586. let params = []
  587. rowData.forEach((row, idx) => {
  588. let item = {
  589. accountId: useAuthStore().getAccountId,
  590. accountName: useAuthStore().getAccountName,
  591. tenantName: row['Tenant Name'].value,
  592. neGroup: row['NE Group Name'].value,
  593. neName: row['NE Name'].value,
  594. neId: row['NE ID'].value,
  595. neType: row['NE Type'].value,
  596. upfNum: row['Connected UPF'].value,
  597. emsName: row['EMS Name'].value,
  598. neAddress: row['NE Address'].value,
  599. neLocLatitude: row['NE Location (Latitude)'].value,
  600. neLocLongitude: row['NE Location (Longitude)'].value,
  601. description: row['Description'].value
  602. }
  603. params.push(item)
  604. })
  605. return params
  606. }
  607. /**
  608. * ne일괄등록 유효성 검사
  609. */
  610. function neValidateData(headers, rowData){
  611. let invalidData = []
  612. const requiredFields = ['NE Name', 'Tenant Name', 'NE Group Name', 'NE Type', 'NE ID', 'Connected UPF', 'EMS Name', 'NE Address', 'NE Location (Latitude)', 'NE Location (Longitude)', 'Description']
  613. // flatMap으로 평탄화 작업
  614. let num = 0
  615. invalidData = rowData.flatMap((item, index) => {
  616. return Object.entries(item).flatMap(([key, { value, cell }]) => {
  617. // 필수 값 체크
  618. if(!requiredFields.includes(key)){
  619. // 양식에 맞지 않는 키값
  620. return { no: ++num, ROW_NUM: index + 2, headerName: key, value, cellNum: cell, errorMsg: '형식에러' };
  621. }
  622. if (requiredFields.includes(key) && useUtil.isNull(value)) {
  623. // 키는 존재하나 데이터가 없는 경우
  624. return { no: ++num, ROW_NUM: index + 2, headerName: key, value, cellNum: cell, errorMsg: '형식에러' };
  625. }
  626. return [];
  627. })
  628. })
  629. return invalidData
  630. }
  631. /**
  632. * 업로드 요청하기 - 각 부모컴포넌트로 이벤트 전달
  633. */
  634. function fnUploadProc(){
  635. emit('uploadProc', uploadParams.value)
  636. }
  637. function fnClearFile(e){
  638. //
  639. fnInit()
  640. isExcel.value = false
  641. setTimeout(() => {
  642. gridApi.value.showNoRowsOverlay();
  643. }, 1);
  644. }
  645. // Grid 데이터 바인딩
  646. function fnOnGridReady(params){
  647. gridApi.value = params.api
  648. }
  649. function fnMakeNeJson(){
  650. let jsonData = {
  651. '@DU': {
  652. 'NE ID': [],
  653. 'NE Type': [],
  654. 'NE Version': [],
  655. 'Release Version': [],
  656. 'Network': [],
  657. 'NE Name': [],
  658. 'Administrative State': [],
  659. 'gNB DU Name': [],
  660. 'gNB DU Name': [],
  661. 'Endpoint CU IP address': [],
  662. 'Local Time Offset': [],
  663. 'NE Serial Number': [],
  664. 'FW Auto Fusing': [],
  665. },
  666. '@GNB_INFORMATION': {
  667. 'NE ID': [],
  668. 'gNB Num': [],
  669. 'gNB ID': [],
  670. 'gNB ID Length': [],
  671. 'Operator Name': [],
  672. 'Access Mode': [],
  673. },
  674. '@GNB_IP_INFORMATION': {
  675. 'NE ID': [],
  676. 'gNB Num': [],
  677. 'IP Index': [],
  678. 'Local IPv4 Address': [],
  679. 'Local IPv6 Address': [],
  680. 'Secondary Local IPv4 Address': [],
  681. 'Secondary Local IPv6 Address': [],
  682. },
  683. '@SERVER_INFORMATION': {
  684. 'NE ID': [],
  685. 'CFM': [],
  686. 'PSM': [],
  687. 'Local IPv4 Address': [],
  688. 'Local IPv6 Address': [],
  689. 'Secondary Local IPv4 Address': [],
  690. 'Secondary Local IPv6 Address': [],
  691. },
  692. '@PLMN_INFORMATION': {
  693. 'NE ID': [],
  694. 'gNB Num': [],
  695. 'PLMN Index': [],
  696. 'MCC': [],
  697. 'MNC': [],
  698. },
  699. '@MAIN_BOARD_INFORMATION': {
  700. 'NE ID': [],
  701. 'Unit Type': [],
  702. 'Unit ID': [],
  703. 'Board Type': [],
  704. },
  705. '@CLOCK_INFORMATION': {
  706. 'NE ID': [],
  707. 'Clock Source ID': [],
  708. 'Clock Source': [],
  709. 'Priority Level': [],
  710. 'Quality Level': [],
  711. },
  712. '@PTP_INFORMATION': {
  713. 'NE ID': [],
  714. 'IP Version': [],
  715. 'First Master IP': [],
  716. 'Second Master IP': [],
  717. 'Clock Profile': [],
  718. 'PTP Domain': [],
  719. 'PTP Hybrid Mode': [],
  720. },
  721. '@DEBUG_PORT_INFORMATION': {
  722. 'NE ID': [],
  723. 'Port Switch': [],
  724. },
  725. '@PORT_INFORMATION': {
  726. 'NE ID': [],
  727. 'Port ID': [],
  728. 'VR ID': [],
  729. 'Port Administrative State': [],
  730. 'Connect Type': [],
  731. 'UDE Type': [],
  732. 'MTU': [],
  733. 'Speed Duplex': [],
  734. 'Fec Mode': [],
  735. },
  736. '@VIRTUAL_ROUTING_INFORMATION': {
  737. 'NE ID': [],
  738. 'VR ID': [],
  739. },
  740. '@IP_INFORMATION': {
  741. 'NE ID': [],
  742. 'CPU ID': [],
  743. 'External Interface Name': [],
  744. 'IP Address': [],
  745. 'IP Get Type': [],
  746. 'Management': [],
  747. 'Control': [],
  748. 'Bearer': [],
  749. 'IEEE1588': [],
  750. },
  751. '@VLAN_INFORMATION': {
  752. 'NE ID': [],
  753. 'CPU ID': [],
  754. 'VLAN Interface Name': [],
  755. 'VLAN ID': [],
  756. 'VR ID': [],
  757. },
  758. '@LAG_INFORMATION': {
  759. 'NE ID': [],
  760. 'CPU ID': [],
  761. 'LAG ID': [],
  762. 'VR ID': [],
  763. 'LAG Interface Name': [],
  764. },
  765. '@ROUTE_INFORMATION': {
  766. 'NE ID': [],
  767. 'CPU ID': [],
  768. 'VR ID': [],
  769. 'IP Prefix': [],
  770. 'IP Prefix Length': [],
  771. 'IP Gateway': [],
  772. 'Route Interface Name': [],
  773. },
  774. '@SYSTEM_LOCATION_INFORMATION': {
  775. 'NE ID': [],
  776. 'User Defined Mode': [],
  777. 'Latitude': [],
  778. 'Longitude': [],
  779. 'Height': [],
  780. },
  781. '@RU_INFORMATION': {
  782. 'NE ID': [],
  783. 'Start Frequency': [],
  784. 'Second Start Frequency': [],
  785. 'Uncertainty Semi Major': [],
  786. 'Uncertainty Semi Minor': [],
  787. 'Orientation Of Majjor Axis': [],
  788. 'Uncertainty Altitude': [],
  789. 'Confidence': []
  790. },
  791. '@IPSEC_INFORMATION': {
  792. 'NE ID': [],
  793. 'CPU ID': [],
  794. 'VR ID': [],
  795. 'Interface Name1': [],
  796. 'Peer IP Version': [],
  797. 'First Peer IP': [],
  798. 'Second Peer IP': [],
  799. 'Inner IP Version': [],
  800. 'Tunnel Mode': [],
  801. 'Interface Name2': [],
  802. 'Interface Name3': [],
  803. 'Interface Name4': [],
  804. 'Interface Name5': [],
  805. 'Interface Name6': [],
  806. 'Crypto Algorithm': [],
  807. 'Hash Algorithm': [],
  808. 'Local ID Type': [],
  809. 'Local ID': [],
  810. },
  811. '@PKI_INFORMATION': {
  812. 'NE ID': [],
  813. 'CPU ID': [],
  814. 'IP Address': [],
  815. 'FQDN': [],
  816. 'Port': [],
  817. 'Path': [],
  818. 'DN': [],
  819. 'DN Domain': [],
  820. 'CA DN': [],
  821. 'Hash Algorithm': [],
  822. },
  823. }
  824. return JSON.stringify(jsonData)
  825. }
  826. </script>