feat: add delete log comfirm
This commit is contained in:
@@ -601,7 +601,8 @@ export interface AppLogListResponse {
|
|||||||
|
|
||||||
export const LOG_CATEGORY = {
|
export const LOG_CATEGORY = {
|
||||||
SYSTEM_NOTICE: 'system_notice',
|
SYSTEM_NOTICE: 'system_notice',
|
||||||
PUSH_LOG: 'push_log'
|
PUSH_LOG: 'push_log',
|
||||||
|
LOGIN_LOG: 'login_log'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const LOG_LEVEL = {
|
export const LOG_LEVEL = {
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
--accent: oklch(0.97 0 0);
|
--accent: oklch(0.97 0 0);
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
--destructive-foreground: oklch(0.577 0.245 27.325);
|
--destructive-foreground: oklch(0.985 0 0);
|
||||||
--border: oklch(0.922 0 0);
|
--border: oklch(0.922 0 0);
|
||||||
--input: oklch(0.922 0 0);
|
--input: oklch(0.922 0 0);
|
||||||
--ring: oklch(0.708 0 0);
|
--ring: oklch(0.708 0 0);
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
--accent: oklch(0.32 0 0);
|
--accent: oklch(0.32 0 0);
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
--destructive-foreground: oklch(0.637 0.237 25.331);
|
--destructive-foreground: oklch(0.985 0 0);
|
||||||
--border: oklch(1 0 0 / 12%);
|
--border: oklch(1 0 0 / 12%);
|
||||||
--input: oklch(1 0 0 / 15%);
|
--input: oklch(1 0 0 / 15%);
|
||||||
--ring: oklch(0.556 0 0);
|
--ring: oklch(0.556 0 0);
|
||||||
|
|||||||
@@ -4,15 +4,20 @@ import type { HTMLAttributes } from "vue"
|
|||||||
import { reactiveOmit } from "@vueuse/core"
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
import { AlertDialogAction } from "reka-ui"
|
import { AlertDialogAction } from "reka-ui"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { buttonVariants } from '@/components/ui/button'
|
import { buttonVariants, type ButtonVariants } from '@/components/ui/button'
|
||||||
|
|
||||||
const props = defineProps<AlertDialogActionProps & { class?: HTMLAttributes["class"] }>()
|
const props = defineProps<AlertDialogActionProps & {
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
variant?: ButtonVariants['variant']
|
||||||
|
size?: ButtonVariants['size']
|
||||||
|
}>()
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class")
|
const delegatedProps = reactiveOmit(props, "class", "variant", "size")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AlertDialogAction v-bind="delegatedProps" :class="cn(buttonVariants(), props.class)">
|
<AlertDialogAction v-bind="delegatedProps"
|
||||||
|
:class="cn(buttonVariants({ variant: props.variant, size: props.size }), props.class)">
|
||||||
<slot />
|
<slot />
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -588,8 +588,7 @@ watch(() => route.query, (newQuery) => {
|
|||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||||
<AlertDialogAction @click="handleClearLogs"
|
<AlertDialogAction @click="handleClearLogs" variant="destructive">
|
||||||
class="bg-red-500 text-white hover:bg-red-600 dark:bg-red-600 dark:text-white dark:hover:bg-red-700">
|
|
||||||
清空
|
清空
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
@@ -607,8 +606,7 @@ watch(() => route.query, (newQuery) => {
|
|||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||||
<AlertDialogAction @click="handleDeleteLog"
|
<AlertDialogAction @click="handleDeleteLog" variant="destructive">
|
||||||
class="bg-red-500 text-white hover:bg-red-600 dark:bg-red-600 dark:text-white dark:hover:bg-red-700">
|
|
||||||
删除
|
删除
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
|
|||||||
@@ -3,7 +3,18 @@ import { ref, onMounted } from 'vue'
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import Pagination from '@/components/Pagination.vue'
|
import Pagination from '@/components/Pagination.vue'
|
||||||
import { RefreshCw, Search, Loader2 } from 'lucide-vue-next'
|
import { RefreshCw, Search, Loader2, Trash2 } from 'lucide-vue-next'
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@/components/ui/alert-dialog'
|
||||||
|
import { LOG_CATEGORY } from '@/api'
|
||||||
import TextOverflow from '@/components/TextOverflow.vue'
|
import TextOverflow from '@/components/TextOverflow.vue'
|
||||||
import { api } from '@/api'
|
import { api } from '@/api'
|
||||||
import { toast } from 'vue-sonner'
|
import { toast } from 'vue-sonner'
|
||||||
@@ -48,6 +59,7 @@ const filterUsername = ref('')
|
|||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
const showClearConfirm = ref(false)
|
||||||
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
// IP 地理位置弹窗
|
// IP 地理位置弹窗
|
||||||
@@ -103,6 +115,17 @@ function handlePageChange(page: number) {
|
|||||||
loadLogs()
|
loadLogs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleClear() {
|
||||||
|
try {
|
||||||
|
await api.appLogs.clear(LOG_CATEGORY.LOGIN_LOG)
|
||||||
|
toast.success('清空成功')
|
||||||
|
currentPage.value = 1
|
||||||
|
loadLogs()
|
||||||
|
} catch (e: any) {
|
||||||
|
toast.error('清空失败: ' + (e.message || ''))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(loadLogs)
|
onMounted(loadLogs)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -115,9 +138,34 @@ onMounted(loadLogs)
|
|||||||
<Input v-model="filterUsername" placeholder="搜索用户名..." class="h-9 pl-9 w-full sm:w-56 text-sm"
|
<Input v-model="filterUsername" placeholder="搜索用户名..." class="h-9 pl-9 w-full sm:w-56 text-sm"
|
||||||
@input="handleSearch" />
|
@input="handleSearch" />
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outline" size="icon" class="h-9 w-9 shrink-0" @click="loadLogs" :disabled="loading">
|
<div class="flex items-center gap-2">
|
||||||
|
<Button variant="outline" size="icon" class="h-9 w-9 shrink-0" @click="loadLogs" :disabled="loading"
|
||||||
|
title="刷新">
|
||||||
<RefreshCw class="h-4 w-4" :class="{ 'animate-spin': loading }" />
|
<RefreshCw class="h-4 w-4" :class="{ 'animate-spin': loading }" />
|
||||||
</Button>
|
</Button>
|
||||||
|
<AlertDialog :open="showClearConfirm" @update:open="showClearConfirm = $event">
|
||||||
|
<Button variant="outline"
|
||||||
|
class="h-9 px-4 shrink-0 text-sm text-destructive hover:bg-destructive/10 hover:text-destructive border-destructive/20"
|
||||||
|
@click="showClearConfirm = true">
|
||||||
|
<Trash2 class="h-4 w-4 sm:mr-2" /> <span class="hidden sm:inline"
|
||||||
|
style="padding-left: 2px;">清空记录</span>
|
||||||
|
</Button>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>确认清空所有登录日志?</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
此操作将永久清空所有记录,操作后无法恢复。确认要继续吗?
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||||
|
<AlertDialogAction @click="handleClear" variant="destructive">
|
||||||
|
确认清空
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,16 @@ import { format } from 'date-fns'
|
|||||||
import {
|
import {
|
||||||
RefreshCw, Trash2, Search, Info, AlertTriangle, AlertCircle
|
RefreshCw, Trash2, Search, Info, AlertTriangle, AlertCircle
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@/components/ui/alert-dialog'
|
||||||
import { useSiteSettings } from '@/composables/useSiteSettings'
|
import { useSiteSettings } from '@/composables/useSiteSettings'
|
||||||
|
|
||||||
const { pageSize } = useSiteSettings()
|
const { pageSize } = useSiteSettings()
|
||||||
@@ -31,6 +41,7 @@ const logs = ref<AppLog[]>([])
|
|||||||
const selectedLogId = ref<string | null>(null)
|
const selectedLogId = ref<string | null>(null)
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
const showClearConfirm = ref(false)
|
||||||
|
|
||||||
const filters = ref({
|
const filters = ref({
|
||||||
level: 'all',
|
level: 'all',
|
||||||
@@ -182,11 +193,28 @@ function onDialogClose(open: boolean) {
|
|||||||
<RefreshCw class="h-4 w-4" :class="{ 'animate-spin': loading }" />
|
<RefreshCw class="h-4 w-4" :class="{ 'animate-spin': loading }" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<AlertDialog :open="showClearConfirm" @update:open="showClearConfirm = $event">
|
||||||
<Button variant="outline"
|
<Button variant="outline"
|
||||||
class="h-9 px-4 shrink-0 text-sm text-destructive hover:bg-destructive/10 hover:text-destructive border-destructive/20"
|
class="h-9 px-4 shrink-0 text-sm text-destructive hover:bg-destructive/10 hover:text-destructive border-destructive/20"
|
||||||
@click="handleClear">
|
@click="showClearConfirm = true">
|
||||||
<Trash2 class="h-4 w-4 sm:mr-2" /> <span class="hidden sm:inline" style="padding-left: 2px;">清空记录</span>
|
<Trash2 class="h-4 w-4 sm:mr-2" /> <span class="hidden sm:inline"
|
||||||
|
style="padding-left: 2px;">清空记录</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>确认清空所有系统事件?</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
此操作将永久清空当前分类下的所有系统事件记录,操作后无法恢复。确认要继续吗?
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||||
|
<AlertDialogAction @click="handleClear" variant="destructive">
|
||||||
|
确认清空
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-lg border bg-card overflow-x-auto">
|
<div class="rounded-lg border bg-card overflow-x-auto">
|
||||||
|
|||||||
@@ -23,6 +23,16 @@ import { format } from 'date-fns'
|
|||||||
import {
|
import {
|
||||||
RefreshCw, Trash2, Check, X, Search
|
RefreshCw, Trash2, Check, X, Search
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@/components/ui/alert-dialog'
|
||||||
import { useSiteSettings } from '@/composables/useSiteSettings'
|
import { useSiteSettings } from '@/composables/useSiteSettings'
|
||||||
|
|
||||||
|
|
||||||
@@ -32,6 +42,7 @@ const logs = ref<AppLog[]>([])
|
|||||||
const selectedLogId = ref<string | null>(null)
|
const selectedLogId = ref<string | null>(null)
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
const showClearConfirm = ref(false)
|
||||||
|
|
||||||
const filters = ref({
|
const filters = ref({
|
||||||
status: 'all',
|
status: 'all',
|
||||||
@@ -172,11 +183,27 @@ function onDialogClose(open: boolean) {
|
|||||||
<RefreshCw class="h-4 w-4" :class="{ 'animate-spin': loading }" />
|
<RefreshCw class="h-4 w-4" :class="{ 'animate-spin': loading }" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<AlertDialog :open="showClearConfirm" @update:open="showClearConfirm = $event">
|
||||||
<Button variant="outline"
|
<Button variant="outline"
|
||||||
class="h-9 px-4 shrink-0 text-sm text-destructive hover:bg-destructive/10 hover:text-destructive border-destructive/20"
|
class="h-9 px-4 shrink-0 text-sm text-destructive hover:bg-destructive/10 hover:text-destructive border-destructive/20"
|
||||||
@click="handleClear">
|
@click="showClearConfirm = true">
|
||||||
<Trash2 class="h-4 w-4 sm:mr-2" /> <span class="hidden sm:inline" style="padding-left: 2px;">清空记录</span>
|
<Trash2 class="h-4 w-4 sm:mr-2" /> <span class="hidden sm:inline" style="padding-left: 2px;">清空记录</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>确认清空所有推送日志?</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
此操作将永久清空当前分类下的所有消息推送历史记录,操作后无法恢复。确认要继续吗?
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||||
|
<AlertDialogAction @click="handleClear" variant="destructive">
|
||||||
|
确认清空
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-lg border bg-card overflow-x-auto">
|
<div class="rounded-lg border bg-card overflow-x-auto">
|
||||||
|
|||||||
Reference in New Issue
Block a user