合并重复消息

This commit is contained in:
2026-06-05 14:08:10 +08:00
parent 30ec678c84
commit 4377a0db0a
7 changed files with 482 additions and 28 deletions
@@ -0,0 +1,110 @@
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
const props = withDefaults(defineProps<{
open: boolean
title: string
message: string
confirmText?: string
cancelText?: string
requireReason?: boolean
reasonLabel?: string
reasonPlaceholder?: string
}>(), {
confirmText: '确认',
cancelText: '取消',
requireReason: false,
reasonLabel: '屏蔽原因',
reasonPlaceholder: '请输入屏蔽原因',
})
const emit = defineEmits<{
cancel: []
confirm: [payload: { reason?: string }]
}>()
const reason = ref('')
const reasonInputRef = ref<HTMLTextAreaElement | null>(null)
const trimmedReason = computed(() => reason.value.trim())
const confirmDisabled = computed(() => props.requireReason && !trimmedReason.value)
function cancel() {
emit('cancel')
}
function confirm() {
if (confirmDisabled.value) {
return
}
emit('confirm', props.requireReason ? { reason: trimmedReason.value } : {})
}
function handleKeydown(event: KeyboardEvent) {
if (!props.open) {
return
}
if (event.key === 'Escape') {
cancel()
}
}
watch(
() => props.open,
async (open) => {
reason.value = ''
if (open && props.requireReason) {
await nextTick()
reasonInputRef.value?.focus()
}
},
)
watch(
() => props.open,
(open) => {
if (open) {
window.addEventListener('keydown', handleKeydown)
} else {
window.removeEventListener('keydown', handleKeydown)
}
},
)
onBeforeUnmount(() => {
window.removeEventListener('keydown', handleKeydown)
})
</script>
<template>
<div v-if="open" class="modal-backdrop" @click.self="cancel">
<section class="confirm-modal" role="dialog" aria-modal="true" :aria-label="title">
<div class="confirm-modal-header">
<div>
<p class="eyebrow">Confirm</p>
<h2>{{ title }}</h2>
</div>
<button class="confirm-modal-close" type="button" aria-label="关闭" @click="cancel">×</button>
</div>
<div class="confirm-modal-body">
<p>{{ message }}</p>
<label v-if="requireReason" class="confirm-modal-reason">
<span>{{ reasonLabel }}</span>
<textarea
ref="reasonInputRef"
v-model="reason"
rows="3"
:placeholder="reasonPlaceholder"
></textarea>
</label>
</div>
<div class="confirm-modal-actions">
<button class="confirm-modal-secondary" type="button" @click="cancel">{{ cancelText }}</button>
<button class="confirm-modal-danger" type="button" :disabled="confirmDisabled" @click="confirm">
{{ confirmText }}
</button>
</div>
</section>
</div>
</template>