diff --git a/electron/main.cjs b/electron/main.cjs index b45b068..e606362 100644 --- a/electron/main.cjs +++ b/electron/main.cjs @@ -1,7 +1,7 @@ // Slim Budget 데스크톱(Electron) 메인 프로세스. // 라이브 사이트(https://app.sblog.kr)를 직접 로드한다 → 프론트 변경이 자동 반영(재설치 불필요), // 같은 출처라 CORS 우회도 불필요. 배포된 웹 게이트가 Electron 을 감지해 안내페이지 대신 전체 앱을 띄운다. -const { app, BrowserWindow, shell, session } = require('electron') +const { app, BrowserWindow, shell, session, ipcMain } = require('electron') const fs = require('fs') const path = require('path') @@ -58,6 +58,7 @@ function createWindow() { webPreferences: { contextIsolation: true, nodeIntegration: false, + preload: path.join(__dirname, 'preload.cjs'), // 한글 IME 첫 글자 중복 입력 방지 — Chromium 맞춤법 검사기가 조합을 방해하는 Electron 알려진 버그 회피 spellcheck: false, }, @@ -91,6 +92,20 @@ function createWindow() { }) } +// 데스크톱 창 크기(해상도) 변경 — 렌더러(설정 화면)에서 호출 +ipcMain.handle('window:setSize', (e, w, h) => { + const win = BrowserWindow.fromWebContents(e.sender) + if (!win) return false + if (win.isMaximized()) win.unmaximize() + win.setSize(Math.max(360, Math.round(w)), Math.max(560, Math.round(h))) + win.center() + return true +}) +ipcMain.handle('window:getSize', (e) => { + const win = BrowserWindow.fromWebContents(e.sender) + return win ? win.getSize() : [0, 0] +}) + app.whenReady().then(async () => { // 시작 시 HTTP 캐시 비움 → 라이브 사이트의 최신 프론트를 항상 로드(옛 HTML 캐시로 인한 미반영 방지). // localStorage(로그인 세션)는 캐시가 아니라 영향 없음. diff --git a/electron/preload.cjs b/electron/preload.cjs new file mode 100644 index 0000000..ac99982 --- /dev/null +++ b/electron/preload.cjs @@ -0,0 +1,8 @@ +// 렌더러(웹)에서 안전하게 호출할 데스크톱 전용 API 노출 (contextIsolation 유지). +const { contextBridge, ipcRenderer } = require('electron') + +contextBridge.exposeInMainWorld('desktop', { + // 데스크톱 창 크기(해상도) 변경 + setWindowSize: (w, h) => ipcRenderer.invoke('window:setSize', w, h), + getWindowSize: () => ipcRenderer.invoke('window:getSize'), +}) diff --git a/src/App.vue b/src/App.vue index bfcdedd..c06bfe2 100644 --- a/src/App.vue +++ b/src/App.vue @@ -68,7 +68,7 @@ watch(() => route.fullPath, () => ui.closeSidebar()) .layout { display: grid; grid-template-areas: - 'top top' + 'left top' 'left body' 'bottom bottom'; grid-template-columns: 220px 1fr; diff --git a/src/components/layout/AppSidebar.vue b/src/components/layout/AppSidebar.vue index 9e128fb..cd1b782 100644 --- a/src/components/layout/AppSidebar.vue +++ b/src/components/layout/AppSidebar.vue @@ -100,13 +100,14 @@ const icons = { .app-sidebar { background: var(--color-background-soft); border-right: 1px solid var(--color-border); - padding: 1rem 0; + padding: 0 0 1rem; } .drawer-head { display: flex; align-items: center; justify-content: space-between; - padding: 0.5rem 1.1rem 0.75rem; + height: 56px; /* 헤더 높이와 맞춰 상단 정렬 (하단 보더 일치) */ + padding: 0 1.1rem; margin-bottom: 0.5rem; border-bottom: 1px solid var(--color-border); } diff --git a/src/views/account/AccountView.vue b/src/views/account/AccountView.vue index 40b98d6..4d05d63 100644 --- a/src/views/account/AccountView.vue +++ b/src/views/account/AccountView.vue @@ -654,7 +654,6 @@ async function doRegisterRecurring() { // ===== 자주 쓰는 내역(빠른 등록 템플릿) ===== const quickEntries = ref([]) const quickEditMode = ref(false) -const quickPickerOpen = ref(false) // 불러오기 모달 피커 async function loadQuick() { try { quickEntries.value = await accountApi.quickEntries() @@ -668,6 +667,7 @@ function quickLabel(q) { // 칩 탭 → 오늘 날짜로 즉시 등록 async function useQuick(q) { if (quickEditMode.value) return + // 1) 등록 — 이 단계 실패만 '등록 실패'로 처리 try { await accountApi.create({ entryDate: todayStr(), @@ -680,11 +680,17 @@ async function useQuick(q) { installmentMonths: null, tagIds: [], }) - await load() - await loadPendingCount() - flash(`'${quickLabel(q)}' ${won(q.amount)} 등록했습니다.`) } catch (e) { flash(e.response?.data?.message || '빠른 등록에 실패했습니다.') + return + } + // 2) 등록 성공 안내 + 목록 갱신(갱신 실패는 등록 성공을 가리지 않게 분리) + flash(`'${quickLabel(q)}' ${won(q.amount)} 등록했습니다.`) + try { + await load() + await loadPendingCount() + } catch { + /* 목록 갱신 실패는 무시 — 다음 진입/새로고침 시 반영 */ } } async function removeQuick(q) { @@ -695,23 +701,6 @@ async function removeQuick(q) { flash(e.response?.data?.message || '삭제에 실패했습니다.') } } -// 신규 추가 시: 자주 쓰는 내역을 폼에 불러오기(채움, 저장은 사용자가) -function applyQuick(q) { - if (!q) return - form.type = q.type - form.category = q.category || '' - form.amount = q.amount - form.memo = q.memo || '' - if (q.walletId) { - form.walletId = q.walletId - form.walletKind = walletKindOf(q.walletId) || form.walletKind - } - syncCategoryMajor() -} -function pickQuick(q) { - applyQuick(q) - quickPickerOpen.value = false -} // 현재 폼 값을 자주 쓰는 내역으로 저장 async function saveAsQuick() { if (form.type !== 'INCOME' && form.type !== 'EXPENSE') return @@ -830,6 +819,7 @@ onMounted(async () => {
+ ⭐ 자주 쓰는 내역
+ {{ periodLabel }} - - +
+ + +
@@ -986,13 +979,6 @@ onMounted(async () => {
- - -