feat: 설정 - 엑셀 가져오기(복구) + 창 크기 가로·세로 분리 조절
CI / build (push) Failing after 12m18s

- backup.js importBackup: .xlsx 파싱 → 이름 기준 payload → POST /account/restore
- SettingsView: '가져오기' 파일 선택 + 파괴적 작업 확인(이중) + 완료 후 새로고침
- accountApi.restore (데모에선 차단)
- 창 크기: 가로/세로 각각 선택(세로 700~1200), 한 축 변경 시 나머지 유지

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-06-28 08:38:38 +09:00
parent 457fd8127a
commit 7d8f947156
3 changed files with 145 additions and 26 deletions
+72 -25
View File
@@ -4,7 +4,7 @@ import { RouterLink } from 'vue-router'
import { Preferences } from '@capacitor/preferences'
import { useDialog } from '@/composables/dialog'
import { useUiStore } from '@/stores/ui'
import { exportBackup } from '@/utils/backup'
import { exportBackup, importBackup } from '@/utils/backup'
const dialog = useDialog()
const ui = useUiStore()
@@ -21,8 +21,32 @@ async function doExport() {
exporting.value = false
}
}
function importBackup() {
dialog.alert('가져오기(복구)는 준비 중입니다. 곧 제공됩니다.')
const importing = ref(false)
const fileInput = ref(null)
function pickImportFile() {
if (importing.value) return
fileInput.value?.click()
}
async function onImportFile(e) {
const file = e.target.files?.[0]
e.target.value = '' // 같은 파일 다시 선택 가능하게 초기화
if (!file) return
const ok = await dialog.confirm(
'가져오기를 하면 현재 가계부의 모든 데이터가 백업 파일 내용으로 교체됩니다.\n(투자 종목·매매내역은 백업에 없어 사라집니다)\n계속하시겠습니까?',
{ title: '데이터 가져오기', okText: '가져오기', danger: true },
)
if (!ok) return
importing.value = true
try {
await importBackup(file)
await dialog.alert('가져오기가 완료되었습니다. 화면을 새로고침합니다.')
window.location.reload()
} catch (err) {
window.alert(err?.response?.data?.message || err?.message || '가져오기에 실패했습니다.')
} finally {
importing.value = false
}
}
const THEMES = [
{ value: 'system', label: '시스템' },
@@ -32,19 +56,22 @@ const THEMES = [
const appVersion = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '-'
const clearing = ref(false)
// PC(데스크톱) 전용: 창 해상도 변경 (Electron preload 의 window.desktop 존재 시에만)
// PC(데스크톱) 전용: 창 크기(가로·세로 각각) 변경 (Electron preload 의 window.desktop 존재 시에만)
const isDesktop = typeof window !== 'undefined' && !!window.desktop
const RESOLUTIONS = [
{ label: '1024 × 720', w: 1024, h: 720 },
{ label: '1280 × 800', w: 1280, h: 800 },
{ label: '1366 × 768', w: 1366, h: 768 },
{ label: '1600 × 900', w: 1600, h: 900 },
{ label: '1920 × 1080', w: 1920, h: 1080 },
]
function onResolution(e) {
const v = e.target.value
const WIDTHS = [900, 1024, 1280, 1366, 1600, 1920]
const HEIGHTS = [700, 800, 900, 1000, 1080, 1200]
// 한 축만 바꾸고 나머지 축은 현재 창 크기를 유지
async function setDim(dim, value) {
const v = Number(value)
if (!v) return
const [w, h] = v.split('x').map(Number)
let cur = [1024, 768]
try {
cur = (await window.desktop?.getWindowSize?.()) || cur
} catch {
/* 무시 */
}
const w = dim === 'w' ? v : cur[0]
const h = dim === 'h' ? v : cur[1]
window.desktop?.setWindowSize(w, h)
}
@@ -95,17 +122,24 @@ async function clearAppData() {
</div>
</section>
<!-- PC(데스크톱) 전용: 해상도 -->
<!-- PC(데스크톱) 전용: 크기 (가로·세로 각각) -->
<section v-if="isDesktop" class="card">
<div class="row">
<div class="row-main">
<span class="row-label">화면 해상도</span>
<span class="row-sub">데스크톱 크기를 선택한 크기로 변경</span>
<span class="row-label"> 크기</span>
<span class="row-sub">가로·세로를 각각 선택</span>
</div>
<div class="size-selects">
<select class="res-select" title="가로(폭)" @change="setDim('w', $event.target.value)">
<option value="">가로</option>
<option v-for="w in WIDTHS" :key="w" :value="w">{{ w }}</option>
</select>
<span class="x">×</span>
<select class="res-select" title="세로(높이)" @change="setDim('h', $event.target.value)">
<option value="">세로</option>
<option v-for="h in HEIGHTS" :key="h" :value="h">{{ h }}</option>
</select>
</div>
<select class="res-select" @change="onResolution">
<option value="">선택</option>
<option v-for="r in RESOLUTIONS" :key="r.label" :value="`${r.w}x${r.h}`">{{ r.label }}</option>
</select>
</div>
</section>
@@ -118,13 +152,14 @@ async function clearAppData() {
</div>
<span class="row-value">{{ exporting ? '내보내는 중…' : '내보내기' }}</span>
</button>
<button type="button" class="row row-btn" @click="importBackup">
<button type="button" class="row row-btn" :disabled="importing" @click="pickImportFile">
<div class="row-main">
<span class="row-label">엑셀에서 가져오기</span>
<span class="row-sub">백업 파일로 데이터 복구</span>
<span class="row-sub">백업 파일로 데이터 복구 (기존 데이터 덮어씀)</span>
</div>
<span class="row-value">가져오기</span>
<span class="row-value">{{ importing ? '복구 중…' : '가져오기' }}</span>
</button>
<input ref="fileInput" type="file" accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" class="hidden-file" @change="onImportFile" />
</section>
<section class="card">
@@ -229,9 +264,21 @@ async function clearAppData() {
overflow: hidden;
flex-shrink: 0;
}
.size-selects {
display: flex;
align-items: center;
gap: 0.3rem;
flex: none;
}
.size-selects .x {
opacity: 0.5;
}
.res-select {
flex: none;
max-width: 150px;
max-width: 90px;
}
.hidden-file {
display: none;
}
.seg-btn {
padding: 0.4rem 0.7rem;