detail.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. <template>
  2. <div>
  3. <div class="inner--headers">
  4. <h2>{{ pageId }}</h2>
  5. <div class="bread--crumbs--wrap">
  6. <span>홈</span>
  7. <span>{{ pageId }}</span>
  8. </div>
  9. </div>
  10. <div class="data--list--wrap">
  11. <div class="btn--actions--wrap">
  12. <div class="left--sections">
  13. <v-btn class="custom-btn btn-pink bdrs--10"
  14. ><i class="ico"></i>개별 배송</v-btn
  15. >
  16. <v-btn class="custom-btn bdrs--10 btn-white" @click="deliLocated()"
  17. ><i class="ico"></i>공동구매 배송</v-btn
  18. >
  19. </div>
  20. <div class="right--sections">
  21. </div>
  22. </div>
  23. <div class="item--section">
  24. <div v-if="imgTemp" class="item--thumb">
  25. <img :src="imgTemp" alt="">
  26. </div>
  27. <div v-else class="item--thumb min--240">
  28. NO IMAGE
  29. </div>
  30. <div class="item--info">
  31. <h2>{{ form.formValue1 }}</h2>
  32. <p>공급가: {{ form.formValue2 }}원</p>
  33. <p>판매가: {{ form.formValue3 }}원</p>
  34. </div>
  35. </div>
  36. <div class="btn--actions--wrap">
  37. <div class="left--sections">
  38. </div>
  39. <div class="right--sections">
  40. <input
  41. ref="excelFileInput"
  42. type="file"
  43. accept=".xlsx,.xls"
  44. @change="handleExcelUpload"
  45. style="display: none"
  46. />
  47. <v-btn class="custom-btn btn-excel" @click="$refs.excelFileInput.click()"
  48. ><i class="ico"></i>엑셀 업로드</v-btn
  49. >
  50. <v-btn class="custom-btn btn-excel"
  51. ><i class="ico"></i>엑셀 다운로드</v-btn
  52. >
  53. </div>
  54. </div>
  55. <div class="tbl-wrapper">
  56. <div class="tbl-wrap">
  57. <!-- ag grid -->
  58. <ag-grid-vue
  59. style="width: 100%; height: calc(10 * 2.94rem)"
  60. class="ag-theme-quartz"
  61. :gridOptions="gridOptions"
  62. :rowData="tblItems"
  63. rowSelection="multiple"
  64. :paginationPageSize="pageObj.pageSize"
  65. :suppressPaginationPanel="true"
  66. @grid-ready="onGridReady"
  67. @rowClicked="detailLocated"
  68. >
  69. </ag-grid-vue>
  70. <!-- 페이징 -->
  71. <div class="ag-grid-custom-pagenations">
  72. <pagination @chg_page="chgPage" :pageObj="pageObj"></pagination>
  73. </div>
  74. </div>
  75. </div>
  76. <div class="view-btm-btn">
  77. <div class="btn-l">
  78. <v-btn class="custom-btn btn-list" @click="listLocated"
  79. ><i class="ico"></i>목록</v-btn
  80. >
  81. </div>
  82. <div class="btn-r">
  83. <v-btn v-if="pageType !== 'D'" class="custom-btn btn-blue2" @click="fnBtnEvt"
  84. ><i class="ico"></i>저장</v-btn
  85. >
  86. </div>
  87. </div>
  88. </div>
  89. </div>
  90. </template>
  91. <script setup>
  92. import "@vuepic/vue-datepicker/dist/main.css";
  93. import { AgGridVue } from "ag-grid-vue3";
  94. import * as XLSX from 'xlsx';
  95. import pagination from "../components/common/pagination.vue";
  96. /************************************************************************
  97. | 레이아웃
  98. ************************************************************************/
  99. definePageMeta({
  100. layout: "default",
  101. });
  102. /************************************************************************
  103. | PROPS
  104. ************************************************************************/
  105. const props = defineProps({
  106. propsData: {
  107. type: Object,
  108. default: () => {},
  109. },
  110. });
  111. /************************************************************************
  112. | 스토어
  113. ************************************************************************/
  114. const useDtStore = useDetailStore();
  115. /************************************************************************
  116. | 전역
  117. ************************************************************************/
  118. const { $toast, $log, $dayjs, $eventBus } = useNuxtApp();
  119. const router = useRouter();
  120. const pageId = ref("배송 관리");
  121. let pageObj = ref({
  122. page: 1, // 현재 페이지
  123. pageMaxNumSize: 10, // 페이지 숫자 최대 표현 개수
  124. pageSize: 10, // 테이블 조회 데이터 개수
  125. totalCnt: 0, // 전체 페이지
  126. });
  127. const imgTemp = ref("");
  128. const tblItems = ref([]); // stat 데이터
  129. const form = ref({
  130. formValue1: "",
  131. formValue2: "",
  132. formValue3: "",
  133. formValue4: "",
  134. formValue5: null,
  135. formValue6: "",
  136. formValue7: "",
  137. formValue8: "0",
  138. formValue8Arr: [
  139. { title: "판매중", value: "0" },
  140. { title: "품절", value: "1" },
  141. ],
  142. formValue9: "Y",
  143. formValue9Arr: [
  144. { title: "노출", value: "Y" },
  145. { title: "비노출", value: "N" },
  146. ],
  147. formValue10: "",
  148. });
  149. /* eslint-disable */
  150. /* prettier-ignore */
  151. pageObj.value.totalCnt = tblItems.value.length;
  152. const remToPx = () => parseFloat(getComputedStyle(document.documentElement).fontSize);
  153. const rowHeightRem = 2.65; // 원하는 rem 값
  154. const rowHeightPx = rowHeightRem * remToPx();
  155. const gridApi = shallowRef();
  156. // gridOption
  157. const gridOptions = {
  158. columnDefs: [
  159. //{ checkboxSelection: true, headerCheckboxSelection: true, width: 0 },
  160. {
  161. headerName: "No",
  162. valueGetter: (params) => params.api.getDisplayedRowCount() - params.node.rowIndex,
  163. sortable: false,
  164. width: 70,
  165. },
  166. {
  167. headerName: "구매자명",
  168. field: "BUYER_NAME",
  169. width: 120,
  170. editable: true,
  171. },
  172. {
  173. headerName: "주소",
  174. field: "ADDRESS",
  175. editable: true,
  176. },
  177. {
  178. headerName: "연락처",
  179. field: "PHONE",
  180. width: 140,
  181. editable: true,
  182. },
  183. {
  184. headerName: "이메일",
  185. field: "EMAIL",
  186. editable: true,
  187. },
  188. {
  189. headerName: "구매수량",
  190. field: "QTY",
  191. width: 120,
  192. editable: true,
  193. },
  194. {
  195. headerName: "총구매금액",
  196. field: "TOTAL",
  197. editable: true,
  198. },
  199. {
  200. headerName: "배송업체",
  201. field: "DELIVERY_COMPANY",
  202. width: 100,
  203. editable: true,
  204. },
  205. {
  206. headerName: "송장번호",
  207. field: "TRACKING_NUMBER",
  208. editable: true,
  209. },
  210. {
  211. headerName: "주문일",
  212. field: "ORDER_DATE",
  213. editable: true,
  214. },
  215. ],
  216. rowData: tblItems.value, // 테이블 데이터
  217. autoSizeStrategy: {
  218. type: "fitGridWidth", // width맞춤
  219. },
  220. suppressMovableColumns: true,
  221. headerHeight: rowHeightPx,
  222. rowHeight: rowHeightPx,
  223. pagination: true,
  224. suppressPaginationPanel: true, // 하단 default 페이징 컨트롤 숨김
  225. //rowSelection: {
  226. // checkboxes: true,
  227. // headerCheckbox: true,
  228. // enableClickSelection: false,
  229. // mode: "multiRow",
  230. //},
  231. };
  232. /************************************************************************
  233. | 함수(METHODS)
  234. ************************************************************************/
  235. const listLocated = () => {
  236. router.push({
  237. path: "/view/common/deli/",
  238. });
  239. };
  240. const onGridReady = (__PARAMS) => {
  241. gridApi.value = __PARAMS.api;
  242. };
  243. const chgPage = (__PAGE) => {
  244. pageObj.value.page = __PAGE;
  245. gridApi.value.paginationGoToPage(__PAGE - 1);
  246. };
  247. const fnBtnEvt = () => {
  248. if (!tblItems.value || tblItems.value.length === 0) {
  249. let param = {
  250. id: pageId,
  251. title: pageId,
  252. content: "저장할 배송 데이터가 없습니다.",
  253. yes: {
  254. text: "확인",
  255. isProc: false,
  256. }
  257. };
  258. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  259. return;
  260. }
  261. fnRegEvt();
  262. };
  263. const fnRegEvt = () => {
  264. let param = {
  265. id: pageId,
  266. title: pageId,
  267. content: "등록하시겠습니까?",
  268. yes: {
  269. text: "등록",
  270. isProc: true,
  271. event: "FN_INSERT",
  272. param: "",
  273. },
  274. no: {
  275. text: "취소",
  276. isProc: false,
  277. },
  278. };
  279. $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
  280. };
  281. // 엑셀 컬럼명 매핑 테이블
  282. const excelColumnMapping = {
  283. '구매자명': 'BUYER_NAME',
  284. '주소': 'ADDRESS',
  285. '연락처': 'PHONE',
  286. '이메일': 'EMAIL',
  287. '구매수량': 'QTY',
  288. '총구매금액': 'TOTAL',
  289. '배송업체': 'DELIVERY_COMPANY',
  290. '송장번호': 'TRACKING_NUMBER',
  291. '주문일': 'ORDER_DATE'
  292. };
  293. const handleExcelUpload = (event) => {
  294. const file = event.target.files[0];
  295. if (!file) return;
  296. const reader = new FileReader();
  297. reader.onload = (e) => {
  298. try {
  299. const data = new Uint8Array(e.target.result);
  300. const workbook = XLSX.read(data, { type: 'array' });
  301. const sheetName = workbook.SheetNames[0];
  302. const worksheet = workbook.Sheets[sheetName];
  303. const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
  304. if (jsonData.length < 2) {
  305. $toast.error('엑셀 파일에 데이터가 없습니다.');
  306. return;
  307. }
  308. const headers = jsonData[0];
  309. const rows = jsonData.slice(1);
  310. // 컬럼명 매핑 및 데이터 변환
  311. const mappedData = rows.map(row => {
  312. const mappedRow = {};
  313. headers.forEach((header, index) => {
  314. const fieldName = excelColumnMapping[header];
  315. if (fieldName && row[index] !== undefined) {
  316. mappedRow[fieldName] = row[index];
  317. }
  318. });
  319. return mappedRow;
  320. }).filter(row => Object.keys(row).length > 0);
  321. if (mappedData.length === 0) {
  322. $toast.error('매핑 가능한 컬럼이 없습니다. 엑셀 헤더명을 확인해주세요.');
  323. return;
  324. }
  325. // ag-grid에 데이터 추가
  326. // 기존 데이터는 지우고 추가
  327. tblItems.value = [];
  328. tblItems.value = [...tblItems.value, ...mappedData];
  329. pageObj.value.totalCnt = tblItems.value.length;
  330. $toast.success(`${mappedData.length}건의 데이터가 추가되었습니다.`);
  331. } catch (error) {
  332. console.error('엑셀 파일 처리 중 오류:', error);
  333. $toast.error('엑셀 파일을 읽는 중 오류가 발생했습니다.');
  334. }
  335. };
  336. reader.readAsArrayBuffer(file);
  337. // 파일 input 초기화
  338. event.target.value = '';
  339. };
  340. const fnDetail = () => {
  341. let req = {
  342. seq: useDtStore.boardInfo.seq,
  343. };
  344. useAxios()
  345. .get(`/item/detail/${req.seq}`)
  346. .then((res) => {
  347. form.value.formValue1 = res.data.NAME;
  348. form.value.formValue2 = res.data.PRICE1;
  349. form.value.formValue3 = res.data.PRICE2;
  350. form.value.formValue4 = res.data.DELI_FEE;
  351. form.value.formValue5 = res.data.THUMB_FILE;
  352. form.value.formValue6 = res.data.SUB_TITLE;
  353. form.value.formValue8 = res.data.STATUS;
  354. form.value.formValue9 = res.data.SHOW_YN;
  355. form.value.formValue10 = res.data.ADD_INFO;
  356. //썸네일 파일이 있으면 넣어줌
  357. if(form.value.formValue5){
  358. imgTemp.value = `https://shopdeli.mycafe24.com/writable/uploads/item/thumb/${form.value.formValue5}`;
  359. }
  360. })
  361. .catch((error) => {
  362. console.error('제품 상세 조회 오류:', error);
  363. $toast.error('제품 정보를 불러오는 중 오류가 발생했습니다.');
  364. })
  365. .finally(() => {
  366. });
  367. };
  368. const fnInsert = () => {
  369. if (!tblItems.value || tblItems.value.length === 0) {
  370. $toast.error('저장할 배송 데이터가 없습니다.');
  371. return;
  372. }
  373. const deliveryData = {
  374. itemSeq: useDtStore.boardInfo.seq,
  375. deliveryList: tblItems.value.map(item => ({
  376. buyerName: item.BUYER_NAME,
  377. address: item.ADDRESS,
  378. phone: item.PHONE,
  379. email: item.EMAIL,
  380. qty: item.QTY,
  381. total: item.TOTAL,
  382. deliveryCompany: item.DELIVERY_COMPANY,
  383. trackingNumber: item.TRACKING_NUMBER,
  384. orderDate: item.ORDER_DATE
  385. }))
  386. };
  387. useAxios()
  388. .post('/delivery/save', deliveryData)
  389. .then((res) => {
  390. $toast.success('배송 데이터가 성공적으로 저장되었습니다.');
  391. // 저장 후 목록으로 이동하거나 데이터 새로고침
  392. listLocated();
  393. })
  394. .catch((error) => {
  395. console.error('배송 데이터 저장 오류:', error);
  396. let errorMessage = '배송 데이터 저장 중 오류가 발생했습니다.';
  397. if (error.response && error.response.data && error.response.data.message) {
  398. errorMessage = error.response.data.message;
  399. }
  400. $toast.error(errorMessage);
  401. })
  402. .finally(() => {
  403. });
  404. };
  405. /************************************************************************
  406. | 팝업 이벤트버스 정의
  407. ************************************************************************/
  408. $eventBus.off("FN_INSERT");
  409. $eventBus.on("FN_INSERT", () => {
  410. fnInsert();
  411. });
  412. /************************************************************************
  413. | WATCH
  414. ************************************************************************/
  415. watch(
  416. () => props,
  417. () => {
  418. searchObj.value = props.propsData;
  419. fnGetStat();
  420. },
  421. { deep: true }
  422. );
  423. onMounted(() => {
  424. fnDetail();
  425. });
  426. </script>