feat: 添加确认模态框组件并在多个页面中实现删除和退出确认功能

This commit is contained in:
2026-01-19 10:00:21 +08:00
parent dbeb181e5d
commit 944836c748
10 changed files with 503 additions and 25 deletions

View File

@@ -1,6 +1,7 @@
<script setup>
import { computed, onMounted, ref } from "vue";
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { apiFetch, ensureMe, userRef } from "../lib/api";
import BbModal from "../components/BbModal.vue";
const loadingUsers = ref(false);
const usersError = ref("");
@@ -22,6 +23,36 @@ const openFolderIds = ref(new Set());
const isAdmin = computed(() => userRef.value?.role === "admin");
const confirmOpen = ref(false);
const confirmTitle = ref("请确认");
const confirmMessage = ref("");
const confirmOkText = ref("确定");
const confirmDanger = ref(false);
let confirmResolve = null;
function askConfirm(message, { title = "请确认", okText = "确定", danger = false } = {}) {
confirmTitle.value = title;
confirmMessage.value = message;
confirmOkText.value = okText;
confirmDanger.value = danger;
confirmOpen.value = true;
return new Promise((resolve) => {
confirmResolve = resolve;
});
}
function resolveConfirm(result) {
const resolve = confirmResolve;
confirmResolve = null;
confirmOpen.value = false;
if (resolve) resolve(Boolean(result));
}
function onConfirmModalUpdate(v) {
if (!v) resolveConfirm(false);
else confirmOpen.value = true;
}
async function loadUsers() {
loadingUsers.value = true;
usersError.value = "";
@@ -62,6 +93,21 @@ async function loadBookmarks() {
}
}
let searchTimer = 0;
watch(
() => q.value,
() => {
window.clearTimeout(searchTimer);
searchTimer = window.setTimeout(() => {
loadBookmarks();
}, 200);
}
);
onBeforeUnmount(() => {
window.clearTimeout(searchTimer);
});
function selectUser(id) {
selectedUserId.value = id;
q.value = "";
@@ -152,7 +198,7 @@ function isFolderOpen(folderId) {
async function deleteBookmark(bookmarkId) {
if (!selectedUserId.value) return;
if (!confirm("确定删除该书签?")) return;
if (!(await askConfirm("确定删除该书签?", { title: "删除书签", okText: "删除", danger: true }))) return;
try {
await apiFetch(`/admin/users/${selectedUserId.value}/bookmarks/${bookmarkId}`, { method: "DELETE" });
await loadBookmarks();
@@ -163,7 +209,7 @@ async function deleteBookmark(bookmarkId) {
async function deleteFolder(folderId) {
if (!selectedUserId.value) return;
if (!confirm("确定删除该文件夹?子文件夹会一起删除,文件夹内书签会变成未分组。")) return;
if (!(await askConfirm("确定删除该文件夹?子文件夹会一起删除,文件夹内书签会变成未分组。", { title: "删除文件夹", okText: "删除", danger: true }))) return;
try {
await apiFetch(`/admin/users/${selectedUserId.value}/folders/${folderId}`, { method: "DELETE" });
await loadFolders();
@@ -240,8 +286,10 @@ onMounted(async () => {
</div>
<div class="bb-row" style="gap: 8px;">
<input v-model="q" class="bb-input bb-input--sm" placeholder="搜索标题/链接" @keyup.enter="loadBookmarks" />
<button class="bb-btn bb-btn--secondary" :disabled="loadingBookmarks" @click="loadBookmarks">搜索</button>
<div class="bb-searchWrap" style="min-width: 260px;">
<input v-model="q" class="bb-input bb-input--sm bb-input--withClear" placeholder="搜索标题/链接" />
<button v-if="q.trim()" class="bb-searchClear" type="button" aria-label="清空搜索" @click="q = ''">×</button>
</div>
</div>
</div>
@@ -326,6 +374,14 @@ onMounted(async () => {
</div>
</section>
</div>
<BbModal :model-value="confirmOpen" :title="confirmTitle" max-width="520px" @update:model-value="onConfirmModalUpdate">
<div class="bb-muted" style="white-space: pre-wrap; line-height: 1.6;">{{ confirmMessage }}</div>
<div class="bb-row" style="justify-content: flex-end; gap: 10px; margin-top: 14px;">
<button class="bb-btn bb-btn--secondary" type="button" @click="resolveConfirm(false)">取消</button>
<button class="bb-btn" :class="confirmDanger ? 'bb-btn--danger' : ''" type="button" @click="resolveConfirm(true)">{{ confirmOkText }}</button>
</div>
</BbModal>
</section>
</template>