test #75
| @@ -247,6 +247,14 @@ async function getCharactersInCuration(curationId) { | ||||
|   return Vue.axios.get(`/admin/chat/character/curation/${curationId}/characters`) | ||||
| } | ||||
|  | ||||
| // 캐릭터별 정산 목록 | ||||
| // params: { startDateStr, endDateStr, sort, page, size } | ||||
| async function getCharacterCalculateList({ startDateStr, endDateStr, sort = 'TOTAL_SALES_DESC', page = 0, size = 30 }) { | ||||
|   return Vue.axios.get('/admin/chat/calculate/characters', { | ||||
|     params: { startDateStr, endDateStr, sort, page, size } | ||||
|   }) | ||||
| } | ||||
|  | ||||
| export { | ||||
|   getCharacterList, | ||||
|   searchCharacters, | ||||
| @@ -273,5 +281,6 @@ export { | ||||
|   addCharacterToCuration, | ||||
|   removeCharacterFromCuration, | ||||
|   updateCurationCharactersOrder, | ||||
|   getCharactersInCuration | ||||
|   getCharactersInCuration, | ||||
|   getCharacterCalculateList | ||||
| } | ||||
|   | ||||
| @@ -116,7 +116,12 @@ export default { | ||||
|                 title: '큐레이션', | ||||
|                 route: '/character/curation', | ||||
|                 items: null | ||||
|               } | ||||
|               }, | ||||
|               { | ||||
|                 title: '정산', | ||||
|                 route: '/character/calculate', | ||||
|                 items: null | ||||
|               }, | ||||
|             ] | ||||
|           }) | ||||
|         } else { | ||||
|   | ||||
| @@ -290,6 +290,11 @@ const routes = [ | ||||
|                 name: 'CharacterCurationDetail', | ||||
|                 component: () => import(/* webpackChunkName: "character" */ '../views/Chat/CharacterCurationDetail.vue') | ||||
|             }, | ||||
|             { | ||||
|               path: '/character/calculate', | ||||
|               name: 'CharacterCalculate', | ||||
|               component: () => import(/* webpackChunkName: "character" */ '../views/Chat/CharacterCalculateList.vue') | ||||
|             }, | ||||
|         ] | ||||
|     }, | ||||
|     { | ||||
|   | ||||
							
								
								
									
										315
									
								
								src/views/Chat/CharacterCalculateList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								src/views/Chat/CharacterCalculateList.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,315 @@ | ||||
| <template> | ||||
|   <div> | ||||
|     <v-toolbar dark> | ||||
|       <v-spacer /> | ||||
|       <v-toolbar-title>캐릭터 정산</v-toolbar-title> | ||||
|       <v-spacer /> | ||||
|     </v-toolbar> | ||||
|  | ||||
|     <v-container> | ||||
|       <v-row class="justify-center align-center text-center"> | ||||
|         <v-col | ||||
|           cols="12" | ||||
|           md="3" | ||||
|         > | ||||
|           <v-menu | ||||
|             ref="menuStart" | ||||
|             v-model="menuStart" | ||||
|             :close-on-content-click="false" | ||||
|             transition="scale-transition" | ||||
|             offset-y | ||||
|             min-width="auto" | ||||
|           > | ||||
|             <template v-slot:activator="{ on, attrs }"> | ||||
|               <v-text-field | ||||
|                 v-model="filters.startDateStr" | ||||
|                 label="시작일" | ||||
|                 readonly | ||||
|                 dense | ||||
|                 v-bind="attrs" | ||||
|                 clearable | ||||
|                 v-on="on" | ||||
|               /> | ||||
|             </template> | ||||
|             <v-date-picker | ||||
|               v-model="filters.startDateStr" | ||||
|               :max="filters.endDateStr && filters.endDateStr < todayStr ? filters.endDateStr : todayStr" | ||||
|               locale="ko-kr" | ||||
|               @input="$refs.menuStart.save(filters.startDateStr)" | ||||
|             /> | ||||
|           </v-menu> | ||||
|         </v-col> | ||||
|         <v-col | ||||
|           cols="12" | ||||
|           md="3" | ||||
|         > | ||||
|           <v-menu | ||||
|             ref="menuEnd" | ||||
|             v-model="menuEnd" | ||||
|             :close-on-content-click="false" | ||||
|             transition="scale-transition" | ||||
|             offset-y | ||||
|             min-width="auto" | ||||
|           > | ||||
|             <template v-slot:activator="{ on, attrs }"> | ||||
|               <v-text-field | ||||
|                 v-model="filters.endDateStr" | ||||
|                 label="종료일" | ||||
|                 readonly | ||||
|                 dense | ||||
|                 v-bind="attrs" | ||||
|                 clearable | ||||
|                 v-on="on" | ||||
|               /> | ||||
|             </template> | ||||
|             <v-date-picker | ||||
|               v-model="filters.endDateStr" | ||||
|               :min="filters.startDateStr" | ||||
|               :max="todayStr" | ||||
|               locale="ko-kr" | ||||
|               @input="$refs.menuEnd.save(filters.endDateStr)" | ||||
|             /> | ||||
|           </v-menu> | ||||
|         </v-col> | ||||
|         <v-col | ||||
|           cols="12" | ||||
|           md="3" | ||||
|         > | ||||
|           <v-select | ||||
|             v-model="filters.sort" | ||||
|             :items="sortItems" | ||||
|             label="정렬" | ||||
|             item-text="text" | ||||
|             item-value="value" | ||||
|             dense | ||||
|           /> | ||||
|         </v-col> | ||||
|         <v-col | ||||
|           cols="12" | ||||
|           md="3" | ||||
|           class="d-flex justify-center align-center" | ||||
|         > | ||||
|           <v-btn | ||||
|             color="primary" | ||||
|             small | ||||
|             :loading="is_loading" | ||||
|             @click="fetchList(1)" | ||||
|           > | ||||
|             조회 | ||||
|           </v-btn> | ||||
|           <v-btn | ||||
|             class="ml-2" | ||||
|             text | ||||
|             small | ||||
|             @click="resetFilters" | ||||
|           > | ||||
|             초기화 | ||||
|           </v-btn> | ||||
|         </v-col> | ||||
|       </v-row> | ||||
|  | ||||
|       <v-row> | ||||
|         <v-col> | ||||
|           <v-simple-table class="elevation-10 text-center"> | ||||
|             <template> | ||||
|               <thead> | ||||
|                 <tr> | ||||
|                   <th class="text-center"> | ||||
|                     이미지 | ||||
|                   </th> | ||||
|                   <th class="text-center"> | ||||
|                     캐릭터명 | ||||
|                   </th> | ||||
|                   <th class="text-center"> | ||||
|                     이미지 단독 구매(캔) | ||||
|                   </th> | ||||
|                   <th class="text-center"> | ||||
|                     메시지 구매(캔) | ||||
|                   </th> | ||||
|                   <th class="text-center"> | ||||
|                     채팅 횟수 구매(캔) | ||||
|                   </th> | ||||
|                   <th class="text-center"> | ||||
|                     합계(캔) | ||||
|                   </th> | ||||
|                   <th class="text-center"> | ||||
|                     합계(원화) | ||||
|                   </th> | ||||
|                   <th class="text-center"> | ||||
|                     정산금액(원) | ||||
|                   </th> | ||||
|                 </tr> | ||||
|               </thead> | ||||
|               <tbody> | ||||
|                 <tr | ||||
|                   v-for="item in items" | ||||
|                   :key="item.characterId" | ||||
|                 > | ||||
|                   <td align="center"> | ||||
|                     <v-img | ||||
|                       :src="item.characterImage" | ||||
|                       max-width="64" | ||||
|                       max-height="64" | ||||
|                       class="rounded-circle" | ||||
|                       contain | ||||
|                     /> | ||||
|                   </td> | ||||
|                   <td class="text-center"> | ||||
|                     {{ item.name }} | ||||
|                   </td> | ||||
|                   <td class="text-center"> | ||||
|                     {{ formatNumber(item.imagePurchaseCan) }} | ||||
|                   </td> | ||||
|                   <td class="text-center"> | ||||
|                     {{ formatNumber(item.messagePurchaseCan) }} | ||||
|                   </td> | ||||
|                   <td class="text-center"> | ||||
|                     {{ formatNumber(item.quotaPurchaseCan) }} | ||||
|                   </td> | ||||
|                   <td class="text-center font-weight-bold"> | ||||
|                     {{ formatNumber(item.totalCan) }} | ||||
|                   </td> | ||||
|                   <td class="text-center"> | ||||
|                     {{ formatCurrency(item.totalKrw) }} | ||||
|                   </td> | ||||
|                   <td class="text-center font-weight-bold"> | ||||
|                     {{ formatCurrency(item.settlementKrw) }} | ||||
|                   </td> | ||||
|                 </tr> | ||||
|                 <tr v-if="!is_loading && items.length === 0"> | ||||
|                   <td | ||||
|                     colspan="7" | ||||
|                     class="text-center grey--text" | ||||
|                   > | ||||
|                     데이터가 없습니다. | ||||
|                   </td> | ||||
|                 </tr> | ||||
|               </tbody> | ||||
|             </template> | ||||
|           </v-simple-table> | ||||
|         </v-col> | ||||
|       </v-row> | ||||
|  | ||||
|       <v-row class="text-center"> | ||||
|         <v-col> | ||||
|           <v-pagination | ||||
|             v-model="page" | ||||
|             :length="total_page" | ||||
|             circle | ||||
|             @input="onPageChange" | ||||
|           /> | ||||
|         </v-col> | ||||
|       </v-row> | ||||
|     </v-container> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { getCharacterCalculateList } from "@/api/character"; | ||||
|  | ||||
| function formatDate(date) { | ||||
|   const pad = (n) => n.toString().padStart(2, "0"); | ||||
|   return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`; | ||||
| } | ||||
|  | ||||
| export default { | ||||
|   name: "CharacterCalculateList", | ||||
|   data() { | ||||
|     const today = new Date(); | ||||
|     const sevenDaysAgo = new Date(); | ||||
|     sevenDaysAgo.setDate(today.getDate() - 7); | ||||
|  | ||||
|     return { | ||||
|       is_loading: false, | ||||
|       menuStart: false, | ||||
|       menuEnd: false, | ||||
|       todayStr: formatDate(today), | ||||
|       page: 1, | ||||
|       size: 30, | ||||
|       total_page: 1, | ||||
|       total_count: 0, | ||||
|       items: [], | ||||
|       sortItems: [ | ||||
|         { text: "매출순", value: "TOTAL_SALES_DESC" }, | ||||
|         { text: "최신캐릭터순", value: "LATEST_DESC" } | ||||
|       ], | ||||
|       filters: { | ||||
|         startDateStr: formatDate(new Date(today.getFullYear(), today.getMonth(), 1)), | ||||
|         endDateStr: formatDate(today), | ||||
|         sort: "TOTAL_SALES_DESC" | ||||
|       } | ||||
|     }; | ||||
|   }, | ||||
|   created() { | ||||
|     this.fetchList(1); | ||||
|   }, | ||||
|   methods: { | ||||
|     notifyError(message) { | ||||
|       this.$dialog && this.$dialog.notify ? this.$dialog.notify.error(message) : alert(message); | ||||
|     }, | ||||
|     onPageChange() { | ||||
|       this.fetchList(this.page); | ||||
|     }, | ||||
|     resetFilters() { | ||||
|       const today = new Date(); | ||||
|       // 이번 달 1일로 시작일 설정 | ||||
|       const firstDay = new Date(today.getFullYear(), today.getMonth(), 1); | ||||
|       this.filters.startDateStr = formatDate(firstDay); | ||||
|       // 종료일은 오늘 | ||||
|       this.filters.endDateStr = formatDate(today); | ||||
|       this.filters.sort = "TOTAL_SALES_DESC"; | ||||
|       // 페이지를 1로 리셋하고 목록 조회 | ||||
|       this.fetchList(1); | ||||
|     }, | ||||
|     async fetchList(page = 1) { | ||||
|       if (this.is_loading) return; | ||||
|       this.is_loading = true; | ||||
|       try { | ||||
|         const params = { | ||||
|           startDateStr: this.filters.startDateStr || null, | ||||
|           endDateStr: this.filters.endDateStr || null, | ||||
|           sort: this.filters.sort, | ||||
|           page: (page - 1), | ||||
|           size: this.size | ||||
|         }; | ||||
|         const res = await getCharacterCalculateList(params); | ||||
|         if (res && res.status === 200) { | ||||
|           const data = res.data && res.data.data ? res.data.data : res.data; | ||||
|           if (data) { | ||||
|             this.total_count = data.totalCount || 0; | ||||
|             this.items = data.items || []; | ||||
|             const totalPage = Math.ceil(this.total_count / this.size); | ||||
|             this.total_page = totalPage > 0 ? totalPage : 1; | ||||
|             this.page = page; | ||||
|           } else { | ||||
|             this.items = []; | ||||
|             this.total_count = 0; | ||||
|             this.total_page = 1; | ||||
|           } | ||||
|         } else { | ||||
|           this.notifyError("목록 조회 중 오류가 발생했습니다."); | ||||
|         } | ||||
|       } catch (e) { | ||||
|         console.error("정산 목록 조회 오류:", e); | ||||
|         this.notifyError("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."); | ||||
|       } finally { | ||||
|         this.is_loading = false; | ||||
|       } | ||||
|     }, | ||||
|     formatNumber(n) { | ||||
|       const num = Number(n || 0); | ||||
|       return num.toLocaleString("ko-KR"); | ||||
|     }, | ||||
|     formatCurrency(n) { | ||||
|       const num = Number(n || 0); | ||||
|       return num.toLocaleString("ko-KR"); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .v-simple-table { | ||||
|   width: 100%; | ||||
| } | ||||
| </style> | ||||
| @@ -10,9 +10,29 @@ | ||||
|       <v-spacer /> | ||||
|       <v-toolbar-title>{{ isEdit ? '캐릭터 수정' : '캐릭터 등록' }}</v-toolbar-title> | ||||
|       <v-spacer /> | ||||
|       <v-btn small outlined color="primary" @click="exportToJson">JSON 다운로드</v-btn> | ||||
|       <input ref="importInput" type="file" accept="application/json,.json" style="display:none" @change="onImportFileChange" /> | ||||
|       <v-btn small color="primary" class="ml-2" @click="$refs.importInput.click()">JSON 업로드</v-btn> | ||||
|       <v-btn | ||||
|         small | ||||
|         outlined | ||||
|         color="primary" | ||||
|         @click="exportToJson" | ||||
|       > | ||||
|         JSON 다운로드 | ||||
|       </v-btn> | ||||
|       <input | ||||
|         ref="importInput" | ||||
|         type="file" | ||||
|         accept="application/json,.json" | ||||
|         style="display:none" | ||||
|         @change="onImportFileChange" | ||||
|       > | ||||
|       <v-btn | ||||
|         small | ||||
|         color="primary" | ||||
|         class="ml-2" | ||||
|         @click="$refs.importInput.click()" | ||||
|       > | ||||
|         JSON 업로드 | ||||
|       </v-btn> | ||||
|     </v-toolbar> | ||||
|  | ||||
|     <v-container> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user