|
|
@@ -166,6 +166,12 @@
|
|
|
<template v-slot:item.amount="{ item }">
|
|
|
{{ formatCurrency(item.amount) }}
|
|
|
</template>
|
|
|
+ <template v-slot:item.deliveryCompany="{ item }">
|
|
|
+ {{ item.deliveryInfo?.company || '-' }}
|
|
|
+ </template>
|
|
|
+ <template v-slot:item.trackingNumber="{ item }">
|
|
|
+ {{ item.deliveryInfo?.number || '-' }}
|
|
|
+ </template>
|
|
|
<template v-slot:item.status="{ item }">
|
|
|
<v-chip
|
|
|
:color="getStatusColor(item.status)"
|
|
|
@@ -269,37 +275,14 @@
|
|
|
{ title: '인플루언서', key: 'influencer', sortable: true },
|
|
|
{ title: '상품명', key: 'productName', sortable: false },
|
|
|
{ title: '주문금액', key: 'amount', sortable: true },
|
|
|
+ { title: '배송업체', key: 'deliveryCompany', sortable: false },
|
|
|
+ { title: '송장번호', key: 'trackingNumber', sortable: false },
|
|
|
{ title: '주문일', key: 'orderDate', sortable: true },
|
|
|
{ title: '상태', key: 'status', sortable: true },
|
|
|
{ title: '액션', key: 'actions', sortable: false }
|
|
|
]);
|
|
|
|
|
|
- const recentOrders = ref([
|
|
|
- {
|
|
|
- orderNo: 'ORD-2025001',
|
|
|
- influencer: '김인플루',
|
|
|
- productName: '프리미엄 화장품 세트',
|
|
|
- amount: 150000,
|
|
|
- orderDate: '2025-01-15',
|
|
|
- status: 'completed'
|
|
|
- },
|
|
|
- {
|
|
|
- orderNo: 'ORD-2025002',
|
|
|
- influencer: '박유튜버',
|
|
|
- productName: '건강기능식품',
|
|
|
- amount: 85000,
|
|
|
- orderDate: '2025-01-14',
|
|
|
- status: 'shipping'
|
|
|
- },
|
|
|
- {
|
|
|
- orderNo: 'ORD-2025003',
|
|
|
- influencer: '이인스타',
|
|
|
- productName: '패션 액세서리',
|
|
|
- amount: 65000,
|
|
|
- orderDate: '2025-01-13',
|
|
|
- status: 'pending'
|
|
|
- }
|
|
|
- ]);
|
|
|
+ const recentOrders = ref([]);
|
|
|
|
|
|
/************************************************************************
|
|
|
| 함수(METHODS)
|
|
|
@@ -348,6 +331,7 @@
|
|
|
const getStatusColor = (status) => {
|
|
|
switch (status) {
|
|
|
case 'completed': return 'success';
|
|
|
+ case 'delivered': return 'primary';
|
|
|
case 'shipping': return 'info';
|
|
|
case 'pending': return 'warning';
|
|
|
case 'cancelled': return 'error';
|
|
|
@@ -358,7 +342,8 @@
|
|
|
// 상태 텍스트
|
|
|
const getStatusText = (status) => {
|
|
|
switch (status) {
|
|
|
- case 'completed': return '완료';
|
|
|
+ case 'completed': return '정산완료';
|
|
|
+ case 'delivered': return '배송완료';
|
|
|
case 'shipping': return '배송중';
|
|
|
case 'pending': return '대기';
|
|
|
case 'cancelled': return '취소';
|
|
|
@@ -384,13 +369,18 @@
|
|
|
if (!salesChart.value) return;
|
|
|
|
|
|
const ctx = salesChart.value.getContext('2d');
|
|
|
+ // 실제 데이터가 있으면 사용, 없으면 빈 차트
|
|
|
+ const salesData = metrics.value.totalSales > 0 ?
|
|
|
+ [0, 0, 0, 0, 0, metrics.value.totalSales] :
|
|
|
+ [0, 0, 0, 0, 0, 0];
|
|
|
+
|
|
|
new ChartJS(ctx, {
|
|
|
type: salesChartType.value,
|
|
|
data: {
|
|
|
labels: ['1월', '2월', '3월', '4월', '5월', '6월'],
|
|
|
datasets: [{
|
|
|
label: '매출',
|
|
|
- data: [12000000, 15000000, 13000000, 18000000, 16000000, 20000000],
|
|
|
+ data: salesData,
|
|
|
borderColor: '#3f51b5',
|
|
|
backgroundColor: salesChartType.value === 'bar' ? '#3f51b5' : 'rgba(63, 81, 181, 0.1)',
|
|
|
borderWidth: 2,
|
|
|
@@ -423,12 +413,19 @@
|
|
|
if (!ordersChart.value) return;
|
|
|
|
|
|
const ctx = ordersChart.value.getContext('2d');
|
|
|
+ // 실제 주문 데이터 계산
|
|
|
+ const totalOrders = metrics.value.totalOrders || 1;
|
|
|
+ const completedRate = metrics.value.settlementRate || 0;
|
|
|
+ const deliveredRate = Math.max(0, completedRate - 10); // 임시 계산
|
|
|
+ const shippingRate = Math.max(0, 100 - completedRate - deliveredRate - 10);
|
|
|
+ const pendingRate = Math.max(0, 100 - completedRate - deliveredRate - shippingRate);
|
|
|
+
|
|
|
new ChartJS(ctx, {
|
|
|
type: 'doughnut',
|
|
|
data: {
|
|
|
- labels: ['신규 주문', '처리중', '배송중', '완료'],
|
|
|
+ labels: ['신규 주문', '배송중', '배송완료', '정산완료'],
|
|
|
datasets: [{
|
|
|
- data: [45, 25, 20, 10],
|
|
|
+ data: [pendingRate, shippingRate, deliveredRate, completedRate],
|
|
|
backgroundColor: ['#4caf50', '#ff9800', '#2196f3', '#9c27b0'],
|
|
|
borderWidth: 0
|
|
|
}]
|
|
|
@@ -449,17 +446,20 @@
|
|
|
if (!settlementChart.value) return;
|
|
|
|
|
|
const ctx = settlementChart.value.getContext('2d');
|
|
|
+ const settlementRate = metrics.value.settlementRate || 0;
|
|
|
+ const pendingRate = 100 - settlementRate;
|
|
|
+
|
|
|
new ChartJS(ctx, {
|
|
|
type: 'bar',
|
|
|
data: {
|
|
|
labels: ['1주', '2주', '3주', '4주'],
|
|
|
datasets: [{
|
|
|
label: '정산 완료',
|
|
|
- data: [95, 92, 98, 94],
|
|
|
+ data: [settlementRate, settlementRate, settlementRate, settlementRate],
|
|
|
backgroundColor: '#4caf50'
|
|
|
}, {
|
|
|
label: '정산 대기',
|
|
|
- data: [5, 8, 2, 6],
|
|
|
+ data: [pendingRate, pendingRate, pendingRate, pendingRate],
|
|
|
backgroundColor: '#ff9800'
|
|
|
}]
|
|
|
},
|
|
|
@@ -489,13 +489,15 @@
|
|
|
if (!categoryChart.value) return;
|
|
|
|
|
|
const ctx = categoryChart.value.getContext('2d');
|
|
|
+ // 실제 데이터가 없으므로 현재는 균등 분배로 표시
|
|
|
+ const totalSales = metrics.value.totalSales || 1;
|
|
|
new ChartJS(ctx, {
|
|
|
type: 'pie',
|
|
|
data: {
|
|
|
- labels: ['화장품', '패션', '건강식품', '전자제품', '기타'],
|
|
|
+ labels: ['전체 상품'],
|
|
|
datasets: [{
|
|
|
- data: [35, 25, 20, 15, 5],
|
|
|
- backgroundColor: ['#e91e63', '#9c27b0', '#3f51b5', '#009688', '#ff5722']
|
|
|
+ data: [100],
|
|
|
+ backgroundColor: ['#3f51b5']
|
|
|
}]
|
|
|
},
|
|
|
options: {
|
|
|
@@ -510,24 +512,129 @@
|
|
|
});
|
|
|
};
|
|
|
|
|
|
+ // 정산 데이터 가져오기
|
|
|
+ const fetchSettlementData = async () => {
|
|
|
+ try {
|
|
|
+ const userInfo = useAuthStore().getUser;
|
|
|
+ const _req = {
|
|
|
+ MEMBER_TYPE: 'VENDOR',
|
|
|
+ COMPANY_NUMBER: userInfo?.COMPANY_NUMBER
|
|
|
+ };
|
|
|
+
|
|
|
+ // 전체 정산 데이터 가져오기
|
|
|
+ const allSettlementResponse = await useAxios().post("/deli/settlement", _req);
|
|
|
+ const allSettlements = allSettlementResponse.data || [];
|
|
|
+
|
|
|
+ // 정산완료된 데이터만 가져오기
|
|
|
+ const completedSettlementResponse = await useAxios().post("/deli/settlement", {
|
|
|
+ ..._req,
|
|
|
+ SETTLEMENT_STATUS: 'COMPLETED'
|
|
|
+ });
|
|
|
+ const completedSettlements = completedSettlementResponse.data || [];
|
|
|
+
|
|
|
+ // 정산 메트릭 계산
|
|
|
+ const totalOrders = allSettlements.length;
|
|
|
+ const completedOrders = completedSettlements.length;
|
|
|
+ const settlementRate = totalOrders > 0 ? ((completedOrders / totalOrders) * 100).toFixed(1) : 0;
|
|
|
+
|
|
|
+ const totalSales = completedSettlements.reduce((sum, item) => sum + (item.TOTAL || 0), 0);
|
|
|
+ const avgOrderValue = totalOrders > 0 ? (totalSales / totalOrders) : 0;
|
|
|
+
|
|
|
+ // 메트릭 업데이트 (변화율은 현재 데이터만으로는 계산 불가하므로 0으로 설정)
|
|
|
+ metrics.value = {
|
|
|
+ totalSales: totalSales,
|
|
|
+ totalOrders: totalOrders,
|
|
|
+ settlementRate: parseFloat(settlementRate),
|
|
|
+ avgOrderValue: avgOrderValue,
|
|
|
+ salesChange: 0,
|
|
|
+ ordersChange: 0,
|
|
|
+ settlementChange: 0,
|
|
|
+ avgOrderChange: 0
|
|
|
+ };
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('정산 데이터 조회 실패:', error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 최근 주문 데이터 가져오기
|
|
|
+ const fetchRecentOrders = async () => {
|
|
|
+ try {
|
|
|
+ const userInfo = useAuthStore().getUser;
|
|
|
+ const _req = {
|
|
|
+ MEMBER_TYPE: 'VENDOR',
|
|
|
+ COMPANY_NUMBER: userInfo?.COMPANY_NUMBER
|
|
|
+ };
|
|
|
+
|
|
|
+ // 배송중 데이터
|
|
|
+ const shippingResponse = await useAxios().post("/deli/shipping", _req);
|
|
|
+ const shippingOrders = shippingResponse.data || [];
|
|
|
+
|
|
|
+ // 배송완료 데이터
|
|
|
+ const deliveredResponse = await useAxios().post("/deli/delivered", _req);
|
|
|
+ const deliveredOrders = deliveredResponse.data || [];
|
|
|
+
|
|
|
+ // 정산완료 데이터
|
|
|
+ const settlementResponse = await useAxios().post("/deli/settlement", {
|
|
|
+ ..._req,
|
|
|
+ SETTLEMENT_STATUS: 'COMPLETED'
|
|
|
+ });
|
|
|
+ const settlementOrders = settlementResponse.data || [];
|
|
|
+
|
|
|
+ // 모든 주문 데이터 통합 및 정렬
|
|
|
+ const allOrders = [
|
|
|
+ ...shippingOrders.map(order => ({ ...order, status: 'shipping' })),
|
|
|
+ ...deliveredOrders.map(order => ({ ...order, status: 'delivered' })),
|
|
|
+ ...settlementOrders.map(order => ({ ...order, status: 'completed' }))
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 날짜순 정렬 (최신순)
|
|
|
+ allOrders.sort((a, b) => {
|
|
|
+ const dateA = new Date(a.DELIVERED_DATE || a.SHIPPING_DATE || a.REG_DATE || a.ORDER_DATE);
|
|
|
+ const dateB = new Date(b.DELIVERED_DATE || b.SHIPPING_DATE || b.REG_DATE || b.ORDER_DATE);
|
|
|
+ return dateB - dateA;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 테이블 형식으로 변환 (최근 20개만)
|
|
|
+ recentOrders.value = allOrders.slice(0, 20).map((order, index) => ({
|
|
|
+ orderNo: `ORD-${order.SEQ || (Date.now() + index)}`,
|
|
|
+ influencer: order.NICK_NAME || '알 수 없음',
|
|
|
+ productName: order.ITEM_NAME || '상품명 없음',
|
|
|
+ amount: order.TOTAL || 0,
|
|
|
+ deliveryCompany: order.DELI_COMP || '-',
|
|
|
+ trackingNumber: order.DELI_NUMB || '-',
|
|
|
+ orderDate: $dayjs(order.ORDER_DATE || order.REG_DATE).format('YYYY-MM-DD'),
|
|
|
+ status: order.status,
|
|
|
+ deliveryInfo: {
|
|
|
+ company: order.DELI_COMP,
|
|
|
+ number: order.DELI_NUMB
|
|
|
+ },
|
|
|
+ originalData: order
|
|
|
+ }));
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('최근 주문 데이터 조회 실패:', error);
|
|
|
+ $toast.error('최근 주문 데이터를 불러오는데 실패했습니다.');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
// 대시보드 데이터 가져오기
|
|
|
const fetchDashboardData = async () => {
|
|
|
loading.value = true;
|
|
|
|
|
|
try {
|
|
|
- const _req = {
|
|
|
- compId: useAuthStore().getCompanyId,
|
|
|
- startDate: dateRange.value[0],
|
|
|
- endDate: dateRange.value[1]
|
|
|
- };
|
|
|
-
|
|
|
- await useAxios()
|
|
|
- .post("/dashboard/metrics", _req)
|
|
|
- .then((res) => {
|
|
|
- if (res.data) {
|
|
|
- metrics.value = res.data;
|
|
|
- }
|
|
|
- });
|
|
|
+ await Promise.all([
|
|
|
+ fetchSettlementData(),
|
|
|
+ fetchRecentOrders()
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 데이터 로드 후 차트 업데이트
|
|
|
+ nextTick(() => {
|
|
|
+ createSalesChart();
|
|
|
+ createOrdersChart();
|
|
|
+ createSettlementChart();
|
|
|
+ createCategoryChart();
|
|
|
+ });
|
|
|
} catch (error) {
|
|
|
$toast.error('대시보드 데이터를 불러오는데 실패했습니다.');
|
|
|
} finally {
|
|
|
@@ -551,6 +658,9 @@
|
|
|
// 기본 1개월 기간 설정
|
|
|
setQuickPeriod('month');
|
|
|
|
|
|
+ // 대시보드 데이터 로드
|
|
|
+ fetchDashboardData();
|
|
|
+
|
|
|
nextTick(() => {
|
|
|
createSalesChart();
|
|
|
createOrdersChart();
|