feat(character-calculator): 캐릭터별 정산 추가
This commit is contained in:
		| @@ -247,6 +247,14 @@ async function getCharactersInCuration(curationId) { | |||||||
|   return Vue.axios.get(`/admin/chat/character/curation/${curationId}/characters`) |   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 { | export { | ||||||
|   getCharacterList, |   getCharacterList, | ||||||
|   searchCharacters, |   searchCharacters, | ||||||
| @@ -273,5 +281,6 @@ export { | |||||||
|   addCharacterToCuration, |   addCharacterToCuration, | ||||||
|   removeCharacterFromCuration, |   removeCharacterFromCuration, | ||||||
|   updateCurationCharactersOrder, |   updateCurationCharactersOrder, | ||||||
|   getCharactersInCuration |   getCharactersInCuration, | ||||||
|  |   getCharacterCalculateList | ||||||
| } | } | ||||||
|   | |||||||
| @@ -116,7 +116,12 @@ export default { | |||||||
|                 title: '큐레이션', |                 title: '큐레이션', | ||||||
|                 route: '/character/curation', |                 route: '/character/curation', | ||||||
|                 items: null |                 items: null | ||||||
|               } |               }, | ||||||
|  |               { | ||||||
|  |                 title: '정산', | ||||||
|  |                 route: '/character/calculate', | ||||||
|  |                 items: null | ||||||
|  |               }, | ||||||
|             ] |             ] | ||||||
|           }) |           }) | ||||||
|         } else { |         } else { | ||||||
|   | |||||||
| @@ -290,6 +290,11 @@ const routes = [ | |||||||
|                 name: 'CharacterCurationDetail', |                 name: 'CharacterCurationDetail', | ||||||
|                 component: () => import(/* webpackChunkName: "character" */ '../views/Chat/CharacterCurationDetail.vue') |                 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> | ||||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung