Pārlūkot izejas kodu

[제품관리] 벤더/인플루언서 화면 구분, [배송관리] 엑셀업로드

DESKTOP-T61HUSC\user 4 mēneši atpakaļ
vecāks
revīzija
ecbfaa1d46

+ 40 - 0
assets/scss/default.scss

@@ -56,6 +56,46 @@
         .right--sections{
           display: flex;
           gap: 1rem;
+          .caption--wrap{
+            display: flex;
+            align-items: center;
+            position: relative;
+            .ico{
+              font-size: 1rem;
+              width: 2rem;
+              height: 2rem;
+              text-align: center;
+              cursor: pointer;
+              line-height: 2rem;
+              border-radius: 50%;
+              background-color: #F74F78;
+              color: #fff;
+              display: inline-block;
+              position: relative;
+              font-style: normal;
+
+            }
+            .caption--box{
+              position: absolute;
+              font-size: 0.875rem;
+              bottom: 100%;
+              border: 2px solid #DFE7EF;
+              background-color: #fff;
+              border-radius: 10px;
+              right:0;
+              line-height: 1.4;
+              padding: 15px 20px;
+              white-space: nowrap;
+              color: #9DA9B6;
+              z-index: 10;
+              display: none;
+            }
+            &:hover{
+              .caption--box{
+                display: block;
+              }
+            }
+          }
         }
         .item--section{
           border: 1px solid #ccc;

+ 189 - 52
pages/view/common/deli/detail.vue

@@ -37,6 +37,19 @@
         <div class="left--sections">
         </div>
         <div class="right--sections">
+          <div class="caption--wrap">
+            <i class="ico">!</i>
+            <div class="caption--box">
+              - 주문일은 YYYY.MM.DD 혹은 YYYY-MM-DD 형태로 입력해 주세요.<br>
+              - 구매자 정보 입력 후 저장 버튼을 꼭 클릭해 주세요.
+            </div>
+          </div>
+          <v-btn class="custom-btn btn-white mini" @click="addEmptyRow"
+            ><i class="ico"></i>항목 추가</v-btn
+          >
+          <v-btn class="custom-btn btn-white mini" @click="deleteSelectedRows"
+            ><i class="ico"></i>항목 삭제</v-btn
+          >
           <input 
             ref="excelFileInput" 
             type="file" 
@@ -47,7 +60,7 @@
           <v-btn class="custom-btn btn-excel" @click="$refs.excelFileInput.click()"
             ><i class="ico"></i>엑셀 업로드</v-btn
           >
-          <v-btn class="custom-btn btn-excel"
+          <v-btn class="custom-btn btn-excel" @click="downloadExcel"
             ><i class="ico"></i>엑셀 다운로드</v-btn
           >
         </div>
@@ -59,12 +72,11 @@
             style="width: 100%; height: calc(10 * 2.94rem)"
             class="ag-theme-quartz"
             :gridOptions="gridOptions"
-            :rowData="tblItems"
             rowSelection="multiple"
+            :rowData="tblItems"
             :paginationPageSize="pageObj.pageSize"
             :suppressPaginationPanel="true"
             @grid-ready="onGridReady"
-            @rowClicked="detailLocated"
           >
           </ag-grid-vue>
 
@@ -81,7 +93,7 @@
           >
         </div>
         <div class="btn-r">
-          <v-btn v-if="pageType !== 'D'" class="custom-btn btn-blue2" @click="fnBtnEvt"
+          <v-btn class="custom-btn btn-blue2" @click="fnRegEvt"
             ><i class="ico"></i>저장</v-btn
           >
         </div>
@@ -162,7 +174,7 @@ import pagination from "../components/common/pagination.vue";
   // gridOption
   const gridOptions = {
     columnDefs: [
-      //{ checkboxSelection: true, headerCheckboxSelection: true, width: 0 },
+      { checkboxSelection: true, headerCheckboxSelection: true, width: 50 },
       {
         headerName: "No",
         valueGetter: (params) => params.api.getDisplayedRowCount() - params.node.rowIndex,
@@ -204,13 +216,13 @@ import pagination from "../components/common/pagination.vue";
       },
       {
         headerName: "배송업체",
-        field: "DELIVERY_COMPANY",
+        field: "DELI_COMP",
         width: 100,
         editable: true,
       },
       {
         headerName: "송장번호",
-        field: "TRACKING_NUMBER",
+        field: "DELI_NUMB",
         editable: true,
       },
       {
@@ -228,12 +240,13 @@ import pagination from "../components/common/pagination.vue";
     rowHeight: rowHeightPx,
     pagination: true,
     suppressPaginationPanel: true, // 하단 default 페이징 컨트롤 숨김
-    //rowSelection: {
-    // checkboxes: true,
-    // headerCheckbox: true,
-    // enableClickSelection: false,
-    // mode: "multiRow",
-    //},
+    rowMultiSelectWithClick: true,
+    rowSelection: {
+      checkboxes: true,
+      headerCheckbox: true,
+      enableClickSelection: false,
+      mode: "multiRow",
+    },
   };
 
   /************************************************************************
@@ -253,25 +266,6 @@ import pagination from "../components/common/pagination.vue";
     gridApi.value.paginationGoToPage(__PAGE - 1);
   };
 
-  
-const fnBtnEvt = () => {
-  if (!tblItems.value || tblItems.value.length === 0) {
-    let param = {
-      id: pageId,
-      title: pageId,
-      content: "저장할 배송 데이터가 없습니다.",
-      yes: {
-        text: "확인",
-        isProc: false,
-      }
-    };
-    $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
-    return;
-  }
-  
-  fnRegEvt();
-};
-
   const fnRegEvt = () => {
     let param = {
       id: pageId,
@@ -298,11 +292,87 @@ const fnBtnEvt = () => {
     '이메일': 'EMAIL',
     '구매수량': 'QTY',
     '총구매금액': 'TOTAL',
-    '배송업체': 'DELIVERY_COMPANY',
-    '송장번호': 'TRACKING_NUMBER',
+    '배송업체': 'DELI_COMP',
+    '송장번호': 'DELI_NUMB',
     '주문일': 'ORDER_DATE'
   };
 
+  const addEmptyRow = () => {
+    const newRow = {
+      BUYER_NAME: "",
+      ADDRESS: "",
+      PHONE: "",
+      EMAIL: "",
+      QTY: "",
+      TOTAL: "",
+      DELI_COMP: "",
+      DELI_NUMB: "",
+      ORDER_DATE: ""
+    };
+    
+    // 맨 앞에 추가 (unshift 사용)
+    tblItems.value.unshift(newRow);
+    pageObj.value.totalCnt = tblItems.value.length;
+    
+    // ag-grid 데이터 갱신
+    if (gridApi.value) {
+      gridApi.value.setGridOption('rowData', tblItems.value);
+    }
+    
+    $toast.success('새 항목이 추가되었습니다.');
+  };
+
+  const deleteSelectedRows = () => {
+    if (!gridApi.value) return;
+    
+    const selectedRows = gridApi.value.getSelectedRows();
+    if (selectedRows.length === 0) {
+      $toast.warning('삭제할 항목을 선택해주세요.');
+      return;
+    }
+    
+    let param = {
+      id: pageId,
+      title: pageId,
+      content: `선택된 ${selectedRows.length}개 항목을 삭제하시겠습니까?`,
+      yes: {
+        text: "삭제",
+        isProc: true,
+        event: "FN_DELETE_SELECTED",
+        param: selectedRows,
+      },
+      no: {
+        text: "취소",
+        isProc: false,
+      },
+    };
+    $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
+  };
+
+  const fnDeleteSelected = (selectedRows) => {
+    // 선택된 행들을 tblItems에서 제거
+    selectedRows.forEach(selectedRow => {
+      const index = tblItems.value.findIndex(item => 
+        item.BUYER_NAME === selectedRow.BUYER_NAME &&
+        item.ADDRESS === selectedRow.ADDRESS &&
+        item.PHONE === selectedRow.PHONE &&
+        item.EMAIL === selectedRow.EMAIL
+      );
+      if (index > -1) {
+        tblItems.value.splice(index, 1);
+      }
+    });
+    
+    pageObj.value.totalCnt = tblItems.value.length;
+    
+    // ag-grid 데이터 갱신
+    if (gridApi.value) {
+      gridApi.value.setGridOption('rowData', tblItems.value);
+    }
+    
+    $toast.success(`${selectedRows.length}개 항목이 삭제되었습니다.`);
+  };
+
   const handleExcelUpload = (event) => {
     const file = event.target.files[0];
     if (!file) return;
@@ -343,10 +413,14 @@ const fnBtnEvt = () => {
 
         // ag-grid에 데이터 추가
         // 기존 데이터는 지우고 추가
-        tblItems.value = [];
-        tblItems.value = [...tblItems.value, ...mappedData];
+        tblItems.value = [...mappedData];
         pageObj.value.totalCnt = tblItems.value.length;
         
+        // ag-grid 데이터 갱신
+        if (gridApi.value) {
+          gridApi.value.setGridOption('rowData', tblItems.value);
+        }
+        
         $toast.success(`${mappedData.length}건의 데이터가 추가되었습니다.`);
         
       } catch (error) {
@@ -360,10 +434,63 @@ const fnBtnEvt = () => {
     event.target.value = '';
   };
 
+  const downloadExcel = () => {
+    if (!tblItems.value || tblItems.value.length === 0) {
+      $toast.warning('다운로드할 데이터가 없습니다.');
+      return;
+    }
+
+    // 한글 헤더명 배열
+    const headers = [
+      '구매자명', '주소', '연락처', '이메일', '구매수량', 
+      '총구매금액', '배송업체', '송장번호', '주문일'
+    ];
+
+    // 데이터를 엑셀 형식으로 변환
+    const excelData = tblItems.value.map(item => [
+      item.BUYER_NAME || '',
+      item.ADDRESS || '',
+      item.PHONE || '',
+      item.EMAIL || '',
+      item.QTY || '',
+      item.TOTAL || '',
+      item.DELI_COMP || '',
+      item.DELI_NUMB || '',
+      item.ORDER_DATE || ''
+    ]);
+
+    // 헤더를 첫 번째 행에 추가
+    const worksheetData = [headers, ...excelData];
+
+    // 워크시트 생성
+    const worksheet = XLSX.utils.aoa_to_sheet(worksheetData);
+    
+    // 워크북 생성
+    const workbook = XLSX.utils.book_new();
+    XLSX.utils.book_append_sheet(workbook, worksheet, '배송관리');
+
+    // 파일명 생성 (현재 날짜 포함)
+    const today = new Date();
+    const dateString = today.getFullYear() + 
+      String(today.getMonth() + 1).padStart(2, '0') + 
+      String(today.getDate()).padStart(2, '0');
+    const fileName = `배송관리_${dateString}.xlsx`;
+
+    // 엑셀 파일 다운로드
+    XLSX.writeFile(workbook, fileName);
+    
+    $toast.success('엑셀 파일이 다운로드되었습니다.');
+  };
+
   const fnDetail = () => {
     let req = {
       seq: useDtStore.boardInfo.seq,
     };
+    let req2 = {
+      item_seq: useDtStore.boardInfo.seq,
+      //인플루언서일 경우 본인의 inf_seq값 보내줘야함
+      //inf_seq: 8,
+    }
     useAxios()
     .get(`/item/detail/${req.seq}`)
     .then((res) => {
@@ -382,7 +509,19 @@ const fnBtnEvt = () => {
         }
       })
       .catch((error) => {
-        console.error('제품 상세 조회 오류:', error);
+        $toast.error('제품 정보를 불러오는 중 오류가 발생했습니다.');
+      })
+      .finally(() => {
+      });
+    // 기 저장된 구매자명 리스트
+    // 제품 seq, 인플루언서 seq가 일치하는 리스트만
+    useAxios()
+    .post(`/deli/list`, req2)
+    .then((res) => {
+      tblItems.value = res.data;
+      pageObj.value.totalCnt = tblItems.value.length;
+      })
+      .catch((error) => {
         $toast.error('제품 정보를 불러오는 중 오류가 발생했습니다.');
       })
       .finally(() => {
@@ -390,13 +529,10 @@ const fnBtnEvt = () => {
   };
 
   const fnInsert = () => {
-    if (!tblItems.value || tblItems.value.length === 0) {
-      $toast.error('저장할 배송 데이터가 없습니다.');
-      return;
-    }
-
     const deliveryData = {
-      itemSeq: useDtStore.boardInfo.seq,
+      item_seq: useDtStore.boardInfo.seq,
+      // 임시 인플루언서 시퀀스
+      inf_seq: 8,
       deliveryList: tblItems.value.map(item => ({
         buyerName: item.BUYER_NAME,
         address: item.ADDRESS,
@@ -404,27 +540,23 @@ const fnBtnEvt = () => {
         email: item.EMAIL,
         qty: item.QTY,
         total: item.TOTAL,
-        deliveryCompany: item.DELIVERY_COMPANY,
-        trackingNumber: item.TRACKING_NUMBER,
-        orderDate: item.ORDER_DATE
+        deliComp: item.DELI_COMP,
+        deliNumb: item.DELI_NUMB,
+        orderDate: item.ORDER_DATE.replaceAll(".", "-")
       }))
     };
 
     useAxios()
-      .post('/delivery/save', deliveryData)
+      .post('/deli/reg', deliveryData)
       .then((res) => {
         $toast.success('배송 데이터가 성공적으로 저장되었습니다.');
-        // 저장 후 목록으로 이동하거나 데이터 새로고침
-        listLocated();
+        location.reload();
       })
       .catch((error) => {
-        console.error('배송 데이터 저장 오류:', error);
         let errorMessage = '배송 데이터 저장 중 오류가 발생했습니다.';
-        
         if (error.response && error.response.data && error.response.data.message) {
           errorMessage = error.response.data.message;
         }
-        
         $toast.error(errorMessage);
       })
       .finally(() => {
@@ -437,6 +569,11 @@ $eventBus.off("FN_INSERT");
 $eventBus.on("FN_INSERT", () => {
   fnInsert();
 });
+
+$eventBus.off("FN_DELETE_SELECTED");
+$eventBus.on("FN_DELETE_SELECTED", (selectedRows) => {
+  fnDeleteSelected(selectedRows);
+});
   /************************************************************************
 |    WATCH
 ************************************************************************/

+ 5 - 4
pages/view/common/item/add.vue

@@ -37,7 +37,7 @@
               <tr>
                 <th class="bg le">공급가<span v-if="pageType !== 'D'" class="bul">*</span></th>
                   <td v-if="pageType == 'D'">
-                    {{ form.formValue2 }}
+                    {{ Number(form.formValue2).toLocaleString() }}
                   </td>
                   <td v-else>
                   <div class="input--wrap">
@@ -55,7 +55,7 @@
               <tr>
                 <th class="bg le">판매가<span v-if="pageType !== 'D'" class="bul">*</span></th>
                 <td v-if="pageType == 'D'">
-                  {{ form.formValue3 }}
+                  {{ Number(form.formValue3).toLocaleString() }}
                 </td>
                 <td v-else>
                   <div class="input--wrap">
@@ -165,6 +165,7 @@
                 <td>
                   <div class="input--wrap" style="width: 50%">
                     <v-file-input
+                      v-if="pageType !== 'D'"
                       v-model="form.formValue7"
                       label="파일은 압축(zip)해서 첨부해 주세요."
                       accept=".zip"
@@ -181,7 +182,7 @@
               <tr>
                 <th class="bg le">상세 내용<span v-if="pageType !== 'D'" class="bul">*</span></th>
                 <td v-if="pageType == 'D'">
-                  {{ editorContentReq }}
+                  <div v-html="editorContentReq"></div>
                 </td>
                 <td v-else>
                   <SunEditorWrapper
@@ -305,7 +306,7 @@ const form = ref({
   formValue4: "",
   formValue5: null,
   formValue6: "",
-  formValue7: "",
+  formValue7: null,
   formValue8: "0",
   formValue8Arr: [
     { title: "판매중", value: "0" },

+ 17 - 7
pages/view/common/item/index.vue

@@ -75,7 +75,7 @@
           <!-- <v-btn class="custom-btn mini btn-white">선택 삭제</v-btn> -->
         </div>
         <div class="right--sections">
-          <v-btn class="custom-btn mini btn-reg" @click="addLocated()"
+          <v-btn class="custom-btn mini btn-reg" v-if="memberType !== 'INFLUENCER'" @click="addLocated()"
             ><i class="ico"></i>제품 등록</v-btn
           >
         </div>
@@ -88,16 +88,16 @@
           <div v-for="(items, index) in paginatedItems" :key="index" @click="toItemDetail(items.SEQ)" class="item">
             <div class="item--img"><img v-if="items.THUMB_FILE" :src="`https://shopdeli.mycafe24.com/writable/uploads/item/thumb/${items.THUMB_FILE}`"></div>
             <h3>{{ items.NAME }}</h3>
-            <p>공급가: {{ items.PRICE1 }}<br>판매가: {{ items.PRICE2 }}</p>
+            <p>공급가: {{ Number(items.PRICE1).toLocaleString() }}<br>판매가: {{ Number(items.PRICE1).toLocaleString() }}</p>
             <span>등록일: {{ items.REGDATE.slice(0, 10) }}</span>
             <span>업데이트 날짜: {{ items.UDPDATE.slice(0, 10) }}</span>
             <div
               v-if="items.STATUS == 1 || isRecentUpdate(items.UDPDATE)"
               class="sold--out"
-              :class="{ 'blue--type': isRecentUpdate(items.UDPDATE) }"
+              :class="{ 'blue--type': isRecentUpdate(items.UDPDATE) && items.STATUS != 1 }"
             >
               <span>
-                {{ isRecentUpdate(items.UDPDATE) ? '업데이트' : '품절' }}
+                {{ items.STATUS == 1 ? '품절' : '업데이트' }}
               </span>
             </div>
           </div>
@@ -136,9 +136,11 @@ import dayjs from 'dayjs';
 |    스토어
  ************************************************************************/
   const useDtStore = useDetailStore();
+  const useAtStore = useAuthStore();
   /************************************************************************
 |    전역
  ************************************************************************/
+  const memberType = useAtStore.auth.memberType;
   const searchModel = ref("");
   const selectedRange = ref('all');
   const itemStartDate = ref("");
@@ -229,7 +231,7 @@ import dayjs from 'dayjs';
     });
     useDtStore.boardInfo.seq = __EVENT;
     //제품 등록한 벤더의 경우 U로, 인플루언서의 경우 D로
-    useDtStore.boardInfo.pageType = "U";
+    memberType == 'INFLUENCER' ? useDtStore.boardInfo.pageType = "D" : useDtStore.boardInfo.pageType = "U";
   };
 
   const itemListGet = async () => {
@@ -238,6 +240,10 @@ import dayjs from 'dayjs';
       SHOW_YN: "",
     };
 
+    if (memberType === "INFLUENCER") {
+      _req.SHOW_YN = "Y";
+    }
+
     await useAxios()
       .post("/item/list", _req)
       .then((res) => {
@@ -254,9 +260,13 @@ import dayjs from 'dayjs';
       keyword: __KEYWORD,
       startDate: searchStartDate.value,
       endDate: searchEndDate.value,
-      //인플루언서의 경우 showYN 추가
-      //showYN: "Y"
+      showYN: ""
     };
+    
+    //인플루언서의 경우 showYN 추가
+    if (memberType === "INFLUENCER") {
+      _req.showYN = "Y";
+    }
 
     useAxios()
       .post("/item/search", _req)