release
This commit is contained in:
parent
df016023be
commit
a9005e6dc4
116
.gitignore
vendored
116
.gitignore
vendored
@ -8,103 +8,63 @@ yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
# Dependencies
|
||||
node_modules
|
||||
.pnpm
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Production build
|
||||
dist/
|
||||
dist-ssr/
|
||||
build/
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Environment variables
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# IDE and Editor files
|
||||
.vscode/
|
||||
!.vscode/extensions.json
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
# Runtime files
|
||||
runtime-env.js.bak
|
||||
config.local.js
|
||||
|
||||
# Build artifacts
|
||||
coverage/
|
||||
.nyc_output/
|
||||
build/
|
||||
dist/
|
||||
|
||||
# OS generated files
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage/
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Vite cache
|
||||
.vite/
|
||||
|
||||
# Vue.js specific
|
||||
.vue/
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
.cache/
|
||||
|
||||
# Test coverage
|
||||
tests/unit/coverage/
|
||||
tests/e2e/coverage/
|
||||
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
|
||||
# AI Marketing Frontend specific
|
||||
/public/runtime-env.js.backup
|
||||
/src/assets/temp/
|
||||
/public/images/uploads/
|
||||
*.backup
|
||||
|
||||
# Vue DevTools
|
||||
.vue-devtools
|
||||
|
||||
# Cypress
|
||||
cypress/videos/
|
||||
cypress/screenshots/
|
||||
|
||||
# ESLint
|
||||
.eslintcache
|
||||
|
||||
# Stylelint
|
||||
.stylelintcache
|
||||
# Package manager files
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
@ -1,185 +1,59 @@
|
||||
//* public/index.html
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>₩ON - AI 마케팅 서비스</title>
|
||||
<meta name="description" content="소상공인을 위한 AI 기반 마케팅 솔루션">
|
||||
<meta name="keywords" content="소상공인, 마케팅, AI, 콘텐츠 생성, 매장 관리">
|
||||
<meta name="author" content="₩ON Team">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="소상공인을 위한 AI 마케팅 솔루션" />
|
||||
<meta name="keywords" content="AI, 마케팅, 소상공인, 콘텐츠, 자동화" />
|
||||
<meta name="author" content="AI 마케팅 팀" />
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="/logo192.png">
|
||||
<!-- PWA 설정 -->
|
||||
<meta name="theme-color" content="#1976D2" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="AI 마케팅" />
|
||||
|
||||
<!-- Web App Manifest -->
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<!-- 파비콘 -->
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" href="/images/logo192.png" />
|
||||
|
||||
<!-- Meta tags for mobile -->
|
||||
<meta name="theme-color" content="#1976D2">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="₩ON">
|
||||
<!-- Manifest -->
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="application-name" content="₩ON">
|
||||
<meta name="msapplication-TileColor" content="#1976D2">
|
||||
<!-- 폰트 사전 로드 -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- Runtime Environment Configuration -->
|
||||
<script src="/runtime-env.js"></script>
|
||||
<title>AI 마케팅 - 소상공인을 위한 스마트 마케팅 솔루션</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<!-- Google Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||
<!-- 런타임 환경 설정 -->
|
||||
<script src="/runtime-env.js"></script>
|
||||
|
||||
<!-- Material Design Icons -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<!-- 앱 스크립트 -->
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
|
||||
<!-- CSS Variables for Theme -->
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #1976D2;
|
||||
--primary-dark: #1565C0;
|
||||
--primary-light: #BBDEFB;
|
||||
--secondary-color: #FFC107;
|
||||
--success-color: #4CAF50;
|
||||
--warning-color: #FF9800;
|
||||
--error-color: #F44336;
|
||||
--info-color: #2196F3;
|
||||
--background-color: #FAFAFA;
|
||||
--surface-color: #FFFFFF;
|
||||
--text-primary: #212121;
|
||||
--text-secondary: #757575;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
font-family: 'Noto Sans KR', sans-serif;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-primary);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Loading Spinner */
|
||||
.loading-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--surface-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border: 4px solid var(--primary-light);
|
||||
border-top: 4px solid var(--primary-color);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 16px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Mobile Optimizations */
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
font-size: 14px;
|
||||
<!-- 서비스 워커 등록 -->
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker
|
||||
.register('/sw.js')
|
||||
.then((registration) => {
|
||||
console.log('SW registered: ', registration)
|
||||
})
|
||||
.catch((registrationError) => {
|
||||
console.log('SW registration failed: ', registrationError)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
.v-application {
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* Prevent zoom on input focus in iOS */
|
||||
@media screen and (max-width: 768px) {
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="password"],
|
||||
input[type="tel"],
|
||||
textarea {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--primary-color);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<div style="text-align: center; padding: 50px;">
|
||||
<h2>JavaScript가 필요합니다</h2>
|
||||
<p>이 애플리케이션을 사용하려면 브라우저에서 JavaScript를 활성화해주세요.</p>
|
||||
</div>
|
||||
</noscript>
|
||||
|
||||
<div id="app">
|
||||
<!-- Initial Loading Screen -->
|
||||
<div class="loading-container" id="initial-loading">
|
||||
<div style="text-align: center;">
|
||||
<div class="loading-spinner"></div>
|
||||
<div class="loading-text">₩ON AI 마케팅 서비스를 불러오는 중...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Service Worker Registration -->
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
.then((registration) => {
|
||||
console.log('SW registered: ', registration);
|
||||
})
|
||||
.catch((registrationError) => {
|
||||
console.log('SW registration failed: ', registrationError);
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,91 +1,68 @@
|
||||
//* public/manifest.json
|
||||
{
|
||||
"name": "₩ON - AI 마케팅 서비스",
|
||||
"short_name": "₩ON",
|
||||
"description": "소상공인을 위한 AI 기반 마케팅 솔루션",
|
||||
"version": "1.0.0",
|
||||
"lang": "ko",
|
||||
"name": "AI 마케팅 - 소상공인을 위한 스마트 마케팅 솔루션",
|
||||
"short_name": "AI 마케팅",
|
||||
"description": "AI를 활용한 소상공인 전용 마케팅 자동화 서비스",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait-primary",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#1976D2",
|
||||
"background_color": "#FAFAFA",
|
||||
"categories": ["business", "productivity", "marketing"],
|
||||
"orientation": "portrait-primary",
|
||||
"scope": "/",
|
||||
"lang": "ko-KR",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/images/logo-192.png",
|
||||
"src": "/images/logo192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/images/logo-512.png",
|
||||
"src": "/images/logo512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/favicon.ico",
|
||||
"sizes": "64x64",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"categories": ["business", "productivity", "marketing"],
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "/images/screenshot-1.png",
|
||||
"src": "/images/screenshot1.png",
|
||||
"sizes": "1280x720",
|
||||
"type": "image/png",
|
||||
"label": "메인 대시보드 화면"
|
||||
"form_factor": "wide"
|
||||
},
|
||||
{
|
||||
"src": "/images/screenshot-2.png",
|
||||
"sizes": "1280x720",
|
||||
"src": "/images/screenshot2.png",
|
||||
"sizes": "750x1334",
|
||||
"type": "image/png",
|
||||
"label": "AI 콘텐츠 생성 화면"
|
||||
"form_factor": "narrow"
|
||||
}
|
||||
],
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "대시보드",
|
||||
"short_name": "대시보드",
|
||||
"description": "매출 현황 및 성과 확인",
|
||||
"description": "메인 대시보드로 이동",
|
||||
"url": "/dashboard",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/images/shortcut-dashboard.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
"src": "/images/dashboard-icon.png",
|
||||
"sizes": "96x96"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "콘텐츠 생성",
|
||||
"short_name": "콘텐츠",
|
||||
"description": "AI 마케팅 콘텐츠 생성",
|
||||
"description": "새 콘텐츠 생성",
|
||||
"url": "/content/create",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/images/shortcut-content.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "매장 관리",
|
||||
"short_name": "매장",
|
||||
"description": "매장 및 메뉴 정보 관리",
|
||||
"url": "/store",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/images/shortcut-store.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
"src": "/images/content-icon.png",
|
||||
"sizes": "96x96"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"related_applications": [],
|
||||
"prefer_related_applications": false,
|
||||
"scope": "/",
|
||||
"id": "won-ai-marketing"
|
||||
]
|
||||
}
|
||||
@ -1,177 +1,28 @@
|
||||
//* public/runtime-env.js
|
||||
window.__runtime_config__ = {
|
||||
// API Gateway URL (단일 진입점)
|
||||
GATEWAY_URL: 'http://20.1.2.3',
|
||||
// API 서버 URL들
|
||||
AUTH_URL: 'http://20.1.2.3/auth',
|
||||
STORE_URL: 'http://20.1.2.3/store',
|
||||
CONTENT_URL: 'http://20.1.2.3/content',
|
||||
RECOMMEND_URL: 'http://20.1.2.3/recommend',
|
||||
|
||||
// 각 마이크로서비스별 URL (Ingress 라우팅)
|
||||
MEMBER_URL: 'http://20.1.2.3/api/member',
|
||||
AUTH_URL: 'http://20.1.2.3/api/auth',
|
||||
STORE_URL: 'http://20.1.2.3/api/store',
|
||||
CONTENT_URL: 'http://20.1.2.3/api/content',
|
||||
RECOMMEND_URL: 'http://20.1.2.3/api/recommendation',
|
||||
// 외부 API 설정
|
||||
CLAUDE_AI_ENABLED: true,
|
||||
WEATHER_API_ENABLED: true,
|
||||
|
||||
// 애플리케이션 설정
|
||||
APP_VERSION: '1.0.0',
|
||||
APP_NAME: '₩ON',
|
||||
// 기능 플래그
|
||||
FEATURES: {
|
||||
ANALYTICS: true,
|
||||
PUSH_NOTIFICATIONS: true,
|
||||
SOCIAL_LOGIN: false,
|
||||
MULTI_LANGUAGE: false,
|
||||
},
|
||||
|
||||
// 환경 설정
|
||||
ENVIRONMENT: 'production',
|
||||
DEBUG_MODE: false,
|
||||
ENV: 'production',
|
||||
DEBUG: false,
|
||||
|
||||
// API 설정
|
||||
API_TIMEOUT: 30000,
|
||||
RETRY_ATTEMPTS: 3,
|
||||
|
||||
// 파일 업로드 설정
|
||||
MAX_FILE_SIZE: 10485760, // 10MB
|
||||
ALLOWED_IMAGE_TYPES: ['image/jpeg', 'image/png', 'image/webp'],
|
||||
ALLOWED_VIDEO_TYPES: ['video/mp4', 'video/webm'],
|
||||
|
||||
// 인증 설정
|
||||
TOKEN_REFRESH_MARGIN: 300, // 5분 전 토큰 갱신
|
||||
SESSION_TIMEOUT: 1800, // 30분
|
||||
|
||||
// UI 설정
|
||||
MOBILE_BREAKPOINT: 768,
|
||||
TABLET_BREAKPOINT: 1024,
|
||||
|
||||
// 페이지네이션 설정
|
||||
DEFAULT_PAGE_SIZE: 20,
|
||||
MAX_PAGE_SIZE: 100,
|
||||
|
||||
// 알림 설정
|
||||
NOTIFICATION_TIMEOUT: 5000,
|
||||
MAX_NOTIFICATIONS: 5,
|
||||
|
||||
// 캐시 설정
|
||||
CACHE_DURATION: 300000, // 5분
|
||||
|
||||
// 지도 API (필요한 경우)
|
||||
// KAKAO_MAP_API_KEY: '',
|
||||
// NAVER_MAP_API_KEY: '',
|
||||
|
||||
// 소셜 로그인 (향후 확장)
|
||||
// KAKAO_APP_KEY: '',
|
||||
// NAVER_CLIENT_ID: '',
|
||||
// GOOGLE_CLIENT_ID: '',
|
||||
|
||||
// 분석 도구 (필요한 경우)
|
||||
// GOOGLE_ANALYTICS_ID: '',
|
||||
// HOTJAR_ID: '',
|
||||
|
||||
// CDN 설정
|
||||
IMAGE_CDN_URL: 'http://20.1.2.3/images',
|
||||
STATIC_CDN_URL: 'http://20.1.2.3/static',
|
||||
|
||||
// 피처 플래그
|
||||
FEATURES: {
|
||||
AI_RECOMMENDATION: true,
|
||||
VOICE_INPUT: false,
|
||||
DARK_MODE: true,
|
||||
OFFLINE_MODE: false,
|
||||
PUSH_NOTIFICATIONS: true,
|
||||
REAL_TIME_UPDATES: true,
|
||||
MULTI_STORE_SUPPORT: false,
|
||||
ADVANCED_ANALYTICS: true
|
||||
},
|
||||
|
||||
// 테마 설정
|
||||
THEME: {
|
||||
PRIMARY_COLOR: '#1976D2',
|
||||
SECONDARY_COLOR: '#FFC107',
|
||||
SUCCESS_COLOR: '#4CAF50',
|
||||
WARNING_COLOR: '#FF9800',
|
||||
ERROR_COLOR: '#F44336',
|
||||
INFO_COLOR: '#2196F3'
|
||||
},
|
||||
|
||||
// 국제화 설정
|
||||
LOCALE: 'ko-KR',
|
||||
TIMEZONE: 'Asia/Seoul',
|
||||
CURRENCY: 'KRW',
|
||||
|
||||
// 콘텐츠 생성 설정
|
||||
AI_CONTENT: {
|
||||
MAX_REGENERATION_COUNT: 5,
|
||||
CONTENT_TYPES: ['sns_post', 'poster', 'banner'],
|
||||
PLATFORMS: ['instagram', 'naver_blog', 'facebook'],
|
||||
TONE_OPTIONS: ['friendly', 'professional', 'humorous', 'elegant'],
|
||||
EMOTION_LEVELS: ['calm', 'normal', 'passionate', 'exaggerated']
|
||||
},
|
||||
|
||||
// 메뉴 카테고리 기본값
|
||||
DEFAULT_MENU_CATEGORIES: ['면류', '밥류', '튀김', '분식', '음료', '디저트', '기타'],
|
||||
|
||||
// 매장 업종 기본값
|
||||
BUSINESS_TYPES: [
|
||||
'한식', '중식', '일식', '양식', '분식', '치킨', '피자', '카페',
|
||||
'베이커리', '아이스크림', '패스트푸드', '기타'
|
||||
],
|
||||
|
||||
// 운영시간 기본값
|
||||
DEFAULT_OPERATING_HOURS: {
|
||||
WEEKDAY: { open: '09:00', close: '22:00' },
|
||||
WEEKEND: { open: '10:00', close: '21:00' }
|
||||
},
|
||||
|
||||
// 에러 메시지
|
||||
ERROR_MESSAGES: {
|
||||
NETWORK_ERROR: '네트워크 연결을 확인해주세요.',
|
||||
TIMEOUT_ERROR: '요청 시간이 초과되었습니다.',
|
||||
UNAUTHORIZED: '로그인이 필요합니다.',
|
||||
FORBIDDEN: '접근 권한이 없습니다.',
|
||||
NOT_FOUND: '요청한 페이지를 찾을 수 없습니다.',
|
||||
SERVER_ERROR: '서버 오류가 발생했습니다.',
|
||||
VALIDATION_ERROR: '입력 정보를 확인해주세요.',
|
||||
FILE_SIZE_ERROR: '파일 크기가 너무 큽니다.',
|
||||
FILE_TYPE_ERROR: '지원하지 않는 파일 형식입니다.'
|
||||
},
|
||||
|
||||
// 성공 메시지
|
||||
SUCCESS_MESSAGES: {
|
||||
LOGIN_SUCCESS: '로그인이 완료되었습니다.',
|
||||
LOGOUT_SUCCESS: '로그아웃이 완료되었습니다.',
|
||||
REGISTER_SUCCESS: '회원가입이 완료되었습니다.',
|
||||
UPDATE_SUCCESS: '정보가 수정되었습니다.',
|
||||
DELETE_SUCCESS: '삭제가 완료되었습니다.',
|
||||
SAVE_SUCCESS: '저장이 완료되었습니다.',
|
||||
UPLOAD_SUCCESS: '업로드가 완료되었습니다.',
|
||||
CONTENT_GENERATED: 'AI 콘텐츠가 생성되었습니다.'
|
||||
},
|
||||
|
||||
// 확인 메시지
|
||||
CONFIRM_MESSAGES: {
|
||||
DELETE_CONFIRM: '정말 삭제하시겠습니까?',
|
||||
LOGOUT_CONFIRM: '로그아웃 하시겠습니까?',
|
||||
CANCEL_CONFIRM: '작업을 취소하시겠습니까?',
|
||||
OVERWRITE_CONFIRM: '기존 내용을 덮어쓰시겠습니까?'
|
||||
}
|
||||
};
|
||||
|
||||
// 환경별 설정 오버라이드
|
||||
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
||||
// 개발 환경 설정
|
||||
window.__runtime_config__.ENVIRONMENT = 'development';
|
||||
window.__runtime_config__.DEBUG_MODE = true;
|
||||
window.__runtime_config__.GATEWAY_URL = 'http://localhost:8080';
|
||||
window.__runtime_config__.MEMBER_URL = 'http://localhost:8080/api/member';
|
||||
window.__runtime_config__.AUTH_URL = 'http://localhost:8080/api/auth';
|
||||
window.__runtime_config__.STORE_URL = 'http://localhost:8080/api/store';
|
||||
window.__runtime_config__.CONTENT_URL = 'http://localhost:8080/api/content';
|
||||
window.__runtime_config__.RECOMMEND_URL = 'http://localhost:8080/api/recommendation';
|
||||
// 버전 정보
|
||||
VERSION: '1.0.0',
|
||||
BUILD_DATE: new Date().toISOString(),
|
||||
}
|
||||
|
||||
// 설정 유효성 검사
|
||||
if (!window.__runtime_config__.GATEWAY_URL) {
|
||||
console.error('GATEWAY_URL이 설정되지 않았습니다.');
|
||||
}
|
||||
|
||||
// 전역 함수로 설정 노출
|
||||
window.getConfig = function(key) {
|
||||
return window.__runtime_config__[key];
|
||||
};
|
||||
|
||||
window.getFeature = function(featureName) {
|
||||
return window.__runtime_config__.FEATURES[featureName] || false;
|
||||
};
|
||||
|
||||
console.log('₩ON Runtime Configuration loaded:', window.__runtime_config__.ENVIRONMENT);
|
||||
|
||||
99
src/main.js
99
src/main.js
@ -16,6 +16,9 @@ import * as directives from 'vuetify/directives'
|
||||
import { mdi } from 'vuetify/iconsets/mdi'
|
||||
import '@mdi/font/css/materialdesignicons.css'
|
||||
|
||||
// 전역 스타일
|
||||
import './styles/main.scss'
|
||||
|
||||
// Vuetify 테마 설정
|
||||
const vuetify = createVuetify({
|
||||
components,
|
||||
@ -32,17 +35,32 @@ const vuetify = createVuetify({
|
||||
info: '#2196F3',
|
||||
success: '#4CAF50',
|
||||
warning: '#FFC107',
|
||||
background: '#F5F5F5',
|
||||
surface: '#FFFFFF'
|
||||
}
|
||||
}
|
||||
}
|
||||
background: '#FFFFFF',
|
||||
surface: '#FFFFFF',
|
||||
'surface-variant': '#F5F5F5',
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
colors: {
|
||||
primary: '#2196F3',
|
||||
secondary: '#616161',
|
||||
accent: '#82B1FF',
|
||||
error: '#FF5252',
|
||||
info: '#2196F3',
|
||||
success: '#4CAF50',
|
||||
warning: '#FFC107',
|
||||
background: '#121212',
|
||||
surface: '#1E1E1E',
|
||||
'surface-variant': '#424242',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
icons: {
|
||||
defaultSet: 'mdi',
|
||||
sets: {
|
||||
mdi
|
||||
}
|
||||
mdi,
|
||||
},
|
||||
},
|
||||
display: {
|
||||
mobileBreakpoint: 'sm',
|
||||
@ -51,15 +69,74 @@ const vuetify = createVuetify({
|
||||
sm: 600,
|
||||
md: 960,
|
||||
lg: 1280,
|
||||
xl: 1920
|
||||
}
|
||||
}
|
||||
xl: 1920,
|
||||
},
|
||||
},
|
||||
defaults: {
|
||||
VCard: {
|
||||
elevation: 2,
|
||||
rounded: 'lg',
|
||||
},
|
||||
VBtn: {
|
||||
rounded: 'lg',
|
||||
},
|
||||
VTextField: {
|
||||
variant: 'outlined',
|
||||
density: 'comfortable',
|
||||
},
|
||||
VSelect: {
|
||||
variant: 'outlined',
|
||||
density: 'comfortable',
|
||||
},
|
||||
VTextarea: {
|
||||
variant: 'outlined',
|
||||
density: 'comfortable',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Pinia 스토어 생성
|
||||
const pinia = createPinia()
|
||||
|
||||
// Vue 앱 생성
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
// 전역 속성 설정
|
||||
app.config.globalProperties.$config = window.__runtime_config__ || {}
|
||||
|
||||
// 에러 핸들링
|
||||
app.config.errorHandler = (err, instance, info) => {
|
||||
console.error('Vue 앱 에러:', err, info)
|
||||
|
||||
// 프로덕션 환경에서 에러 리포팅
|
||||
if (import.meta.env.PROD) {
|
||||
// 에러 리포팅 서비스에 전송
|
||||
// reportError(err, instance, info)
|
||||
}
|
||||
}
|
||||
|
||||
// 플러그인 등록
|
||||
app.use(pinia)
|
||||
app.use(router)
|
||||
app.use(vuetify)
|
||||
|
||||
// 개발 모드 설정
|
||||
if (import.meta.env.DEV) {
|
||||
app.config.performance = true
|
||||
window.__VUE_DEVTOOLS_GLOBAL_HOOK__ = window.__VUE_DEVTOOLS_GLOBAL_HOOK__ || {}
|
||||
}
|
||||
|
||||
// 앱 마운트
|
||||
app.mount('#app')
|
||||
|
||||
// 서비스 워커 등록 (프로덕션에서만)
|
||||
if (import.meta.env.PROD && 'serviceWorker' in navigator) {
|
||||
window.addEventListener('load', async () => {
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.register('/sw.js')
|
||||
console.log('서비스 워커 등록 성공:', registration)
|
||||
} catch (error) {
|
||||
console.log('서비스 워커 등록 실패:', error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
256
src/styles/main.scss
Normal file
256
src/styles/main.scss
Normal file
@ -0,0 +1,256 @@
|
||||
//* src/styles/main.scss
|
||||
/**
|
||||
* 메인 스타일시트
|
||||
* 전역 스타일 및 커스텀 스타일 정의
|
||||
*/
|
||||
|
||||
// 변수 정의
|
||||
:root {
|
||||
--primary-color: #1976d2;
|
||||
--secondary-color: #424242;
|
||||
--success-color: #4caf50;
|
||||
--warning-color: #ffc107;
|
||||
--error-color: #ff5252;
|
||||
--info-color: #2196f3;
|
||||
|
||||
--background-color: #ffffff;
|
||||
--surface-color: #ffffff;
|
||||
--surface-variant-color: #f5f5f5;
|
||||
|
||||
--text-primary: #212121;
|
||||
--text-secondary: #757575;
|
||||
--text-disabled: #bdbdbd;
|
||||
|
||||
--border-color: #e0e0e0;
|
||||
--divider-color: #f5f5f5;
|
||||
|
||||
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
|
||||
--border-radius-sm: 4px;
|
||||
--border-radius-md: 8px;
|
||||
--border-radius-lg: 12px;
|
||||
--border-radius-xl: 16px;
|
||||
|
||||
--transition-fast: 0.15s ease;
|
||||
--transition-normal: 0.3s ease;
|
||||
--transition-slow: 0.5s ease;
|
||||
}
|
||||
|
||||
// 다크 테마 변수
|
||||
[data-theme='dark'] {
|
||||
--background-color: #121212;
|
||||
--surface-color: #1e1e1e;
|
||||
--surface-variant-color: #424242;
|
||||
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #aaaaaa;
|
||||
--text-disabled: #666666;
|
||||
|
||||
--border-color: #333333;
|
||||
--divider-color: #2a2a2a;
|
||||
}
|
||||
|
||||
// 기본 스타일 리셋
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Roboto', 'Noto Sans KR', sans-serif;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
// 링크 스타일
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
transition: var(--transition-fast);
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
// 버튼 공통 스타일
|
||||
.btn-gradient {
|
||||
background: linear-gradient(135deg, var(--primary-color), #1565c0);
|
||||
color: white;
|
||||
border: none;
|
||||
transition: var(--transition-normal);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
}
|
||||
|
||||
// 카드 스타일
|
||||
.card-hover {
|
||||
transition: var(--transition-normal);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
}
|
||||
|
||||
// 유틸리티 클래스
|
||||
.text-truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.text-truncate-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.text-truncate-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 스크롤바 스타일
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--surface-variant-color);
|
||||
border-radius: var(--border-radius-md);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border-color);
|
||||
border-radius: var(--border-radius-md);
|
||||
|
||||
&:hover {
|
||||
background: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
// 로딩 애니메이션
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-pulse {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
// 페이드 인 애니메이션
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn var(--transition-normal);
|
||||
}
|
||||
|
||||
// 반응형 유틸리티
|
||||
@media (max-width: 600px) {
|
||||
.mobile-hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-full-width {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 601px) {
|
||||
.desktop-hide {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 접근성 개선
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
// 포커스 스타일 개선
|
||||
.focus-outline {
|
||||
&:focus {
|
||||
outline: 2px solid var(--primary-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// 인쇄 스타일
|
||||
@media print {
|
||||
.no-print {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.v-navigation-drawer,
|
||||
.v-app-bar,
|
||||
.v-bottom-navigation {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.v-main {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 고대비 모드 지원
|
||||
@media (prefers-contrast: high) {
|
||||
:root {
|
||||
--border-color: #000000;
|
||||
--text-secondary: #000000;
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
--border-color: #ffffff;
|
||||
--text-secondary: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
// 움직임 줄이기 설정 존중
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
@ -1,26 +1,38 @@
|
||||
//* vite.config.js
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vuetify from 'vite-plugin-vuetify'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vuetify({
|
||||
autoImport: true,
|
||||
theme: {
|
||||
defaultTheme: 'light'
|
||||
}
|
||||
})
|
||||
],
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
host: true
|
||||
}
|
||||
host: true,
|
||||
open: true,
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
vendor: ['vue', 'vue-router', 'pinia'],
|
||||
vuetify: ['vuetify'],
|
||||
icons: ['@mdi/font'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `@import "@/styles/variables.scss";`,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user