feat: 네이티브 alert/confirm/prompt → 공용 인앱 다이얼로그 (PC 'sb_pt' 제거)
CI / build (push) Failing after 15m30s
CI / build (push) Failing after 15m30s
- composables/dialog + components/ui/AppDialog(App 루트 마운트) - window.alert 전역 오버라이드로 모든 alert 인앱화 - confirm/prompt(동기)는 각 호출부를 await dialog.confirm/prompt 로 교체 (가계부·예산·고정지출·계좌·태그·관리자·게시판·설정 — 삭제/열람제한 등) - 웹/PC/APK 일관 UI, 의미있는 제목 부여 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,9 +2,11 @@
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { adminApi } from '@/api/adminApi'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useDialog } from '@/composables/dialog'
|
||||
import IconBtn from '@/components/ui/IconBtn.vue'
|
||||
|
||||
const auth = useAuthStore()
|
||||
const dialog = useDialog()
|
||||
const myId = computed(() => auth.user?.id)
|
||||
|
||||
const members = ref([])
|
||||
@@ -89,7 +91,7 @@ async function changeStatus(m, status) {
|
||||
}
|
||||
|
||||
async function remove(m) {
|
||||
if (!confirm(`'${m.name}(${m.loginId})' 회원을 삭제할까요? 되돌릴 수 없습니다.`)) return
|
||||
if (!(await dialog.confirm(`'${m.name}(${m.loginId})' 회원을 삭제할까요?\n되돌릴 수 없습니다.`, { title: '회원 삭제', danger: true }))) return
|
||||
try {
|
||||
await adminApi.removeMember(m.id)
|
||||
members.value = members.value.filter((x) => x.id !== m.id)
|
||||
|
||||
@@ -3,9 +3,11 @@ import { onBeforeUnmount, onMounted, ref, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import Sortable from 'sortablejs'
|
||||
import { accountApi } from '@/api/accountApi'
|
||||
import { useDialog } from '@/composables/dialog'
|
||||
import IconBtn from '@/components/ui/IconBtn.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const dialog = useDialog()
|
||||
|
||||
const tags = ref([])
|
||||
const loading = ref(false)
|
||||
@@ -79,7 +81,7 @@ async function saveTag(tag) {
|
||||
}
|
||||
|
||||
async function removeTag(tag) {
|
||||
if (!confirm(`'${tag.name}' 태그를 삭제할까요? (내역에서 연결도 해제됩니다)`)) return
|
||||
if (!(await dialog.confirm(`'${tag.name}' 태그를 삭제할까요?\n내역에서 연결도 해제됩니다.`, { title: '태그 삭제', danger: true }))) return
|
||||
try {
|
||||
await accountApi.removeTag(tag.id)
|
||||
await load()
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<script setup>
|
||||
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { accountApi } from '@/api/accountApi'
|
||||
import { useDialog } from '@/composables/dialog'
|
||||
import IconBtn from '@/components/ui/IconBtn.vue'
|
||||
import { imageToBlob, parseReceiptText } from '@/utils/receiptOcr'
|
||||
import { cardNotif } from '@/native/cardNotif'
|
||||
import { Capacitor } from '@capacitor/core'
|
||||
// @capacitor/camera 는 네이티브에서만 동적 로드 (웹은 file input 사용 — 정적 import 시 모바일 웹 청크 로드 실패 유발)
|
||||
|
||||
const dialog = useDialog()
|
||||
const now = new Date()
|
||||
const year = ref(now.getFullYear())
|
||||
const month = ref(now.getMonth() + 1)
|
||||
@@ -570,7 +572,7 @@ async function submit() {
|
||||
}
|
||||
|
||||
async function remove(e) {
|
||||
if (!confirm('이 내역을 삭제하시겠습니까?')) return
|
||||
if (!(await dialog.confirm('이 내역을 삭제하시겠습니까?', { title: '내역 삭제', danger: true }))) return
|
||||
try {
|
||||
await accountApi.remove(e.id)
|
||||
await load()
|
||||
|
||||
@@ -3,9 +3,11 @@ import { onBeforeUnmount, onMounted, reactive, ref, watch, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import Sortable from 'sortablejs'
|
||||
import { accountApi } from '@/api/accountApi'
|
||||
import { useDialog } from '@/composables/dialog'
|
||||
import IconBtn from '@/components/ui/IconBtn.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const dialog = useDialog()
|
||||
|
||||
const TABS = [
|
||||
{ key: 'BANK', label: '은행계좌' },
|
||||
@@ -285,7 +287,7 @@ async function submit() {
|
||||
}
|
||||
|
||||
async function remove(w) {
|
||||
if (!confirm(`'${w.name}' 을(를) 삭제할까요?`)) return
|
||||
if (!(await dialog.confirm(`'${w.name}' 을(를) 삭제할까요?`, { title: '삭제', danger: true }))) return
|
||||
try {
|
||||
await accountApi.removeWallet(w.id)
|
||||
await load()
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { accountApi } from '@/api/accountApi'
|
||||
import { useDialog } from '@/composables/dialog'
|
||||
import IconBtn from '@/components/ui/IconBtn.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const dialog = useDialog()
|
||||
|
||||
const now = new Date()
|
||||
const year = ref(now.getFullYear())
|
||||
@@ -224,7 +226,7 @@ async function submit() {
|
||||
}
|
||||
|
||||
async function remove(s) {
|
||||
if (!confirm(`'${s.category}' 예산을 삭제할까요?`)) return
|
||||
if (!(await dialog.confirm(`'${s.category}' 예산을 삭제할까요?`, { title: '예산 삭제', danger: true }))) return
|
||||
try {
|
||||
await accountApi.removeBudget(s.id)
|
||||
await load()
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { accountApi } from '@/api/accountApi'
|
||||
import { useDialog } from '@/composables/dialog'
|
||||
import IconBtn from '@/components/ui/IconBtn.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const dialog = useDialog()
|
||||
|
||||
const recurrings = ref([])
|
||||
const wallets = ref([])
|
||||
@@ -224,7 +226,7 @@ async function submit() {
|
||||
}
|
||||
|
||||
async function remove(r) {
|
||||
if (!confirm(`'${r.title}' 고정 지출를 삭제할까요? (이미 생성된 내역은 유지됩니다)`)) return
|
||||
if (!(await dialog.confirm(`'${r.title}' 고정 지출를 삭제할까요?\n이미 생성된 내역은 유지됩니다.`, { title: '고정지출 삭제', danger: true }))) return
|
||||
try {
|
||||
await accountApi.removeRecurring(r.id)
|
||||
await load()
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { adminApi } from '@/api/adminApi'
|
||||
import { useDialog } from '@/composables/dialog'
|
||||
import IconBtn from '@/components/ui/IconBtn.vue'
|
||||
|
||||
const dialog = useDialog()
|
||||
const categories = ref([])
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
@@ -62,7 +64,7 @@ async function saveCategory(cat) {
|
||||
}
|
||||
|
||||
async function removeCategory(cat) {
|
||||
if (!confirm(`'${cat.name}' 카테고리와 소속 태그를 모두 삭제할까요?`)) return
|
||||
if (!(await dialog.confirm(`'${cat.name}' 카테고리와 소속 태그를 모두 삭제할까요?`, { title: '카테고리 삭제', danger: true }))) return
|
||||
try {
|
||||
await adminApi.removeCategory(cat.id)
|
||||
await load()
|
||||
@@ -84,7 +86,7 @@ async function addTag(cat) {
|
||||
}
|
||||
|
||||
async function removeTag(tag) {
|
||||
if (!confirm(`'${tag.name}' 태그를 삭제할까요?`)) return
|
||||
if (!(await dialog.confirm(`'${tag.name}' 태그를 삭제할까요?`, { title: '태그 삭제', danger: true }))) return
|
||||
try {
|
||||
await adminApi.removeTag(tag.id)
|
||||
await load()
|
||||
|
||||
@@ -3,6 +3,7 @@ import { onMounted, ref, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { boardApi } from '@/api/boardApi'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useDialog } from '@/composables/dialog'
|
||||
import { formatRelative } from '@/utils/datetime'
|
||||
import MarkdownViewer from '@/components/editor/MarkdownViewer.vue'
|
||||
import MarkdownEditor from '@/components/editor/MarkdownEditor.vue'
|
||||
@@ -12,6 +13,7 @@ import { DEFAULT_BOARD } from '@/constants/boards'
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
const dialog = useDialog()
|
||||
|
||||
const postId = route.params.id
|
||||
// 게시판(카테고리): URL 우선, 없으면 글의 category, 그래도 없으면 기본
|
||||
@@ -49,7 +51,7 @@ async function load() {
|
||||
}
|
||||
|
||||
async function removePost() {
|
||||
if (!confirm('이 게시글을 삭제하시겠습니까?')) return
|
||||
if (!(await dialog.confirm('이 게시글을 삭제하시겠습니까?', { title: '게시글 삭제', danger: true }))) return
|
||||
try {
|
||||
await boardApi.remove(postId)
|
||||
router.replace(`/board/${category.value}`)
|
||||
@@ -59,7 +61,7 @@ async function removePost() {
|
||||
}
|
||||
|
||||
async function blockPost() {
|
||||
const reason = prompt('열람 제한 사유 (선택):', '')
|
||||
const reason = await dialog.prompt('열람 제한 사유 (선택):', { title: '열람 제한', placeholder: '사유(선택)' })
|
||||
if (reason === null) return
|
||||
try {
|
||||
await boardApi.block(postId, reason)
|
||||
@@ -94,7 +96,7 @@ async function addComment() {
|
||||
}
|
||||
|
||||
async function removeComment(c) {
|
||||
if (!confirm('댓글을 삭제하시겠습니까?')) return
|
||||
if (!(await dialog.confirm('댓글을 삭제하시겠습니까?', { title: '댓글 삭제', danger: true }))) return
|
||||
try {
|
||||
await boardApi.removeComment(c.id)
|
||||
post.value.comments = post.value.comments.filter((x) => x.id !== c.id)
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
import { ref } from 'vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { Preferences } from '@capacitor/preferences'
|
||||
import { useDialog } from '@/composables/dialog'
|
||||
|
||||
const dialog = useDialog()
|
||||
const appVersion = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '-'
|
||||
const clearing = ref(false)
|
||||
|
||||
async function clearAppData() {
|
||||
if (clearing.value) return
|
||||
const ok = window.confirm(
|
||||
'앱에 저장된 모든 데이터(로그인 정보·캐시)를 삭제합니다.\n' +
|
||||
'삭제 후 로그아웃되며 첫 화면으로 이동합니다.\n계속하시겠습니까?',
|
||||
const ok = await dialog.confirm(
|
||||
'앱에 저장된 모든 데이터(로그인 정보·캐시)를 삭제합니다.\n삭제 후 로그아웃되며 첫 화면으로 이동합니다.\n계속하시겠습니까?',
|
||||
{ title: 'App Data 삭제', okText: '삭제', danger: true },
|
||||
)
|
||||
if (!ok) return
|
||||
clearing.value = true
|
||||
|
||||
Reference in New Issue
Block a user