2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 06:47:20 +08:00

feat(web): 添加删除确认5秒倒计时组件,防止误删 (#1271)

* Initial plan

* feat(web): 添加删除确认5秒倒计时组件,防止误删

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix(web): 修复倒计时定时器并支持多语言格式化

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: 秒数不需要多语言

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
This commit is contained in:
Copilot
2026-01-23 22:33:49 +08:00
committed by GitHub
parent 03e45a0907
commit 9a47d3c2bb
5 changed files with 140 additions and 12 deletions

View File

@@ -0,0 +1,124 @@
<script setup lang="ts">
import { NPopconfirm } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const props = defineProps({
// 确认按钮文本
positiveText: {
type: String,
default: ''
},
// 取消按钮文本
negativeText: {
type: String,
default: ''
},
// 是否显示图标
showIcon: {
type: Boolean,
default: false
},
// 倒计时秒数
countdown: {
type: Number,
default: 5
}
})
const emit = defineEmits<{
(e: 'positiveClick'): void
(e: 'negativeClick'): void
}>()
// 倒计时计数器
const countdownValue = ref(0)
// 定时器 ID
let timer: ReturnType<typeof setInterval> | null = null
// 计算按钮禁用状态
const isDisabled = computed(() => countdownValue.value > 0)
// 计算确认按钮文本
const computedPositiveText = computed(() => {
const text = props.positiveText || $gettext('Confirm')
if (countdownValue.value > 0) {
return `${text} (${countdownValue.value}s)`
}
return text
})
// 计算取消按钮文本
const computedNegativeText = computed(() => {
return props.negativeText || $gettext('Cancel')
})
// 开始倒计时
const startCountdown = () => {
// 先清理已有的定时器,防止多个定时器并行运行
stopCountdown()
countdownValue.value = props.countdown
timer = setInterval(() => {
countdownValue.value--
if (countdownValue.value <= 0) {
stopCountdown()
}
}, 1000)
}
// 停止倒计时
const stopCountdown = () => {
if (timer) {
clearInterval(timer)
timer = null
}
}
// 重置倒计时
const resetCountdown = () => {
stopCountdown()
countdownValue.value = 0
}
// 处理显示状态变化
const handleUpdateShow = (show: boolean) => {
if (show) {
startCountdown()
} else {
resetCountdown()
}
}
// 处理确认点击
const handlePositiveClick = () => {
if (!isDisabled.value) {
emit('positiveClick')
}
return !isDisabled.value
}
// 组件卸载时清理定时器
onUnmounted(() => {
stopCountdown()
})
</script>
<template>
<n-popconfirm
:show-icon="showIcon"
:positive-button-props="{ disabled: isDisabled }"
:positive-text="computedPositiveText"
:negative-text="computedNegativeText"
@update:show="handleUpdateShow"
@positive-click="handlePositiveClick"
@negative-click="emit('negativeClick')"
>
<template #default>
<slot></slot>
</template>
<template #trigger>
<slot name="trigger"></slot>
</template>
</n-popconfirm>
</template>

View File

@@ -1,8 +1,9 @@
<script setup lang="ts">
import { NButton, NInput, NPopconfirm, NTag } from 'naive-ui'
import { NButton, NInput, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import database from '@/api/panel/database'
import DeleteConfirm from '@/components/common/DeleteConfirm.vue'
const { $gettext } = useGettext()
@@ -81,7 +82,7 @@ const columns: any = [
render(row: any) {
return [
h(
NPopconfirm,
DeleteConfirm,
{
onPositiveClick: () => handleDelete(row.server_id, row.name)
},

View File

@@ -1,9 +1,10 @@
<script setup lang="ts">
import copy2clipboard from '@vavt/copy2clipboard'
import { NButton, NFlex, NInput, NInputGroup, NPopconfirm, NTag } from 'naive-ui'
import { NButton, NFlex, NInput, NInputGroup, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import database from '@/api/panel/database'
import DeleteConfirm from '@/components/common/DeleteConfirm.vue'
import { formatDateTime } from '@/utils'
import UpdateUserModal from '@/views/database/UpdateUserModal.vue'
@@ -169,7 +170,7 @@ const columns: any = [
}
),
h(
NPopconfirm,
DeleteConfirm,
{
onPositiveClick: () => handleDelete(row.id)
},

View File

@@ -1,9 +1,10 @@
<script lang="ts" setup>
import { NButton, NDataTable, NFlex, NPopconfirm, NSwitch, NTag } from 'naive-ui'
import { NButton, NDataTable, NFlex, NSwitch, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import project from '@/api/panel/project'
import systemctl from '@/api/panel/systemctl'
import DeleteConfirm from '@/components/common/DeleteConfirm.vue'
import RealtimeLog from '@/components/common/RealtimeLog.vue'
import { useFileStore } from '@/store'
@@ -178,7 +179,7 @@ const columns: any = [
{ default: () => $gettext('Edit') }
),
h(
NPopconfirm,
DeleteConfirm,
{
showIcon: false,
onPositiveClick: () => handleDelete(row.id)
@@ -299,14 +300,14 @@ watch(type, () => {
<n-button type="primary" @click="createModal = true">
{{ $gettext('Create Project') }}
</n-button>
<n-popconfirm @positive-click="bulkDelete">
<delete-confirm @positive-click="bulkDelete">
<template #trigger>
<n-button type="error" :disabled="selectedRowKeys.length === 0" ghost>
{{ $gettext('Delete') }}
</n-button>
</template>
{{ $gettext('Are you sure you want to delete the selected projects?') }}
</n-popconfirm>
</delete-confirm>
</n-flex>
<n-data-table
striped

View File

@@ -1,8 +1,9 @@
<script lang="ts" setup>
import { NButton, NCheckbox, NDataTable, NFlex, NInput, NPopconfirm, NSwitch, NTag } from 'naive-ui'
import { NButton, NCheckbox, NDataTable, NFlex, NInput, NSwitch, NTag } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
import website from '@/api/panel/website'
import DeleteConfirm from '@/components/common/DeleteConfirm.vue'
import { useFileStore } from '@/store'
import { isNullOrUndef } from '@/utils'
@@ -139,7 +140,7 @@ const columns: any = [
}
),
h(
NPopconfirm,
DeleteConfirm,
{
showIcon: false,
onPositiveClick: () => handleDelete(row.id)
@@ -283,7 +284,7 @@ onMounted(() => {
<n-button type="primary" @click="bulkCreateModal = true">
{{ $gettext('Bulk Create Website') }}
</n-button>
<n-popconfirm @positive-click="bulkDelete">
<delete-confirm @positive-click="bulkDelete">
<template #trigger>
<n-button type="error" :disabled="selectedRowKeys.length === 0" ghost>
{{ $gettext('Delete') }}
@@ -294,7 +295,7 @@ onMounted(() => {
'This will delete the website directory but not the database with the same name. Are you sure you want to delete the selected websites?'
)
}}
</n-popconfirm>
</delete-confirm>
</n-flex>
<n-data-table
striped