You've already forked SmartisanNote.Remake
修复_Modal_组件输入框焦点获取问题并重构弹框实现
This commit is contained in:
@@ -91,6 +91,7 @@
|
|||||||
|
|
||||||
--confirmFontSize: 0.8rem;
|
--confirmFontSize: 0.8rem;
|
||||||
--confirmBg: rgba(0, 0, 0, 0.15);
|
--confirmBg: rgba(0, 0, 0, 0.15);
|
||||||
|
--confirmBtnColor: #000000cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
<transition name="settings-slide" v-show="isSettingsRoute" appear>
|
<transition name="settings-slide" v-show="isSettingsRoute" appear>
|
||||||
<SettingsPage class="setting-page" />
|
<SettingsPage class="setting-page" />
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<Modal ref="modalRef" showInput />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -25,7 +23,7 @@
|
|||||||
import { ref, watch, computed, onMounted } from 'vue'
|
import { ref, watch, computed, onMounted } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import '@/common/base.css'
|
import '@/common/base.css'
|
||||||
import Modal from '@/components/Modal.vue'
|
import { initModalService } from '@/utils/modalService'
|
||||||
|
|
||||||
// 导入页面组件
|
// 导入页面组件
|
||||||
import NoteListPage from './pages/NoteListPage.vue'
|
import NoteListPage from './pages/NoteListPage.vue'
|
||||||
@@ -73,6 +71,11 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 初始化弹框服务
|
||||||
|
onMounted(() => {
|
||||||
|
initModalService()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
@@ -46,55 +46,7 @@
|
|||||||
<img
|
<img
|
||||||
class="image_8 code-fun-ml-12"
|
class="image_8 code-fun-ml-12"
|
||||||
:src="isSelectionMode ? 'assets/icons/drawable-xxhdpi/icon_folder_trash.png' : 'assets/icons/drawable-xxhdpi/btn_add_folder.png'"
|
:src="isSelectionMode ? 'assets/icons/drawable-xxhdpi/icon_folder_trash.png' : 'assets/icons/drawable-xxhdpi/btn_add_folder.png'"
|
||||||
@click="isSelectionMode ? handleDeleteSelectedFolders() : handleAddFolder" />
|
@click="isSelectionMode ? handleDeleteSelectedFolders() : handleAddFolder()" />
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 添加文件夹的模态框 -->
|
|
||||||
|
|
||||||
<div v-if="showAddFolderModal" class="modal-overlay" @click="showAddFolderModal = false">
|
|
||||||
<div class="modal-content" @click.stop>
|
|
||||||
<h3>添加文件夹</h3>
|
|
||||||
|
|
||||||
<input v-model="newFolderName" placeholder="请输入文件夹名称" class="folder-input" @keyup.enter="confirmAddFolder" ref="folderInput" />
|
|
||||||
|
|
||||||
<div class="modal-actions">
|
|
||||||
<button @click="showAddFolderModal = false" class="cancel-btn">取消</button>
|
|
||||||
|
|
||||||
<button @click="confirmAddFolder" class="confirm-btn">确定</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 重命名文件夹的模态框 -->
|
|
||||||
|
|
||||||
<div v-if="showRenameFolderModal" class="modal-overlay" @click="showRenameFolderModal = false">
|
|
||||||
<div class="modal-content" @click.stop>
|
|
||||||
<h3>重命名文件夹</h3>
|
|
||||||
|
|
||||||
<input v-model="renameFolderName" placeholder="请输入文件夹名称" class="folder-input" @keyup.enter="confirmRenameFolder" ref="folderInput" />
|
|
||||||
|
|
||||||
<div class="modal-actions">
|
|
||||||
<button @click="showRenameFolderModal = false" class="cancel-btn">取消</button>
|
|
||||||
|
|
||||||
<button @click="confirmRenameFolder" class="confirm-btn">确定</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 确认删除文件夹的模态框 -->
|
|
||||||
|
|
||||||
<div v-if="showDeleteConfirmModal" class="modal-overlay" @click="showDeleteConfirmModal = false">
|
|
||||||
<div class="modal-content" @click.stop>
|
|
||||||
<h3>删除文件夹</h3>
|
|
||||||
|
|
||||||
<p>确定要删除选中的 {{ selectedFolders.length }} 个文件夹吗?文件夹中的便签将移至"全部便签"。</p>
|
|
||||||
|
|
||||||
<div class="modal-actions">
|
|
||||||
<button @click="showDeleteConfirmModal = false" class="cancel-btn">取消</button>
|
|
||||||
|
|
||||||
<button @click="confirmDeleteSelectedFolders" class="confirm-btn">确定</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,6 +57,7 @@
|
|||||||
import { ref, computed, nextTick } from 'vue'
|
import { ref, computed, nextTick } from 'vue'
|
||||||
import { useAppStore } from '../stores/useAppStore'
|
import { useAppStore } from '../stores/useAppStore'
|
||||||
import FolderItem from './FolderItem.vue'
|
import FolderItem from './FolderItem.vue'
|
||||||
|
import { showConfirm, showPrompt } from '../utils/modalService'
|
||||||
|
|
||||||
const store = useAppStore()
|
const store = useAppStore()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -158,18 +111,18 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 添加文件夹相关状态
|
|
||||||
const showAddFolderModal = ref(false)
|
|
||||||
const newFolderName = ref('')
|
|
||||||
const folderInput = ref(null)
|
|
||||||
// 重命名文件夹相关状态
|
// 重命名文件夹相关状态
|
||||||
|
|
||||||
const showRenameFolderModal = ref(false)
|
const showRenameFolderModal = ref(false)
|
||||||
|
|
||||||
const renameFolderId = ref(null)
|
const renameFolderId = ref(null)
|
||||||
|
|
||||||
const renameFolderName = ref('')
|
const renameFolderName = ref('')
|
||||||
// 选择模式相关状态
|
// 选择模式相关状态
|
||||||
|
|
||||||
const isSelectionMode = ref(false)
|
const isSelectionMode = ref(false)
|
||||||
|
|
||||||
const selectedFolders = ref([])
|
const selectedFolders = ref([])
|
||||||
const showDeleteConfirmModal = ref(false)
|
|
||||||
// 计算自定义文件夹(排除系统文件夹)
|
// 计算自定义文件夹(排除系统文件夹)
|
||||||
const customFolders = computed(() => {
|
const customFolders = computed(() => {
|
||||||
return store.folders.filter(folder => !['all', 'starred', 'trash', 'archive'].includes(folder.id))
|
return store.folders.filter(folder => !['all', 'starred', 'trash', 'archive'].includes(folder.id))
|
||||||
@@ -217,26 +170,29 @@ const handleFolderClick = folderId => {
|
|||||||
|
|
||||||
// 处理删除选中的文件夹
|
// 处理删除选中的文件夹
|
||||||
|
|
||||||
const handleDeleteSelectedFolders = () => {
|
const handleDeleteSelectedFolders = async () => {
|
||||||
if (selectedFolders.value.length === 0) return
|
if (selectedFolders.value.length === 0) return
|
||||||
showDeleteConfirmModal.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认删除选中的文件夹
|
|
||||||
|
|
||||||
const confirmDeleteSelectedFolders = async () => {
|
|
||||||
try {
|
try {
|
||||||
|
const confirmed = await showConfirm(`确定要删除选中的 ${selectedFolders.value.length} 个文件夹吗?文件夹中的便签将移至"全部便签"。`, '删除文件夹')
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
// 删除选中的文件夹
|
// 删除选中的文件夹
|
||||||
|
|
||||||
for (const folderId of selectedFolders.value) {
|
for (const folderId of selectedFolders.value) {
|
||||||
// 跳过系统文件夹
|
// 跳过系统文件夹
|
||||||
|
|
||||||
if (['all', 'starred', 'trash', 'archive'].includes(folderId)) continue
|
if (['all', 'starred', 'trash', 'archive'].includes(folderId)) continue
|
||||||
|
|
||||||
await store.deleteFolder(folderId)
|
await store.deleteFolder(folderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空选中项并退出选择模式
|
// 清空选中项并退出选择模式
|
||||||
|
|
||||||
selectedFolders.value = []
|
selectedFolders.value = []
|
||||||
|
|
||||||
isSelectionMode.value = false
|
isSelectionMode.value = false
|
||||||
showDeleteConfirmModal.value = false
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('删除文件夹失败:', error)
|
console.error('删除文件夹失败:', error)
|
||||||
}
|
}
|
||||||
@@ -244,52 +200,41 @@ const confirmDeleteSelectedFolders = async () => {
|
|||||||
|
|
||||||
// 处理删除文件夹
|
// 处理删除文件夹
|
||||||
|
|
||||||
const handleDeleteFolder = folderId => {
|
const handleDeleteFolder = async folderId => {
|
||||||
// 阻止事件冒泡到父元素
|
// 阻止事件冒泡到父元素
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
// 确认删除
|
// 确认删除
|
||||||
if (confirm(`确定要删除文件夹 "${getFolderName(folderId)}" 吗?文件夹中的便签将移至"全部便签"。`)) {
|
|
||||||
try {
|
try {
|
||||||
store.deleteFolder(folderId)
|
const confirmed = await showConfirm(`确定要删除文件夹 "${getFolderName(folderId)}" 吗?文件夹中的便签将移至"全部便签"。`, '删除文件夹')
|
||||||
|
if (confirmed) {
|
||||||
|
await store.deleteFolder(folderId)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('删除文件夹失败:', error)
|
console.error('删除文件夹失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 处理编辑文件夹
|
// 处理编辑文件夹
|
||||||
|
|
||||||
const handleEditFolder = folderId => {
|
const handleEditFolder = async folderId => {
|
||||||
// 阻止事件冒泡到父元素
|
// 阻止事件冒泡到父元素
|
||||||
|
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
const folder = store.folders.find(f => f.id === folderId)
|
const folder = store.folders.find(f => f.id === folderId)
|
||||||
|
|
||||||
if (folder) {
|
if (folder) {
|
||||||
renameFolderId.value = folderId
|
|
||||||
renameFolderName.value = folder.name
|
|
||||||
showRenameFolderModal.value = true
|
|
||||||
|
|
||||||
// 在下次DOM更新后聚焦输入框
|
|
||||||
nextTick(() => {
|
|
||||||
if (folderInput.value) {
|
|
||||||
folderInput.value.focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认重命名文件夹
|
|
||||||
|
|
||||||
const confirmRenameFolder = async () => {
|
|
||||||
if (renameFolderName.value.trim() && renameFolderId.value) {
|
|
||||||
try {
|
try {
|
||||||
await store.updateFolder(renameFolderId.value, { name: renameFolderName.value.trim() })
|
const newName = await showPrompt('请输入文件夹名称', '重命名文件夹', '请输入文件夹名称', folder.name)
|
||||||
showRenameFolderModal.value = false
|
|
||||||
renameFolderId.value = null
|
if (newName && newName.trim()) {
|
||||||
renameFolderName.value = ''
|
await store.updateFolder(folderId, { name: newName.trim() })
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('重命名文件夹失败:', error)
|
// 用户取消操作或出现错误
|
||||||
|
|
||||||
|
console.log('重命名文件夹操作已取消或出现错误:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,35 +276,21 @@ const handleTrashClick = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAddFolder = event => {
|
const handleAddFolder = async () => {
|
||||||
// 阻止事件冒泡到父元素
|
|
||||||
event.stopPropagation()
|
|
||||||
showAddFolderModal.value = true
|
|
||||||
newFolderName.value = ''
|
|
||||||
|
|
||||||
// 在下次DOM更新后聚焦输入框
|
|
||||||
nextTick(() => {
|
|
||||||
if (folderInput.value) {
|
|
||||||
folderInput.value.focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmAddFolder = async () => {
|
|
||||||
if (newFolderName.value.trim()) {
|
|
||||||
try {
|
try {
|
||||||
|
const folderName = await showPrompt('请输入文件夹名称', '添加文件夹', '请输入文件夹名称')
|
||||||
|
if (folderName && folderName.trim()) {
|
||||||
const newFolder = {
|
const newFolder = {
|
||||||
name: newFolderName.value.trim(),
|
name: folderName.trim(),
|
||||||
id: `folder_${Date.now()}`, // 生成唯一ID
|
id: `folder_${Date.now()}`, // 生成唯一ID
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
}
|
}
|
||||||
|
|
||||||
await store.addFolder(newFolder)
|
await store.addFolder(newFolder)
|
||||||
showAddFolderModal.value = false
|
|
||||||
newFolderName.value = ''
|
|
||||||
} catch (error) {
|
|
||||||
console.error('添加文件夹失败:', error)
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 用户取消操作或出现错误
|
||||||
|
console.log('添加文件夹操作已取消或出现错误:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -368,104 +299,37 @@ const confirmAddFolder = async () => {
|
|||||||
.page {
|
.page {
|
||||||
.section_7 {
|
.section_7 {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
|
|
||||||
background-color: #00000000;
|
background-color: #00000000;
|
||||||
|
|
||||||
.section_8 {
|
.section_8 {
|
||||||
padding: 0.29rem 0.92rem;
|
padding: 0.29rem 0.92rem;
|
||||||
|
|
||||||
background-color: #f4f4f4;
|
background-color: #f4f4f4;
|
||||||
|
|
||||||
border: solid 0.063rem #f0ece7;
|
border: solid 0.063rem #f0ece7;
|
||||||
|
|
||||||
.image_7,
|
.image_7,
|
||||||
.image_8 {
|
.image_8 {
|
||||||
border-radius: 0.63rem;
|
border-radius: 0.63rem;
|
||||||
|
|
||||||
width: 2.2rem;
|
width: 2.2rem;
|
||||||
|
|
||||||
height: 1.75rem;
|
height: 1.75rem;
|
||||||
|
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
|
||||||
background: url(/assets/icons/drawable-xxhdpi/folder_bottom_button_normal.9.png), #fdfbfb;
|
background: url(/assets/icons/drawable-xxhdpi/folder_bottom_button_normal.9.png), #fdfbfb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text_11 {
|
.text_11 {
|
||||||
color: #cacaca;
|
color: #cacaca;
|
||||||
|
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
|
|
||||||
line-height: 1.16rem;
|
line-height: 1.16rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 1001;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
background: white;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 1.5rem;
|
|
||||||
text-align: center;
|
|
||||||
width: 80%;
|
|
||||||
max-width: 300px;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.folder-input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.8rem;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 0.3rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
button {
|
|
||||||
flex: 1;
|
|
||||||
padding: 0.8rem;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0.3rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 0 0.5rem;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-btn {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
color: #333;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-btn {
|
|
||||||
background-color: #4a90e2;
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #357ae8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="visible" class="pd-mask" @click="handleMaskClick">
|
<div v-if="visible" class="pd-mask" @click="handleMaskClick">
|
||||||
<div class="pd-confirm" @click.stop>
|
<div class="pd-confirm" @click.stop>
|
||||||
<h2 class="pd-title">{{ title }}</h2>
|
<h2 class="pd-title">{{ dynamicTitle || title }}</h2>
|
||||||
<div class="pd-input-container" v-if="showInput">
|
|
||||||
<input v-model="inputModel" class="pd-input" :placeholder="inputPlaceholder" @keyup.enter="handleConfirm" />
|
<div class="pd-input-container" v-if="dynamicShowInput || showInput">
|
||||||
|
<input ref="inputRef" v-model="inputModel" class="pd-input" :placeholder="dynamicInputPlaceholder || inputPlaceholder" @keyup.enter="handleConfirm" />
|
||||||
</div>
|
</div>
|
||||||
<p class="pd-message" v-else>{{ message }}</p>
|
|
||||||
<div class="pd-plan"></div>
|
<p class="pd-message" v-else>{{ dynamicMessage || message }}</p>
|
||||||
|
|
||||||
<div class="pd-buttons">
|
<div class="pd-buttons">
|
||||||
<button v-if="showConfirm" class="pd-button pd-confirm-btn" @click="handleConfirm">
|
<button v-if="dynamicShowConfirm || showConfirm" class="pd-button pd-confirm-btn" @click="handleConfirm">
|
||||||
{{ confirmText }}
|
{{ dynamicConfirmText || confirmText }}
|
||||||
</button>
|
</button>
|
||||||
<button v-if="showCancel" class="pd-button pd-cancel" @click="handleCancel">
|
|
||||||
{{ cancelText }}
|
<button v-if="dynamicShowCancel || showCancel" class="pd-button pd-cancel" @click="handleCancel">
|
||||||
|
{{ dynamicCancelText || cancelText }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -20,7 +23,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { watch, onMounted, onUnmounted } from 'vue'
|
import { ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
|
||||||
|
|
||||||
|
// 响应式数据用于动态更新 Modal 内容
|
||||||
|
|
||||||
|
const dynamicTitle = ref('')
|
||||||
|
|
||||||
|
const dynamicMessage = ref('')
|
||||||
|
|
||||||
|
const dynamicConfirmText = ref('确认')
|
||||||
|
|
||||||
|
const dynamicCancelText = ref('取消')
|
||||||
|
|
||||||
|
const dynamicShowConfirm = ref(true)
|
||||||
|
|
||||||
|
const dynamicShowCancel = ref(true)
|
||||||
|
|
||||||
|
const dynamicMaskClosable = ref(false)
|
||||||
|
|
||||||
|
const dynamicShowInput = ref(false)
|
||||||
|
|
||||||
|
const dynamicInputPlaceholder = ref('请输入文字')
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: {
|
title: {
|
||||||
@@ -69,26 +92,48 @@ const emit = defineEmits(['confirm', 'cancel', 'update:visible'])
|
|||||||
const visible = defineModel('visible', { type: Boolean, default: false })
|
const visible = defineModel('visible', { type: Boolean, default: false })
|
||||||
const inputModel = defineModel('inputValue', { type: String, default: '' })
|
const inputModel = defineModel('inputValue', { type: String, default: '' })
|
||||||
|
|
||||||
|
const inputRef = ref()
|
||||||
|
|
||||||
// Promise 控制变量
|
// Promise 控制变量
|
||||||
|
|
||||||
let resolvePromise, rejectPromise
|
let resolvePromise, rejectPromise
|
||||||
|
|
||||||
// 返回 Promise 的方法,模拟原生 confirm 行为
|
// 返回 Promise 的方法,模拟原生 confirm 行为
|
||||||
const show = (options = {}) => {
|
|
||||||
// 更新 props 值
|
|
||||||
Object.assign(props, options)
|
|
||||||
|
|
||||||
// 如果提供了 inputValue,则更新输入框的值
|
const show = (options = {}) => {
|
||||||
if (options.inputValue !== undefined) {
|
// 更新动态数据
|
||||||
inputModel.value = options.inputValue
|
|
||||||
}
|
if (options.title !== undefined) dynamicTitle.value = options.title
|
||||||
|
|
||||||
|
if (options.message !== undefined) dynamicMessage.value = options.message
|
||||||
|
|
||||||
|
if (options.confirmText !== undefined) dynamicConfirmText.value = options.confirmText
|
||||||
|
|
||||||
|
if (options.cancelText !== undefined) dynamicCancelText.value = options.cancelText
|
||||||
|
|
||||||
|
if (options.showConfirm !== undefined) dynamicShowConfirm.value = options.showConfirm
|
||||||
|
|
||||||
|
if (options.showCancel !== undefined) dynamicShowCancel.value = options.showCancel
|
||||||
|
|
||||||
|
if (options.maskClosable !== undefined) dynamicMaskClosable.value = options.maskClosable
|
||||||
|
|
||||||
|
if (options.showInput !== undefined) dynamicShowInput.value = options.showInput
|
||||||
|
|
||||||
|
if (options.inputPlaceholder !== undefined) dynamicInputPlaceholder.value = options.inputPlaceholder
|
||||||
|
|
||||||
|
if (options.inputValue !== undefined) inputModel.value = options.inputValue
|
||||||
|
|
||||||
// 显示对话框
|
// 显示对话框
|
||||||
|
|
||||||
visible.value = true
|
visible.value = true
|
||||||
|
|
||||||
emit('update:visible', true)
|
emit('update:visible', true)
|
||||||
|
|
||||||
// 返回 Promise
|
// 返回 Promise
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
resolvePromise = resolve
|
resolvePromise = resolve
|
||||||
|
|
||||||
rejectPromise = reject
|
rejectPromise = reject
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -145,11 +190,29 @@ onUnmounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 监听 visible 变化
|
// 监听 visible 变化
|
||||||
watch(visible, newVal => {
|
watch(visible, (newVal, oldVal) => {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
lockBodyScroll()
|
lockBodyScroll()
|
||||||
|
// 如果模态框包含输入框,则自动获取焦点
|
||||||
|
if (dynamicShowInput.value || props.showInput) {
|
||||||
|
// 使用 nextTick 确保在 DOM 更新后聚焦
|
||||||
|
nextTick(() => {
|
||||||
|
// 添加更长的延迟确保动画完成
|
||||||
|
setTimeout(() => {
|
||||||
|
// 确保 inputRef 存在并且可见
|
||||||
|
if (inputRef.value && inputRef.value.offsetParent !== null) {
|
||||||
|
inputRef.value.focus()
|
||||||
|
inputRef.value.select() // 选中所有文本,提升用户体验
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
unlockBodyScroll()
|
unlockBodyScroll()
|
||||||
|
// 重置输入框引用,确保下次打开时能正确获取焦点
|
||||||
|
if (inputRef.value) {
|
||||||
|
inputRef.value.blur()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -191,19 +254,6 @@ defineExpose({ show })
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pd-plan {
|
|
||||||
background: inherit;
|
|
||||||
background: rgba(255, 255, 255, 0.66);
|
|
||||||
filter: blur(10px) saturate(2);
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pd-title {
|
.pd-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
|
|||||||
113
src/utils/modalService.js
Normal file
113
src/utils/modalService.js
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import Modal from '@/components/Modal.vue'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局弹框服务
|
||||||
|
* 提供统一的弹框调用接口,使用项目中已有的 Modal 组件
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 创建一个全局的 Modal 实例容器
|
||||||
|
let modalInstance = null
|
||||||
|
let modalContainer = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 Modal 实例
|
||||||
|
* 在应用启动时调用一次
|
||||||
|
*/
|
||||||
|
export const initModalService = () => {
|
||||||
|
if (!modalContainer) {
|
||||||
|
// 创建一个隐藏的 div 作为 Modal 的挂载点
|
||||||
|
modalContainer = document.createElement('div')
|
||||||
|
document.body.appendChild(modalContainer)
|
||||||
|
|
||||||
|
// 创建 Modal 实例
|
||||||
|
const app = createApp(Modal)
|
||||||
|
modalInstance = app.mount(modalContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return modalInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示确认弹框
|
||||||
|
* @param {string} message - 弹框消息
|
||||||
|
* @param {string} title - 弹框标题
|
||||||
|
* @param {Object} options - 弹框选项
|
||||||
|
* @returns {Promise<boolean>} 用户确认返回 true,取消返回 false
|
||||||
|
*/
|
||||||
|
export const showConfirm = (message, title = '确认', options = {}) => {
|
||||||
|
// 确保 Modal 实例已初始化
|
||||||
|
if (!modalInstance) {
|
||||||
|
initModalService()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示弹框
|
||||||
|
return modalInstance.show({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
showInput: false,
|
||||||
|
showConfirm: true,
|
||||||
|
showCancel: true,
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示提示弹框
|
||||||
|
* @param {string} message - 弹框消息
|
||||||
|
* @param {string} title - 弹框标题
|
||||||
|
* @param {Object} options - 弹框选项
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export const showAlert = (message, title = '提示', options = {}) => {
|
||||||
|
// 确保 Modal 实例已初始化
|
||||||
|
if (!modalInstance) {
|
||||||
|
initModalService()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示弹框
|
||||||
|
return modalInstance.show({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
showInput: false,
|
||||||
|
showConfirm: true,
|
||||||
|
showCancel: false,
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示输入弹框
|
||||||
|
* @param {string} message - 弹框消息
|
||||||
|
* @param {string} title - 弹框标题
|
||||||
|
* @param {string} placeholder - 输入框占位符
|
||||||
|
* @param {string} defaultValue - 输入框默认值
|
||||||
|
* @param {Object} options - 弹框选项
|
||||||
|
* @returns {Promise<string|null>} 用户输入的值,取消返回 null
|
||||||
|
*/
|
||||||
|
export const showPrompt = (message, title = '输入', placeholder = '请输入文字', defaultValue = '', options = {}) => {
|
||||||
|
// 确保 Modal 实例已初始化
|
||||||
|
if (!modalInstance) {
|
||||||
|
initModalService()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示弹框
|
||||||
|
return modalInstance.show({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
showInput: true,
|
||||||
|
showConfirm: true,
|
||||||
|
showCancel: true,
|
||||||
|
inputPlaceholder: placeholder,
|
||||||
|
inputValue: defaultValue,
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认导出所有方法
|
||||||
|
export default {
|
||||||
|
initModalService,
|
||||||
|
showConfirm,
|
||||||
|
showAlert,
|
||||||
|
showPrompt
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user