feat(ui): 添加全局Snackbar组件并替换局部实现
将原本分散在各组件的Snackbar功能统一为全局组件,通过Pinia store管理状态 移除CreateTemplateScreen中的局部Snackbar实现,改用全局组件 添加Snackbar store提供多种消息类型方法(success/error/warning/info)
This commit is contained in:
1 parent
55a85e7111
commit
ae20a30616
5 files changed
+191
-35
No files matched your search
@@ -2,6 +2,7 @@
|
||||
import { DefaultLayout } from '@/renderer/components/layout'
|
||||
import GlobalDialog from '@/renderer/components/GlobalDialog.vue'
|
||||
import UpdateDialog from '@/renderer/components/UpdateDialog.vue'
|
||||
import GlobalSnackbar from '@/renderer/components/GlobalSnackbar.vue'
|
||||
import { onMounted } from 'vue'
|
||||
import { useAppStore } from '@/renderer/store/app'
|
||||
|
||||
@@ -75,6 +76,7 @@ const checkForUpdates = async () => {
|
||||
</DefaultLayout>
|
||||
<GlobalDialog />
|
||||
<UpdateDialog />
|
||||
<GlobalSnackbar />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<v-snackbar
|
||||
v-model="snackbar.isVisible"
|
||||
:color="snackbar.color"
|
||||
:timeout="snackbar.timeout"
|
||||
location="top"
|
||||
>
|
||||
{{ snackbar.message }}
|
||||
<template #actions>
|
||||
<v-btn
|
||||
v-for="(action, index) in snackbar.actions"
|
||||
:key="index"
|
||||
:color="action.color || 'white'"
|
||||
variant="text"
|
||||
@click="action.handler && action.handler()"
|
||||
>
|
||||
{{ action.text }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
icon
|
||||
variant="text"
|
||||
@click="snackbar.hide"
|
||||
>
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSnackbarStore } from '@/renderer/store/snackbar'
|
||||
|
||||
const snackbar = useSnackbarStore()
|
||||
</script>
|
||||
@@ -255,23 +255,7 @@
|
||||
|
||||
|
||||
|
||||
<!-- Snackbar for error messages -->
|
||||
<v-snackbar
|
||||
v-model="snackbarVisible"
|
||||
:color="snackbarColor"
|
||||
:timeout="2000"
|
||||
location="top"
|
||||
>
|
||||
{{ snackbarMessage }}
|
||||
<template #actions>
|
||||
<v-btn
|
||||
icon
|
||||
@click="snackbarVisible = false"
|
||||
>
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
@@ -279,12 +263,14 @@
|
||||
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useTemplateStore, TreeNode } from '@/renderer/store/template'
|
||||
import { useSnackbarStore } from '@/renderer/store/snackbar'
|
||||
import TemplateTree from '@/renderer/components/TemplateTree.vue'
|
||||
import { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const templateStore = useTemplateStore()
|
||||
const snackbar = useSnackbarStore()
|
||||
|
||||
// Extract methods from store
|
||||
const { getTemplateById } = templateStore
|
||||
@@ -305,10 +291,7 @@ const fileFormValid = ref(false)
|
||||
const directoryInputRef = ref<HTMLInputElement | null>(null)
|
||||
const fileInputRef = ref<HTMLInputElement | null>(null)
|
||||
|
||||
// Snackbar variables
|
||||
const snackbarVisible = ref(false)
|
||||
const snackbarMessage = ref('')
|
||||
const snackbarColor = ref('error') // Can be 'error', 'success', 'warning', etc.
|
||||
|
||||
|
||||
// New item state
|
||||
const newDirectoryPath = ref('')
|
||||
@@ -460,7 +443,7 @@ const confirmAddDirectory = () => {
|
||||
(child) => child.name === newDirectoryPath.value
|
||||
)
|
||||
if (duplicateExists) {
|
||||
showSnackbar('当前目录下已存在同名节点!', 'error')
|
||||
snackbar.error('当前目录下已存在同名节点!')
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -470,7 +453,7 @@ const confirmAddDirectory = () => {
|
||||
(node) => node.name === newDirectoryPath.value
|
||||
)
|
||||
if (duplicateExists) {
|
||||
showSnackbar('根目录下已存在同名节点!', 'error')
|
||||
snackbar.error('根目录下已存在同名节点!')
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -636,7 +619,7 @@ const selectAndAddFile = async () => {
|
||||
(child) => child.name === newFilePath.value
|
||||
)
|
||||
if (duplicateExists) {
|
||||
showSnackbar('当前目录下已存在同名节点!', 'error')
|
||||
snackbar.error('当前目录下已存在同名节点!')
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -646,7 +629,7 @@ const selectAndAddFile = async () => {
|
||||
(node) => node.name === newFilePath.value
|
||||
)
|
||||
if (duplicateExists) {
|
||||
showSnackbar('根目录下已存在同名节点!', 'error')
|
||||
snackbar.error('根目录下已存在同名节点!')
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -654,7 +637,7 @@ const selectAndAddFile = async () => {
|
||||
// Also check for duplicates using the full path (for backward compatibility)
|
||||
const hasDuplicate = checkForDuplicateInTree(treeStructure.value, fullPath)
|
||||
if (hasDuplicate) {
|
||||
showSnackbar('该文件路径已存在!')
|
||||
snackbar.error('该文件路径已存在!')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -681,7 +664,7 @@ const selectAndAddFile = async () => {
|
||||
}
|
||||
|
||||
if (duplicateNameExists) {
|
||||
showSnackbar('当前目录下已存在同名节点!', 'error')
|
||||
snackbar.error('当前目录下已存在同名节点!')
|
||||
} else {
|
||||
const newFileNode: TreeNode = {
|
||||
id: newId,
|
||||
@@ -722,7 +705,7 @@ const selectAndAddFile = async () => {
|
||||
}
|
||||
|
||||
// Otherwise, show file selection dialog
|
||||
showSnackbar('请先选择一个文件或文件夹', 'warning')
|
||||
snackbar.warning('请先选择一个文件或文件夹')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -818,7 +801,7 @@ const editItem = (item: TreeItem) => {
|
||||
showEditFileDialog(nodeData)
|
||||
}
|
||||
} else {
|
||||
showSnackbar('无法找到对应的节点', 'error')
|
||||
snackbar.error('无法找到对应的节点')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -916,12 +899,7 @@ const removeItemFromTree = (nodes: TreeNode[], itemId: string): TreeNode[] => {
|
||||
})
|
||||
}
|
||||
|
||||
// Show snackbar message
|
||||
const showSnackbar = (message: string, color: string = 'error') => {
|
||||
snackbarMessage.value = message
|
||||
snackbarColor.value = color
|
||||
snackbarVisible.value = true
|
||||
}
|
||||
|
||||
|
||||
// Methods for file/folder selection
|
||||
const selectSourceFileOrFolder = async () => {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export interface SnackbarAction {
|
||||
text: string
|
||||
color?: string
|
||||
handler?: () => void
|
||||
}
|
||||
|
||||
export const useSnackbarStore = defineStore('snackbar', () => {
|
||||
const isVisible = ref(false)
|
||||
const message = ref('')
|
||||
const color = ref('info')
|
||||
const timeout = ref(3000)
|
||||
const actions = ref<SnackbarAction[]>([])
|
||||
|
||||
const show = (msg: string, clr = 'info', tout = 3000, actionList: SnackbarAction[] = []) => {
|
||||
message.value = msg
|
||||
color.value = clr
|
||||
timeout.value = tout
|
||||
actions.value = actionList
|
||||
isVisible.value = true
|
||||
}
|
||||
|
||||
const success = (msg: string, tout = 3000) => show(msg, 'success', tout)
|
||||
const error = (msg: string, tout = 3000) => show(msg, 'error', tout)
|
||||
const warning = (msg: string, tout = 3000) => show(msg, 'warning', tout)
|
||||
const info = (msg: string, tout = 3000) => show(msg, 'info', tout)
|
||||
|
||||
const hide = () => {
|
||||
isVisible.value = false
|
||||
actions.value = []
|
||||
}
|
||||
|
||||
return {
|
||||
isVisible,
|
||||
message,
|
||||
color,
|
||||
timeout,
|
||||
actions,
|
||||
show,
|
||||
success,
|
||||
error,
|
||||
warning,
|
||||
info,
|
||||
hide
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user