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:
124
web/src/components/common/DeleteConfirm.vue
Normal file
124
web/src/components/common/DeleteConfirm.vue
Normal 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>
|
||||
@@ -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)
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user