release
This commit is contained in:
parent
e0a23ed6d2
commit
d03895c81b
32
src/App.vue
32
src/App.vue
@ -54,12 +54,10 @@
|
|||||||
|
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
|
|
||||||
<!-- 알림 버튼 -->
|
<!-- 종모양 알림 제거하고 로그아웃 버튼으로 대체 -->
|
||||||
<v-btn icon color="white" @click="showNotifications = true">
|
<v-btn icon color="white" @click="logout">
|
||||||
<v-badge v-if="notificationCount > 0" :content="notificationCount" color="error">
|
<v-icon>mdi-logout</v-icon>
|
||||||
<v-icon>mdi-bell</v-icon>
|
<v-tooltip activator="parent" location="bottom">로그아웃</v-tooltip>
|
||||||
</v-badge>
|
|
||||||
<v-icon v-else>mdi-bell</v-icon>
|
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
|
|
||||||
@ -68,25 +66,6 @@
|
|||||||
<router-view />
|
<router-view />
|
||||||
</v-main>
|
</v-main>
|
||||||
|
|
||||||
<!-- 알림 다이얼로그 -->
|
|
||||||
<v-dialog v-model="showNotifications" max-width="500">
|
|
||||||
<v-card>
|
|
||||||
<v-card-title>
|
|
||||||
<span class="text-h6">알림</span>
|
|
||||||
<v-spacer />
|
|
||||||
<v-btn icon variant="text" @click="showNotifications = false">
|
|
||||||
<v-icon>mdi-close</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
<div v-if="notifications.length === 0" class="text-center pa-4">
|
|
||||||
<v-icon size="48" color="grey-lighten-2">mdi-bell-off</v-icon>
|
|
||||||
<p class="text-grey mt-2">새로운 알림이 없습니다</p>
|
|
||||||
</div>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
|
|
||||||
<!-- 글로벌 스낵바 -->
|
<!-- 글로벌 스낵바 -->
|
||||||
<v-snackbar
|
<v-snackbar
|
||||||
v-model="snackbar.show"
|
v-model="snackbar.show"
|
||||||
@ -115,15 +94,12 @@ const appStore = useAppStore()
|
|||||||
|
|
||||||
// 반응형 데이터
|
// 반응형 데이터
|
||||||
const drawer = ref(false)
|
const drawer = ref(false)
|
||||||
const showNotifications = ref(false)
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
// 컴퓨티드 속성
|
// 컴퓨티드 속성
|
||||||
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
||||||
const isLoginPage = computed(() => route.name === 'Login')
|
const isLoginPage = computed(() => route.name === 'Login')
|
||||||
const userStore = computed(() => authStore)
|
const userStore = computed(() => authStore)
|
||||||
const notificationCount = computed(() => appStore.notificationCount || 0)
|
|
||||||
const notifications = computed(() => appStore.notifications || [])
|
|
||||||
const snackbar = computed(() => appStore.snackbar)
|
const snackbar = computed(() => appStore.snackbar)
|
||||||
|
|
||||||
// 현재 페이지 제목
|
// 현재 페이지 제목
|
||||||
|
|||||||
@ -314,14 +314,7 @@
|
|||||||
{{ useAI ? 'AI 콘텐츠 생성' : '콘텐츠 저장' }}
|
{{ useAI ? 'AI 콘텐츠 생성' : '콘텐츠 저장' }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-btn
|
|
||||||
variant="outlined"
|
|
||||||
block
|
|
||||||
@click="saveDraft"
|
|
||||||
:disabled="!formData.title"
|
|
||||||
>
|
|
||||||
임시저장
|
|
||||||
</v-btn>
|
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,23 @@
|
|||||||
//* src/views/DashboardView.vue
|
//* src/views/DashboardView.vue
|
||||||
<template>
|
<template>
|
||||||
<v-container fluid class="pa-4">
|
<div>
|
||||||
<!-- 주요 지표 카드 - 새로운 스타일 적용 (3개 카드) -->
|
<!-- 상단 AppBar - 로그아웃 버튼을 알림 위치로 이동 -->
|
||||||
|
<v-app-bar color="primary" dark app>
|
||||||
|
<v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon>
|
||||||
|
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
|
||||||
|
<!-- 알림 자리에 로그아웃 버튼 배치 (알림 기능 삭제) -->
|
||||||
|
<v-btn icon @click="handleLogout">
|
||||||
|
<v-icon>mdi-logout</v-icon>
|
||||||
|
<v-tooltip activator="parent" location="bottom">로그아웃</v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
</v-app-bar>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 메인 컨텐츠 -->
|
||||||
|
<v-container fluid class="pa-4" style="margin-top: 80px;">
|
||||||
|
<!-- 주요 지표 카드 - 제목과 증감율 위치 변경 -->
|
||||||
<v-row class="mb-6">
|
<v-row class="mb-6">
|
||||||
<v-col
|
<v-col
|
||||||
v-for="(metric, index) in dashboardMetrics"
|
v-for="(metric, index) in dashboardMetrics"
|
||||||
@ -17,11 +33,18 @@
|
|||||||
border
|
border
|
||||||
>
|
>
|
||||||
<v-card-text class="pa-6">
|
<v-card-text class="pa-6">
|
||||||
<!-- 카드 헤더 -->
|
<!-- 제목 먼저 표시 -->
|
||||||
<div class="d-flex align-center justify-between mb-3">
|
<div class="d-flex align-center justify-between mb-2">
|
||||||
|
<h4 class="metric-title text-subtitle-1 font-weight-bold">
|
||||||
|
{{ metric.title }}
|
||||||
|
</h4>
|
||||||
<div class="metric-icon-wrapper" :class="`bg-${metric.color}`">
|
<div class="metric-icon-wrapper" :class="`bg-${metric.color}`">
|
||||||
<v-icon :icon="metric.icon" size="24" :color="`${metric.color}-darken-2`" />
|
<v-icon :icon="metric.icon" size="20" :color="`${metric.color}-darken-2`" />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 증감율 두 번째로 표시 -->
|
||||||
|
<div class="mb-3">
|
||||||
<v-chip
|
<v-chip
|
||||||
:color="
|
:color="
|
||||||
metric.trend === 'up' ? 'success' : metric.trend === 'down' ? 'error' : 'warning'
|
metric.trend === 'up' ? 'success' : metric.trend === 'down' ? 'error' : 'warning'
|
||||||
@ -43,7 +66,7 @@
|
|||||||
</v-chip>
|
</v-chip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 메트릭 값 - 애니메이션 적용 -->
|
<!-- 메트릭 값 세 번째로 표시 -->
|
||||||
<div class="metric-value-wrapper mb-2">
|
<div class="metric-value-wrapper mb-2">
|
||||||
<span
|
<span
|
||||||
class="metric-value text-h4 font-weight-bold"
|
class="metric-value text-h4 font-weight-bold"
|
||||||
@ -54,13 +77,8 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 메트릭 제목 -->
|
<!-- 추가 정보 마지막에 표시 -->
|
||||||
<p class="metric-title text-body-2 text-grey-darken-1 mb-0 font-weight-medium">
|
<div class="metric-detail">
|
||||||
{{ metric.title }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- 추가 정보 -->
|
|
||||||
<div class="metric-detail mt-2">
|
|
||||||
<span class="text-caption text-grey">
|
<span class="text-caption text-grey">
|
||||||
{{ metric.detail || '전월 대비' }}
|
{{ metric.detail || '전월 대비' }}
|
||||||
</span>
|
</span>
|
||||||
@ -70,19 +88,19 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<!-- 매출 추이 분석 차트 -->
|
<!-- 매출 추이 분석 차트 - 명확한 임시 데이터로 구성 -->
|
||||||
<v-row class="mb-6">
|
<v-row class="mb-6">
|
||||||
<v-col cols="12" lg="8">
|
<v-col cols="12" lg="8">
|
||||||
<v-card elevation="0" border class="chart-card h-100">
|
<v-card elevation="0" border class="chart-card h-100">
|
||||||
<v-card-title class="pa-6 pb-0">
|
<v-card-title class="pa-6 pb-0">
|
||||||
<div class="d-flex align-center justify-between w-100">
|
<div class="d-flex align-center justify-between w-100">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-h6 font-weight-bold mb-1">매출 추이 분석</h3>
|
<h3 class="text-h6 font-weight-bold mb-1">📊 매출 추이 분석</h3>
|
||||||
<p class="text-body-2 text-grey-darken-1 mb-0">
|
<p class="text-body-2 text-grey-darken-1 mb-0">
|
||||||
최근 {{ chartPeriod.replace('d', '일') }}간 매출 현황
|
최근 {{ getCurrentPeriodLabel() }}간 매출 현황
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<v-btn-toggle
|
<v-btn-toggle class="d-flex align-center justify-end w-100"
|
||||||
v-model="chartPeriod"
|
v-model="chartPeriod"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
density="compact"
|
density="compact"
|
||||||
@ -98,25 +116,114 @@
|
|||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-card-text class="pa-6">
|
<v-card-text class="pa-6">
|
||||||
<div class="sales-chart-container">
|
<div class="sales-chart-container">
|
||||||
<!-- Chart.js 차트가 들어갈 영역 -->
|
<!-- 실제 같은 차트 구현 -->
|
||||||
<canvas id="salesChart" class="sales-chart" style="height: 300px"></canvas>
|
<div class="real-chart">
|
||||||
|
<!-- 차트 헤더 정보 -->
|
||||||
|
<div class="chart-header-info mb-4">
|
||||||
|
<div class="d-flex justify-between align-center">
|
||||||
|
|
||||||
<!-- Chart.js 없을 때 표시될 플레이스홀더 -->
|
<div class="chart-legend d-flex">
|
||||||
<div v-if="!chartInitialized" class="chart-placeholder">
|
<div class="legend-item mr-4">
|
||||||
<v-icon size="64" color="grey-lighten-2">mdi-chart-line</v-icon>
|
<span class="legend-dot" style="background: #1976D2;"></span>
|
||||||
<p class="text-grey mt-2 mb-1">매출 추이 차트</p>
|
<span class="text-caption">실제 매출</span>
|
||||||
<p class="text-caption text-grey">Chart.js를 설치하면 실제 차트가 표시됩니다</p>
|
</div>
|
||||||
|
<div class="legend-item">
|
||||||
<!-- 간단한 가짜 차트 -->
|
<span class="legend-dot" style="background: #FF5722;"></span>
|
||||||
<div class="fake-chart mt-4">
|
<span class="text-caption">목표 매출</span>
|
||||||
<div class="fake-chart-bars">
|
|
||||||
<div v-for="(value, i) in fakeChartData" :key="i" class="fake-bar">
|
|
||||||
<div class="fake-bar-fill" :style="{ height: `${value}%` }"></div>
|
|
||||||
<span class="fake-bar-label">{{ i + 1 }}일</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 차트 영역 -->
|
||||||
|
<div class="chart-area" style="height: 300px; position: relative;">
|
||||||
|
<!-- Y축 라벨 -->
|
||||||
|
<div class="y-axis-labels">
|
||||||
|
<div v-for="(label, i) in yAxisLabels" :key="i"
|
||||||
|
class="y-label"
|
||||||
|
:style="{ bottom: `${i * 20}%` }">
|
||||||
|
{{ label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 그리드 -->
|
||||||
|
<div class="chart-grid">
|
||||||
|
<div v-for="i in 6" :key="i"
|
||||||
|
class="grid-line"
|
||||||
|
:style="{ bottom: `${(i-1) * 20}%` }"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 실제 라인 차트 (Canvas 활용) -->
|
||||||
|
<canvas
|
||||||
|
ref="chartCanvas"
|
||||||
|
class="chart-canvas"
|
||||||
|
width="800"
|
||||||
|
height="300"
|
||||||
|
@mousemove="handleMouseMove"
|
||||||
|
@mouseleave="hideTooltip">
|
||||||
|
</canvas>
|
||||||
|
|
||||||
|
<!-- 데이터 포인트 -->
|
||||||
|
<div class="data-points">
|
||||||
|
<div
|
||||||
|
v-for="(point, i) in chartDataPoints"
|
||||||
|
:key="i"
|
||||||
|
class="data-point"
|
||||||
|
:style="{
|
||||||
|
left: `${point.x}%`,
|
||||||
|
bottom: `${point.y}%`
|
||||||
|
}"
|
||||||
|
@mouseenter="showDataTooltip(i, $event)"
|
||||||
|
@mouseleave="hideDataTooltip">
|
||||||
|
<div class="point-circle sales-point"></div>
|
||||||
|
<div class="point-circle target-point"
|
||||||
|
:style="{ bottom: `${point.targetY - point.y}%` }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 툴팁 -->
|
||||||
|
<div v-if="tooltip.show"
|
||||||
|
class="chart-tooltip"
|
||||||
|
:style="{ left: tooltip.x + 'px', top: tooltip.y + 'px' }">
|
||||||
|
<div class="tooltip-content">
|
||||||
|
<div class="tooltip-title">{{ tooltip.title }}</div>
|
||||||
|
<div class="tooltip-sales">매출: {{ formatCurrency(tooltip.sales) }}</div>
|
||||||
|
<div class="tooltip-target">목표: {{ formatCurrency(tooltip.target) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- X축 라벨 -->
|
||||||
|
<div class="x-axis-labels mt-3">
|
||||||
|
<div class="d-flex justify-between px-8">
|
||||||
|
<span v-for="item in currentChartData" :key="item.label"
|
||||||
|
class="x-label text-caption">
|
||||||
|
{{ item.label }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 차트 통계 정보 -->
|
||||||
|
<div class="chart-stats mt-4 pa-3" style="background: #F5F5F5; border-radius: 8px;">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="4" class="text-center">
|
||||||
|
<div class="text-caption text-grey-darken-1">평균 매출</div>
|
||||||
|
<div class="text-subtitle-2 font-weight-bold text-primary">{{ avgSales }}</div>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="4" class="text-center">
|
||||||
|
<div class="text-caption text-grey-darken-1">최고 매출</div>
|
||||||
|
<div class="text-subtitle-2 font-weight-bold text-success">{{ maxSales }}</div>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="4" class="text-center">
|
||||||
|
<div class="text-caption text-grey-darken-1">목표 달성률</div>
|
||||||
|
<div class="text-subtitle-2 font-weight-bold"
|
||||||
|
:class="achievementRate >= 100 ? 'text-success' : 'text-warning'">
|
||||||
|
{{ achievementRate }}%
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
@ -163,28 +270,35 @@
|
|||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<!-- 로딩 오버레이 -->
|
|
||||||
<v-overlay v-if="loading" class="align-center justify-center">
|
|
||||||
<v-progress-circular color="primary" indeterminate size="64" />
|
|
||||||
</v-overlay>
|
|
||||||
</v-container>
|
</v-container>
|
||||||
|
|
||||||
|
<!-- 로그아웃 확인 다이얼로그 -->
|
||||||
|
<v-dialog v-model="logoutDialog" max-width="400">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="text-h6">로그아웃 확인</v-card-title>
|
||||||
|
<v-card-text>정말 로그아웃 하시겠습니까?</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="grey" variant="text" @click="logoutDialog = false">취소</v-btn>
|
||||||
|
<v-btn color="error" variant="text" @click="confirmLogout">로그아웃</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
import { ref, onMounted, onBeforeUnmount, computed, nextTick } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useAuthStore } from '@/store/auth'
|
import { useAuthStore } from '@/store/auth'
|
||||||
import { useAppStore } from '@/store/app'
|
import { useAppStore } from '@/store/app'
|
||||||
import { formatCurrency, formatNumber, formatRelativeTime } from '@/utils/formatters'
|
import { formatCurrency, formatNumber, formatRelativeTime } from '@/utils/formatters'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 대시보드 메인 페이지 - 수정된 버전
|
* 대시보드 메인 페이지 - 완전 수정된 버전
|
||||||
* - 새로운 카드 스타일 적용
|
* - AppBar에서 알림 자리에 로그아웃 버튼 배치 (알림 기능 삭제)
|
||||||
* - 숫자 증가 애니메이션 추가
|
* - 카드에서 제목과 증감율 위치 변경
|
||||||
* - 빠른 액션 제거
|
* - 매출 추이 분석에 실제같은 차트와 명확한 임시 데이터 적용
|
||||||
* - 매출 추이 분석 차트 추가
|
|
||||||
* - 최근 활동 제거
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -192,56 +306,92 @@ const authStore = useAuthStore()
|
|||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
// 반응형 데이터
|
// 반응형 데이터
|
||||||
|
const drawer = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const aiLoading = ref(false)
|
const aiLoading = ref(false)
|
||||||
const chartInitialized = ref(false)
|
|
||||||
const chartPeriod = ref('7d')
|
const chartPeriod = ref('7d')
|
||||||
const animatedValues = ref({})
|
const animatedValues = ref({})
|
||||||
|
const logoutDialog = ref(false)
|
||||||
|
const chartCanvas = ref(null)
|
||||||
|
const currentTime = ref('')
|
||||||
|
|
||||||
// 비즈니스 정보
|
// 툴팁 관련
|
||||||
const businessName = ref('김사장님의 분식점')
|
const tooltip = ref({
|
||||||
|
show: false,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
title: '',
|
||||||
|
sales: 0,
|
||||||
|
target: 0
|
||||||
|
})
|
||||||
|
|
||||||
// 대시보드 지표 - 새로운 스타일 적용 (AI 추천 활용 카드 제거)
|
// 대시보드 지표 - 제목과 증감율 위치 변경
|
||||||
const dashboardMetrics = ref([
|
const dashboardMetrics = ref([
|
||||||
{
|
{
|
||||||
title: '오늘의 매출',
|
title: '오늘의 매출',
|
||||||
value: 123000,
|
value: 567000,
|
||||||
displayValue: '₩123,000',
|
displayValue: '₩567,000',
|
||||||
change: '+15%',
|
change: '전일 대비 +15%',
|
||||||
trend: 'up',
|
trend: 'up',
|
||||||
icon: 'mdi-cash-multiple',
|
icon: 'mdi-cash-multiple',
|
||||||
color: 'success',
|
color: 'success'
|
||||||
detail: '목표 달성률: 85%',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '이번 달 매출',
|
title: '이번 달 매출',
|
||||||
value: 2450000,
|
value: 12450000,
|
||||||
displayValue: '₩2,450,000',
|
displayValue: '₩12,450,000',
|
||||||
change: '+8%',
|
change: '전월 대비 +8%',
|
||||||
trend: 'up',
|
trend: 'up',
|
||||||
icon: 'mdi-trending-up',
|
icon: 'mdi-trending-up',
|
||||||
color: 'primary',
|
color: 'primary'
|
||||||
detail: '목표까지 ₩450,000',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '일일 조회수',
|
title: '일일 조회수',
|
||||||
value: 1234,
|
value: 2547,
|
||||||
displayValue: '1,234',
|
displayValue: '2,547',
|
||||||
change: '+23%',
|
change: '전일 대비 +23%',
|
||||||
trend: 'up',
|
trend: 'up',
|
||||||
icon: 'mdi-eye',
|
icon: 'mdi-eye',
|
||||||
color: 'warning',
|
color: 'warning'
|
||||||
detail: '참여율: 4.2%',
|
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
// AI 추천 (간소화)
|
// 실제 차트 데이터 (더 구체적이고 현실적인 데이터)
|
||||||
|
const chartData = ref({
|
||||||
|
'7d': [
|
||||||
|
{ label: '6일전', sales: 45, target: 50, date: '06-04' },
|
||||||
|
{ label: '5일전', sales: 52, target: 50, date: '06-05' },
|
||||||
|
{ label: '4일전', sales: 38, target: 50, date: '06-06' },
|
||||||
|
{ label: '3일전', sales: 65, target: 50, date: '06-07' },
|
||||||
|
{ label: '2일전', sales: 48, target: 50, date: '06-08' },
|
||||||
|
{ label: '어제', sales: 72, target: 50, date: '06-09' },
|
||||||
|
{ label: '오늘', sales: 57, target: 50, date: '06-10' },
|
||||||
|
],
|
||||||
|
'30d': [
|
||||||
|
{ label: '1주차', sales: 320, target: 350, date: '5/13-19' },
|
||||||
|
{ label: '2주차', sales: 385, target: 350, date: '5/20-26' },
|
||||||
|
{ label: '3주차', sales: 425, target: 350, date: '5/27-6/2' },
|
||||||
|
{ label: '4주차', sales: 468, target: 350, date: '6/3-9' },
|
||||||
|
{ label: '이번주', sales: 380, target: 350, date: '6/10-16' },
|
||||||
|
],
|
||||||
|
'90d': [
|
||||||
|
{ label: '3월', sales: 1250, target: 1400, date: '2024-03' },
|
||||||
|
{ label: '4월', sales: 1380, target: 1400, date: '2024-04' },
|
||||||
|
{ label: '5월', sales: 1520, target: 1400, date: '2024-05' },
|
||||||
|
{ label: '6월', sales: 1450, target: 1400, date: '2024-06' },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
// Y축 라벨
|
||||||
|
const yAxisLabels = ref(['0', '25', '50', '75', '100'])
|
||||||
|
|
||||||
|
// AI 추천
|
||||||
const aiRecommendations = ref([
|
const aiRecommendations = ref([
|
||||||
{
|
{
|
||||||
type: 'info',
|
type: 'info',
|
||||||
icon: 'mdi-weather-rainy',
|
icon: 'mdi-weather-sunny',
|
||||||
title: '날씨 기반 추천',
|
title: '날씨 기반 추천',
|
||||||
content: '오늘은 비가 와서 따뜻한 음식이 인기 있을 것 같아요.',
|
content: '오늘은 맑은 날씨로 야외 테이크아웃 주문이 증가할 예정입니다.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@ -253,12 +403,71 @@ const aiRecommendations = ref([
|
|||||||
type: 'warning',
|
type: 'warning',
|
||||||
icon: 'mdi-clock-outline',
|
icon: 'mdi-clock-outline',
|
||||||
title: '시간대 팁',
|
title: '시간대 팁',
|
||||||
content: '점심시간(12-14시)에 주문이 집중됩니다.',
|
content: '점심시간(12-14시) 주문 집중. 미리 재료를 준비하세요.',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
// 가짜 차트 데이터 (Chart.js 없을 때 표시용)
|
// 계산된 속성들
|
||||||
const fakeChartData = ref([65, 78, 82, 45, 90, 67, 88])
|
const currentChartData = computed(() => chartData.value[chartPeriod.value])
|
||||||
|
|
||||||
|
const chartDataPoints = computed(() => {
|
||||||
|
const data = currentChartData.value
|
||||||
|
const maxSales = Math.max(...data.map(d => Math.max(d.sales, d.target)))
|
||||||
|
|
||||||
|
return data.map((item, index) => ({
|
||||||
|
x: 10 + (index * 80 / (data.length - 1)), // 10%에서 90%까지 분산
|
||||||
|
y: 10 + ((item.sales / maxSales) * 80), // 10%에서 90%까지 높이
|
||||||
|
targetY: 10 + ((item.target / maxSales) * 80),
|
||||||
|
sales: item.sales,
|
||||||
|
target: item.target,
|
||||||
|
label: item.label
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
const avgSales = computed(() => {
|
||||||
|
const data = currentChartData.value
|
||||||
|
const avg = data.reduce((sum, item) => sum + item.sales, 0) / data.length
|
||||||
|
const unit = chartPeriod.value === '90d' ? 100 : chartPeriod.value === '30d' ? 10 : 1
|
||||||
|
return formatCurrency(avg * unit * 10000)
|
||||||
|
})
|
||||||
|
|
||||||
|
const maxSales = computed(() => {
|
||||||
|
const data = currentChartData.value
|
||||||
|
const max = Math.max(...data.map(d => d.sales))
|
||||||
|
const unit = chartPeriod.value === '90d' ? 100 : chartPeriod.value === '30d' ? 10 : 1
|
||||||
|
return formatCurrency(max * unit * 10000)
|
||||||
|
})
|
||||||
|
|
||||||
|
const achievementRate = computed(() => {
|
||||||
|
const data = currentChartData.value
|
||||||
|
const totalSales = data.reduce((sum, item) => sum + item.sales, 0)
|
||||||
|
const totalTarget = data.reduce((sum, item) => sum + item.target, 0)
|
||||||
|
return Math.round((totalSales / totalTarget) * 100)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 현재 기간 라벨
|
||||||
|
const getCurrentPeriodLabel = () => {
|
||||||
|
switch (chartPeriod.value) {
|
||||||
|
case '7d': return '7일'
|
||||||
|
case '30d': return '30일'
|
||||||
|
case '90d': return '90일'
|
||||||
|
default: return '7일'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 시간 업데이트
|
||||||
|
const updateTime = () => {
|
||||||
|
const now = new Date()
|
||||||
|
const options = {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
weekday: 'short',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
}
|
||||||
|
currentTime.value = now.toLocaleString('ko-KR', options)
|
||||||
|
}
|
||||||
|
|
||||||
// 숫자 애니메이션 함수
|
// 숫자 애니메이션 함수
|
||||||
const animateNumber = (targetValue, index, duration = 2000) => {
|
const animateNumber = (targetValue, index, duration = 2000) => {
|
||||||
@ -301,84 +510,118 @@ const startMetricsAnimation = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 차트 업데이트
|
// 차트 그리기
|
||||||
const updateChart = (period) => {
|
const drawChart = async () => {
|
||||||
console.log('차트 기간 변경:', period)
|
await nextTick()
|
||||||
// Chart.js 구현 시 여기에 차트 업데이트 로직 추가
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chart.js 초기화 (설치 후 사용)
|
if (!chartCanvas.value) return
|
||||||
/*
|
|
||||||
const initSalesChart = () => {
|
|
||||||
const ctx = document.getElementById('salesChart')
|
|
||||||
if (ctx && window.Chart) {
|
|
||||||
chartInitialized.value = true
|
|
||||||
|
|
||||||
new Chart(ctx, {
|
const canvas = chartCanvas.value
|
||||||
type: 'line',
|
const ctx = canvas.getContext('2d')
|
||||||
data: {
|
const data = currentChartData.value
|
||||||
labels: ['6일전', '5일전', '4일전', '3일전', '2일전', '어제', '오늘'],
|
|
||||||
datasets: [{
|
// 캔버스 초기화
|
||||||
label: '매출액',
|
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||||
data: [390000, 425000, 520000, 380000, 450000, 520000, 567000],
|
|
||||||
borderColor: '#1976D2',
|
const padding = 60
|
||||||
backgroundColor: 'rgba(25, 118, 210, 0.1)',
|
const chartWidth = canvas.width - padding * 2
|
||||||
tension: 0.4,
|
const chartHeight = canvas.height - padding * 2
|
||||||
fill: true,
|
|
||||||
pointBackgroundColor: '#1976D2',
|
const maxValue = Math.max(...data.map(d => Math.max(d.sales, d.target)))
|
||||||
pointBorderColor: '#ffffff',
|
|
||||||
pointBorderWidth: 2,
|
// 매출 라인 그리기
|
||||||
pointRadius: 6,
|
ctx.beginPath()
|
||||||
pointHoverRadius: 8
|
ctx.strokeStyle = '#1976D2'
|
||||||
}]
|
ctx.lineWidth = 3
|
||||||
},
|
ctx.lineCap = 'round'
|
||||||
options: {
|
ctx.lineJoin = 'round'
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
data.forEach((item, index) => {
|
||||||
plugins: {
|
const x = padding + (index * chartWidth / (data.length - 1))
|
||||||
legend: {
|
const y = padding + chartHeight - ((item.sales / maxValue) * chartHeight)
|
||||||
display: false
|
|
||||||
},
|
if (index === 0) {
|
||||||
tooltip: {
|
ctx.moveTo(x, y)
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
} else {
|
||||||
titleColor: '#ffffff',
|
ctx.lineTo(x, y)
|
||||||
bodyColor: '#ffffff',
|
|
||||||
borderColor: '#1976D2',
|
|
||||||
borderWidth: 1,
|
|
||||||
callbacks: {
|
|
||||||
label: function(context) {
|
|
||||||
return '매출: ₩' + context.parsed.y.toLocaleString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
grid: {
|
|
||||||
display: false
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
color: '#666666'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
beginAtZero: true,
|
|
||||||
grid: {
|
|
||||||
color: 'rgba(0, 0, 0, 0.1)'
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
color: '#666666',
|
|
||||||
callback: function(value) {
|
|
||||||
return '₩' + (value / 1000) + 'K'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ctx.stroke()
|
||||||
|
|
||||||
|
// 목표 라인 그리기
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.strokeStyle = '#FF5722'
|
||||||
|
ctx.lineWidth = 2
|
||||||
|
ctx.setLineDash([5, 5])
|
||||||
|
|
||||||
|
data.forEach((item, index) => {
|
||||||
|
const x = padding + (index * chartWidth / (data.length - 1))
|
||||||
|
const y = padding + chartHeight - ((item.target / maxValue) * chartHeight)
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
ctx.moveTo(x, y)
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(x, y)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.stroke()
|
||||||
|
ctx.setLineDash([])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 차트 업데이트
|
||||||
|
const updateChart = async (period) => {
|
||||||
|
console.log('차트 기간 변경:', period)
|
||||||
|
await nextTick()
|
||||||
|
drawChart()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 툴팁 표시
|
||||||
|
const showDataTooltip = (index, event) => {
|
||||||
|
const data = currentChartData.value[index]
|
||||||
|
const unit = chartPeriod.value === '90d' ? 100 : chartPeriod.value === '30d' ? 10 : 1
|
||||||
|
|
||||||
|
tooltip.value = {
|
||||||
|
show: true,
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY - 80,
|
||||||
|
title: data.label,
|
||||||
|
sales: data.sales * unit * 10000,
|
||||||
|
target: data.target * unit * 10000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideDataTooltip = () => {
|
||||||
|
tooltip.value.show = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseMove = (event) => {
|
||||||
|
// 차트 위 마우스 이동 처리
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideTooltip = () => {
|
||||||
|
tooltip.value.show = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 로그아웃 핸들러
|
||||||
|
const handleLogout = () => {
|
||||||
|
logoutDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmLogout = () => {
|
||||||
|
try {
|
||||||
|
// 로그아웃 처리
|
||||||
|
authStore.logout()
|
||||||
|
appStore.showSnackbar('로그아웃되었습니다.', 'success')
|
||||||
|
router.push('/login')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('로그아웃 실패:', error)
|
||||||
|
appStore.showSnackbar('로그아웃에 실패했습니다.', 'error')
|
||||||
|
} finally {
|
||||||
|
logoutDialog.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
// 데이터 새로고침
|
// 데이터 새로고침
|
||||||
const refreshData = async () => {
|
const refreshData = async () => {
|
||||||
@ -402,6 +645,10 @@ onMounted(async () => {
|
|||||||
console.log('DashboardView 마운트됨')
|
console.log('DashboardView 마운트됨')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 시간 업데이트
|
||||||
|
updateTime()
|
||||||
|
setInterval(updateTime, 60000) // 1분마다 업데이트
|
||||||
|
|
||||||
// 초기 데이터 로드
|
// 초기 데이터 로드
|
||||||
await refreshData()
|
await refreshData()
|
||||||
|
|
||||||
@ -410,10 +657,10 @@ onMounted(async () => {
|
|||||||
startMetricsAnimation()
|
startMetricsAnimation()
|
||||||
}, 500)
|
}, 500)
|
||||||
|
|
||||||
// Chart.js 초기화 (설치 후 주석 해제)
|
// 차트 그리기
|
||||||
// setTimeout(() => {
|
setTimeout(() => {
|
||||||
// initSalesChart()
|
drawChart()
|
||||||
// }, 1000)
|
}, 1000)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('대시보드 초기화 실패:', error)
|
console.error('대시보드 초기화 실패:', error)
|
||||||
}
|
}
|
||||||
@ -426,7 +673,12 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 메트릭 카드 스타일 */
|
/* AppBar 사용자 정보 스타일 */
|
||||||
|
.header-user-info {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 메트릭 카드 스타일 - 수정된 버전 */
|
||||||
.metric-card {
|
.metric-card {
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -457,13 +709,18 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.metric-icon-wrapper {
|
.metric-icon-wrapper {
|
||||||
width: 48px;
|
width: 40px;
|
||||||
height: 48px;
|
height: 40px;
|
||||||
border-radius: 12px;
|
border-radius: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
opacity: 0.1;
|
opacity: 0.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-title {
|
||||||
|
color: var(--v-theme-on-surface);
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric-value {
|
.metric-value {
|
||||||
@ -477,11 +734,6 @@ onBeforeUnmount(() => {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric-title {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metric-detail {
|
.metric-detail {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
@ -499,69 +751,156 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
.sales-chart-container {
|
.sales-chart-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sales-chart {
|
|
||||||
width: 100% !important;
|
|
||||||
height: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-placeholder {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 2px dashed #e2e8f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 가짜 차트 스타일 */
|
|
||||||
.fake-chart {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fake-chart-bars {
|
|
||||||
display: flex;
|
|
||||||
align-items: end;
|
|
||||||
gap: 8px;
|
|
||||||
height: 60px;
|
|
||||||
padding: 0 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fake-bar {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fake-bar-fill {
|
/* 실제 차트 스타일 */
|
||||||
|
.real-chart {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: linear-gradient(to top, #1976d2, #42a5f5);
|
height: 100%;
|
||||||
border-radius: 2px 2px 0 0;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
animation: barGrow 1s ease-out forwards;
|
|
||||||
transform-origin: bottom;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fake-bar-label {
|
.chart-header-info {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid #E5E7EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-legend {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #6B7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-area {
|
||||||
|
position: relative;
|
||||||
|
background: linear-gradient(135deg, #FAFAFA 0%, #F5F5F5 100%);
|
||||||
|
border: 1px solid #E5E7EB;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.y-axis-labels {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.y-label {
|
||||||
|
position: absolute;
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
color: #666;
|
color: #6B7280;
|
||||||
margin-top: 4px;
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes barGrow {
|
.chart-grid {
|
||||||
from {
|
position: absolute;
|
||||||
transform: scaleY(0);
|
left: 40px;
|
||||||
}
|
right: 0;
|
||||||
to {
|
top: 0;
|
||||||
transform: scaleY(1);
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid-line {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1px;
|
||||||
|
background: #E5E7EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-canvas {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-points {
|
||||||
|
position: absolute;
|
||||||
|
left: 40px;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-point {
|
||||||
|
position: absolute;
|
||||||
|
transform: translate(-50%, 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-circle {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sales-point {
|
||||||
|
background: #1976D2;
|
||||||
|
border: 2px solid white;
|
||||||
|
box-shadow: 0 2px 4px rgba(25, 118, 210, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target-point {
|
||||||
|
background: #FF5722;
|
||||||
|
border: 2px solid white;
|
||||||
|
box-shadow: 0 2px 4px rgba(255, 87, 34, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-circle:hover {
|
||||||
|
transform: scale(1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.x-axis-labels {
|
||||||
|
padding: 0 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.x-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #6B7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 툴팁 스타일 */
|
||||||
|
.chart-tooltip {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-content {
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
color: white;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-sales,
|
||||||
|
.tooltip-target {
|
||||||
|
margin: 2px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* AI 추천 카드 스타일 */
|
/* AI 추천 카드 스타일 */
|
||||||
@ -584,13 +923,21 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
/* 반응형 디자인 */
|
/* 반응형 디자인 */
|
||||||
@media (max-width: 960px) {
|
@media (max-width: 960px) {
|
||||||
|
.header-user-info {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.metric-value {
|
.metric-value {
|
||||||
font-size: 1.75rem !important;
|
font-size: 1.75rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric-icon-wrapper {
|
.metric-icon-wrapper {
|
||||||
width: 40px;
|
width: 36px;
|
||||||
height: 40px;
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-area {
|
||||||
|
height: 250px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,12 +946,12 @@ onBeforeUnmount(() => {
|
|||||||
font-size: 1.5rem !important;
|
font-size: 1.5rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-placeholder {
|
.chart-area {
|
||||||
padding: 16px;
|
height: 200px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fake-chart-bars {
|
.y-axis-labels {
|
||||||
height: 40px;
|
width: 30px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -613,12 +960,17 @@ onBeforeUnmount(() => {
|
|||||||
background-color: rgb(var(--v-theme-surface-variant));
|
background-color: rgb(var(--v-theme-surface-variant));
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-theme--dark .chart-placeholder {
|
.v-theme--dark .chart-area {
|
||||||
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
background: linear-gradient(135deg, #1E293B 0%, #0F172A 100%);
|
||||||
border-color: #334155;
|
border-color: #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-theme--dark .fake-bar-fill {
|
.v-theme--dark .grid-line {
|
||||||
background: linear-gradient(to top, #60a5fa, #93c5fd);
|
background: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-theme--dark .chart-stats {
|
||||||
|
background: #1E293B !important;
|
||||||
|
border-color: #334155;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user