mediaAdd.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. <template>
  2. <div>
  3. <div class="inner--headers">
  4. <h2>{{ pageId }}</h2>
  5. <div class="bread--crumbs--wrap">
  6. <span>홈</span>
  7. <span>미디어 관리</span>
  8. <span>{{ pageId }}</span>
  9. </div>
  10. </div>
  11. <div class="view-wrap mt--45">
  12. <div class="view-box">
  13. <div class="view-box-top">
  14. <h3 v-if="pageType == 'I'">MEDIA LIBRARY 등록</h3>
  15. <h3 v-else>MEDIA LIBRARY 수정</h3>
  16. </div>
  17. <div class="view-box-btm">
  18. <div class="form-style1">
  19. <v-form ref="addForm">
  20. <table>
  21. <colgroup>
  22. <col style="width: 12.5rem" />
  23. <col />
  24. </colgroup>
  25. <tbody>
  26. <tr>
  27. <th>언어<span class="bul">*</span></th>
  28. <td>
  29. <v-radio-group
  30. v-model="form.formValue0"
  31. inline
  32. hide-details
  33. class="radio--group"
  34. >
  35. <v-radio label="Korean" value="KR"></v-radio>
  36. <v-radio label="English" value="EN"></v-radio>
  37. <v-radio label="Chinese" value="CN"></v-radio>
  38. <v-radio label="Japanese" value="JP"></v-radio>
  39. </v-radio-group>
  40. </td>
  41. </tr>
  42. <tr>
  43. <th>제목<span class="bul">*</span></th>
  44. <td>
  45. <v-text-field
  46. v-model="form.formValue1"
  47. class="custom-input mini"
  48. placeholder="제목을 입력해주세요."
  49. :rules="[useValid.required('제목')]"
  50. ></v-text-field>
  51. </td>
  52. </tr>
  53. <tr>
  54. <th>해시태그<span class="bul">*</span></th>
  55. <td>
  56. <v-text-field
  57. v-model="form.formValue5"
  58. class="custom-input mini"
  59. placeholder="해시태그를 입력해주세요. 예) 태그, 태그 형태로 여러개 입력시 ,를 이용하여 입력해주세요."
  60. :rules="[useValid.required('해시태그')]"
  61. ></v-text-field>
  62. </td>
  63. </tr>
  64. <tr>
  65. <th>노출여부<span class="bul">*</span></th>
  66. <td>
  67. <v-radio-group
  68. class="radio--group"
  69. v-model="form.formValue2"
  70. inline
  71. hide-details
  72. >
  73. <v-radio label="노출" value="Y"></v-radio>
  74. <v-radio label="비노출" value="N"></v-radio>
  75. </v-radio-group>
  76. </td>
  77. </tr>
  78. <tr>
  79. <th>날짜</th>
  80. <td>
  81. <v-text-field
  82. v-model="form.formValue7"
  83. style="max-width: 400px"
  84. class="custom-input mini"
  85. placeholder="날짜를 입력해주세요. 예) 2024-12-25"
  86. ></v-text-field>
  87. </td>
  88. </tr>
  89. <tr>
  90. <th>사진<span class="bul">*</span></th>
  91. <td class="media--editor">
  92. <span class="caution"
  93. >* 사진 첨부만 가능합니다. 입력하신 텍스트는 저장되지
  94. 않습니다.</span
  95. >
  96. <SunEditorWrapper
  97. ref="sunEditorWrapper"
  98. :initialContent="editorContentReq"
  99. />
  100. </td>
  101. </tr>
  102. </tbody>
  103. </table>
  104. </v-form>
  105. </div>
  106. </div>
  107. </div>
  108. <div class="view-btm-btn">
  109. <div class="btn-l">
  110. <v-btn class="custom-btn btn-list" @click="listLocated"
  111. ><i class="ico"></i>목록</v-btn
  112. >
  113. <v-btn v-if="pageType == 'U'" class="custom-btn btn-del" @click="fnDelEvt"
  114. ><i class="ico"></i>삭제</v-btn
  115. >
  116. </div>
  117. <div class="btn-r">
  118. <v-btn v-if="pageType == 'I'" class="custom-btn btn-blue2" @click="fnRegCheck"
  119. ><i class="ico"></i>저장</v-btn
  120. >
  121. <v-btn v-else class="custom-btn btn-blue2" @click="fnRegCheck"
  122. ><i class="ico"></i>수정</v-btn
  123. >
  124. </div>
  125. </div>
  126. </div>
  127. </div>
  128. </template>
  129. <script setup>
  130. import SunEditorWrapper from "@/components/sunEdt.vue";
  131. import useAxios from "@/composables/useAxios";
  132. /************************************************************************
  133. | 레이아웃
  134. ************************************************************************/
  135. definePageMeta({
  136. layout: "default",
  137. });
  138. /************************************************************************
  139. | 스토어
  140. ************************************************************************/
  141. const useDtStore = useDetailStore();
  142. /************************************************************************
  143. | 전역
  144. ************************************************************************/
  145. const { $toast, $log, $dayjs, $eventBus } = useNuxtApp();
  146. const router = useRouter();
  147. const pageId = ref("MEDIA LIBRARY");
  148. const sunEditorWrapper = ref(null); //에디터용 전역
  149. const updatedContent = ref(null); //에디터용 전역
  150. const editorContentReq = ref(""); //에디터용 전역
  151. const addForm = ref(null);
  152. const index = ref(null);
  153. const imageIndex = ref(0);
  154. const items = ref([]);
  155. const quillEditor = ref(null);
  156. const customToolbar = ref([["image"]]); // 에디터에서 이미지 첨부만 가능하게끔
  157. const imgTemp = ref(null);
  158. const rowId = ref();
  159. const form = ref({
  160. formValue0: "KR",
  161. formValue1: "",
  162. formValue2: "Y",
  163. // formValue3: "",
  164. formValue4: "",
  165. formValue5: "",
  166. // formValue6: [],
  167. // fileResponse: null,
  168. formValue7: "",
  169. });
  170. const uploadFiles = ref([
  171. {
  172. file_name: "",
  173. ogn_name: "",
  174. },
  175. ]);
  176. const pageType = ref("");
  177. const apiUrl = ref("");
  178. apiUrl.value = import.meta.env.VITE_APP_API_URL;
  179. /************************************************************************
  180. | 함수(METHODS)
  181. ************************************************************************/
  182. const listLocated = () => {
  183. router.push({
  184. path: "/view/media/mediaList",
  185. });
  186. };
  187. /*======================================================================
  188. | 작성 시퀀스
  189. | 1. 작성 컨펌
  190. | 2. 버튼 체크
  191. | 3. 등록시 -> 등록 API 호출
  192. ======================================================================*/
  193. const fnRegCheck = () => {
  194. nextTick(() => {
  195. if (addForm.value && typeof addForm.value.validate === "function") {
  196. addForm.value
  197. .validate()
  198. .then((isValid) => {
  199. if (isValid.valid) {
  200. if (pageType.value == "I") {
  201. fnRegEvt();
  202. } else {
  203. fnUpdEvt();
  204. }
  205. } else {
  206. let param = {
  207. id: pageId,
  208. title: "미디어 라이브러리",
  209. content: "필수항목을 입력해주세요.",
  210. yes: {
  211. text: "확인",
  212. isProc: false,
  213. },
  214. no: {
  215. text: "취소",
  216. isProc: false,
  217. },
  218. };
  219. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  220. }
  221. })
  222. .catch((err) => {
  223. console.error("벨리데이션 에러", err);
  224. });
  225. } else {
  226. console.error("항목 누락체크[fnRegCheck]]");
  227. }
  228. });
  229. };
  230. const fnRegEvt = () => {
  231. // 공고날짜 체크
  232. const datePattern = /^\d{4}\-\d{2}\-\d{2}$/;
  233. if (form.value.formValue7 && !datePattern.test(form.value.formValue7)) {
  234. let param = {
  235. id: pageId,
  236. title: "공고사항",
  237. content: "날짜를 올바른 형식(YYYY-MM-DD)으로 입력해주세요",
  238. yes: {
  239. text: "확인",
  240. isProc: false,
  241. },
  242. no: {
  243. text: "취소",
  244. isProc: false,
  245. },
  246. };
  247. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  248. return false;
  249. } else {
  250. let param = {
  251. id: pageId,
  252. title: "미디어 라이브러리",
  253. content: "등록하시겠습니까?",
  254. yes: {
  255. text: "등록",
  256. isProc: true,
  257. event: "FN_INSERT",
  258. param: "",
  259. },
  260. no: {
  261. text: "취소",
  262. isProc: false,
  263. },
  264. };
  265. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  266. }
  267. };
  268. const fnInsert = async () => {
  269. //BASE64에서 실제 파일서버에 파일 전성후 내용 컨텐츠 REAL주소로 변경
  270. await editorContent();
  271. let frm = new FormData();
  272. let wterGet = localStorage.getItem("tempAccess");
  273. let params = JSON.stringify({
  274. wter: wterGet,
  275. brd_cd: "BR01",
  276. brd_lang: form.value.formValue0,
  277. title: form.value.formValue1,
  278. show_yn: form.value.formValue2,
  279. content: updatedContent.value
  280. .replace(/<\/?div[^>]*>/g, "")
  281. .replace(/<p>.*?<\/p>/gs, "")
  282. .trim(),
  283. hash_tag: form.value.formValue5,
  284. cmd_date: form.value.formValue7,
  285. });
  286. const hasImgTag = /<img\s+[^>]*>/i.test(updatedContent.value);
  287. if (!hasImgTag) {
  288. let param = {
  289. id: pageId,
  290. title: "미디어 라이브러리",
  291. content: "사진 첨부는 필수입니다.",
  292. yes: {
  293. text: "확인",
  294. isProc: false,
  295. },
  296. no: {
  297. text: "취소",
  298. isProc: false,
  299. },
  300. };
  301. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  302. return false;
  303. }
  304. frm.append("params", params);
  305. useAxios()
  306. .post("/brd/ins", frm, { headers: { "Content-Type": "multipart/form-data" } })
  307. .then((res) => {
  308. router.push("/view/media/mediaList");
  309. })
  310. .catch((error) => {
  311. //$log.debug("[equipMgmtReg][fnGetTenantList][error]");
  312. //useErrorHandler().fnSetCommErrorHandle(error, fnGetTenantList);
  313. })
  314. .finally(() => {
  315. //$log.debug("[equipMgmtReg][fnGetTenantList][finished]");
  316. //objSlt.value.tenantNameList = _cloneDeep(temp);
  317. });
  318. };
  319. const fnUpdEvt = () => {
  320. // 공고날짜 체크
  321. const datePattern = /^\d{4}\-\d{2}\-\d{2}$/;
  322. if (form.value.formValue7 && !datePattern.test(form.value.formValue7)) {
  323. let param = {
  324. id: pageId,
  325. title: "공고사항",
  326. content: "날짜를 올바른 형식(YYYY-MM-DD)으로 입력해주세요",
  327. yes: {
  328. text: "확인",
  329. isProc: false,
  330. },
  331. no: {
  332. text: "취소",
  333. isProc: false,
  334. },
  335. };
  336. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  337. return false;
  338. } else {
  339. let param = {
  340. id: pageId,
  341. title: "미디어 라이브러리",
  342. content: "수정하시겠습니까?",
  343. yes: {
  344. text: "확인",
  345. isProc: true,
  346. event: "FN_UPDATE",
  347. param: "",
  348. },
  349. no: {
  350. text: "취소",
  351. isProc: false,
  352. },
  353. };
  354. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  355. }
  356. };
  357. const fnUpdate = async () => {
  358. //BASE64에서 실제 파일서버에 파일 전성후 내용 컨텐츠 REAL주소로 변경
  359. await editorContent();
  360. let frm = new FormData();
  361. let wterGet = localStorage.getItem("tempAccess");
  362. let params = JSON.stringify({
  363. seq: useDtStore.boardInfo.seq,
  364. wter: wterGet,
  365. brd_cd: "BR01",
  366. brd_lang: form.value.formValue0,
  367. title: form.value.formValue1,
  368. show_yn: form.value.formValue2,
  369. content: updatedContent.value
  370. .replace(/<\/?div[^>]*>/g, "")
  371. .replace(/<p>.*?<\/p>/gs, "")
  372. .trim(),
  373. hash_tag: form.value.formValue5,
  374. cmd_date: form.value.formValue7,
  375. });
  376. const hasImgTag = /<img\s+[^>]*>/i.test(updatedContent.value);
  377. if (!hasImgTag) {
  378. let param = {
  379. id: pageId,
  380. title: "미디어 라이브러리",
  381. content: "사진 첨부는 필수입니다.",
  382. yes: {
  383. text: "확인",
  384. isProc: false,
  385. },
  386. no: {
  387. text: "취소",
  388. isProc: false,
  389. },
  390. };
  391. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  392. return false;
  393. }
  394. frm.append("params", params);
  395. useAxios()
  396. .post("/brd/upd", frm, { headers: { "Content-Type": "multipart/form-data" } })
  397. .then((res) => {
  398. router.push("/view/media/mediaList");
  399. })
  400. .catch((error) => {
  401. $log.debug("[equipMgmtReg][fnGetTenantList][error]");
  402. //useErrorHandler().fnSetCommErrorHandle(error, fnGetTenantList);
  403. })
  404. .finally(() => {
  405. //$log.debug("[equipMgmtReg][fnGetTenantList][finished]");
  406. //objSlt.value.tenantNameList = _cloneDeep(temp);
  407. });
  408. };
  409. const fnDelEvt = () => {
  410. let param = {
  411. id: pageId,
  412. title: "미디어 라이브러리",
  413. content: "삭제하시겠습니까?",
  414. yes: {
  415. text: "확인",
  416. isProc: true,
  417. event: "FN_DELETE",
  418. param: "",
  419. },
  420. no: {
  421. text: "취소",
  422. isProc: false,
  423. },
  424. };
  425. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  426. };
  427. const fnDelete = () => {
  428. let wterGet = localStorage.getItem("tempAccess");
  429. let req = {
  430. brd_cd: "BR01",
  431. seq: useDtStore.boardInfo.seq,
  432. wter: wterGet,
  433. };
  434. useAxios()
  435. .post("/brd/del", req)
  436. .then((res) => {
  437. router.push("/view/media/mediaList");
  438. })
  439. .catch((error) => {
  440. //$log.debug("[equipMgmtReg][fnGetTenantList][error]");
  441. //useErrorHandler().fnSetCommErrorHandle(error, fnGetTenantList);
  442. })
  443. .finally(() => {
  444. //$log.debug("[equipMgmtReg][fnGetTenantList][finished]");
  445. //objSlt.value.tenantNameList = _cloneDeep(temp);
  446. });
  447. };
  448. const fnDetail = async () => {
  449. //BASE64에서 실제 파일서버에 파일 전성후 내용 컨텐츠 REAL주소로 변경
  450. await editorContent();
  451. let req = {
  452. seq: useDtStore.boardInfo.seq,
  453. };
  454. useAxios()
  455. .post("/brd/detail", req)
  456. .then((res) => {
  457. //console.log(res);
  458. form.value.formValue0 = res.data.brd_lang;
  459. form.value.formValue1 = res.data.title;
  460. form.value.formValue2 = res.data.show_yn;
  461. form.value.formValue4 = res.data.content
  462. .replace(/<\/?div[^>]*>/g, "")
  463. .replace(/<p>.*?<\/p>/gs, "")
  464. .trim();
  465. form.value.formValue5 = res.data.hash_tag;
  466. //if(result.files) this.uploadFiles = result.files
  467. //에디터에 컨텐츠 전달
  468. editorContentReq.value = res.data.content;
  469. uploadFiles.value[0].file_name = res.data.file_title;
  470. uploadFiles.value[0].ogn_name = res.data.ogn_f_title;
  471. form.value.formValue7 = res.data.cmd_date;
  472. })
  473. .catch((error) => {
  474. //$log.debug("[equipMgmtReg][fnGetTenantList][error]");
  475. //useErrorHandler().fnSetCommErrorHandle(error, fnGetTenantList);
  476. })
  477. .finally(() => {
  478. //$log.debug("[equipMgmtReg][fnGetTenantList][finished]");
  479. //objSlt.value.tenantNameList = _cloneDeep(temp);
  480. });
  481. };
  482. /*=======================================================================
  483. | 최종 에디터 이미지 url치환 : S
  484. /*=======================================================================*/
  485. const editorContent = async () => {
  486. const content = sunEditorWrapper.value.getEditorContent();
  487. updatedContent.value = await processEditorContent(content);
  488. console.log("Updated content:", updatedContent.value);
  489. };
  490. // Base64 데이터를 Blob으로 변환
  491. const base64ToBlob = (base64, mimeType) => {
  492. const byteString = atob(base64.split(",")[1]);
  493. const arrayBuffer = new ArrayBuffer(byteString.length);
  494. const uint8Array = new Uint8Array(arrayBuffer);
  495. for (let i = 0; i < byteString.length; i++) {
  496. uint8Array[i] = byteString.charCodeAt(i);
  497. }
  498. return new Blob([uint8Array], { type: mimeType });
  499. };
  500. // Base64 데이터를 File 객체로 변환
  501. const base64ToFile = (base64, mimeType, fileName) => {
  502. const blob = base64ToBlob(base64, mimeType);
  503. return new File([blob], fileName, { type: mimeType });
  504. };
  505. // 이미지 업로드 처리 (useAxios)
  506. const uploadImage = async (file) => {
  507. const formDataEdt = new FormData();
  508. formDataEdt.append("picObj", file);
  509. return useAxios()
  510. .post("/pic/upload", formDataEdt, {
  511. headers: { "Content-Type": "multipart/form-data" },
  512. })
  513. .then((res) => {
  514. const filePath = res.data.ogn_name.path.replace(/.*\/files\//, "");
  515. const fileName = res.data.ogn_name.file_name;
  516. return `${apiUrl.value}/images/${filePath}/${fileName}`; // 최종 URL 반환
  517. })
  518. .catch((error) => {
  519. console.error("Image upload failed:", error);
  520. return null;
  521. });
  522. };
  523. // 에디터 내용 처리 및 이미지 업로드
  524. const processEditorContent = async (content) => {
  525. const parser = new DOMParser();
  526. const doc = parser.parseFromString(content, "text/html");
  527. const images = doc.querySelectorAll("img");
  528. for (let i = 0; i < images.length; i++) {
  529. const img = images[i];
  530. const src = img.src;
  531. if (src.startsWith("data:image")) {
  532. // MIME 타입과 파일 이름 추출
  533. const mimeType = src.split(";")[0].split(":")[1];
  534. const extension = mimeType.split("/")[1];
  535. const fileName = `image-${i + 1}.${extension}`;
  536. // Base64 데이터를 File 객체로 변환
  537. const file = base64ToFile(src, mimeType, fileName);
  538. // 이미지 업로드 및 URL 반환
  539. const finalUrl = await uploadImage(file);
  540. if (finalUrl) {
  541. img.src = finalUrl; // 이미지 src 업데이트
  542. }
  543. }
  544. }
  545. return doc.body.innerHTML; // 최종 수정된 HTML 반환
  546. };
  547. /*=======================================================================
  548. | 최종 에디터 이미지 url치환 : E
  549. /*=======================================================================*/
  550. /************************************************************************
  551. | 팝업 이벤트버스 정의
  552. ************************************************************************/
  553. $eventBus.off("FN_INSERT");
  554. $eventBus.on("FN_INSERT", () => {
  555. fnInsert();
  556. });
  557. $eventBus.off("FN_DELETE");
  558. $eventBus.on("FN_DELETE", () => {
  559. fnDelete();
  560. });
  561. $eventBus.off("FN_UPDATE");
  562. $eventBus.on("FN_UPDATE", () => {
  563. fnUpdate();
  564. });
  565. $eventBus.off("FN_CONFIRM");
  566. $eventBus.on("FN_CONFIRM");
  567. /************************************************************************
  568. | 라이프사이클
  569. ************************************************************************/
  570. onMounted(() => {
  571. pageType.value = useDtStore.boardInfo.pageType;
  572. //상세 등록 아니 리스트 클릭시 상세 정보로 접근
  573. if (pageType.value == "U") {
  574. fnDetail();
  575. }
  576. });
  577. /************************************************************************
  578. | WATCH
  579. ************************************************************************/
  580. </script>