add.vue 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016
  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="data--list--wrap">
  12. <div class="table--wrap">
  13. <v-form ref="addForm">
  14. <table>
  15. <colgroup>
  16. <col width="20%" />
  17. <col width="80%" />
  18. </colgroup>
  19. <tbody>
  20. <tr>
  21. <th class="bg le">제품명<span v-if="pageType !== 'D'" class="bul">*</span></th>
  22. <td v-if="pageType == 'D'">
  23. {{ form.formValue1 }}
  24. </td>
  25. <td v-else>
  26. <div class="input--wrap">
  27. <v-text-field
  28. maxlength="50"
  29. v-model="form.formValue1"
  30. :rules="[useValid.required('제품명')]"
  31. class="custom-input mini left"
  32. placeholder="제품명을 입력하세요"
  33. ></v-text-field>
  34. </div>
  35. </td>
  36. </tr>
  37. <tr v-if="itemType == 'G' && pageType !== 'D'">
  38. <th class="bg le">인플루언서<span v-if="pageType !== 'D'" class="bul">*</span></th>
  39. <td>
  40. <div class="input--wrap">
  41. <v-text-field
  42. maxlength="50"
  43. v-model="form.contact_inf_display"
  44. style="width: 30%;"
  45. :rules="[useValid.required('인플루언서')]"
  46. readonly=""
  47. class="custom-input mini left"
  48. placeholder="인플루언서를 선택하세요"
  49. ></v-text-field>
  50. <v-btn class="custom-btn btn-white mini" @click="openInfluencerModal">선택</v-btn>
  51. </div>
  52. </td>
  53. </tr>
  54. <tr v-if="itemType == 'G'">
  55. <th class="bg le">공동구매 기간<span v-if="pageType !== 'D'" class="bul">*</span></th>
  56. <td>
  57. <div class="search--inner">
  58. <div class="calendar--wrap ml--0" v-if="pageType == 'D'">
  59. <span class="text">{{ form.order_start_date?.slice(0, 10) }}&nbsp;~&nbsp;{{ form.order_end_date?.slice(0, 10) }}</span>
  60. </div>
  61. <div class="calendar-wrap ml--0" v-if="pageType !== 'D'">
  62. <div class="calendar">
  63. <VueDatePicker
  64. :format="datePickerFormat"
  65. v-model="form.order_start_date"
  66. placeholder="날짜를 선택하세요"
  67. :auto-apply="true"
  68. week-start="0"
  69. ></VueDatePicker>
  70. </div>
  71. <span class="text">~</span>
  72. <div class="calendar">
  73. <VueDatePicker
  74. v-model="form.order_end_date"
  75. :format="datePickerFormat"
  76. placeholder="날짜를 선택하세요"
  77. :auto-apply="true"
  78. week-start="0"
  79. :min-date="form.order_start_date"
  80. ></VueDatePicker>
  81. </div>
  82. </div>
  83. </div>
  84. </td>
  85. </tr>
  86. <tr>
  87. <th class="bg le">공급가<span v-if="pageType !== 'D'" class="bul">*</span></th>
  88. <td v-if="pageType == 'D'">
  89. {{ Number(form.formValue2).toLocaleString() }}
  90. </td>
  91. <td v-else>
  92. <div class="input--wrap">
  93. <v-text-field
  94. maxlength="50"
  95. v-model="form.formValue2"
  96. style="width: 20%"
  97. :rules="[useValid.required('공급가')]"
  98. class="custom-input mini left"
  99. placeholder="공급가를 입력하세요"
  100. ></v-text-field>
  101. </div>
  102. </td>
  103. </tr>
  104. <tr>
  105. <th class="bg le">판매가<span v-if="pageType !== 'D'" class="bul">*</span></th>
  106. <td v-if="pageType == 'D'">
  107. {{ Number(form.formValue3).toLocaleString() }}
  108. </td>
  109. <td v-else>
  110. <div class="input--wrap">
  111. <v-text-field
  112. maxlength="50"
  113. v-model="form.formValue3"
  114. style="width: 20%"
  115. :rules="[useValid.required('판매가')]"
  116. class="custom-input mini left"
  117. placeholder="판매가를 입력하세요"
  118. ></v-text-field>
  119. </div>
  120. </td>
  121. </tr>
  122. <tr>
  123. <th class="bg le">배송비<span v-if="pageType !== 'D'" class="bul">*</span></th>
  124. <td v-if="pageType == 'D'">
  125. {{ form.formValue4 }}
  126. </td>
  127. <td v-else>
  128. <div class="input--wrap">
  129. <v-textarea
  130. v-model="form.formValue4"
  131. class="custom-textarea"
  132. no-resize=""
  133. placeholder="배송비를 입력해주세요"
  134. :rules="[useValid.required('배송비')]"
  135. ></v-textarea>
  136. </div>
  137. </td>
  138. </tr>
  139. <tr v-if="itemType == 'G'">
  140. <th class="bg le">공동구매 링크</th>
  141. <td v-if="pageType == 'D'">
  142. <a
  143. v-if="form.order_link"
  144. :href="form.order_link"
  145. target="_blank"
  146. rel="noopener noreferrer"
  147. class="order-link"
  148. >
  149. {{ form.order_link }}
  150. <v-icon size="small" class="ml-1">mdi-open-in-new</v-icon>
  151. </a>
  152. <span v-else class="no-link">링크가 없습니다</span>
  153. </td>
  154. <td v-else>
  155. <div class="input--wrap">
  156. <v-text-field
  157. maxlength="50"
  158. v-model="form.order_link"
  159. style="width: 100%"
  160. class="custom-input mini left"
  161. placeholder="공동구매 링크를 입력하세요"
  162. ></v-text-field>
  163. </div>
  164. </td>
  165. </tr>
  166. <tr>
  167. <th class="bg le">썸네일 이미지</th>
  168. <td>
  169. <div class="equip--image--wrap">
  170. <!--이미지가 없을 때-->
  171. <div class="equip--image" v-show="!form.formValue5">
  172. <img src="/assets/img/ic_no_img.svg" />
  173. </div>
  174. <!--이미지 첨부했을 때-->
  175. <div class="equip--image" v-show="form.formValue5">
  176. <div class="images-wrapper">
  177. <img id="preview_image" :src="imgTemp" />
  178. </div>
  179. </div>
  180. <div class="equip--image--select" v-if="pageType !== 'D'">
  181. <div class="form--group">
  182. <label
  183. for="fileUpload_pic"
  184. class="file--btn"
  185. @click="fnPicFileUploadOpen()"
  186. >파일 선택</label
  187. >
  188. <v-file-input
  189. v-model="form.formValue5"
  190. id="fileUpload_pic"
  191. ref="fileupload_pic"
  192. accept=".jpg, .jpeg, .png, .gif"
  193. variant="plain"
  194. hide-details
  195. placeholder="선택된 파일 없음"
  196. prepend-icon=""
  197. class="custom-input"
  198. style="max-width: 400px"
  199. height="33px"
  200. :clearable="false"
  201. @change="fnUploadPicFileCheck()"
  202. >
  203. <!-- <template #append>
  204. <div class="v-input__icon v-input__icon--clear">
  205. <button
  206. @click="clearFile"
  207. type="button"
  208. aria-label="clear icon"
  209. tabindex="-1"
  210. class="v-icon notranslate v-icon--link mdi mdi-close"
  211. ></button>
  212. </div>
  213. </template> -->
  214. </v-file-input>
  215. </div>
  216. <p class="equip--image--desc">
  217. (권장 이미지 : gif, jpg, jpeg, png)
  218. </p>
  219. </div>
  220. </div>
  221. </td>
  222. </tr>
  223. <tr>
  224. <th class="bg le">소타이틀<span v-if="pageType !== 'D'" class="bul">*</span></th>
  225. <td v-if="pageType == 'D'">
  226. {{ form.formValue6 }}
  227. </td>
  228. <td v-else>
  229. <div class="input--wrap">
  230. <v-text-field
  231. v-model="form.formValue6"
  232. class="custom-input mini"
  233. placeholder="소타이틀을 입력해주세요"
  234. :rules="[useValid.required('소타이틀')]"
  235. ></v-text-field>
  236. </div>
  237. </td>
  238. </tr>
  239. <tr>
  240. <th class="bg le">상세 다운로드</th>
  241. <td>
  242. <div class="input--wrap" style="width: 50%">
  243. <v-file-input
  244. v-if="pageType !== 'D'"
  245. v-model="form.formValue7"
  246. label="파일은 압축(zip)해서 첨부해 주세요."
  247. accept=".zip"
  248. variant="outlined"
  249. hide-details=""
  250. density="comfortable"
  251. ></v-file-input>
  252. <div class="down--file" @click="fnDownloadFile()">
  253. <span>{{ zipInfo.original_name }}</span>
  254. </div>
  255. </div>
  256. </td>
  257. </tr>
  258. <tr>
  259. <th class="bg le">상세 내용<span v-if="pageType !== 'D'" class="bul">*</span></th>
  260. <td v-if="pageType == 'D'">
  261. <div v-html="editorContentReq"></div>
  262. </td>
  263. <td v-else>
  264. <SunEditorWrapper
  265. ref="sunEditorWrapper"
  266. :initialContent="editorContentReq"
  267. />
  268. </td>
  269. </tr>
  270. <tr>
  271. <th class="bg le">상태<span v-if="pageType !== 'D'" class="bul">*</span></th>
  272. <td v-if="pageType == 'D'">
  273. {{ form.formValue8 == '0' ? '판매중' : '품절' }}
  274. </td>
  275. <td v-else>
  276. <div class="input--wrap" style="width: 20%">
  277. <v-select
  278. variant="outlined"
  279. class="custom-select"
  280. v-model="form.formValue8"
  281. :items="form.formValue8Arr"
  282. >
  283. </v-select>
  284. </div>
  285. </td>
  286. </tr>
  287. <tr v-if="pageType !== 'D'">
  288. <th class="bg le">노출 상태<span v-if="pageType !== 'D'" class="bul">*</span></th>
  289. <td>
  290. <div class="input--wrap" style="width: 20%">
  291. <v-select
  292. variant="outlined"
  293. style="width: 20%"
  294. class="custom-select"
  295. v-model="form.formValue9"
  296. :items="form.formValue9Arr"
  297. >
  298. </v-select>
  299. </div>
  300. </td>
  301. </tr>
  302. <tr>
  303. <th class="bg le">업데이트 내역</th>
  304. <td v-if="pageType == 'D'">
  305. {{ form.formValue10 }}
  306. </td>
  307. <td v-else>
  308. <div class="input--wrap">
  309. <v-textarea
  310. v-model="form.formValue10"
  311. class="custom-textarea"
  312. no-resize=""
  313. placeholder="업데이트 내역을 입력해주세요"
  314. ></v-textarea>
  315. </div>
  316. </td>
  317. </tr>
  318. </tbody>
  319. </table>
  320. </v-form>
  321. </div>
  322. <div class="view-btm-btn">
  323. <div class="btn-l">
  324. <v-btn class="custom-btn btn-list" @click="listLocated"
  325. ><i class="ico"></i>목록</v-btn
  326. >
  327. <v-btn v-show="pageType == 'U'" class="custom-btn btn-del" @click="fnDelEvt"
  328. ><i class="ico"></i>삭제</v-btn
  329. >
  330. </div>
  331. <div class="btn-r">
  332. <v-btn v-if="pageType !== 'D'" class="custom-btn btn-blue2" @click="fnBtnEvt"
  333. ><i class="ico"></i>저장</v-btn
  334. >
  335. </div>
  336. </div>
  337. </div>
  338. <!-- 인플루언서 선택 모달 -->
  339. <v-dialog v-model="influencerModal" max-width="600px" persistent>
  340. <v-card>
  341. <v-card-title class="text-h6">인플루언서 선택</v-card-title>
  342. <v-card-text>
  343. <div v-if="influencerList.length === 0" class="text-center pa-4">
  344. <p>승인된 인플루언서가 없습니다.</p>
  345. </div>
  346. <div v-else>
  347. <v-list>
  348. <v-list-item
  349. v-for="influencer in influencerList"
  350. :key="influencer.SEQ"
  351. @click="selectInfluencer(influencer)"
  352. class="cursor-pointer"
  353. >
  354. <v-list-item-content>
  355. <v-list-item-title>{{ influencer.INFLUENCER_NICKNAME }} ({{influencer.INFLUENCER_NAME }}) </v-list-item-title>
  356. </v-list-item-content>
  357. </v-list-item>
  358. </v-list>
  359. </div>
  360. </v-card-text>
  361. <v-card-actions>
  362. <v-spacer></v-spacer>
  363. <v-btn color="grey darken-1" text @click="closeInfluencerModal">취소</v-btn>
  364. </v-card-actions>
  365. </v-card>
  366. </v-dialog>
  367. </div>
  368. </template>
  369. <script setup>
  370. import SunEditorWrapper from "@/components/sunEdt.vue";
  371. import useAxios from "@/composables/useAxios";
  372. import VueDatePicker from "@vuepic/vue-datepicker";
  373. import "@vuepic/vue-datepicker/dist/main.css";
  374. import dayjs from 'dayjs';
  375. /************************************************************************
  376. | 레이아웃
  377. ************************************************************************/
  378. definePageMeta({
  379. layout: "default",
  380. });
  381. /************************************************************************
  382. | 스토어
  383. ************************************************************************/
  384. const useDtStore = useDetailStore();
  385. const useAtStore = useAuthStore();
  386. /************************************************************************
  387. | 전역
  388. ************************************************************************/
  389. const { $toast, $log, $dayjs, $eventBus } = useNuxtApp();
  390. const router = useRouter();
  391. const pageId = ref("");
  392. const itemType = useDtStore.boardInfo.itemType;
  393. const datePickerFormat = "yyyy-MM-dd";
  394. const sunEditorWrapper = ref(null); //에디터용 전역
  395. const updatedContent = ref(null); //에디터용 전역
  396. const editorContentReq = ref(); //에디터용 전역
  397. const addForm = ref(null);
  398. const index = ref(null);
  399. const imageIndex = ref(0);
  400. const items = ref([]);
  401. const quillEditor = ref(null);
  402. const imgTemp = ref("");
  403. const zipInfo = ref({
  404. file_path: "",
  405. original_name: ""
  406. })
  407. const rowId = ref();
  408. const form = ref({
  409. formValue1: "",
  410. formValue2: "",
  411. formValue3: "",
  412. formValue4: "",
  413. formValue5: null,
  414. formValue6: "",
  415. formValue7: null,
  416. formValue8: "0",
  417. formValue8Arr: [
  418. { title: "판매중", value: "0" },
  419. { title: "품절", value: "1" },
  420. ],
  421. formValue9: "Y",
  422. formValue9Arr: [
  423. { title: "노출", value: "Y" },
  424. { title: "비노출", value: "N" },
  425. ],
  426. formValue10: "",
  427. contact_inf: "", // 실제 전송될 INFLUENCER_SEQ
  428. contact_inf_display: "", // 화면에 표시될 이름
  429. order_link: "",
  430. order_start_date: "",
  431. order_end_date: "",
  432. });
  433. // 인플루언서 관련 변수
  434. const influencerModal = ref(false);
  435. const influencerList = ref([]);
  436. const apiUrl = ref("");
  437. apiUrl.value = import.meta.env.VITE_APP_API_URL;
  438. const objProc = ref({
  439. validErrorMessage: "",
  440. });
  441. const pageType = ref("");
  442. /************************************************************************
  443. | 함수(METHODS)
  444. ************************************************************************/
  445. const listLocated = () => {
  446. router.push({
  447. path: "/view/common/item",
  448. });
  449. useDtStore.boardInfo.itemType = itemType;
  450. };
  451. const fnPicFileUploadOpen = () => {
  452. let fileUpload = document.getElementById("fileupload_pic");
  453. if (fileUpload != null) {
  454. fileUpload.click();
  455. }
  456. };
  457. const fnUploadPicFileCheck = () => {
  458. if (form.value.formValue5) {
  459. // 10Mb 이상은 업로드 불가
  460. if (form.value.formValue5.size > 10 * 1024 * 1024) {
  461. fnOpenCommPop("10mb 이상은 업로드가 불가합니다.");
  462. form.value.formValue5 = null;
  463. return;
  464. }
  465. // 이미지 파일 형식 체크
  466. let extension = form.value.formValue5.name.split(".").pop().toLowerCase();
  467. if (
  468. extension != "jpg" &&
  469. extension != "jpeg" &&
  470. extension != "png" &&
  471. extension != "gif"
  472. ) {
  473. fnOpenCommPop("파일 형식 또는 확장자가 올바르지 않습니다.");
  474. form.value.formValue5 = null;
  475. return;
  476. }
  477. objProc.validErrorMessage = "";
  478. // 이미지 미리보기
  479. let previewImage = new Image();
  480. let tempImageUrl = window.URL.createObjectURL(form.value.formValue5);
  481. //console.log(tempImageUrl);
  482. previewImage.src = tempImageUrl;
  483. items.value[0] = tempImageUrl;
  484. imgTemp.value = tempImageUrl;
  485. }
  486. };
  487. const fnDownloadFile = () => {
  488. window.location.href = `https://shopdeli.mycafe24.com/item/download/${zipInfo.value.file_path}`;
  489. }
  490. // 인플루언서 목록 조회
  491. const getInfluencerList = async () => {
  492. try {
  493. const params = {
  494. vendorSeq: useAtStore.auth.seq,
  495. status: 'APPROVED', // 승인된 인플루언서만
  496. sortBy: 'latest',
  497. page: 1,
  498. size: 100, // 충분한 수량
  499. };
  500. //console.log('🔍 getInfluencerList 호출됨:', params);
  501. const response = await useAxios().post('/api/vendor-influencer/requests', params);
  502. //console.log('📥 API 응답:', response.data);
  503. if (response.data.success) {
  504. const items = response.data.data.items || [];
  505. //console.log('📋 받아온 인플루언서 목록:', items.length, items);
  506. influencerList.value = items;
  507. } else {
  508. //console.error('❌ API 실패:', response.data.message);
  509. influencerList.value = [];
  510. }
  511. } catch (error) {
  512. //console.error('❌ 인플루언서 목록 조회 실패:', error);
  513. influencerList.value = [];
  514. }
  515. };
  516. // 인플루언서 선택 모달 열기
  517. const openInfluencerModal = async () => {
  518. await getInfluencerList();
  519. influencerModal.value = true;
  520. };
  521. // 인플루언서 선택 모달 닫기
  522. const closeInfluencerModal = () => {
  523. influencerModal.value = false;
  524. };
  525. // 인플루언서 선택
  526. const selectInfluencer = (influencer) => {
  527. // 화면에 표시할 이름
  528. const displayName = influencer.INFLUENCER_NICKNAME
  529. ? `${influencer.INFLUENCER_NICKNAME} (${influencer.INFLUENCER_NAME})`
  530. : influencer.INFLUENCER_NAME;
  531. // 실제 전송할 SEQ 값과 표시용 이름 분리 저장
  532. form.value.contact_inf = influencer.INFLUENCER_SEQ;
  533. form.value.contact_inf_display = displayName;
  534. influencerModal.value = false;
  535. };
  536. // 인플루언서 이름 가져오기
  537. const getInfluencerNameBySeq = async (influencerSeq) => {
  538. try {
  539. if (!influencerSeq) return '';
  540. // 이미 로드된 목록에서 찾기
  541. if (influencerList.value.length > 0) {
  542. const influencer = influencerList.value.find(inf => inf.INFLUENCER_SEQ == influencerSeq);
  543. if (influencer) {
  544. return influencer.INFLUENCER_NICKNAME
  545. ? `${influencer.INFLUENCER_NICKNAME} (${influencer.INFLUENCER_NAME})`
  546. : influencer.INFLUENCER_NAME;
  547. }
  548. }
  549. // 목록에 없으면 API 호출해서 전체 목록 가져오기
  550. await getInfluencerList();
  551. const influencer = influencerList.value.find(inf => inf.INFLUENCER_SEQ == influencerSeq);
  552. if (influencer) {
  553. return influencer.INFLUENCER_NICKNAME
  554. ? `${influencer.INFLUENCER_NICKNAME} (${influencer.INFLUENCER_NAME})`
  555. : influencer.INFLUENCER_NAME;
  556. }
  557. return `인플루언서 ID: ${influencerSeq}`;
  558. } catch (error) {
  559. //console.error('인플루언서 이름 조회 실패:', error);
  560. return `인플루언서 ID: ${influencerSeq}`;
  561. }
  562. };
  563. /*======================================================================
  564. | 작성 시퀀스
  565. | 1. 작성 컨펌
  566. | 2. 버튼 체크
  567. | 3. 등록시 -> 등록 API 호출
  568. ======================================================================*/
  569. const fnBtnEvt = async () => {
  570. await editorContent();
  571. nextTick(() => {
  572. if (addForm.value && typeof addForm.value.validate === "function") {
  573. addForm.value
  574. .validate()
  575. .then((isValid) => {
  576. if (
  577. isValid.valid &&
  578. updatedContent.value != undefined &&
  579. updatedContent.value != null &&
  580. updatedContent.value != "<p><br></p>" &&
  581. form.value.formValue4 != null
  582. ) {
  583. if (pageType.value == "I") fnRegEvt();
  584. else fnUpdEvt();
  585. } else {
  586. let param = {
  587. id: pageId,
  588. title: pageId,
  589. content: "필수항목을 입력해주세요.",
  590. yes: {
  591. text: "확인",
  592. isProc: false,
  593. }
  594. };
  595. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  596. }
  597. })
  598. .catch((err) => {
  599. });
  600. } else {
  601. }
  602. });
  603. };
  604. const fnOpenCommPop = (__TEXT) => {
  605. let param = {
  606. id: pageId,
  607. title: "알림",
  608. content: __TEXT,
  609. yes: {
  610. text: "확인",
  611. isProc: false,
  612. },
  613. };
  614. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  615. };
  616. const fnRegEvt = () => {
  617. let param = {
  618. id: pageId,
  619. title: pageId,
  620. content: "등록하시겠습니까?",
  621. yes: {
  622. text: "등록",
  623. isProc: true,
  624. event: "FN_INSERT",
  625. param: "",
  626. },
  627. no: {
  628. text: "취소",
  629. isProc: false,
  630. },
  631. };
  632. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  633. };
  634. const fnUpdEvt = () => {
  635. let param = {
  636. id: pageId,
  637. title: pageId,
  638. content: "수정하시겠습니까?",
  639. yes: {
  640. text: "확인",
  641. isProc: true,
  642. event: "FN_UPDATE",
  643. param: "",
  644. },
  645. no: {
  646. text: "취소",
  647. isProc: false,
  648. },
  649. };
  650. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  651. };
  652. const fnInsert = async () => {
  653. const formData = new FormData();
  654. formData.append('name', form.value.formValue1);
  655. formData.append('price1', form.value.formValue2);
  656. formData.append('price2', form.value.formValue3);
  657. formData.append('deli_fee', form.value.formValue4);
  658. formData.append('thumb_file', form.value.formValue5);
  659. formData.append('sub_title', form.value.formValue6);
  660. formData.append('detail', updatedContent.value);
  661. formData.append('zip_file', form.value.formValue7);
  662. formData.append('status', form.value.formValue8);
  663. formData.append('show_yn', form.value.formValue9);
  664. formData.append('add_info', form.value.formValue10);
  665. formData.append('order_link', form.value.order_link);
  666. formData.append('order_start_date', dayjs(form.value.order_start_date).format('YYYY-MM-DD'));
  667. formData.append('order_end_date', dayjs(form.value.order_end_date).format('YYYY-MM-DD'));
  668. formData.append('item_type', itemType);
  669. formData.append('contact_inf', form.value.contact_inf);
  670. // 벤더사의 COMPANY_NUMBER 사용
  671. const memberCompanyNumber = useAtStore.auth.companyNumber || "1";
  672. formData.append('company_number', memberCompanyNumber);
  673. useAxios()
  674. .post('/item/reg', formData, {
  675. headers: {'Content-Type': 'multipart/form-data'},
  676. })
  677. .then((res) => {
  678. router.push("/view/common/item");
  679. })
  680. .catch((error) => {
  681. })
  682. .finally(() => {
  683. });
  684. };
  685. const fnUpdate = async () => {
  686. let req = {
  687. seq: useDtStore.boardInfo.seq,
  688. };
  689. const formData = new FormData();
  690. formData.append('name', form.value.formValue1);
  691. formData.append('price1', form.value.formValue2);
  692. formData.append('price2', form.value.formValue3);
  693. formData.append('deli_fee', form.value.formValue4);
  694. if (form.value.formValue5 instanceof File) {
  695. formData.append('thumb_file', form.value.formValue5);
  696. }
  697. formData.append('sub_title', form.value.formValue6);
  698. formData.append('detail', updatedContent.value);
  699. if (form.value.formValue7 instanceof File) {
  700. formData.append('zip_file', form.value.formValue7);
  701. }
  702. formData.append('status', form.value.formValue8);
  703. formData.append('show_yn', form.value.formValue9);
  704. formData.append('add_info', form.value.formValue10);
  705. formData.append('order_link', form.value.order_link);
  706. formData.append('order_start_date', dayjs(form.value.order_start_date).format('YYYY-MM-DD'));
  707. formData.append('order_end_date', dayjs(form.value.order_end_date).format('YYYY-MM-DD'));
  708. formData.append('contact_inf', form.value.contact_inf);
  709. // 벤더사의 COMPANY_NUMBER 사용
  710. const memberCompanyNumber = useAtStore.auth.companyNumber || "1";
  711. formData.append('company_number', memberCompanyNumber);
  712. try {
  713. const res = await useAxios().post(`/item/update/${req.seq}`, formData, {
  714. headers: { 'Content-Type': 'multipart/form-data' },
  715. });
  716. router.push("/view/common/item");
  717. } catch (error) {
  718. }
  719. };
  720. const fnDelEvt = () => {
  721. let param = {
  722. id: pageId,
  723. title: pageId,
  724. content: "삭제하시겠습니까?",
  725. yes: {
  726. text: "확인",
  727. isProc: true,
  728. event: "FN_DELETE",
  729. param: "",
  730. },
  731. no: {
  732. text: "취소",
  733. isProc: false,
  734. },
  735. };
  736. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  737. };
  738. const fnDelete = () => {
  739. let req = {
  740. seq: useDtStore.boardInfo.seq,
  741. };
  742. useAxios()
  743. .post(`/item/delete/${req.seq}`)
  744. .then((res) => {
  745. router.push("/view/common/item");
  746. })
  747. .catch((error) => {
  748. })
  749. .finally(() => {
  750. });
  751. };
  752. const fnDetail = () => {
  753. let req = {
  754. seq: useDtStore.boardInfo.seq,
  755. };
  756. useAxios()
  757. .get(`/item/detail/${req.seq}`)
  758. .then((res) => {
  759. form.value.formValue1 = res.data.NAME;
  760. form.value.formValue2 = res.data.PRICE1;
  761. form.value.formValue3 = res.data.PRICE2;
  762. form.value.formValue4 = res.data.DELI_FEE;
  763. form.value.formValue5 = res.data.THUMB_FILE;
  764. form.value.formValue6 = res.data.SUB_TITLE;
  765. zipInfo.value.file_path = res.data.ZIP_FILE;
  766. zipInfo.value.original_name = res.data.ZIP_FILE_ORIGIN;
  767. //에디터에 컨텐츠 전달
  768. editorContentReq.value = res.data.DETAIL;
  769. form.value.formValue8 = res.data.STATUS;
  770. form.value.formValue9 = res.data.SHOW_YN;
  771. form.value.formValue10 = res.data.ADD_INFO;
  772. form.value.order_link = res.data.ORDER_LINK;
  773. form.value.order_start_date = res.data.ORDER_START_DATE;
  774. form.value.order_end_date = res.data.ORDER_END_DATE;
  775. form.value.contact_inf = res.data.CONTACT_INF;
  776. // contact_inf가 SEQ 값이면 해당 인플루언서 이름을 표시용으로 설정
  777. if (res.data.CONTACT_INF) {
  778. getInfluencerNameBySeq(res.data.CONTACT_INF).then(name => {
  779. form.value.contact_inf_display = name;
  780. });
  781. }
  782. //썸네일 파일이 있으면 넣어줌
  783. if(form.value.formValue5){
  784. imgTemp.value = `https://shopdeli.mycafe24.com/writable/uploads/item/thumb/${form.value.formValue5}`;
  785. }
  786. })
  787. .catch((error) => {
  788. })
  789. .finally(() => {
  790. });
  791. };
  792. /*=======================================================================
  793. | 최종 에디터 이미지 url치환 : S
  794. /*=======================================================================*/
  795. const editorContent = async () => {
  796. const content = sunEditorWrapper.value.getEditorContent();
  797. updatedContent.value = await processEditorContent(content);
  798. console.log("Updated content:", updatedContent.value);
  799. };
  800. // Base64 데이터를 Blob으로 변환
  801. const base64ToBlob = (base64, mimeType) => {
  802. const byteString = atob(base64.split(",")[1]);
  803. const arrayBuffer = new ArrayBuffer(byteString.length);
  804. const uint8Array = new Uint8Array(arrayBuffer);
  805. for (let i = 0; i < byteString.length; i++) {
  806. uint8Array[i] = byteString.charCodeAt(i);
  807. }
  808. return new Blob([uint8Array], { type: mimeType });
  809. };
  810. // Base64 데이터를 File 객체로 변환
  811. const base64ToFile = (base64, mimeType, fileName) => {
  812. const blob = base64ToBlob(base64, mimeType);
  813. return new File([blob], fileName, { type: mimeType });
  814. };
  815. // 이미지 업로드 처리 (useAxios)
  816. const uploadImage = async (file) => {
  817. const formDataEdt = new FormData();
  818. formDataEdt.append("picObj", file);
  819. return useAxios()
  820. .post("/pic/upload", formDataEdt, {
  821. headers: { "Content-Type": "multipart/form-data" },
  822. })
  823. .then((res) => {
  824. const filePath = res.data.ogn_name.path.replace(/.*\/files\//, "");
  825. const fileName = res.data.ogn_name.file_name;
  826. return `${apiUrl.value}/images/${filePath}/${fileName}`; // 최종 URL 반환
  827. })
  828. .catch((error) => {
  829. console.error("Image upload failed:", error);
  830. return null;
  831. });
  832. };
  833. // 에디터 내용 처리 및 이미지 업로드
  834. const processEditorContent = async (content) => {
  835. const parser = new DOMParser();
  836. const doc = parser.parseFromString(content, "text/html");
  837. const images = doc.querySelectorAll("img");
  838. for (let i = 0; i < images.length; i++) {
  839. const img = images[i];
  840. const src = img.src;
  841. if (src.startsWith("data:image")) {
  842. // MIME 타입과 파일 이름 추출
  843. const mimeType = src.split(";")[0].split(":")[1];
  844. const extension = mimeType.split("/")[1];
  845. const fileName = `image-${i + 1}.${extension}`;
  846. // Base64 데이터를 File 객체로 변환
  847. const file = base64ToFile(src, mimeType, fileName);
  848. // 이미지 업로드 및 URL 반환
  849. const finalUrl = await uploadImage(file);
  850. if (finalUrl) {
  851. img.src = finalUrl; // 이미지 src 업데이트
  852. }
  853. }
  854. }
  855. return doc.body.innerHTML; // 최종 수정된 HTML 반환
  856. };
  857. /*=======================================================================
  858. | 최종 에디터 이미지 url치환 : E
  859. /*=======================================================================*/
  860. /************************************************************************
  861. | 팝업 이벤트버스 정의
  862. ************************************************************************/
  863. $eventBus.off("FN_INSERT");
  864. $eventBus.on("FN_INSERT", () => {
  865. fnInsert();
  866. });
  867. $eventBus.off("FN_UPDATE");
  868. $eventBus.on("FN_UPDATE", () => {
  869. fnUpdate();
  870. });
  871. $eventBus.off("FN_DELETE");
  872. $eventBus.on("FN_DELETE", () => {
  873. fnDelete();
  874. });
  875. /************************************************************************
  876. | 라이프사이클
  877. ************************************************************************/
  878. onMounted(() => {
  879. pageType.value = useDtStore.boardInfo.pageType;
  880. if(pageType.value == "I"){
  881. if(itemType == "G"){
  882. pageId.value = "공동구매 제품 등록"
  883. } else {
  884. pageId.value = "제품 등록"
  885. }
  886. } else if(pageType.value == "U"){
  887. if(itemType == "G"){
  888. pageId.value = "공동구매 제품 수정"
  889. } else {
  890. pageId.value = "제품 수정"
  891. }
  892. } else {
  893. if(itemType == "G"){
  894. pageId.value = "공동구매 제품 상세"
  895. } else {
  896. pageId.value = "제품 상세"
  897. }
  898. }
  899. //상세 등록 아니 리스트 클릭시 상세 정보로 접근
  900. if (pageType.value !== "I") {
  901. fnDetail();
  902. }
  903. });
  904. /************************************************************************
  905. | WATCH
  906. ************************************************************************/
  907. // 시작일이 변경될 때, 종료일이 시작일보다 이전이면 종료일을 시작일과 같게 설정
  908. watch(() => form.value.order_start_date, (newStartDate) => {
  909. if (newStartDate && form.value.order_end_date && form.value.order_end_date < newStartDate) {
  910. form.value.order_end_date = newStartDate;
  911. }
  912. });
  913. // 종료일이 변경될 때, 종료일이 시작일보다 이전이면 시작일과 같게 설정
  914. watch(() => form.value.order_end_date, (newEndDate) => {
  915. if (newEndDate && form.value.order_start_date && newEndDate < form.value.order_start_date) {
  916. form.value.order_end_date = form.value.order_start_date;
  917. }
  918. });
  919. </script>
  920. <style scoped>
  921. .cursor-pointer {
  922. cursor: pointer;
  923. }
  924. .cursor-pointer:hover {
  925. background-color: #f5f5f5;
  926. }
  927. .order-link {
  928. color: #1976d2;
  929. text-decoration: none;
  930. display: inline-flex;
  931. align-items: center;
  932. transition: color 0.2s;
  933. }
  934. .order-link:hover {
  935. color: #1565c0;
  936. text-decoration: underline;
  937. }
  938. .no-link {
  939. color: #999;
  940. font-style: italic;
  941. }
  942. </style>