Merge branch 'dev'
This commit is contained in:
+16
-1
@@ -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(로그인 세션)는 캐시가 아니라 영향 없음.
|
||||
|
||||
@@ -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'),
|
||||
})
|
||||
+1
-1
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
<!-- 자주 쓰는 내역(빠른 등록) — 칩 탭 시 오늘 날짜로 즉시 등록 -->
|
||||
<div v-if="quickEntries.length" class="quick-bar">
|
||||
<span class="quick-bar-label">⭐ 자주 쓰는 내역</span>
|
||||
<button
|
||||
v-for="q in quickEntries" :key="q.id" type="button" class="quick-chip"
|
||||
:title="quickEditMode ? '' : '오늘 날짜로 바로 등록'" @click="useQuick(q)"
|
||||
@@ -848,14 +838,17 @@ onMounted(async () => {
|
||||
</div>
|
||||
|
||||
<div class="month-nav">
|
||||
<span class="mn-spacer"></span>
|
||||
<IconBtn icon="chevronLeft" title="이전 달" size="sm" @click="prevMonth" />
|
||||
<span class="period">{{ periodLabel }}</span>
|
||||
<IconBtn icon="chevronRight" title="다음 달" size="sm" @click="nextMonth" />
|
||||
<IconBtn
|
||||
icon="filter" title="검색·필터" class="filter-toggle"
|
||||
:variant="hasFilter ? 'primary' : 'default'" @click="filterOpen = !filterOpen"
|
||||
/>
|
||||
<IconBtn icon="plus" title="내역 추가" variant="primary" @click="openCreate" />
|
||||
<div class="mn-actions">
|
||||
<IconBtn
|
||||
icon="filter" title="검색·필터" class="filter-toggle"
|
||||
:variant="hasFilter ? 'primary' : 'default'" @click="filterOpen = !filterOpen"
|
||||
/>
|
||||
<IconBtn icon="plus" title="내역 추가" variant="primary" @click="openCreate" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="filterOpen" class="filter-bar">
|
||||
@@ -986,13 +979,6 @@ onMounted(async () => {
|
||||
<div v-if="ocrRunning" class="receipt-progress"><div class="bar"></div></div>
|
||||
</div>
|
||||
|
||||
<!-- 신규 추가 시: 자주 쓰는 내역 불러오기(모달 피커) -->
|
||||
<button
|
||||
v-if="!editId && quickEntries.length"
|
||||
type="button" class="to-recurring to-quick"
|
||||
:disabled="submitting" @click="quickPickerOpen = true"
|
||||
>⭐ 자주 쓰는 내역 불러오기</button>
|
||||
|
||||
<label>거래일<input v-model="form.entryDate" type="date" :disabled="submitting" /></label>
|
||||
<label>구분
|
||||
<select v-model="form.type" :disabled="submitting" @change="onTypeChange">
|
||||
@@ -1177,29 +1163,6 @@ onMounted(async () => {
|
||||
</Teleport>
|
||||
|
||||
<!-- 자주 쓰는 내역 불러오기 (모달 피커) -->
|
||||
<Teleport to="body">
|
||||
<Transition name="fade">
|
||||
<div v-if="quickPickerOpen" class="modal-backdrop quick-picker-backdrop" @click.self="quickPickerOpen = false">
|
||||
<div class="modal quick-picker" role="dialog" aria-modal="true">
|
||||
<button class="close" type="button" @click="quickPickerOpen = false">×</button>
|
||||
<h2>자주 쓰는 내역 불러오기</h2>
|
||||
<ul v-if="quickEntries.length" class="qp-list">
|
||||
<li v-for="q in quickEntries" :key="q.id">
|
||||
<button type="button" class="qp-item" @click="pickQuick(q)">
|
||||
<span class="qp-label">{{ quickLabel(q) }}</span>
|
||||
<span class="qp-meta">
|
||||
<span v-if="q.category" class="qp-cat">{{ q.category }}</span>
|
||||
<span class="qp-amt" :class="q.type === 'INCOME' ? 'income' : 'expense'">{{ q.type === 'INCOME' ? '+' : '-' }}{{ won(q.amount) }}</span>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else class="msg">저장된 자주 쓰는 내역이 없습니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
|
||||
<!-- 문자/푸시 붙여넣기 (모달) -->
|
||||
<Teleport to="body">
|
||||
<Transition name="fade">
|
||||
@@ -1265,6 +1228,16 @@ button.primary {
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
/* 좌측 spacer + 우측 액션을 같은 flex:1 로 둬서 가운데 네비를 정확히 중앙 유지 */
|
||||
.mn-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
.mn-actions {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.period {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
@@ -1839,6 +1812,12 @@ button.primary {
|
||||
margin: 0.4rem 0 0.2rem;
|
||||
align-items: center;
|
||||
}
|
||||
.quick-bar-label {
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
opacity: 0.65;
|
||||
margin-right: 0.2rem;
|
||||
}
|
||||
.quick-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -243,10 +243,13 @@ onMounted(() => {
|
||||
<template>
|
||||
<section class="budget">
|
||||
<div class="month-nav">
|
||||
<span class="mn-spacer"></span>
|
||||
<IconBtn icon="chevronLeft" title="이전 달" size="sm" @click="prevMonth" />
|
||||
<span class="period">{{ periodLabel }}</span>
|
||||
<IconBtn icon="chevronRight" title="다음 달" size="sm" @click="nextMonth" />
|
||||
<IconBtn icon="plus" title="예산 추가" variant="primary" @click="openCreate" />
|
||||
<div class="mn-actions">
|
||||
<IconBtn icon="plus" title="예산 추가" variant="primary" @click="openCreate" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 월 예상 수입 vs 총 예산 -->
|
||||
@@ -412,6 +415,15 @@ button.primary {
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.mn-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
.mn-actions {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.period {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -412,14 +412,19 @@ button:disabled {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.search-type {
|
||||
flex: none;
|
||||
padding: 0.5rem 0.6rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
background: var(--color-background-soft);
|
||||
color: var(--color-text);
|
||||
}
|
||||
.search-form > .icon-btn {
|
||||
flex: none;
|
||||
}
|
||||
.search-form input {
|
||||
flex: 1;
|
||||
min-width: 0; /* flex 자식이 내용 최소폭 아래로 줄어들 수 있게 — 검색 버튼이 모달 밖으로 밀리는 문제 방지 */
|
||||
padding: 0.5rem 0.7rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -7,15 +7,6 @@ const auth = useAuthStore()
|
||||
const router = useRouter()
|
||||
|
||||
const u = computed(() => auth.user || {})
|
||||
const isLocal = computed(() => (u.value.provider || 'LOCAL') === 'LOCAL')
|
||||
|
||||
const providerLabel = computed(() => {
|
||||
switch (u.value.provider) {
|
||||
case 'NAVER': return '네이버'
|
||||
case 'LOCAL':
|
||||
default: return '일반(아이디/비밀번호)'
|
||||
}
|
||||
})
|
||||
const roleLabel = computed(() => (u.value.role === 'ADMIN' ? '관리자' : '일반회원'))
|
||||
|
||||
function goEdit() {
|
||||
@@ -27,10 +18,6 @@ function goEdit() {
|
||||
<div class="account-info">
|
||||
|
||||
<section class="card">
|
||||
<div class="row">
|
||||
<span class="k">아이디</span>
|
||||
<span class="v">{{ u.loginId || '-' }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="k">이름</span>
|
||||
<span class="v">{{ u.name || '-' }}</span>
|
||||
@@ -39,18 +26,13 @@ function goEdit() {
|
||||
<span class="k">이메일</span>
|
||||
<span class="v">{{ u.email || '-' }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="k">가입유형</span>
|
||||
<span class="v">{{ providerLabel }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="k">권한</span>
|
||||
<span class="v">{{ roleLabel }}</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button v-if="isLocal" type="button" class="primary-btn" @click="goEdit">정보변경</button>
|
||||
<p v-else class="hint">소셜 로그인 계정은 앱에서 가입정보를 변경할 수 없습니다.</p>
|
||||
<button type="button" class="primary-btn" @click="goEdit">이름 변경</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { computed, reactive, ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { authApi } from '@/api/authApi'
|
||||
@@ -7,17 +7,22 @@ import { authApi } from '@/api/authApi'
|
||||
const auth = useAuthStore()
|
||||
const router = useRouter()
|
||||
|
||||
const step = ref('verify') // 'verify' → 'edit'
|
||||
// 소셜(구글 등) 계정은 비밀번호가 없어 재인증 없이 이름만 변경
|
||||
const isSocial = computed(() => (auth.user?.provider || 'LOCAL') !== 'LOCAL')
|
||||
|
||||
const step = ref('verify') // 'verify' → 'edit' (소셜은 곧바로 edit)
|
||||
const password = ref('') // verify 단계에서 입력한 현재 비밀번호 (비밀번호 변경 시 재사용)
|
||||
const form = reactive({ name: '', email: '' })
|
||||
const pw = reactive({ next: '', confirm: '' }) // 비밀번호 변경(선택)
|
||||
const pw = reactive({ next: '', confirm: '' }) // 비밀번호 변경(선택, LOCAL 전용)
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
onMounted(() => {
|
||||
// 소셜 계정은 비밀번호 인증 불가 → 계정정보로 돌려보냄
|
||||
if ((auth.user?.provider || 'LOCAL') !== 'LOCAL') {
|
||||
router.replace('/settings/account')
|
||||
// 소셜 계정: 비밀번호 인증 단계 건너뛰고 바로 이름 변경 화면으로
|
||||
if (isSocial.value) {
|
||||
form.name = auth.user?.name || ''
|
||||
form.email = auth.user?.email || ''
|
||||
step.value = 'edit'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -48,13 +53,14 @@ async function save() {
|
||||
error.value = '이름을 입력하세요.'
|
||||
return
|
||||
}
|
||||
const email = form.email.trim()
|
||||
// 소셜 계정은 이메일(구글 제공) 유지, LOCAL 만 폼에서 편집
|
||||
const email = isSocial.value ? (auth.user?.email || null) : (form.email.trim() || null)
|
||||
if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
error.value = '이메일 형식이 올바르지 않습니다.'
|
||||
return
|
||||
}
|
||||
// 비밀번호 변경(선택) — 새 비밀번호를 입력한 경우에만 검증/적용
|
||||
const changePw = !!pw.next
|
||||
// 비밀번호 변경(선택, LOCAL 전용) — 새 비밀번호를 입력한 경우에만 검증/적용
|
||||
const changePw = !isSocial.value && !!pw.next
|
||||
if (changePw) {
|
||||
if (pw.next.length < 8 || pw.next.length > 64) {
|
||||
error.value = '새 비밀번호는 8~64자여야 합니다.'
|
||||
@@ -112,7 +118,7 @@ function cancel() {
|
||||
|
||||
<!-- 2단계: 가입정보 변경 -->
|
||||
<form v-else class="card form" @submit.prevent="save">
|
||||
<label class="field">
|
||||
<label v-if="!isSocial" class="field">
|
||||
<span class="flabel">아이디</span>
|
||||
<input :value="auth.user?.loginId" type="text" disabled />
|
||||
<span class="fnote">아이디는 변경할 수 없습니다.</span>
|
||||
@@ -121,21 +127,23 @@ function cancel() {
|
||||
<span class="flabel">이름</span>
|
||||
<input v-model="form.name" type="text" maxlength="100" placeholder="이름" :disabled="loading" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<label v-if="!isSocial" class="field">
|
||||
<span class="flabel">이메일</span>
|
||||
<input v-model="form.email" type="email" maxlength="255" placeholder="이메일 (선택)" :disabled="loading" />
|
||||
</label>
|
||||
|
||||
<hr class="divider" />
|
||||
<p class="section-label">비밀번호 변경 <span class="opt">(변경 시에만 입력)</span></p>
|
||||
<label class="field">
|
||||
<span class="flabel">새 비밀번호</span>
|
||||
<input v-model="pw.next" type="password" autocomplete="new-password" maxlength="64" placeholder="새 비밀번호 (8~64자)" :disabled="loading" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span class="flabel">새 비밀번호 확인</span>
|
||||
<input v-model="pw.confirm" type="password" autocomplete="new-password" maxlength="64" placeholder="새 비밀번호 확인" :disabled="loading" />
|
||||
</label>
|
||||
<template v-if="!isSocial">
|
||||
<hr class="divider" />
|
||||
<p class="section-label">비밀번호 변경 <span class="opt">(변경 시에만 입력)</span></p>
|
||||
<label class="field">
|
||||
<span class="flabel">새 비밀번호</span>
|
||||
<input v-model="pw.next" type="password" autocomplete="new-password" maxlength="64" placeholder="새 비밀번호 (8~64자)" :disabled="loading" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span class="flabel">새 비밀번호 확인</span>
|
||||
<input v-model="pw.confirm" type="password" autocomplete="new-password" maxlength="64" placeholder="새 비밀번호 확인" :disabled="loading" />
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<p v-if="error" class="error">{{ error }}</p>
|
||||
<div class="actions">
|
||||
|
||||
@@ -15,6 +15,22 @@ const THEMES = [
|
||||
const appVersion = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '-'
|
||||
const clearing = ref(false)
|
||||
|
||||
// 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
|
||||
if (!v) return
|
||||
const [w, h] = v.split('x').map(Number)
|
||||
window.desktop?.setWindowSize(w, h)
|
||||
}
|
||||
|
||||
async function clearAppData() {
|
||||
if (clearing.value) return
|
||||
const ok = await dialog.confirm(
|
||||
@@ -62,6 +78,20 @@ async function clearAppData() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- PC(데스크톱) 전용: 창 해상도 -->
|
||||
<section v-if="isDesktop" class="card">
|
||||
<div class="row">
|
||||
<div class="row-main">
|
||||
<span class="row-label">화면 해상도</span>
|
||||
<span class="row-sub">데스크톱 창 크기를 선택한 크기로 변경</span>
|
||||
</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>
|
||||
|
||||
<section class="card">
|
||||
<RouterLink to="/settings/account" class="row row-link">
|
||||
<div class="row-main">
|
||||
@@ -164,6 +194,10 @@ async function clearAppData() {
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.res-select {
|
||||
flex: none;
|
||||
max-width: 150px;
|
||||
}
|
||||
.seg-btn {
|
||||
padding: 0.4rem 0.7rem;
|
||||
border: 0;
|
||||
|
||||
Reference in New Issue
Block a user