에이전트 기능 #97

Merged
klaus merged 10 commits from test into main 2026-04-14 10:00:04 +00:00
7 changed files with 1225 additions and 45 deletions
Showing only changes of commit d5a75cd29f - Show all commits

View File

@@ -1,5 +1,11 @@
import Vue from 'vue' import Vue from 'vue'
// 공통: 페이지 파라미터 변환(1-based UI → 0-based Spring Pageable)
function toZeroBased(page) {
const p = Number(page || 1)
return Math.max(0, p - 1)
}
// 에이전트 리스트 조회 // 에이전트 리스트 조회
// 서버 스펙에 페이지네이션이 없다면 단순 GET으로 사용 // 서버 스펙에 페이지네이션이 없다면 단순 GET으로 사용
// 추후 필요 시 params(page,size) 확장 가능 // 추후 필요 시 params(page,size) 확장 가능
@@ -72,6 +78,53 @@ async function removeAgentCreator(payload) {
return Vue.axios.post('/admin/partner/agent/assignment/remove', payload) return Vue.axios.post('/admin/partner/agent/assignment/remove', payload)
} }
// =========================
// 정산 상세 - 에이전트별(크리에이터 기준 집계)
// 공통 Request: startDateStr, endDateStr, Spring Pageable(page,size,sort)
// 공통 Response: ApiResponse<GetAgentSettlementByCreatorResponse>
// { success, message, data: { totalCount, total:{...}, items:[...] } }
function buildSettlementParams({ startDateStr, endDateStr, page = 1, size = 20, sort }) {
const params = {
startDateStr,
endDateStr,
page: toZeroBased(page),
size
}
if (sort) params.sort = sort
return params
}
async function getAgentLiveSettlementByCreator(agentId, { startDateStr, endDateStr, page = 1, size = 20, sort } = {}) {
return Vue.axios.get(`/admin/partner/agent/${agentId}/calculate/live-by-creator`, {
params: buildSettlementParams({ startDateStr, endDateStr, page, size, sort })
})
}
async function getAgentContentSettlementByCreator(agentId, { startDateStr, endDateStr, page = 1, size = 20, sort } = {}) {
return Vue.axios.get(`/admin/partner/agent/${agentId}/calculate/content-by-creator`, {
params: buildSettlementParams({ startDateStr, endDateStr, page, size, sort })
})
}
async function getAgentCommunitySettlementByCreator(agentId, { startDateStr, endDateStr, page = 1, size = 20, sort } = {}) {
return Vue.axios.get(`/admin/partner/agent/${agentId}/calculate/community-by-creator`, {
params: buildSettlementParams({ startDateStr, endDateStr, page, size, sort })
})
}
async function getAgentContentDonationSettlementByCreator(agentId, { startDateStr, endDateStr, page = 1, size = 20, sort } = {}) {
return Vue.axios.get(`/admin/partner/agent/${agentId}/calculate/content-donation-by-creator`, {
params: buildSettlementParams({ startDateStr, endDateStr, page, size, sort })
})
}
async function getAgentChannelDonationSettlementByCreator(agentId, { startDateStr, endDateStr, page = 1, size = 20, sort } = {}) {
return Vue.axios.get(`/admin/partner/agent/${agentId}/calculate/channel-donation-by-creator`, {
params: buildSettlementParams({ startDateStr, endDateStr, page, size, sort })
})
}
export { export {
getAgentList, getAgentList,
getAgentSettlementRatioList, getAgentSettlementRatioList,
@@ -83,4 +136,10 @@ export {
searchAdminAgentAssignableCreators, searchAdminAgentAssignableCreators,
assignAgentCreator, assignAgentCreator,
removeAgentCreator, removeAgentCreator,
// 에이전트 정산 상세 (크리에이터 기준)
getAgentLiveSettlementByCreator,
getAgentContentSettlementByCreator,
getAgentCommunitySettlementByCreator,
getAgentContentDonationSettlementByCreator,
getAgentChannelDonationSettlementByCreator,
} }

View File

@@ -7,27 +7,244 @@
> >
<v-icon>mdi-arrow-left</v-icon> <v-icon>mdi-arrow-left</v-icon>
</v-btn> </v-btn>
<v-toolbar-title>에이전트 정산 상세 - 채널 후원</v-toolbar-title> <v-toolbar-title>{{ displayNickname }} 정산 상세 - 채널 후원</v-toolbar-title>
<v-spacer /> <v-spacer />
</v-toolbar> </v-toolbar>
<v-container> <v-container>
<v-row> <!-- 필터 영역 -->
<v-col> <v-row
<h3>채널 후원 정산 상세 페이지 (준비중)</h3> class="mt-2 mb-2"
<p>Agent ID: {{ agentId }}</p> align="center"
justify="end"
>
<v-col
cols="12"
md="3"
>
<v-menu
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-bind="attrs"
label="시작일"
readonly
dense
:value="startDateStr"
v-on="on"
/>
</template>
<v-date-picker
v-model="startDateStr"
scrollable
@input="menuStart = false"
/>
</v-menu>
</v-col>
<v-col
cols="12"
md="3"
>
<v-menu
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-bind="attrs"
label="종료일"
readonly
dense
:value="endDateStr"
v-on="on"
/>
</template>
<v-date-picker
v-model="endDateStr"
scrollable
@input="menuEnd = false"
/>
</v-menu>
</v-col>
<v-col
cols="12"
md="2"
>
<v-btn
color="primary"
:loading="isLoading"
@click="onSearch"
>
조회
</v-btn>
</v-col> </v-col>
</v-row> </v-row>
<!-- 테이블 영역 -->
<v-data-table
:headers="headers"
:items="items"
:loading="isLoading"
:items-per-page="pageSize"
class="elevation-1"
disable-pagination
hide-default-footer
>
<!-- 최상단 합계 -->
<template v-slot:body.prepend>
<tr>
<td class="text-center">
합계
</td>
<td class="text-center">
{{ numberFormat(total.count) }}
</td>
<td class="text-center">
{{ numberFormat(total.totalCan) }}
</td>
<td class="text-center">
{{ currencyKRW(total.krw) }}
</td>
<td class="text-center">
{{ currencyKRW(total.fee) }}
</td>
<td class="text-center">
{{ currencyKRW(total.settlementAmount) }}
</td>
<td class="text-center">
{{ currencyKRW(total.tax) }}
</td>
<td class="text-center">
{{ currencyKRW(total.depositAmount) }}
</td>
<td class="text-center">
{{ currencyKRW(total.agentSettlementAmount) }}
</td>
</tr>
</template>
<template v-slot:item.count="{ item }">
{{ numberFormat(item.count) }}
</template>
<template v-slot:item.totalCan="{ item }">
{{ numberFormat(item.totalCan) }}
</template>
<template v-slot:item.krw="{ item }">
{{ currencyKRW(item.krw) }}
</template>
<template v-slot:item.fee="{ item }">
{{ currencyKRW(item.fee) }}
</template>
<template v-slot:item.settlementAmount="{ item }">
{{ currencyKRW(item.settlementAmount) }}
</template>
<template v-slot:item.tax="{ item }">
{{ currencyKRW(item.tax) }}
</template>
<template v-slot:item.depositAmount="{ item }">
{{ currencyKRW(item.depositAmount) }}
</template>
<template v-slot:item.agentSettlementAmount="{ item }">
{{ currencyKRW(item.agentSettlementAmount) }}
</template>
</v-data-table>
<!-- 페이지네이션 -->
<div class="d-flex justify-center mt-2">
<v-pagination
v-model="page"
:length="totalPages"
:total-visible="7"
@input="onPageChange"
/>
</div>
</v-container> </v-container>
</div> </div>
</template> </template>
<script> <script>
import { getAgentChannelDonationSettlementByCreator } from '@/api/agent'
export default { export default {
name: 'AgentChannelDonationSettlement', name: 'AgentChannelDonationSettlement',
props: { props: { agentId: { type: [String, Number], required: true } },
agentId: { data() {
type: [String, Number], const today = new Date()
required: true const yyyy = today.getFullYear()
const mm = String(today.getMonth() + 1).padStart(2, '0')
const dd = String(today.getDate()).padStart(2, '0')
const firstDay = `${yyyy}-${mm}-01`
const endDay = `${yyyy}-${mm}-${dd}`
return {
startDateStr: firstDay,
endDateStr: endDay,
menuStart: false,
menuEnd: false,
page: 1,
pageSize: 20,
isLoading: false,
totalCount: 0,
totalPages: 1,
total: { count: 0, totalCan: 0, krw: 0, fee: 0, settlementAmount: 0, tax: 0, depositAmount: 0, agentSettlementAmount: 0 },
items: [],
headers: [
{ text: '닉네임', value: 'creatorNickname', align: 'center' },
{ text: '건수', value: 'count', align: 'center', width: 100 },
{ text: '총 CAN', value: 'totalCan', align: 'center', width: 120 },
{ text: '원화', value: 'krw', align: 'center', width: 140 },
{ text: '수수료', value: 'fee', align: 'center', width: 120 },
{ text: '정산금액', value: 'settlementAmount', align: 'center', width: 140 },
{ text: '세금', value: 'tax', align: 'center', width: 120 },
{ text: '입금액', value: 'depositAmount', align: 'center', width: 140 },
{ text: '에이전트 정산', value: 'agentSettlementAmount', align: 'center', width: 160 },
]
}
},
computed: {
displayNickname() {
const q = (this.$route && this.$route.query) || {}
return q.nickname || '에이전트'
}
},
mounted() { this.fetchList() },
methods: {
numberFormat(n) { return new Intl.NumberFormat('ko-KR').format(Number(n || 0)) },
currencyKRW(n) { return new Intl.NumberFormat('ko-KR', { style: 'currency', currency: 'KRW', maximumFractionDigits: 0 }).format(Number(n || 0)) },
onSearch() { this.page = 1; this.fetchList() },
onPageChange() { this.fetchList() },
async fetchList() {
this.isLoading = true
try {
const res = await getAgentChannelDonationSettlementByCreator(this.agentId, {
startDateStr: this.startDateStr,
endDateStr: this.endDateStr,
page: this.page,
size: this.pageSize,
})
let payload = res && res.data ? res.data : null
if (payload && payload.data && (!payload.items && !payload.totalCount)) payload = payload.data
const data = payload || { totalCount: 0, total: {}, items: [] }
this.totalCount = Number(data.totalCount || 0)
this.totalPages = Math.max(1, Math.ceil(this.totalCount / this.pageSize))
const defTotal = { count: 0, totalCan: 0, krw: 0, fee: 0, settlementAmount: 0, tax: 0, depositAmount: 0, agentSettlementAmount: 0 }
this.total = Object.assign({}, defTotal, data.total || {})
this.items = Array.isArray(data.items) ? data.items : []
} catch (e) {
this.totalCount = 0
this.totalPages = 1
this.total = { count: 0, totalCan: 0, krw: 0, fee: 0, settlementAmount: 0, tax: 0, depositAmount: 0, agentSettlementAmount: 0 }
this.items = []
} finally {
this.isLoading = false
}
} }
} }
} }

View File

@@ -7,27 +7,244 @@
> >
<v-icon>mdi-arrow-left</v-icon> <v-icon>mdi-arrow-left</v-icon>
</v-btn> </v-btn>
<v-toolbar-title>에이전트 정산 상세 - 커뮤니티</v-toolbar-title> <v-toolbar-title>{{ displayNickname }} 정산 상세 - 커뮤니티</v-toolbar-title>
<v-spacer /> <v-spacer />
</v-toolbar> </v-toolbar>
<v-container> <v-container>
<v-row> <!-- 필터 영역 -->
<v-col> <v-row
<h3>커뮤니티 정산 상세 페이지 (준비중)</h3> class="mt-2 mb-2"
<p>Agent ID: {{ agentId }}</p> align="center"
justify="end"
>
<v-col
cols="12"
md="3"
>
<v-menu
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-bind="attrs"
label="시작일"
readonly
dense
:value="startDateStr"
v-on="on"
/>
</template>
<v-date-picker
v-model="startDateStr"
scrollable
@input="menuStart = false"
/>
</v-menu>
</v-col>
<v-col
cols="12"
md="3"
>
<v-menu
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-bind="attrs"
label="종료일"
readonly
dense
:value="endDateStr"
v-on="on"
/>
</template>
<v-date-picker
v-model="endDateStr"
scrollable
@input="menuEnd = false"
/>
</v-menu>
</v-col>
<v-col
cols="12"
md="2"
>
<v-btn
color="primary"
:loading="isLoading"
@click="onSearch"
>
조회
</v-btn>
</v-col> </v-col>
</v-row> </v-row>
<!-- 테이블 영역 -->
<v-data-table
:headers="headers"
:items="items"
:loading="isLoading"
:items-per-page="pageSize"
class="elevation-1"
disable-pagination
hide-default-footer
>
<!-- 최상단 합계 -->
<template v-slot:body.prepend>
<tr>
<td class="text-center">
합계
</td>
<td class="text-center">
{{ numberFormat(total.count) }}
</td>
<td class="text-center">
{{ numberFormat(total.totalCan) }}
</td>
<td class="text-center">
{{ currencyKRW(total.krw) }}
</td>
<td class="text-center">
{{ currencyKRW(total.fee) }}
</td>
<td class="text-center">
{{ currencyKRW(total.settlementAmount) }}
</td>
<td class="text-center">
{{ currencyKRW(total.tax) }}
</td>
<td class="text-center">
{{ currencyKRW(total.depositAmount) }}
</td>
<td class="text-center">
{{ currencyKRW(total.agentSettlementAmount) }}
</td>
</tr>
</template>
<template v-slot:item.count="{ item }">
{{ numberFormat(item.count) }}
</template>
<template v-slot:item.totalCan="{ item }">
{{ numberFormat(item.totalCan) }}
</template>
<template v-slot:item.krw="{ item }">
{{ currencyKRW(item.krw) }}
</template>
<template v-slot:item.fee="{ item }">
{{ currencyKRW(item.fee) }}
</template>
<template v-slot:item.settlementAmount="{ item }">
{{ currencyKRW(item.settlementAmount) }}
</template>
<template v-slot:item.tax="{ item }">
{{ currencyKRW(item.tax) }}
</template>
<template v-slot:item.depositAmount="{ item }">
{{ currencyKRW(item.depositAmount) }}
</template>
<template v-slot:item.agentSettlementAmount="{ item }">
{{ currencyKRW(item.agentSettlementAmount) }}
</template>
</v-data-table>
<!-- 페이지네이션 -->
<div class="d-flex justify-center mt-2">
<v-pagination
v-model="page"
:length="totalPages"
:total-visible="7"
@input="onPageChange"
/>
</div>
</v-container> </v-container>
</div> </div>
</template> </template>
<script> <script>
import { getAgentCommunitySettlementByCreator } from '@/api/agent'
export default { export default {
name: 'AgentCommunitySettlement', name: 'AgentCommunitySettlement',
props: { props: { agentId: { type: [String, Number], required: true } },
agentId: { data() {
type: [String, Number], const today = new Date()
required: true const yyyy = today.getFullYear()
const mm = String(today.getMonth() + 1).padStart(2, '0')
const dd = String(today.getDate()).padStart(2, '0')
const firstDay = `${yyyy}-${mm}-01`
const endDay = `${yyyy}-${mm}-${dd}`
return {
startDateStr: firstDay,
endDateStr: endDay,
menuStart: false,
menuEnd: false,
page: 1,
pageSize: 20,
isLoading: false,
totalCount: 0,
totalPages: 1,
total: { count: 0, totalCan: 0, krw: 0, fee: 0, settlementAmount: 0, tax: 0, depositAmount: 0, agentSettlementAmount: 0 },
items: [],
headers: [
{ text: '닉네임', value: 'creatorNickname', align: 'center' },
{ text: '건수', value: 'count', align: 'center', width: 100 },
{ text: '총 CAN', value: 'totalCan', align: 'center', width: 120 },
{ text: '원화', value: 'krw', align: 'center', width: 140 },
{ text: '수수료', value: 'fee', align: 'center', width: 120 },
{ text: '정산금액', value: 'settlementAmount', align: 'center', width: 140 },
{ text: '세금', value: 'tax', align: 'center', width: 120 },
{ text: '입금액', value: 'depositAmount', align: 'center', width: 140 },
{ text: '에이전트 정산', value: 'agentSettlementAmount', align: 'center', width: 160 },
]
}
},
computed: {
displayNickname() {
const q = (this.$route && this.$route.query) || {}
return q.nickname || '에이전트'
}
},
mounted() { this.fetchList() },
methods: {
numberFormat(n) { return new Intl.NumberFormat('ko-KR').format(Number(n || 0)) },
currencyKRW(n) { return new Intl.NumberFormat('ko-KR', { style: 'currency', currency: 'KRW', maximumFractionDigits: 0 }).format(Number(n || 0)) },
onSearch() { this.page = 1; this.fetchList() },
onPageChange() { this.fetchList() },
async fetchList() {
this.isLoading = true
try {
const res = await getAgentCommunitySettlementByCreator(this.agentId, {
startDateStr: this.startDateStr,
endDateStr: this.endDateStr,
page: this.page,
size: this.pageSize,
})
let payload = res && res.data ? res.data : null
if (payload && payload.data && (!payload.items && !payload.totalCount)) payload = payload.data
const data = payload || { totalCount: 0, total: {}, items: [] }
this.totalCount = Number(data.totalCount || 0)
this.totalPages = Math.max(1, Math.ceil(this.totalCount / this.pageSize))
const defTotal = { count: 0, totalCan: 0, krw: 0, fee: 0, settlementAmount: 0, tax: 0, depositAmount: 0, agentSettlementAmount: 0 }
this.total = Object.assign({}, defTotal, data.total || {})
this.items = Array.isArray(data.items) ? data.items : []
} catch (e) {
this.totalCount = 0
this.totalPages = 1
this.total = { count: 0, totalCan: 0, krw: 0, fee: 0, settlementAmount: 0, tax: 0, depositAmount: 0, agentSettlementAmount: 0 }
this.items = []
} finally {
this.isLoading = false
}
} }
} }
} }

View File

@@ -7,27 +7,244 @@
> >
<v-icon>mdi-arrow-left</v-icon> <v-icon>mdi-arrow-left</v-icon>
</v-btn> </v-btn>
<v-toolbar-title>에이전트 정산 상세 - 콘텐츠 후원</v-toolbar-title> <v-toolbar-title>{{ displayNickname }} 정산 상세 - 콘텐츠 후원</v-toolbar-title>
<v-spacer /> <v-spacer />
</v-toolbar> </v-toolbar>
<v-container> <v-container>
<v-row> <!-- 필터 영역 -->
<v-col> <v-row
<h3>콘텐츠 후원 정산 상세 페이지 (준비중)</h3> class="mt-2 mb-2"
<p>Agent ID: {{ agentId }}</p> align="center"
justify="end"
>
<v-col
cols="12"
md="3"
>
<v-menu
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-bind="attrs"
label="시작일"
readonly
dense
:value="startDateStr"
v-on="on"
/>
</template>
<v-date-picker
v-model="startDateStr"
scrollable
@input="menuStart = false"
/>
</v-menu>
</v-col>
<v-col
cols="12"
md="3"
>
<v-menu
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-bind="attrs"
label="종료일"
readonly
dense
:value="endDateStr"
v-on="on"
/>
</template>
<v-date-picker
v-model="endDateStr"
scrollable
@input="menuEnd = false"
/>
</v-menu>
</v-col>
<v-col
cols="12"
md="2"
>
<v-btn
color="primary"
:loading="isLoading"
@click="onSearch"
>
조회
</v-btn>
</v-col> </v-col>
</v-row> </v-row>
<!-- 테이블 영역 -->
<v-data-table
:headers="headers"
:items="items"
:loading="isLoading"
:items-per-page="pageSize"
class="elevation-1"
disable-pagination
hide-default-footer
>
<!-- 최상단 합계 -->
<template v-slot:body.prepend>
<tr>
<td class="text-center">
합계
</td>
<td class="text-center">
{{ numberFormat(total.count) }}
</td>
<td class="text-center">
{{ numberFormat(total.totalCan) }}
</td>
<td class="text-center">
{{ currencyKRW(total.krw) }}
</td>
<td class="text-center">
{{ currencyKRW(total.fee) }}
</td>
<td class="text-center">
{{ currencyKRW(total.settlementAmount) }}
</td>
<td class="text-center">
{{ currencyKRW(total.tax) }}
</td>
<td class="text-center">
{{ currencyKRW(total.depositAmount) }}
</td>
<td class="text-center">
{{ currencyKRW(total.agentSettlementAmount) }}
</td>
</tr>
</template>
<template v-slot:item.count="{ item }">
{{ numberFormat(item.count) }}
</template>
<template v-slot:item.totalCan="{ item }">
{{ numberFormat(item.totalCan) }}
</template>
<template v-slot:item.krw="{ item }">
{{ currencyKRW(item.krw) }}
</template>
<template v-slot:item.fee="{ item }">
{{ currencyKRW(item.fee) }}
</template>
<template v-slot:item.settlementAmount="{ item }">
{{ currencyKRW(item.settlementAmount) }}
</template>
<template v-slot:item.tax="{ item }">
{{ currencyKRW(item.tax) }}
</template>
<template v-slot:item.depositAmount="{ item }">
{{ currencyKRW(item.depositAmount) }}
</template>
<template v-slot:item.agentSettlementAmount="{ item }">
{{ currencyKRW(item.agentSettlementAmount) }}
</template>
</v-data-table>
<!-- 페이지네이션 -->
<div class="d-flex justify-center mt-2">
<v-pagination
v-model="page"
:length="totalPages"
:total-visible="7"
@input="onPageChange"
/>
</div>
</v-container> </v-container>
</div> </div>
</template> </template>
<script> <script>
import { getAgentContentDonationSettlementByCreator } from '@/api/agent'
export default { export default {
name: 'AgentContentDonationSettlement', name: 'AgentContentDonationSettlement',
props: { props: { agentId: { type: [String, Number], required: true } },
agentId: { data() {
type: [String, Number], const today = new Date()
required: true const yyyy = today.getFullYear()
const mm = String(today.getMonth() + 1).padStart(2, '0')
const dd = String(today.getDate()).padStart(2, '0')
const firstDay = `${yyyy}-${mm}-01`
const endDay = `${yyyy}-${mm}-${dd}`
return {
startDateStr: firstDay,
endDateStr: endDay,
menuStart: false,
menuEnd: false,
page: 1,
pageSize: 20,
isLoading: false,
totalCount: 0,
totalPages: 1,
total: { count: 0, totalCan: 0, krw: 0, fee: 0, settlementAmount: 0, tax: 0, depositAmount: 0, agentSettlementAmount: 0 },
items: [],
headers: [
{ text: '닉네임', value: 'creatorNickname', align: 'center' },
{ text: '건수', value: 'count', align: 'center', width: 100 },
{ text: '총 CAN', value: 'totalCan', align: 'center', width: 120 },
{ text: '원화', value: 'krw', align: 'center', width: 140 },
{ text: '수수료', value: 'fee', align: 'center', width: 120 },
{ text: '정산금액', value: 'settlementAmount', align: 'center', width: 140 },
{ text: '세금', value: 'tax', align: 'center', width: 120 },
{ text: '입금액', value: 'depositAmount', align: 'center', width: 140 },
{ text: '에이전트 정산', value: 'agentSettlementAmount', align: 'center', width: 160 },
]
}
},
computed: {
displayNickname() {
const q = (this.$route && this.$route.query) || {}
return q.nickname || '에이전트'
}
},
mounted() { this.fetchList() },
methods: {
numberFormat(n) { return new Intl.NumberFormat('ko-KR').format(Number(n || 0)) },
currencyKRW(n) { return new Intl.NumberFormat('ko-KR', { style: 'currency', currency: 'KRW', maximumFractionDigits: 0 }).format(Number(n || 0)) },
onSearch() { this.page = 1; this.fetchList() },
onPageChange() { this.fetchList() },
async fetchList() {
this.isLoading = true
try {
const res = await getAgentContentDonationSettlementByCreator(this.agentId, {
startDateStr: this.startDateStr,
endDateStr: this.endDateStr,
page: this.page,
size: this.pageSize,
})
let payload = res && res.data ? res.data : null
if (payload && payload.data && (!payload.items && !payload.totalCount)) payload = payload.data
const data = payload || { totalCount: 0, total: {}, items: [] }
this.totalCount = Number(data.totalCount || 0)
this.totalPages = Math.max(1, Math.ceil(this.totalCount / this.pageSize))
const defTotal = { count: 0, totalCan: 0, krw: 0, fee: 0, settlementAmount: 0, tax: 0, depositAmount: 0, agentSettlementAmount: 0 }
this.total = Object.assign({}, defTotal, data.total || {})
this.items = Array.isArray(data.items) ? data.items : []
} catch (e) {
this.totalCount = 0
this.totalPages = 1
this.total = { count: 0, totalCan: 0, krw: 0, fee: 0, settlementAmount: 0, tax: 0, depositAmount: 0, agentSettlementAmount: 0 }
this.items = []
} finally {
this.isLoading = false
}
} }
} }
} }

View File

@@ -7,27 +7,244 @@
> >
<v-icon>mdi-arrow-left</v-icon> <v-icon>mdi-arrow-left</v-icon>
</v-btn> </v-btn>
<v-toolbar-title>에이전트 정산 상세 - 콘텐츠</v-toolbar-title> <v-toolbar-title>{{ displayNickname }} 정산 상세 - 콘텐츠</v-toolbar-title>
<v-spacer /> <v-spacer />
</v-toolbar> </v-toolbar>
<v-container> <v-container>
<v-row> <!-- 필터 영역 -->
<v-col> <v-row
<h3>콘텐츠 정산 상세 페이지 (준비중)</h3> class="mt-2 mb-2"
<p>Agent ID: {{ agentId }}</p> align="center"
justify="end"
>
<v-col
cols="12"
md="3"
>
<v-menu
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-bind="attrs"
label="시작일"
readonly
dense
:value="startDateStr"
v-on="on"
/>
</template>
<v-date-picker
v-model="startDateStr"
scrollable
@input="menuStart = false"
/>
</v-menu>
</v-col>
<v-col
cols="12"
md="3"
>
<v-menu
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-bind="attrs"
label="종료일"
readonly
dense
:value="endDateStr"
v-on="on"
/>
</template>
<v-date-picker
v-model="endDateStr"
scrollable
@input="menuEnd = false"
/>
</v-menu>
</v-col>
<v-col
cols="12"
md="2"
>
<v-btn
color="primary"
:loading="isLoading"
@click="onSearch"
>
조회
</v-btn>
</v-col> </v-col>
</v-row> </v-row>
<!-- 테이블 영역 -->
<v-data-table
:headers="headers"
:items="items"
:loading="isLoading"
:items-per-page="pageSize"
class="elevation-1"
disable-pagination
hide-default-footer
>
<!-- 최상단 합계 -->
<template v-slot:body.prepend>
<tr>
<td class="text-center">
합계
</td>
<td class="text-center">
{{ numberFormat(total.count) }}
</td>
<td class="text-center">
{{ numberFormat(total.totalCan) }}
</td>
<td class="text-center">
{{ currencyKRW(total.krw) }}
</td>
<td class="text-center">
{{ currencyKRW(total.fee) }}
</td>
<td class="text-center">
{{ currencyKRW(total.settlementAmount) }}
</td>
<td class="text-center">
{{ currencyKRW(total.tax) }}
</td>
<td class="text-center">
{{ currencyKRW(total.depositAmount) }}
</td>
<td class="text-center">
{{ currencyKRW(total.agentSettlementAmount) }}
</td>
</tr>
</template>
<template v-slot:item.count="{ item }">
{{ numberFormat(item.count) }}
</template>
<template v-slot:item.totalCan="{ item }">
{{ numberFormat(item.totalCan) }}
</template>
<template v-slot:item.krw="{ item }">
{{ currencyKRW(item.krw) }}
</template>
<template v-slot:item.fee="{ item }">
{{ currencyKRW(item.fee) }}
</template>
<template v-slot:item.settlementAmount="{ item }">
{{ currencyKRW(item.settlementAmount) }}
</template>
<template v-slot:item.tax="{ item }">
{{ currencyKRW(item.tax) }}
</template>
<template v-slot:item.depositAmount="{ item }">
{{ currencyKRW(item.depositAmount) }}
</template>
<template v-slot:item.agentSettlementAmount="{ item }">
{{ currencyKRW(item.agentSettlementAmount) }}
</template>
</v-data-table>
<!-- 페이지네이션 -->
<div class="d-flex justify-center mt-2">
<v-pagination
v-model="page"
:length="totalPages"
:total-visible="7"
@input="onPageChange"
/>
</div>
</v-container> </v-container>
</div> </div>
</template> </template>
<script> <script>
import { getAgentContentSettlementByCreator } from '@/api/agent'
export default { export default {
name: 'AgentContentSettlement', name: 'AgentContentSettlement',
props: { props: { agentId: { type: [String, Number], required: true } },
agentId: { data() {
type: [String, Number], const today = new Date()
required: true const yyyy = today.getFullYear()
const mm = String(today.getMonth() + 1).padStart(2, '0')
const dd = String(today.getDate()).padStart(2, '0')
const firstDay = `${yyyy}-${mm}-01`
const endDay = `${yyyy}-${mm}-${dd}`
return {
startDateStr: firstDay,
endDateStr: endDay,
menuStart: false,
menuEnd: false,
page: 1,
pageSize: 20,
isLoading: false,
totalCount: 0,
totalPages: 1,
total: { count: 0, totalCan: 0, krw: 0, fee: 0, settlementAmount: 0, tax: 0, depositAmount: 0, agentSettlementAmount: 0 },
items: [],
headers: [
{ text: '닉네임', value: 'creatorNickname', align: 'center' },
{ text: '건수', value: 'count', align: 'center', width: 100 },
{ text: '총 CAN', value: 'totalCan', align: 'center', width: 120 },
{ text: '원화', value: 'krw', align: 'center', width: 140 },
{ text: '수수료', value: 'fee', align: 'center', width: 120 },
{ text: '정산금액', value: 'settlementAmount', align: 'center', width: 140 },
{ text: '세금', value: 'tax', align: 'center', width: 120 },
{ text: '입금액', value: 'depositAmount', align: 'center', width: 140 },
{ text: '에이전트 정산', value: 'agentSettlementAmount', align: 'center', width: 160 },
]
}
},
computed: {
displayNickname() {
const q = (this.$route && this.$route.query) || {}
return q.nickname || '에이전트'
}
},
mounted() { this.fetchList() },
methods: {
numberFormat(n) { return new Intl.NumberFormat('ko-KR').format(Number(n || 0)) },
currencyKRW(n) { return new Intl.NumberFormat('ko-KR', { style: 'currency', currency: 'KRW', maximumFractionDigits: 0 }).format(Number(n || 0)) },
onSearch() { this.page = 1; this.fetchList() },
onPageChange() { this.fetchList() },
async fetchList() {
this.isLoading = true
try {
const res = await getAgentContentSettlementByCreator(this.agentId, {
startDateStr: this.startDateStr,
endDateStr: this.endDateStr,
page: this.page,
size: this.pageSize,
})
let payload = res && res.data ? res.data : null
if (payload && payload.data && (!payload.items && !payload.totalCount)) payload = payload.data
const data = payload || { totalCount: 0, total: {}, items: [] }
this.totalCount = Number(data.totalCount || 0)
this.totalPages = Math.max(1, Math.ceil(this.totalCount / this.pageSize))
const defTotal = { count: 0, totalCan: 0, krw: 0, fee: 0, settlementAmount: 0, tax: 0, depositAmount: 0, agentSettlementAmount: 0 }
this.total = Object.assign({}, defTotal, data.total || {})
this.items = Array.isArray(data.items) ? data.items : []
} catch (e) {
this.totalCount = 0
this.totalPages = 1
this.total = { count: 0, totalCan: 0, krw: 0, fee: 0, settlementAmount: 0, tax: 0, depositAmount: 0, agentSettlementAmount: 0 }
this.items = []
} finally {
this.isLoading = false
}
} }
} }
} }

View File

@@ -206,7 +206,7 @@ export default {
} }
const name = nameMap[type] const name = nameMap[type]
if (!name) return if (!name) return
this.$router.push({ name, params: { agentId: id } }) this.$router.push({ name, params: { agentId: id }, query: { nickname: item.agentNickname } })
} }
} }
} }

View File

@@ -7,27 +7,280 @@
> >
<v-icon>mdi-arrow-left</v-icon> <v-icon>mdi-arrow-left</v-icon>
</v-btn> </v-btn>
<v-toolbar-title>에이전트 정산 상세 - 라이브</v-toolbar-title> <v-toolbar-title>{{ displayNickname }} 정산 상세 - 라이브</v-toolbar-title>
<v-spacer /> <v-spacer />
</v-toolbar> </v-toolbar>
<v-container> <v-container>
<v-row> <!-- 필터 영역 -->
<v-col> <v-row
<h3>라이브 정산 상세 페이지 (준비중)</h3> class="mt-2 mb-2"
<p>Agent ID: {{ agentId }}</p> align="center"
justify="end"
>
<v-col
cols="12"
md="3"
>
<v-menu
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-bind="attrs"
label="시작일"
readonly
dense
:value="startDateStr"
v-on="on"
/>
</template>
<v-date-picker
v-model="startDateStr"
scrollable
@input="menuStart = false"
/>
</v-menu>
</v-col>
<v-col
cols="12"
md="3"
>
<v-menu
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-bind="attrs"
label="종료일"
readonly
dense
:value="endDateStr"
v-on="on"
/>
</template>
<v-date-picker
v-model="endDateStr"
scrollable
@input="menuEnd = false"
/>
</v-menu>
</v-col>
<v-col
cols="12"
md="2"
>
<v-btn
color="primary"
:loading="isLoading"
@click="onSearch"
>
조회
</v-btn>
</v-col> </v-col>
</v-row> </v-row>
<!-- 테이블 영역 -->
<v-data-table
:headers="headers"
:items="items"
:loading="isLoading"
:items-per-page="pageSize"
class="elevation-1"
disable-pagination
hide-default-footer
>
<!-- 최상단 합계 -->
<template v-slot:body.prepend>
<tr>
<td class="text-center">
합계
</td>
<td class="text-center">
{{ numberFormat(total.count) }}
</td>
<td class="text-center">
{{ numberFormat(total.totalCan) }}
</td>
<td class="text-center">
{{ currencyKRW(total.krw) }}
</td>
<td class="text-center">
{{ currencyKRW(total.fee) }}
</td>
<td class="text-center">
{{ currencyKRW(total.settlementAmount) }}
</td>
<td class="text-center">
{{ currencyKRW(total.tax) }}
</td>
<td class="text-center">
{{ currencyKRW(total.depositAmount) }}
</td>
<td class="text-center">
{{ currencyKRW(total.agentSettlementAmount) }}
</td>
</tr>
</template>
<template v-slot:item.count="{ item }">
{{ numberFormat(item.count) }}
</template>
<template v-slot:item.totalCan="{ item }">
{{ numberFormat(item.totalCan) }}
</template>
<template v-slot:item.krw="{ item }">
{{ currencyKRW(item.krw) }}
</template>
<template v-slot:item.fee="{ item }">
{{ currencyKRW(item.fee) }}
</template>
<template v-slot:item.settlementAmount="{ item }">
{{ currencyKRW(item.settlementAmount) }}
</template>
<template v-slot:item.tax="{ item }">
{{ currencyKRW(item.tax) }}
</template>
<template v-slot:item.depositAmount="{ item }">
{{ currencyKRW(item.depositAmount) }}
</template>
<template v-slot:item.agentSettlementAmount="{ item }">
{{ currencyKRW(item.agentSettlementAmount) }}
</template>
</v-data-table>
<!-- 페이지네이션 -->
<div class="d-flex justify-center mt-2">
<v-pagination
v-model="page"
:length="totalPages"
:total-visible="7"
@input="onPageChange"
/>
</div>
</v-container> </v-container>
</div> </div>
</template> </template>
<script> <script>
import { getAgentLiveSettlementByCreator } from '@/api/agent'
export default { export default {
name: 'AgentLiveSettlement', name: 'AgentLiveSettlement',
props: { props: {
agentId: { agentId: { type: [String, Number], required: true }
type: [String, Number], },
required: true data() {
const today = new Date()
const yyyy = today.getFullYear()
const mm = String(today.getMonth() + 1).padStart(2, '0')
const dd = String(today.getDate()).padStart(2, '0')
const firstDay = `${yyyy}-${mm}-01`
const endDay = `${yyyy}-${mm}-${dd}`
return {
// 필터
startDateStr: firstDay,
endDateStr: endDay,
menuStart: false,
menuEnd: false,
page: 1,
pageSize: 20,
// 데이터
isLoading: false,
totalCount: 0,
totalPages: 1,
total: {
count: 0,
totalCan: 0,
krw: 0,
fee: 0,
settlementAmount: 0,
tax: 0,
depositAmount: 0,
agentSettlementAmount: 0
},
items: [],
headers: [
{ text: '닉네임', value: 'creatorNickname', align: 'center' },
{ text: '건수', value: 'count', align: 'center', width: 100 },
{ text: '총 CAN', value: 'totalCan', align: 'center', width: 120 },
{ text: '원화', value: 'krw', align: 'center', width: 140 },
{ text: '수수료', value: 'fee', align: 'center', width: 120 },
{ text: '정산금액', value: 'settlementAmount', align: 'center', width: 140 },
{ text: '세금', value: 'tax', align: 'center', width: 120 },
{ text: '입금액', value: 'depositAmount', align: 'center', width: 140 },
{ text: '에이전트 정산', value: 'agentSettlementAmount', align: 'center', width: 160 },
]
}
},
computed: {
displayNickname() {
const q = (this.$route && this.$route.query) || {}
return q.nickname || '에이전트'
}
},
mounted() {
this.fetchList()
},
methods: {
numberFormat(n) {
const v = Number(n || 0)
return new Intl.NumberFormat('ko-KR').format(v)
},
currencyKRW(n) {
const v = Number(n || 0)
return new Intl.NumberFormat('ko-KR', { style: 'currency', currency: 'KRW', maximumFractionDigits: 0 }).format(v)
},
onSearch() {
this.page = 1
this.fetchList()
},
onPageChange() {
this.fetchList()
},
async fetchList() {
this.isLoading = true
try {
const res = await getAgentLiveSettlementByCreator(this.agentId, {
startDateStr: this.startDateStr,
endDateStr: this.endDateStr,
// UI는 1-based, API 유틸이 0-based로 변환함
page: this.page,
size: this.pageSize,
})
// 일부 API가 { data: {...} } 래핑일 수 있으므로 방어적으로 파싱
let payload = res && res.data ? res.data : null
if (payload && payload.data && (!payload.items && !payload.totalCount)) {
payload = payload.data
}
const data = payload || { totalCount: 0, total: {}, items: [] }
this.totalCount = Number(data.totalCount || 0)
this.totalPages = Math.max(1, Math.ceil(this.totalCount / this.pageSize))
const defTotal = {
count: 0, totalCan: 0, krw: 0, fee: 0, settlementAmount: 0, tax: 0, depositAmount: 0, agentSettlementAmount: 0
}
this.total = Object.assign({}, defTotal, data.total || {})
this.items = Array.isArray(data.items) ? data.items : []
} catch (e) {
this.totalCount = 0
this.totalPages = 1
this.total = { count: 0, totalCan: 0, krw: 0, fee: 0, settlementAmount: 0, tax: 0, depositAmount: 0, agentSettlementAmount: 0 }
this.items = []
} finally {
this.isLoading = false
}
} }
} }
} }